Compare commits

...

No commits in common. "main" and "brainy-2.0-clean" have entirely different histories.

823 changed files with 143890 additions and 218898 deletions

View file

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

View file

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

View file

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

View file

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

52
.gitignore vendored
View file

@ -18,7 +18,6 @@ build/
# Runtime data
brainy-data/
.brainy/
*.log
*.pid
*.seed
@ -31,9 +30,6 @@ coverage/
# Test results
tests/results/
# Filesystem test artifacts (created by integration tests)
test-*/
# IDE files
.vscode/
.idea/
@ -50,9 +46,6 @@ tmp/
temp/
*.tmp
# Planning and instruction files
plan.md
# Package files
*.tgz
@ -62,20 +55,8 @@ INTERNAL_NOTES.md
TODO_PRIVATE.md
*.tar.gz
# Strategy and planning documents (private)
.strategy/
# Removed: PRODUCTION_*.md (now these should be public documentation)
DISTRIBUTED_*.md
*_ASSESSMENT.md
*_ANALYSIS.md
*_TRUTH*.md
# Models (downloaded at runtime)
models/
models-cache/
# But include bundled WASM model assets
!assets/models/
models/CLAUDE.md
# Development planning files (not for commit)
PLAN.md
@ -83,34 +64,3 @@ 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

View file

@ -1,84 +0,0 @@
# Source files (not needed in package)
src/
tests/
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
.gitignore
.npmignore
tsconfig.json
vitest.config.ts
vitest.config.mts
*.config.js
*.config.ts
.eslintrc*
.prettierrc*
# Test files
test-*.js
test-*.ts
*.test.ts
*.test.js
*.spec.ts
*.spec.js
# Temporary and log files
*.log
*.tmp
tmp/
temp/
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
View file

@ -1 +0,0 @@
22

File diff suppressed because it is too large Load diff

208
CLAUDE.md
View file

@ -1,208 +0,0 @@
# 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:** `@soulcraft/brainy@7.31.5` (latest published; 8.0.0 release candidate on `feat/8.0-u64-ids`)
---
## 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)

View file

@ -43,38 +43,15 @@ Feature requests are welcome! Please provide:
#### Development Setup
**Quick Setup (Recommended):**
```bash
# Clone your fork
git clone https://github.com/your-username/brainy.git
cd brainy
# Run setup script (installs all dependencies including Rust)
./scripts/setup-dev.sh
```
**Manual Setup:**
```bash
# Clone your fork
git clone https://github.com/your-username/brainy.git
cd brainy
# Install system dependencies (Ubuntu/Debian)
sudo apt-get install -y build-essential pkg-config libssl-dev
# Install Rust (for WASM embedding engine)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
source ~/.cargo/env
rustup target add wasm32-unknown-unknown
cargo install wasm-pack
# Install Node.js dependencies
# Install dependencies
npm install
# Build Candle WASM embedding engine
npm run build:candle
# Build TypeScript
# Build the project
npm run build
# Run tests
@ -158,11 +135,11 @@ npm run test:watch
```typescript
import { describe, it, expect } from 'vitest'
import { Brainy } from '../src'
import { BrainyData } from '../src'
describe('Feature Name', () => {
it('should do something specific', async () => {
const brain = new Brainy()
const brain = new BrainyData()
await brain.init()
// Test implementation
@ -207,16 +184,16 @@ import { BrainyAugmentation } from '../types'
export class MyAugmentation extends BrainyAugmentation {
name = 'MyAugmentation'
async onInit(brain: Brainy): Promise<void> {
async onInit(brain: BrainyData): Promise<void> {
// Initialize augmentation
}
async onAdd(item: any, brain: Brainy): Promise<any> {
async onAdd(item: any, brain: BrainyData): Promise<any> {
// Process before adding
return item
}
async onSearch(query: any, results: any[], brain: Brainy): Promise<any[]> {
async onSearch(query: any, results: any[], brain: BrainyData): Promise<any[]> {
// Process search results
return results
}
@ -259,10 +236,10 @@ Add examples for new features:
```typescript
// examples/feature-name.ts
import { Brainy } from 'brainy'
import { BrainyData } from 'brainy'
async function exampleUsage() {
const brain = new Brainy()
const brain = new BrainyData()
await brain.init()
// Show feature usage

View file

@ -1,72 +0,0 @@
# 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"]

241
MIGRATION.md Normal file
View file

@ -0,0 +1,241 @@
# Migration Guide: Brainy 1.x → 2.0
This guide helps you migrate from Brainy 1.x to the new 2.0 release with Triple Intelligence Engine.
## 🚨 Breaking Changes Summary
### 1. API Consolidation: 15+ Methods → 2 Clean APIs
Brainy 2.0 consolidates all search methods into just 2 primary APIs:
- `search()` - Vector similarity search
- `find()` - Intelligent natural language queries
### 2. Search Result Format Changed
**Before (1.x):**
```typescript
const results = await brain.search("query")
// Returns: [["id1", 0.9], ["id2", 0.8]]
```
**After (2.0):**
```typescript
const results = await brain.search("query")
// Returns: [{id: "id1", score: 0.9, content: "...", metadata: {...}}, ...]
```
### 3. Method Signature Changes
**Before (1.x):**
```typescript
// Old 3-parameter search
await brain.search(query, limit, options)
await brain.searchByVector(vector, k)
await brain.searchByNounTypes(query, k, types)
await brain.searchWithMetadata(query, k, filters)
// ... 15+ different methods
```
**After (2.0):**
```typescript
// New unified 2-parameter API
await brain.search(query, options)
await brain.find(query, options)
```
## 📦 New Unified API Reference
### `search()` - Vector Similarity Search
```typescript
await brain.search(query, {
// Pagination
limit?: number, // Max results (default: 10, max: 10000)
offset?: number, // Skip N results
cursor?: string, // Cursor-based pagination
// Filtering
metadata?: any, // O(log n) metadata filters
nounTypes?: string[], // Filter by types
itemIds?: string[], // Search within specific items
// Performance
parallel?: boolean, // Enable parallel search (default: true)
timeout?: number, // Operation timeout in ms
// Response Options
includeVectors?: boolean,
includeContent?: boolean
})
```
### `find()` - Intelligent Natural Language Queries
```typescript
// Simple natural language query
await brain.find("recent JavaScript frameworks with good performance")
// Structured query with Triple Intelligence
await brain.find({
like: "JavaScript", // Vector similarity
where: { // Metadata filtering
year: { greaterThan: 2020 },
performance: "high"
},
related: { // Graph relationships
to: "React",
depth: 2
}
}, {
limit: 10,
mode: 'auto' // auto | semantic | structured
})
```
## 🔄 Migration Steps
### Step 1: Update Search Calls
```typescript
// OLD (1.x)
const results = await brain.search("query", 10, {
metadata: { type: "document" }
})
// NEW (2.0)
const results = await brain.search("query", {
limit: 10,
metadata: { type: "document" }
})
```
### Step 2: Update Result Handling
```typescript
// OLD (1.x)
const results = await brain.search("query")
results.forEach(([id, score]) => {
console.log(`ID: ${id}, Score: ${score}`)
})
// NEW (2.0)
const results = await brain.search("query")
results.forEach(result => {
console.log(`ID: ${result.id}, Score: ${result.score}`)
console.log(`Content: ${result.content}`)
console.log(`Metadata:`, result.metadata)
})
```
### Step 3: Replace Deprecated Methods
| Old Method (1.x) | New Method (2.0) |
|-----------------|------------------|
| `searchByVector(vector, k)` | `search(vector, { limit: k })` |
| `searchByNounTypes(q, k, types)` | `search(q, { limit: k, nounTypes: types })` |
| `searchWithMetadata(q, k, filters)` | `search(q, { limit: k, metadata: filters })` |
| `searchWithCursor(q, k, cursor)` | `search(q, { limit: k, cursor })` |
| `searchSimilar(id, k)` | `search(id, { limit: k, mode: 'similar' })` |
| `semanticSearch(q)` | `find(q)` |
| `complexSearch(q, filters, opts)` | `find({ like: q, where: filters }, opts)` |
### Step 4: Update Storage Configuration
**Before (1.x):**
```typescript
const brain = new BrainyData({
type: 'filesystem',
path: './data'
})
```
**After (2.0):**
```typescript
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: './data'
}
})
```
### Step 5: Update CLI Commands
If using the CLI, update your commands:
```bash
# OLD (1.x)
brainy search-similar --id xyz --limit 5
# NEW (2.0)
brainy search xyz --limit 5 --mode similar
```
## ✨ New Features in 2.0
### Triple Intelligence Engine
- Vector search + Graph relationships + Metadata filtering
- O(log n) performance on all operations
- 220+ pre-computed NLP patterns
### Zero Configuration
- Works instantly with no setup
- Automatic model loading
- Smart defaults for everything
### Enhanced Natural Language
```typescript
// Natural language queries now understand context
await brain.find("Show me recent React components with tests")
await brain.find("Popular JavaScript libraries similar to Vue")
await brain.find("Documentation about authentication from last month")
```
### Improved Performance
- 3ms average search latency
- 24MB memory footprint
- Worker-based embeddings
- Automatic caching
## 🔍 Validation
After migration, validate your system:
```typescript
// Test basic search
const results = await brain.search("test query")
console.assert(results[0].id !== undefined, "Result should have ID")
console.assert(results[0].score !== undefined, "Result should have score")
// Test natural language
const nlpResults = await brain.find("recent important documents")
console.assert(Array.isArray(nlpResults), "Should return array")
// Test metadata filtering
const filtered = await brain.search("*", {
metadata: { type: "document" }
})
console.assert(filtered.length > 0, "Should find filtered results")
```
## 💡 Tips
1. **Start with `find()`** for natural language queries
2. **Use `search()`** for vector similarity when you know exactly what you want
3. **Leverage metadata filters** for O(log n) performance
4. **Enable cursor pagination** for large result sets
5. **Use the new CLI** for testing: `brainy find "your query"`
## 📚 Resources
- [API Documentation](docs/api/README.md)
- [Triple Intelligence Guide](docs/architecture/triple-intelligence.md)
- [Natural Language Guide](docs/guides/natural-language.md)
- [Getting Started](docs/guides/getting-started.md)
## 🆘 Need Help?
- GitHub Issues: [github.com/brainy-org/brainy/issues](https://github.com/brainy-org/brainy/issues)
- Documentation: [docs/README.md](docs/README.md)
---
*Brainy 2.0 - Zero-Configuration AI Database with Triple Intelligence™*

504
README.md
View file

@ -1,217 +1,303 @@
<p align="center">
<img src="https://raw.githubusercontent.com/soulcraftlabs/brainy/main/brainy.png" alt="Brainy" width="180">
</p>
<h1 align="center">Brainy</h1>
# Brainy
<p align="center">
<b>Three database paradigms. One API. Zero configuration.</b><br>
The in-process knowledge database for TypeScript — vector search, graph traversal,<br>
and metadata filtering unified in a single query.
<img src="brainy.png" alt="Brainy Logo" width="200">
</p>
<p align="center">
<a href="https://www.npmjs.com/package/@soulcraft/brainy"><img src="https://img.shields.io/npm/v/@soulcraft/brainy.svg" alt="npm version"></a>
<a href="https://www.npmjs.com/package/@soulcraft/brainy"><img src="https://img.shields.io/npm/dm/@soulcraft/brainy.svg" alt="npm downloads"></a>
<a href="https://github.com/soulcraftlabs/brainy/actions/workflows/ci.yml"><img src="https://github.com/soulcraftlabs/brainy/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
<a href="https://soulcraft.com/docs"><img src="https://img.shields.io/badge/docs-soulcraft.com-blue.svg" alt="Documentation"></a>
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
<a href="https://www.typescriptlang.org/"><img src="https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg" alt="TypeScript"></a>
</p>
[![npm version](https://badge.fury.io/js/brainy.svg)](https://www.npmjs.com/package/brainy)
[![npm downloads](https://img.shields.io/npm/dm/brainy.svg)](https://www.npmjs.com/package/brainy)
[![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)
[![TypeScript](https://img.shields.io/badge/%3C%2F%3E-TypeScript-%230074c1.svg)](https://www.typescriptlang.org/)
<p align="center">
<a href="#quick-start">Quick start</a> ·
<a href="#one-query-three-engines">One query</a> ·
<a href="#feature-tour">Features</a> ·
<a href="#from-laptop-to-hundreds-of-millions">Scale with Cor</a> ·
<a href="#documentation">Docs</a>
</p>
**🧠 Brainy 2.0 - Zero-Configuration AI Database with Triple Intelligence™**
The industry's first truly zero-configuration AI database that combines vector similarity, metadata filtering, and graph relationships with O(log n) performance. Production-ready with 3ms search latency, 220 pre-computed NLP patterns, and only 24MB memory footprint.
## 🎉 What's New in 2.0
- **Triple Intelligence™**: Unified Vector + Metadata + Graph queries in one API
- **API Consolidation**: 15+ methods → 2 clean APIs (`search()` and `find()`)
- **Natural Language**: Ask questions in plain English
- **Zero Configuration**: Works instantly, no setup required
- **O(log n) Performance**: Binary search on sorted indices
- **220+ NLP Patterns**: Pre-computed for instant understanding
- **Universal Compatibility**: Node.js, Browser, Edge, Workers
## ⚡ Quick Start
```bash
npm install brainy
```
```javascript
import { BrainyData } from 'brainy'
const brain = new BrainyData()
await brain.init()
// Add data with automatic embedding
await brain.addNoun("JavaScript is a programming language", {
type: "language",
year: 1995
})
// Natural language search
const results = await brain.find("programming languages from the 90s")
// Vector similarity with metadata filtering
const filtered = await brain.search("JavaScript", {
metadata: { type: "language" },
limit: 5
})
```
## 🚀 Key Features
### Triple Intelligence Engine
Combines three search paradigms in one unified API:
- **Vector Search**: Semantic similarity with HNSW indexing
- **Metadata Filtering**: O(log n) field lookups with binary search
- **Graph Relationships**: Navigate connected knowledge
### Natural Language Understanding
```javascript
// Ask questions naturally
await brain.find("Show me recent React components with tests")
await brain.find("Popular JavaScript libraries similar to Vue")
await brain.find("Documentation about authentication from last month")
```
### Zero Configuration Philosophy
- **No API keys required** - Built-in embedding models
- **No external dependencies** - Everything included
- **No complex setup** - Works instantly
- **Smart defaults** - Optimized out of the box
### Production Performance
- **3ms average search** - Lightning fast queries
- **24MB memory footprint** - Efficient resource usage
- **Worker-based embeddings** - Non-blocking operations
- **Automatic caching** - Intelligent result caching
## 📚 Core API
### `search()` - Vector Similarity
```javascript
const results = await brain.search("machine learning", {
limit: 10, // Number of results
metadata: { type: "article" }, // Filter by metadata
includeContent: true // Include full content
})
```
### `find()` - Natural Language Queries
```javascript
// Simple natural language
const results = await brain.find("recent important documents")
// Structured query with Triple Intelligence
const results = await brain.find({
like: "JavaScript", // Vector similarity
where: { // Metadata filters
year: { greaterThan: 2020 },
important: true
},
related: { to: "React" } // Graph relationships
})
```
### CRUD Operations
```javascript
// Create
const id = await brain.addNoun(data, metadata)
// Read
const item = await brain.getNoun(id)
// Update
await brain.updateNoun(id, newData, newMetadata)
// Delete
await brain.deleteNoun(id)
// Bulk operations
await brain.import(arrayOfData)
const exported = await brain.export({ format: 'json' })
```
## 🎯 Use Cases
### Knowledge Management
```javascript
// Store and search documentation
await brain.addNoun(documentContent, {
title: "API Guide",
category: "documentation",
version: "2.0"
})
const docs = await brain.find("API documentation for version 2")
```
### Semantic Search
```javascript
// Find similar content
const similar = await brain.search(existingContent, {
limit: 5,
threshold: 0.8
})
```
### AI Memory Layer
```javascript
// Store conversation context
await brain.addNoun(userMessage, {
userId: "123",
timestamp: Date.now(),
session: "abc"
})
// Retrieve relevant context
const context = await brain.find(`previous conversations with user 123`)
```
## 💾 Storage Options
Brainy supports multiple storage backends:
```javascript
// Memory (default for testing)
const brain = new BrainyData({
storage: { type: 'memory' }
})
// FileSystem (Node.js)
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: './data'
}
})
// Browser Storage (OPFS)
const brain = new BrainyData({
storage: { type: 'opfs' }
})
// S3 Compatible (Production)
const brain = new BrainyData({
storage: {
type: 's3',
bucket: 'my-bucket',
region: 'us-east-1'
}
})
```
## 🛠️ CLI
Brainy includes a powerful CLI for testing and management:
```bash
# Install globally
npm install -g brainy
# Add data
brainy add "JavaScript is awesome" --metadata '{"type":"opinion"}'
# Search
brainy search "programming"
# Natural language find
brainy find "awesome programming languages"
# Interactive mode
brainy chat
# Export data
brainy export --format json > backup.json
```
## 🔌 Augmentations
Extend Brainy with powerful augmentations:
```bash
# List available augmentations
brainy augment list
# Install an augmentation
brainy augment install explorer
# Connect to Brain Cloud
brainy cloud setup
```
## 🏢 Enterprise Features - Included for Everyone
Brainy includes enterprise-grade capabilities at no extra cost. **No premium tiers, no paywalls.**
- **Scales to 10M+ items** with consistent 3ms search latency
- **Write-Ahead Logging (WAL)** for zero data loss durability
- **Distributed architecture** with sharding and replication
- **Read/write separation** for horizontal scaling
- **Connection pooling** and request deduplication
- **Built-in monitoring** with metrics and health checks
- **Production ready** with circuit breakers and backpressure
📖 **[Read the full Enterprise Features guide →](docs/ENTERPRISE-FEATURES.md)**
## 📊 Benchmarks
| Operation | Performance | Memory |
|-----------|------------|--------|
| Initialize | 450ms | 24MB |
| Add Item | 12ms | +0.1MB |
| Vector Search (1k items) | 3ms | - |
| Metadata Filter (10k items) | 0.8ms | - |
| Natural Language Query | 15ms | - |
| Bulk Import (1000 items) | 2.3s | +8MB |
| **Production Scale (10M items)** | **5.8ms** | **12GB** |
## 🔄 Migration from 1.x
See [MIGRATION.md](MIGRATION.md) for detailed upgrade instructions.
Key changes:
- Search methods consolidated into `search()` and `find()`
- Result format now includes full objects with metadata
- New natural language capabilities
## 🤝 Contributing
We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
## 📖 Documentation
- [Getting Started Guide](docs/guides/getting-started.md)
- [API Reference](docs/api/README.md)
- [Architecture Overview](docs/architecture/overview.md)
- [Natural Language Guide](docs/guides/natural-language.md)
- [Triple Intelligence](docs/architecture/triple-intelligence.md)
## 🏢 Enterprise & Cloud
**Brain Cloud** - Managed Brainy with team sync, persistent memory, and enterprise connectors.
```bash
# Get started with free trial
brainy cloud setup
```
Visit [soulcraft.com](https://soulcraft.com) for more information.
## 📄 License
MIT © Brainy Contributors
---
Built because we were tired of stitching a vector store to a graph database to a document store — and spending weeks on plumbing before writing a line of business logic. Brainy indexes every fact **three ways at once** and lets one call query them together:
| You write | Brainy indexes it as | You query it with |
|---|---|---|
| `data: 'Ada wrote the first program'` | a **384-dim vector** (local embedding — no API key) | `find({ query: 'computing pioneers' })` |
| `metadata: { field: 'CS', year: 1843 }` | **structured fields** (O(1) exact, O(log n) range) | `find({ where: { year: { lessThan: 1900 } } })` |
| `relate({ from: ada, to: babbage })` | a **typed, directed graph edge** | `find({ connected: { to: babbage, depth: 2 } })` |
It runs **inside your process** — no server, no Docker, nothing to operate — and persists to plain files you can snapshot with a hard link.
**New here?** → **[What is Brainy? — plain-language overview, no jargon](docs/eli5.md)**
## Quick start
```bash
bun add @soulcraft/brainy # Bun ≥ 1.1 — recommended
npm install @soulcraft/brainy # Node.js ≥ 22
```
```javascript
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
const brain = new Brainy() // in-memory; one line swaps to disk
await brain.init()
// Text auto-embeds locally; metadata auto-indexes
const react = await brain.add({
data: 'React is a JavaScript library for building user interfaces',
type: NounType.Concept,
subtype: 'library',
metadata: { category: 'frontend', year: 2013 }
})
const next = await brain.add({
data: 'Next.js is a React framework with server-side rendering',
type: NounType.Concept,
subtype: 'framework',
metadata: { category: 'frontend', year: 2016 }
})
await brain.relate({ from: next, to: react, type: VerbType.DependsOn, subtype: 'runtime' })
```
## One query, three engines
```javascript
const results = await brain.find({
query: 'modern frontend frameworks', // vector — what it means
where: { year: { greaterThan: 2015 } }, // metadata — what it is
connected: { to: react, depth: 2 } // graph — what it touches
})
```
Every clause is optional; any combination composes. Under the hood Brainy plans the query across an HNSW vector index, a roaring-bitmap field index, and an adjacency graph index — and re-validates every result against your predicate before returning it, so a corrupt index can never hand you a wrong answer.
## Feature tour
### The database is a value
Pin it, rewind it, fork it. Snapshot isolation without a server.
```javascript
const db = brain.now() // pin current state — O(1)
await brain.transact([ // atomic all-or-nothing, CAS-guarded
{ op: 'update', id: order, metadata: { status: 'paid' } },
{ op: 'relate', from: invoice, to: order, type: VerbType.References, subtype: 'billing' }
], { ifAtGeneration: db.generation })
await db.get(order) // still 'pending' — pinned forever
await brain.get(order) // 'paid' — live
const lastWeek = await brain.asOf(Date.now() - 7 * 86_400_000) // full query surface, past state
const whatIf = await db.with([{ op: 'remove', id: order }]) // speculative — never touches disk
await brain.now().persist('/backups/today') // instant hard-link snapshot
```
**[Consistency model](docs/concepts/consistency-model.md)** · **[Snapshots & time travel](docs/guides/snapshots-and-time-travel.md)**
### Local embeddings — no API keys
Strings embed on-device with a bundled MiniLM model (WASM). Semantic search works offline, in CI, and on air-gapped machines, at zero cost per call. Hybrid keyword + semantic ranking is the default:
```javascript
await brain.find({ query: 'David Smith' }) // auto: text + semantic
await brain.find({ query: 'AI concepts', searchMode: 'semantic' }) // semantic only
```
### A typed graph, not a bag of edges
42 entity types × 127 relationship types form a shared vocabulary for any domain — healthcare (`Patient → diagnoses → Condition`), finance (`Account → transfers → Transaction`), yours. Your own taxonomy layers on with `subtype`, enforced at write time:
```javascript
await brain.add({ data: 'Avery Brooks', type: NounType.Person, subtype: 'employee' })
brain.counts.bySubtype(NounType.Person) // O(1) — { employee: 12, customer: 847 }
brain.requireSubtype(NounType.Person, { values: ['employee', 'customer'], required: true })
```
**[Type system](docs/architecture/noun-verb-taxonomy.md)** · **[Subtypes & facets](docs/guides/subtypes-and-facets.md)**
### Graph analytics built in
```javascript
await brain.graph.rank() // which entities matter most (centrality)
await brain.graph.communities() // natural clusters
await brain.graph.path(a, b) // how two things connect
await brain.graph.subgraph([seed], { depth: 2 }) // bounded neighborhood → { nodes, edges }
await brain.graph.export() // whole graph, one O(N+E) streaming pass
```
### Write-time aggregations
`SUM` / `COUNT` / `AVG` / `MIN` / `MAX` with `GROUP BY` and time windows, maintained incrementally on every write — reads are O(1) lookups, not scans. **[Aggregation guide](docs/guides/aggregation.md)**
### Import anything
```javascript
await brain.import('customers.csv')
await brain.import('sales.xlsx') // every sheet
await brain.import('research-paper.pdf') // tables extracted
await brain.import('https://api.example.com/data.json')
```
Entities auto-classify on the way in; `brain.extractEntities(text)` exposes the same NER ensemble directly. **[Import guide](docs/guides/import-anything.md)**
### A filesystem that understands content
```javascript
await brain.vfs.writeFile('/docs/readme.md', 'Project documentation')
await brain.vfs.search('React components with hooks') // semantic file search
```
**[VFS quick start](docs/vfs/QUICK_START.md)**
### Operations-grade by default
- **Single-writer, many-reader** — an exclusive lock protects the data directory; `Brainy.openReadOnly()` and the `brainy inspect` CLI examine a live brain from another process, safely.
- **Self-upgrading data files** — a 7.x brain opens under 8.x and migrates itself behind an observable lock (`getIndexStatus().migration`), with an automatic pre-upgrade backup. No migration scripts.
- **No silent wrong answers** — cold-open guards self-heal or throw typed errors (`MetadataIndexNotReadyError`, `GraphIndexNotReadyError`); they never return `[]` for data that exists.
**[Multi-process model](docs/concepts/multi-process.md)** · **[Inspection guide](docs/guides/inspection.md)**
## From laptop to hundreds of millions
Brainy's TypeScript engines take you a long way. When you outgrow them, add the native engine — **the API doesn't change**:
```bash
npm install @soulcraft/cor
```
```javascript
const brain = new Brainy({ storage: { type: 'filesystem', path: './data' } })
await brain.init() // @soulcraft/cor detected — same code, native engines underneath
```
Installing the package is the opt-in: if `@soulcraft/cor` is present, it loads and announces itself in the init log; if it's present but broken, `init()` **throws** — an installed accelerator never silently vanishes behind the JS engines. Opt out with `plugins: []`, or pin exactly what loads with `plugins: ['@soulcraft/cor']`. [`@soulcraft/cor`](https://www.npmjs.com/package/@soulcraft/cor) (Brainy 8.x ↔ Cor 3.x, version-matched) registers Rust implementations behind every provider seam: SIMD distance kernels, memory-mapped storage, a disk-native vector index that doesn't need your dataset in RAM, durable LSM field/graph indexes that serve cold opens instantly, and native aggregation. Recall@10 measured **0.99 / 0.96 / 0.96 at 1M / 10M / 100M vectors** in Cor's release gate.
Open core, commercial accelerator: Brainy is MIT and complete on its own; Cor is licensed and funds both.
## Performance
- JS distance kernels: **~6× faster cosine, ~1.4× euclidean** than 7.x (measured: [`tests/benchmarks/distance-microbench.mjs`](tests/benchmarks/distance-microbench.mjs), 384-dim, median of 41).
- Whole-graph reads are single **O(N + E)** cursor walks — a consumer-measured 19k-edge export dropped from ~27 s of per-node calls to one scan.
- Full numbers and capacity planning: **[docs/PERFORMANCE.md](docs/PERFORMANCE.md)** · **[docs/SCALING.md](docs/SCALING.md)**
## Use cases
**AI agent memory** — persistent semantic recall with relationship tracking · **Knowledge bases** — auto-linking and meaning-aware navigation · **Semantic search** over codebases, documents, media · **Enterprise data** — CRM, catalogs, institutional memory · **Games & simulations** — worlds and characters that remember.
## Documentation
| Start | Core | Going deeper |
|---|---|---|
| [Brainy explained simply](docs/eli5.md) | [API reference](docs/api/README.md) | [Architecture overview](docs/architecture/overview.md) |
| [Installation](docs/guides/installation.md) | [Data model](docs/DATA_MODEL.md) | [Consistency model](docs/concepts/consistency-model.md) |
| [Natural-language queries](docs/guides/natural-language.md) | [Query operators](docs/QUERY_OPERATORS.md) | [Multi-process model](docs/concepts/multi-process.md) |
| | [Find system](docs/FIND_SYSTEM.md) | [Scaling](docs/SCALING.md) |
## Requirements
**Bun ≥ 1.1** (recommended) or **Node.js ≥ 22**. Brainy 8.x is server-only; the 7.x line remains on npm for browser use.
## Contributing & license
Contributions welcome — see **[CONTRIBUTING.md](CONTRIBUTING.md)**. MIT © Brainy Contributors.
<p align="center">
<strong>Built with ❤️ by the Brainy community</strong><br>
<em>Zero-Configuration AI Database with Triple Intelligence™</em>
</p>

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -7,7 +7,7 @@
*/
import { program } from 'commander'
import { Brainy } from '../dist/index.js'
import { BrainyData } from '../dist/brainyData.js'
import chalk from 'chalk'
import inquirer from 'inquirer'
import ora from 'ora'
@ -61,7 +61,7 @@ async function getBrainy() {
if (!brainyInstance) {
const spinner = ora('Initializing Brainy...').start()
try {
brainyInstance = new Brainy()
brainyInstance = new BrainyData()
await brainyInstance.init()
spinner.succeed('Brainy initialized')
} catch (error) {
@ -444,7 +444,7 @@ async function showStatistics(brain) {
const spinner = ora('Gathering statistics...').start()
try {
const stats = brain.getStats()
const stats = await brain.getStatistics()
spinner.succeed('Statistics loaded')
console.log(boxen(

View file

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

File diff suppressed because it is too large Load diff

2037
bun.lock

File diff suppressed because it is too large Load diff

View file

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

View file

@ -1,295 +0,0 @@
# 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`.

View file

@ -1,468 +0,0 @@
---
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**

View file

@ -0,0 +1,303 @@
# Creating Augmentations for Brainy
## The BrainyAugmentation Interface
Every augmentation implements this simple yet powerful interface:
```typescript
interface BrainyAugmentation {
// Identification
name: string // Unique name for your augmentation
// Execution control
timing: 'before' | 'after' | 'around' | 'replace' // When to execute
operations: string[] // Which operations to intercept
priority: number // Execution order (higher = first)
// Lifecycle methods
initialize(context: AugmentationContext): Promise<void>
execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T>
shutdown?(): Promise<void> // Optional cleanup
}
```
## Creating a Storage Augmentation
Storage augmentations are special - they provide the storage backend for Brainy:
```typescript
import { StorageAugmentation } from 'brainy/augmentations'
import { MyCustomStorage } from './my-storage'
export class MyStorageAugmentation extends StorageAugmentation {
private config: MyStorageConfig
constructor(config: MyStorageConfig) {
super()
this.name = 'my-custom-storage'
this.config = config
}
// Called during storage resolution phase
async provideStorage(): Promise<StorageAdapter> {
const storage = new MyCustomStorage(this.config)
this.storageAdapter = storage
return storage
}
// Called during augmentation initialization
protected async onInitialize(): Promise<void> {
await this.storageAdapter!.init()
this.log(`Custom storage initialized`)
}
}
```
### Using Your Storage Augmentation
```typescript
// Register before brain.init()
const brain = new BrainyData()
brain.augmentations.register(new MyStorageAugmentation({
connectionString: 'redis://localhost:6379'
}))
await brain.init() // Will use your storage!
```
## Creating a Feature Augmentation
Here's a complete example of a caching augmentation:
```typescript
import { BaseAugmentation, BrainyAugmentation } from 'brainy/augmentations'
export class CachingAugmentation extends BaseAugmentation {
private cache = new Map<string, any>()
constructor() {
super()
this.name = 'smart-cache'
this.timing = 'around' // Wrap operations
this.operations = ['search'] // Only cache searches
this.priority = 50 // Mid-priority
}
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
if (operation === 'search') {
// Check cache
const cacheKey = JSON.stringify(params)
if (this.cache.has(cacheKey)) {
this.log('Cache hit!')
return this.cache.get(cacheKey)
}
// Execute and cache
const result = await next()
this.cache.set(cacheKey, result)
return result
}
// Pass through other operations
return next()
}
protected async onInitialize(): Promise<void> {
this.log('Cache initialized')
}
async shutdown(): Promise<void> {
this.cache.clear()
await super.shutdown()
}
}
```
## The Four Timing Modes
### 1. `before` - Pre-processing
```typescript
timing = 'before'
async execute(op, params, next) {
// Validate/transform input
const validated = await validate(params)
return next(validated) // Pass modified params
}
```
### 2. `after` - Post-processing
```typescript
timing = 'after'
async execute(op, params, next) {
const result = await next()
// Log, analyze, or modify result
console.log(`Operation ${op} returned:`, result)
return result
}
```
### 3. `around` - Wrapping (middleware)
```typescript
timing = 'around'
async execute(op, params, next) {
console.log('Starting', op)
try {
const result = await next()
console.log('Success', op)
return result
} catch (error) {
console.log('Failed', op, error)
throw error
}
}
```
### 4. `replace` - Complete replacement
```typescript
timing = 'replace'
async execute(op, params, next) {
// Don't call next() - replace entirely!
return myCustomImplementation(params)
}
```
## Operations You Can Intercept
Common operations in Brainy:
- `'storage'` - Storage resolution (special)
- `'add'`, `'addNoun'` - Adding data
- `'search'`, `'similar'` - Searching
- `'update'`, `'delete'` - Modifications
- `'saveNoun'`, `'saveVerb'` - Storage operations
- `'all'` - Intercept everything
## Context Available to Augmentations
```typescript
interface AugmentationContext {
brain: BrainyData // The brain instance
storage: StorageAdapter // Storage backend
config: BrainyDataConfig // Configuration
log: (message: string, level?: 'info' | 'warn' | 'error') => void
}
```
## Real-World Examples
### 1. Redis Storage Augmentation
```typescript
export class RedisStorageAugmentation extends StorageAugmentation {
async provideStorage(): Promise<StorageAdapter> {
return new RedisAdapter({
host: 'localhost',
port: 6379,
// Implement full StorageAdapter interface
})
}
}
```
### 2. Audit Trail Augmentation
```typescript
export class AuditAugmentation extends BaseAugmentation {
timing = 'after'
operations = ['add', 'update', 'delete']
async execute(op, params, next) {
const result = await next()
// Log to audit trail
await this.logAudit({
operation: op,
params,
result,
timestamp: new Date(),
user: this.context.config.currentUser
})
return result
}
}
```
### 3. Rate Limiting Augmentation
```typescript
export class RateLimitAugmentation extends BaseAugmentation {
timing = 'before'
operations = ['search']
private limiter = new RateLimiter({ rps: 100 })
async execute(op, params, next) {
await this.limiter.acquire() // Wait if rate limited
return next()
}
}
```
## Publishing to Brain Cloud Marketplace
Future capability for premium augmentations:
```typescript
// package.json
{
"name": "@brain-cloud/redis-storage",
"brainy": {
"type": "augmentation",
"category": "storage",
"premium": true
}
}
// Users can install via:
// brainy augment install redis-storage
```
## Best Practices
1. **Use BaseAugmentation** - Provides common functionality
2. **Set appropriate priority** - Storage (100), System (80-99), Features (10-50)
3. **Be selective with operations** - Don't use 'all' unless necessary
4. **Handle errors gracefully** - Don't break the chain
5. **Clean up in shutdown()** - Release resources
6. **Log appropriately** - Use context.log() for consistent output
7. **Document your augmentation** - Include examples
## Testing Your Augmentation
```typescript
import { BrainyData } from 'brainy'
import { MyAugmentation } from './my-augmentation'
describe('MyAugmentation', () => {
let brain: BrainyData
beforeEach(async () => {
brain = new BrainyData()
brain.augmentations.register(new MyAugmentation())
await brain.init()
})
afterEach(async () => {
await brain.destroy()
})
it('should enhance searches', async () => {
// Test your augmentation's effect
const results = await brain.search('test')
expect(results).toHaveProperty('enhanced', true)
})
})
```
## Summary
Augmentations are Brainy's extension system. They can:
- Replace storage backends
- Add caching layers
- Implement audit trails
- Add rate limiting
- Sync with external systems
- Transform data
- And much more!
The unified BrainyAugmentation interface makes it easy to create powerful extensions while maintaining consistency across the entire system.

View file

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

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

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

View file

@ -0,0 +1,83 @@
# 🤖 Model Loading Quick Reference
## 🚀 Common Scenarios
### ✅ Development (Zero Config)
```typescript
const brain = new BrainyData()
await brain.init() // Downloads automatically
```
### 🐳 Docker Production
```dockerfile
RUN npm run download-models
ENV BRAINY_ALLOW_REMOTE_MODELS=false
```
### ☁️ Serverless/Lambda
```bash
# Build step
npm run download-models
# Runtime
export BRAINY_ALLOW_REMOTE_MODELS=false
```
### 🔒 Air-Gapped/Offline
```bash
# Connected machine
npm run download-models
tar -czf brainy-models.tar.gz ./models
# Offline machine
tar -xzf brainy-models.tar.gz
export BRAINY_ALLOW_REMOTE_MODELS=false
```
### 🌐 Browser/CDN
```html
<!-- Automatic - no setup needed -->
<script type="module">
import { BrainyData } from 'brainy'
const brain = new BrainyData()
await brain.init() // Works in browser
</script>
```
## 🚨 Troubleshooting
| Error | Solution |
|-------|----------|
| "Failed to load embedding model" | `npm run download-models` |
| "ENOENT: no such file" | Check `BRAINY_MODELS_PATH` |
| "Network timeout" | Set `BRAINY_ALLOW_REMOTE_MODELS=false` |
| "Permission denied" | `chmod 755 ./models` |
| "Out of memory" | Increase container memory limit |
## 🎯 Environment Variables
| Variable | Values | Purpose |
|----------|--------|---------|
| `BRAINY_ALLOW_REMOTE_MODELS` | `true`/`false` | Allow/block downloads |
| `BRAINY_MODELS_PATH` | `./models` | Model storage path |
| `NODE_ENV` | `production` | Environment detection |
## 📦 Model Info
- **Model**: All-MiniLM-L6-v2
- **Dimensions**: 384 (fixed)
- **Size**: ~80MB download, ~330MB uncompressed
- **Location**: `./models/Xenova/all-MiniLM-L6-v2/`
## ✅ Verification Commands
```bash
# Check models exist
ls ./models/Xenova/all-MiniLM-L6-v2/onnx/model.onnx
# Test offline mode
BRAINY_ALLOW_REMOTE_MODELS=false npm test
# Download fresh models
rm -rf ./models && npm run download-models
```

View file

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

View file

@ -1,486 +0,0 @@
---
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.

View file

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

View file

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

387
docs/QUICK-START.md Normal file
View file

@ -0,0 +1,387 @@
# 🚀 Brainy Quick Start Guide
Get up and running with Brainy in 5 minutes!
## Installation
```bash
npm install brainy
```
Or install globally for CLI access:
```bash
npm install -g brainy
```
## Basic Usage
### 1. Initialize Brainy
```javascript
import { BrainyData } from 'brainy'
const brain = new BrainyData()
await brain.init()
```
That's it! No configuration needed. Brainy automatically:
- Downloads embedding models (first time only)
- Sets up storage (in-memory by default)
- Initializes all augmentations
- Configures optimal settings
### 2. Add Your First Data
```javascript
// Add a simple string
await brain.addNoun("JavaScript is a versatile programming language")
// Add with metadata
await brain.addNoun("React is a JavaScript library", {
type: "library",
category: "frontend",
popularity: "high"
})
// Add structured data
await brain.addNoun({
title: "Introduction to TypeScript",
content: "TypeScript adds static typing to JavaScript",
author: "John Doe"
}, {
type: "article",
date: "2024-01-15"
})
```
### 3. Search Your Data
```javascript
// Simple vector search
const results = await brain.search("programming languages")
// Natural language query
const articles = await brain.find("recent articles about TypeScript")
// With metadata filtering
const libraries = await brain.search("JavaScript", {
metadata: { type: "library" },
limit: 5
})
```
## Real-World Examples
### Example 1: Document Search System
```javascript
import { BrainyData } from 'brainy'
import fs from 'fs'
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: './document-index'
}
})
await brain.init()
// Index documents
const documents = [
{ file: 'api-guide.md', content: fs.readFileSync('./docs/api-guide.md', 'utf8') },
{ file: 'tutorial.md', content: fs.readFileSync('./docs/tutorial.md', 'utf8') },
{ file: 'faq.md', content: fs.readFileSync('./docs/faq.md', 'utf8') }
]
for (const doc of documents) {
await brain.addNoun(doc.content, {
filename: doc.file,
type: 'documentation',
indexed: new Date().toISOString()
})
}
// Search documents
const results = await brain.find("how to authenticate users")
console.log(`Found ${results.length} relevant documents:`)
results.forEach(r => console.log(`- ${r.metadata.filename} (${(r.score * 100).toFixed(1)}% match)`))
```
### Example 2: AI Chat with Memory
```javascript
import { BrainyData } from 'brainy'
const brain = new BrainyData()
await brain.init()
class ChatWithMemory {
constructor(brain) {
this.brain = brain
this.sessionId = Date.now().toString()
}
async addMessage(role, content) {
await this.brain.addNoun(content, {
role,
sessionId: this.sessionId,
timestamp: Date.now()
})
}
async getContext(query, limit = 5) {
// Find relevant previous messages
const relevant = await this.brain.find(query, { limit })
return relevant.map(r => ({
role: r.metadata.role,
content: r.content
}))
}
async chat(userMessage) {
// Store user message
await this.addMessage('user', userMessage)
// Get relevant context
const context = await this.getContext(userMessage)
// Your AI logic here (OpenAI, Anthropic, etc.)
const aiResponse = await callYourAI(userMessage, context)
// Store AI response
await this.addMessage('assistant', aiResponse)
return aiResponse
}
}
const chat = new ChatWithMemory(brain)
const response = await chat.chat("What did we discuss about JavaScript?")
```
### Example 3: Semantic Code Search
```javascript
import { BrainyData } from 'brainy'
import { glob } from 'glob'
import fs from 'fs'
const brain = new BrainyData()
await brain.init()
// Index all JavaScript files
const files = await glob('src/**/*.js')
for (const file of files) {
const content = fs.readFileSync(file, 'utf8')
// Extract functions
const functions = content.match(/function\s+(\w+)|const\s+(\w+)\s*=/g) || []
await brain.addNoun(content, {
file,
type: 'code',
language: 'javascript',
functions: functions.map(f => f.replace(/function\s+|const\s+|=/g, '').trim())
})
}
// Search for code
const results = await brain.find("authentication middleware")
console.log('Relevant code files:')
results.forEach(r => {
console.log(`\n${r.metadata.file}:`)
console.log(` Functions: ${r.metadata.functions.join(', ')}`)
console.log(` Relevance: ${(r.score * 100).toFixed(1)}%`)
})
```
## CLI Quick Examples
```bash
# Add data from CLI
brainy add "React is a JavaScript library for building UIs"
# Search
brainy search "JavaScript frameworks"
# Natural language find
brainy find "popular frontend libraries"
# Interactive chat mode
brainy chat
# Import JSON data
brainy import data.json
# Export your brain
brainy export --format json > backup.json
# Check status
brainy status
```
## Advanced Features
### Triple Intelligence Query
```javascript
// Combine vector search + metadata filters + graph relationships
const results = await brain.find({
like: "React", // Vector similarity
where: { // Metadata filtering
type: "library",
popularity: "high",
year: { greaterThan: 2015 }
},
related: { // Graph relationships
to: "JavaScript",
depth: 2
}
}, {
limit: 10,
includeContent: true
})
```
### Pagination
```javascript
// Cursor-based pagination for large result sets
let cursor = null
do {
const results = await brain.search("programming", {
limit: 100,
cursor
})
// Process batch
results.forEach(processResult)
cursor = results.nextCursor
} while (cursor)
```
### Performance Optimization
```javascript
// Pre-filter with metadata for faster searches
const results = await brain.search("*", {
metadata: {
type: "article",
category: "tech",
date: { greaterThan: "2024-01-01" }
},
limit: 1000
})
```
## Storage Options
### Memory (Testing)
```javascript
const brain = new BrainyData() // Default
```
### FileSystem (Development)
```javascript
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: './brain-data'
}
})
```
### Browser (OPFS)
```javascript
const brain = new BrainyData({
storage: { type: 'opfs' }
})
```
### S3 (Production)
```javascript
const brain = new BrainyData({
storage: {
type: 's3',
bucket: 'my-brain-bucket',
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY,
secretAccessKey: process.env.AWS_SECRET_KEY
}
}
})
```
## Tips & Best Practices
1. **Use metadata liberally** - It enables O(log n) filtering
2. **Batch operations when possible** - Use `import()` for bulk data
3. **Enable caching for production** - Automatic with default settings
4. **Use cursor pagination** - For large result sets
5. **Leverage natural language** - `find()` understands context
## Common Patterns
### Similarity Search
```javascript
// Find similar items to an existing one
const item = await brain.getNoun(id)
const similar = await brain.search(item.content, { limit: 5 })
```
### Time-based Queries
```javascript
// Recent items
const recent = await brain.search("*", {
metadata: {
timestamp: { greaterThan: Date.now() - 86400000 } // Last 24 hours
}
})
```
### Category Browsing
```javascript
// Get all items in a category
const category = await brain.search("*", {
metadata: { category: "tutorials" },
limit: 100
})
```
## Troubleshooting
### Models not loading?
```bash
# Clear cache and re-download
rm -rf ~/.cache/brainy
npm run download-models
```
### Slow initialization?
- First run downloads models (~25MB)
- Subsequent runs use cache (< 500ms)
- Use `storage: { type: 'memory' }` for testing
### Out of memory?
- Use filesystem or S3 storage for large datasets
- Enable worker threads (automatic in Node.js)
- Increase Node memory: `NODE_OPTIONS='--max-old-space-size=4096'`
## Next Steps
- 📖 Read the [full documentation](../README.md)
- 🏗️ Learn about [augmentations](augmentations/README.md)
- 🧠 Understand [Triple Intelligence](architecture/triple-intelligence.md)
- ☁️ Explore [Brain Cloud](https://soulcraft.com)
## Get Help
- GitHub Issues: [github.com/brainy-org/brainy](https://github.com/brainy-org/brainy)
- Documentation: [Full Docs](../README.md)
- Examples: [/examples](../../examples)
---
**Ready to build something amazing? You're all set! 🚀**

View file

@ -1,129 +1,120 @@
# Brainy Documentation
> The multi-dimensional AI database with Triple Intelligence — vector search, graph traversal, and metadata filtering in one unified API.
Welcome to the comprehensive documentation for Brainy, the multi-dimensional AI database with Triple Intelligence Engine.
## Quick Start
## 📊 Implementation Status
- ✅ **Production Ready**: Core features working today
- 🚧 **In Development**: Features coming soon
- 📅 **Roadmap**: See [ROADMAP.md](../ROADMAP.md)
## Quick Links
### Getting Started
- [Quick Start Guide](./guides/getting-started.md) - Get up and running in minutes
- [Enterprise for Everyone](./guides/enterprise-for-everyone.md) - **No limits, no tiers, everything free**
- [Natural Language Queries](./guides/natural-language.md) - Query with plain English
### Core Concepts
- [Zero Configuration](./architecture/zero-config.md) - **Auto-adapts to any environment**
- [Noun-Verb Taxonomy](./architecture/noun-verb-taxonomy.md) - **Revolutionary data model**
- [Triple Intelligence](./architecture/triple-intelligence.md) - Unified query system
- [Architecture Overview](./architecture/overview.md) - System design
### API Documentation
- [API Reference](./api/README.md) - Complete API documentation
- [TypeScript Types](./api/types.md) - Type definitions
### Advanced Topics
- [Augmentations System](./architecture/augmentations.md) - **Enterprise plugins & neural import**
- [Storage Architecture](./architecture/storage.md) - Storage adapter system
- [Performance Tuning](./guides/performance.md) - Optimization guide
- [Migration Guide](../MIGRATION.md) - Upgrading from 1.x
## What is Brainy?
Brainy is a next-generation AI database that combines:
- **Vector Search**: Semantic similarity using HNSW indexing
- **Graph Relationships**: Complex relationship mapping and traversal
- **Field Filtering**: Precise metadata filtering with O(1) lookups
- **Natural Language**: Query in plain English
## Key Features
### 🧠 Triple Intelligence Engine
All three intelligence types (vector, graph, field) work together in every query for optimal results.
### 📝 Noun-Verb Taxonomy
Model your data naturally as entities (nouns) and relationships (verbs) - no complex schemas needed.
### 🌍 Natural Language Queries
Ask questions in plain English and Brainy understands your intent:
```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
})
await brain.find("recent articles about AI with high ratings")
```
---
### ⚡ Production Ready
- Universal storage (FileSystem, S3, OPFS, Memory)
- Zero configuration with intelligent defaults
- Full TypeScript support
- Cross-platform compatibility
## Core Documentation
## Quick Example
| 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 |
```typescript
import { BrainyData } from 'brainy'
---
// Initialize
const brain = new BrainyData()
await brain.init()
## Architecture
// Add entities (nouns)
const articleId = await brain.addNoun("Revolutionary AI Breakthrough", {
type: "article",
category: "technology",
rating: 4.8
})
| 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 |
const authorId = await brain.addNoun("Dr. Sarah Chen", {
type: "person",
role: "researcher"
})
---
// Create relationships (verbs)
await brain.addVerb(authorId, articleId, "authored", {
date: "2024-01-15",
contribution: "primary"
})
## Virtual Filesystem (VFS)
// Query naturally
const results = await brain.find("highly rated technology articles by researchers")
```
| 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 |
## Documentation Structure
See [vfs/](./vfs/) for the complete VFS documentation set.
```
docs/
├── README.md # This file
├── guides/ # User guides
│ ├── getting-started.md # Quick start guide
│ ├── natural-language.md # NLP query guide
│ └── performance.md # Performance tuning
├── architecture/ # Technical architecture
│ ├── overview.md # System overview
│ ├── noun-verb-taxonomy.md # Data model
│ ├── triple-intelligence.md # Query system
│ └── storage.md # Storage layer
└── api/ # API documentation
├── README.md # API overview
├── brainy-data.md # Main class
└── types.md # TypeScript types
```
---
## Community
## 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 |
---
- **GitHub**: [github.com/brainy-org/brainy](https://github.com/brainy-org/brainy)
- **Issues**: [Report bugs or request features](https://github.com/brainy-org/brainy/issues)
- **Discussions**: [Join the conversation](https://github.com/brainy-org/brainy/discussions)
## License

View file

@ -1,131 +0,0 @@
# 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.)

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

@ -0,0 +1,359 @@
# 🔍 Brainy 2.0 Augmentation System Architecture Audit (REVISED)
**Author**: Senior Architecture Review
**Date**: 2025-08-25
**Status**: 🟡 **WORKING BUT NEEDS MARKETPLACE FEATURES**
## Executive Summary
The augmentation system core execution is WORKING correctly through `AugmentationRegistry`. The system properly executes augmentations before/after operations. However, there's no discovery, installation, or marketplace integration for the brain-cloud registry vision.
---
## 🟢 What's Actually Working
### 1. Execution Mechanism ✅
The `AugmentationRegistry` class properly implements:
```typescript
async execute<T>(operation: string, params: any, mainOperation: () => Promise<T>): Promise<T>
```
- Chains augmentations correctly
- Respects timing (before/after/around)
- Handles operation filtering
- Works with all 27 augmentations
### 2. Registration System ✅
```typescript
brain.augmentations.register(augmentation)
```
- Two-phase initialization works (storage first)
- Context injection works
- Lifecycle management works
### 3. Clean Interface ✅
- 100% of augmentations use `BrainyAugmentation`
- `BaseAugmentation` provides solid foundation
- Proper TypeScript types
### 4. Auto-Configuration ✅
```typescript
new BrainyData({
cache: true, // Auto-registers CacheAugmentation
index: true, // Auto-registers IndexAugmentation
storage: 's3' // Auto-registers S3StorageAugmentation
})
```
---
## 🟡 Missing for Marketplace Vision
### 1. No Package Discovery
**Current**: Manual registration only
```typescript
// Current (manual)
import { NotionSynapse } from './my-custom-synapse'
brain.augmentations.register(new NotionSynapse())
// Needed
await brain.discover('notion') // Search npm/brain-cloud
await brain.install('@soulcraft/notion-synapse')
```
### 2. No Installation Mechanism
**Current**: Must be bundled at build time
**Needed**: Dynamic installation
```typescript
interface AugmentationMarketplace {
search(query: string): Promise<Package[]>
install(packageId: string): Promise<void>
uninstall(packageId: string): Promise<void>
listInstalled(): Promise<Package[]>
checkUpdates(): Promise<Update[]>
}
```
### 3. No Brain Cloud Registry Client
**Current**: No registry concept
**Needed**: Registry integration
```typescript
class BrainCloudRegistry {
private apiUrl = 'https://api.soulcraft.com/brain-cloud'
async search(query: string): Promise<AugmentationPackage[]> {
const response = await fetch(`${this.apiUrl}/augmentations/search?q=${query}`)
return response.json()
}
async getPackage(id: string): Promise<AugmentationPackage> {
const response = await fetch(`${this.apiUrl}/augmentations/${id}`)
return response.json()
}
}
```
### 4. No License Management
**Current**: All augmentations free/bundled
**Needed**: License verification
```typescript
interface LicenseManager {
verify(packageId: string, licenseKey: string): Promise<boolean>
activate(packageId: string, licenseKey: string): Promise<void>
deactivate(packageId: string): Promise<void>
getStatus(packageId: string): Promise<LicenseStatus>
}
```
### 5. No Version Management
**Current**: No versioning
**Needed**: Semver support
```typescript
interface VersionManager {
checkCompatibility(pkg: Package, brainyVersion: string): boolean
resolveConflicts(packages: Package[]): Package[]
upgrade(packageId: string, toVersion: string): Promise<void>
}
```
---
## 📋 Implementation Plan for Marketplace
### Phase 1: Local Package Discovery (1 week)
```typescript
class LocalPackageDiscovery {
async discover(): Promise<Package[]> {
// 1. Search node_modules for brainy augmentations
const packages = await glob('node_modules/@*/package.json')
// 2. Filter for brainy augmentations
return packages.filter(pkg => pkg.brainy?.type === 'augmentation')
}
async load(packageId: string): Promise<BrainyAugmentation> {
// Dynamic import
const module = await import(packageId)
return new module.default()
}
}
```
### Phase 2: NPM Integration (1 week)
```typescript
class NPMRegistry {
async search(query: string): Promise<Package[]> {
// Search npm for packages with brainy keyword
const response = await fetch(
`https://registry.npmjs.org/-/v1/search?text=${query}+keywords:brainy-augmentation`
)
return response.json()
}
async install(packageId: string): Promise<void> {
// Use npm programmatically
await exec(`npm install ${packageId}`)
// Auto-register after install
const aug = await this.load(packageId)
this.brain.augmentations.register(aug)
}
}
```
### Phase 3: Brain Cloud Registry (2 weeks)
```typescript
class BrainCloudMarketplace {
private registry = new BrainCloudRegistry()
private licenses = new LicenseManager()
private installer = new AugmentationInstaller()
async browse(category?: string): Promise<MarketplaceListing[]> {
const packages = await this.registry.list(category)
return packages.map(pkg => ({
...pkg,
installed: this.isInstalled(pkg.id),
licensed: this.isLicensed(pkg.id),
updates: this.hasUpdates(pkg.id)
}))
}
async purchase(packageId: string): Promise<void> {
// 1. Process payment
const license = await this.processPayment(packageId)
// 2. Activate license
await this.licenses.activate(packageId, license)
// 3. Install package
await this.install(packageId)
}
}
```
### Phase 4: Developer Tools (1 week)
```typescript
// CLI for augmentation development
class AugmentationCLI {
async create(name: string): Promise<void> {
// Scaffold new augmentation project
await this.scaffold(name, 'augmentation-template')
}
async test(path: string): Promise<void> {
// Test augmentation locally
const aug = await this.load(path)
await this.runTests(aug)
}
async publish(path: string): Promise<void> {
// Publish to brain-cloud
const pkg = await this.package(path)
await this.registry.publish(pkg)
}
}
```
---
## 🏗️ Recommended Architecture
### 1. Augmentation Package Structure
```json
{
"name": "@soulcraft/notion-synapse",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"brainy": {
"type": "augmentation",
"class": "NotionSynapse",
"timing": "after",
"operations": ["addNoun", "updateNoun"],
"priority": 20,
"license": "premium",
"price": 9.99,
"compatibility": ">=2.0.0",
"dependencies": []
},
"keywords": ["brainy-augmentation", "notion", "sync"]
}
```
### 2. Installation Flow
```typescript
// User flow
await brain.marketplace.search('notion')
// Returns: [@soulcraft/notion-synapse, @community/notion-sync, ...]
await brain.marketplace.install('@soulcraft/notion-synapse')
// 1. Check license (prompt for purchase if needed)
// 2. Check compatibility
// 3. Install dependencies
// 4. Download package
// 5. Load augmentation
// 6. Register with brain
// 7. Initialize
// Now it's working!
brain.augmentations.list()
// [..., { name: '@soulcraft/notion-synapse', enabled: true }]
```
### 3. Discovery UI
```typescript
// Web UI component
<AugmentationMarketplace>
<SearchBar />
<Categories>
<Category name="Storage" count={12} />
<Category name="Sync" count={8} />
<Category name="AI" count={15} />
</Categories>
<Results>
<AugmentationCard
name="Notion Synapse"
author="Soulcraft"
rating={4.8}
installs={1200}
price={9.99}
onInstall={...}
/>
</Results>
</AugmentationMarketplace>
```
---
## 🎯 Priority for 2.0 Release
### Must Have (Release Blockers)
- ✅ Working execution (DONE)
- ✅ Clean interface (DONE)
- ✅ Documentation (DONE)
- ⏳ Fix augmentationPipeline.ts removal
- ⏳ Test all 27 augmentations work
### Nice to Have (2.0.x)
- Local package discovery
- NPM integration
- Basic CLI tools
### Future (2.1+)
- Brain Cloud Registry
- License management
- Payment processing
- Marketplace UI
- Developer portal
---
## 📊 Current State Assessment
| Component | Status | Notes |
|-----------|--------|-------|
| Core Execution | ✅ Working | AugmentationRegistry.execute() works |
| Registration | ✅ Working | Manual registration works |
| Auto-Config | ✅ Working | Cache, index, storage auto-register |
| Lifecycle | ✅ Working | Init, execute, shutdown work |
| Discovery | ❌ Missing | No package discovery |
| Installation | ❌ Missing | No dynamic installation |
| Marketplace | ❌ Missing | No registry client |
| Licensing | ❌ Missing | No license management |
| Versioning | ❌ Missing | No version checks |
---
## 💡 Recommendations
### For 2.0 Release
1. **Ship with manual registration** - It works!
2. **Document how to create augmentations** - Critical for adoption
3. **Create 2-3 example augmentations** - Show the patterns
4. **Add basic CLI for testing** - Help developers
### For 2.1 (Q1 2025)
1. **Add NPM discovery** - Find installed augmentations
2. **Dynamic loading** - Import augmentations at runtime
3. **Basic marketplace API** - List available augmentations
4. **Version checking** - Ensure compatibility
### For 3.0 (Q2 2025)
1. **Full marketplace** - Browse, search, install
2. **Payment integration** - Premium augmentations
3. **Developer portal** - Publish augmentations
4. **Enterprise features** - Private registries
---
## ✅ Good News Summary
The augmentation system WORKS! The core architecture is solid:
- Execution mechanism is correct
- Registration works
- Lifecycle management works
- All 27 augmentations function properly
What's missing is the marketplace/discovery layer, which can be added incrementally without breaking the core system. The 2.0 release can ship with manual augmentation registration, and the marketplace features can be added in 2.1+.
**Recommendation: Ship 2.0 with current system, add marketplace in 2.1**

View file

@ -4,8 +4,10 @@
## ✅ Actually Implemented Augmentations (12+)
### 1. WAL (Write-Ahead Logging) Augmentation ✅
Full implementation with crash recovery, checkpointing, and replay.
```typescript
import { WALAugmentation } from 'brainy'
// Fully working with all features documented
```
@ -73,10 +75,10 @@ import { MemoryStorageAugmentation } from 'brainy'
```
### 11. Server Search Augmentation ✅
Server-side search delegation over a conduit.
Distributed search capabilities.
```typescript
import { ServerSearchConduitAugmentation } from 'brainy'
// Forwards queries to a remote Brainy server
// Distributed query execution
```
### 12. Neural Import Augmentation ✅
@ -100,7 +102,7 @@ await neuralImport.detectRelationships(entities)
await neuralImport.generateInsights(data)
```
### Operation Modes (Fully Implemented!)
### Distributed Operation Modes (Fully Implemented!)
```typescript
// Read-only mode with optimized caching
const readerMode = new ReaderMode()
@ -138,7 +140,7 @@ monitor.getThrottlingMetrics() // Rate limiting info
## 📊 Statistics System (Fully Working!)
```typescript
const stats = await brain.getStats()
const stats = await brain.getStatistics()
// Returns comprehensive metrics:
{
nouns: {
@ -206,7 +208,7 @@ if (device === 'webgpu') {
// CUDA detection in Node:
if (device === 'cuda') {
// Future: GPU acceleration support
// Requires ONNX Runtime GPU packages
}
```
@ -239,16 +241,21 @@ const cacheConfig = await getCacheAutoConfig()
## 🎨 How to Use Hidden Features
### Enable Reader / Writer Modes
### Enable Distributed Modes
```typescript
const brain = new Brainy({
mode: 'reader' // or 'writer' or 'hybrid'
const brain = new BrainyData({
mode: 'reader', // or 'writer' or 'hybrid'
distributed: {
role: 'reader',
cacheStrategy: 'aggressive',
prefetch: true
}
})
```
### Use Neural Import
```typescript
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new NeuralImportAugmentation({
confidenceThreshold: 0.7,
@ -264,7 +271,7 @@ await brain.neuralImport('data.csv')
### Access Statistics
```typescript
// Get comprehensive stats
const stats = await brain.getStats()
const stats = await brain.getStatistics()
// Get specific service stats
const nounStats = await brain.getStatistics({
@ -280,7 +287,7 @@ const freshStats = await brain.getStatistics({
## 📝 What Needs Documentation
These features EXIST but need better docs:
1. Reader / writer operation modes
1. Distributed operation modes
2. Neural import full API
3. 3-level cache configuration
4. Performance monitoring API
@ -292,7 +299,7 @@ These features EXIST but need better docs:
## 💡 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
- Distributed operations
- AI-powered import
- Advanced caching
- Performance monitoring

View file

@ -15,7 +15,7 @@ High-performance deduplication for streaming data ingestion.
```typescript
import { EntityRegistryAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new EntityRegistryAugmentation({
maxCacheSize: 100000, // Track up to 100k unique entities
@ -26,8 +26,8 @@ const brain = new Brainy({
})
// Automatically prevents duplicate entities
await brain.add("Same content", { id: "123" }) // Added
await brain.add("Same content", { id: "123" }) // Skipped (duplicate)
await brain.addNoun("Same content", { id: "123" }) // Added
await brain.addNoun("Same content", { id: "123" }) // Skipped (duplicate)
```
**Benefits:**
@ -36,13 +36,17 @@ await brain.add("Same content", { id: "123" }) // Skipped (duplicate)
- Custom hash field selection
- Perfect for real-time data streams
### 2. WAL (Write-Ahead Logging) Augmentation ✅ Available
Enterprise-grade durability and crash recovery.
```typescript
import { WALAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new WALAugmentation({
walPath: './wal', // WAL directory
checkpointInterval: 1000, // Checkpoint every 1000 operations
compression: true, // Enable log compression
maxLogSize: 100 * 1024 * 1024 // 100MB max log size
@ -51,10 +55,13 @@ const brain = new Brainy({
})
// All operations are now durably logged
await brain.addNoun("Critical data") // Written to WAL before storage
// Recover from crash
const recovered = new Brainy({
const recovered = new BrainyData({
augmentations: [new WALAugmentation({ recover: true })]
})
await recovered.init() // Automatically replays WAL
```
**Features:**
@ -71,7 +78,7 @@ AI-powered relationship strength calculation.
```typescript
import { IntelligentVerbScoringAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new IntelligentVerbScoringAugmentation({
factors: {
@ -85,8 +92,8 @@ const brain = new Brainy({
})
// Relationships automatically get intelligent scores
await brain.relate(user1, product1, "viewed", { timestamp: Date.now() })
await brain.relate(user1, product1, "purchased", { timestamp: Date.now() })
await brain.addVerb(user1, product1, "viewed", { timestamp: Date.now() })
await brain.addVerb(user1, product1, "purchased", { timestamp: Date.now() })
// Automatically calculates relationship strength based on multiple factors
// Query using intelligent scores
@ -111,7 +118,7 @@ Automatically extracts and registers entities from text.
```typescript
import { AutoRegisterEntitiesAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new AutoRegisterEntitiesAugmentation({
types: ['person', 'organization', 'location', 'product'],
@ -122,7 +129,7 @@ const brain = new Brainy({
})
// Automatically extracts and registers entities
await brain.add(
await brain.addNoun(
"Apple CEO Tim Cook announced the new iPhone 15 in Cupertino",
{ type: "news" }
)
@ -147,7 +154,7 @@ Optimizes bulk operations for maximum throughput.
```typescript
import { BatchProcessingAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new BatchProcessingAugmentation({
batchSize: 100,
@ -160,7 +167,7 @@ const brain = new Brainy({
// Operations are automatically batched
for (let i = 0; i < 10000; i++) {
await brain.add(`Item ${i}`) // Internally batched
await brain.addNoun(`Item ${i}`) // Internally batched
}
// Processes in optimized batches of 100
```
@ -178,7 +185,7 @@ Intelligent multi-level caching system.
```typescript
import { CachingAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new CachingAugmentation({
levels: {
@ -211,7 +218,7 @@ Reduces storage size while maintaining query performance.
```typescript
import { CompressionAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new CompressionAugmentation({
algorithm: 'brotli',
@ -223,7 +230,7 @@ const brain = new Brainy({
})
// Data automatically compressed/decompressed
await brain.add(largeDocument) // Compressed before storage
await brain.addNoun(largeDocument) // Compressed before storage
const doc = await brain.getNoun(id) // Decompressed on retrieval
```
@ -240,7 +247,7 @@ Real-time performance monitoring and metrics.
```typescript
import { MonitoringAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new MonitoringAugmentation({
metrics: ['operations', 'latency', 'cache', 'memory'],
@ -279,7 +286,7 @@ brain.on('metrics', (metrics) => {
```typescript
import { NeuralImportAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new NeuralImportAugmentation({
autoStructure: true,
@ -375,7 +382,7 @@ import { Augmentation } from 'brainy'
class CustomAugmentation extends Augmentation {
name = 'CustomAugmentation'
async onInit(brain: Brainy): Promise<void> {
async onInit(brain: BrainyData): Promise<void> {
// Initialize augmentation
console.log('Custom augmentation initialized')
}
@ -408,7 +415,7 @@ class CustomAugmentation extends Augmentation {
}
// Use custom augmentation
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [new CustomAugmentation()]
})
```
@ -420,7 +427,7 @@ const brain = new Brainy({
```typescript
interface AugmentationHooks {
// Initialization
onInit(brain: Brainy): Promise<void>
onInit(brain: BrainyData): Promise<void>
onShutdown(): Promise<void>
// Noun operations
@ -459,7 +466,7 @@ interface AugmentationHooks {
```typescript
// Combine multiple augmentations
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
// Order matters - executed in sequence
new EntityRegistryAugmentation(), // Deduplication first
@ -467,6 +474,7 @@ const brain = new Brainy({
new IntelligentVerbScoringAugmentation(), // Scoring
new CompressionAugmentation(), // Compression
new CachingAugmentation(), // Caching
new WALAugmentation(), // Durability
new MonitoringAugmentation() // Monitoring last
]
})

View file

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

View file

@ -1,538 +0,0 @@
# 🎯 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.*

View file

@ -1,941 +0,0 @@
# Index Architecture
Brainy uses a sophisticated **3-tier index architecture** that enables "Triple Intelligence" - the unified combination of vector similarity, graph relationships, and metadata filtering. This document provides a comprehensive architectural overview of how these indexes work internally and coordinate with each other.
## Overview: The Three Main Indexes + Sub-Indexes
Brainy has **3 main indexes** at the top level, each with multiple sub-indexes managed automatically:
### Main Indexes (Level 1)
| Index | Purpose | Data Structure | Complexity | File Location | rebuild() Method |
|-------|---------|----------------|------------|---------------|------------------|
| **TypeAwareVectorIndex** | Type-aware vector similarity search | 42 type-specific hierarchical graphs | O(log n) search | `src/hnsw/typeAwareHNSWIndex.ts` | ✅ Line 403 |
| **MetadataIndexManager** | Fast metadata filtering | Chunked sparse indices with bloom filters + zone maps + roaring bitmaps | O(1) exact, O(log n) ranges | `src/utils/metadataIndex.ts` | ✅ Line 2318 |
| **GraphAdjacencyIndex** | Relationship traversal | 2 verb-id LSM-trees + tombstone-filtered adjacency derivation | O(degree) per hop | `src/graph/graphAdjacencyIndex.ts` | ✅ Line 389 |
### Sub-Indexes (Level 2)
**TypeAwareVectorIndex contains:**
- **42 type-specific vector indexes** - One per NounType (automatically rebuilt via parent)
**MetadataIndexManager contains:**
- **ChunkManager** - Adaptive chunked sparse indexing
- **EntityIdMapper** - UUID ↔ integer mapping for roaring bitmaps
- **FieldTypeInference** - DuckDB-inspired value-based field type detection
- **Field Sparse Indexes** - Per-field sparse indexes with roaring bitmaps (dynamic count)
- **Sorted Indexes** - Support orderBy queries (automatically maintained)
- **Word Index (`__words__`)** - Text search via FNV-1a word hashes
**GraphAdjacencyIndex contains:**
- **lsmTreeSource** - Source → Targets (outgoing edges)
- **lsmTreeTarget** - Target → Sources (incoming edges)
- **lsmTreeVerbsBySource** - Source → Verb IDs
- **lsmTreeVerbsByTarget** - Target → Verb IDs
All indexes share a **UnifiedCache** for coordinated memory management, ensuring fair resource allocation and preventing any single index from monopolizing memory.
## 1. MetadataIndex - Fast Field Filtering
**Purpose**: Enable O(1) field-value lookups and O(log n) range queries on metadata fields using adaptive chunked sparse indexing.
### Internal Architecture
```typescript
class MetadataIndexManager {
// Chunked sparse indices: field → SparseIndex (replaces flat files)
private sparseIndices = new Map<string, SparseIndex>()
// Chunk management
private chunkManager: ChunkManager
private chunkingStrategy: AdaptiveChunkingStrategy
// Lightweight field statistics
private fieldIndexes = new Map<string, FieldIndexData>() // value → count
private fieldStats = new Map<string, FieldStats>() // cardinality tracking
// Type-field affinity for NLP understanding
private typeFieldAffinity = new Map<string, Map<string, number>>()
// Shared memory management
private unifiedCache: UnifiedCache
}
```
### Key Data Structures
#### Chunked Sparse Index
```typescript
// SparseIndex: Directory of chunks for a field
// Example: field="status"
class SparseIndex {
field: string
chunks: ChunkDescriptor[] // Metadata about each chunk
bloomFilters: BloomFilter[] // Fast membership testing
}
// ChunkDescriptor: Metadata about a chunk
interface ChunkDescriptor {
chunkId: number
valueCount: number // How many unique values in this chunk
idCount: number // Total entity IDs
zoneMap: ZoneMap // Min/max for range queries
lastUpdated: number
}
// Actual chunk data stored separately
class ChunkData {
chunkId: number
field: string
entries: Map<value, RoaringBitmap32> // ~50 values per chunk (roaring bitmaps!)
}
```
**Performance**:
- O(1) exact lookup with bloom filters (1% false positive rate)
- O(log n) range queries with zone maps
- 630x file reduction (560k flat files → 89 chunk files)
#### Roaring Bitmap Optimization
**Problem Solved**: JavaScript `Set<string>` for storing entity IDs was inefficient:
- Memory overhead: ~40 bytes per UUID string (36 chars + overhead)
- Slow intersection: JavaScript array filtering for multi-field queries
- No hardware acceleration
**Solution**: Replace `Set<string>` with `RoaringBitmap32` (WebAssembly implementation) for 90% memory savings and hardware-accelerated operations. Uses `roaring-wasm` package for universal compatibility (Node.js, browsers, serverless) without requiring native compilation.
```typescript
// EntityIdMapper: UUID ↔ Integer mapping
class EntityIdMapper {
private uuidToInt = new Map<string, number>()
private intToUuid = new Map<number, string>()
private nextId = 1
getOrAssign(uuid: string): number {
// O(1) mapping: UUIDs → integers for bitmap storage
let intId = this.uuidToInt.get(uuid)
if (!intId) {
intId = this.nextId++
this.uuidToInt.set(uuid, intId)
this.intToUuid.set(intId, uuid)
}
return intId
}
intsIterableToUuids(ints: Iterable<number>): string[] {
// Convert bitmap results back to UUIDs
const result: string[] = []
for (const intId of ints) {
const uuid = this.intToUuid.get(intId)
if (uuid) result.push(uuid)
}
return result
}
}
// ChunkData now uses RoaringBitmap32 instead of Set<string>
class ChunkData {
chunkId: number
field: string
entries: Map<string, RoaringBitmap32> // value → bitmap of integer IDs
}
```
**Key Benefits**:
- **90% memory savings**: Roaring bitmaps compress much better than UUID strings
- **Hardware-accelerated operations**: SIMD instructions (AVX2/SSE4.2) for ultra-fast bitmap AND/OR
- **Portable serialization**: Cross-platform compatible format (Java/Go/Node.js)
- **Lazy conversion**: UUIDs converted to integers only once, not per query
**Multi-Field Intersection (THE BIG WIN!)**:
```typescript
// Before: JavaScript array filtering
async getIdsForFilter(filter: {status: 'active', role: 'admin'}): Promise<string[]> {
// 1. Fetch UUID arrays for each field
const statusIds = await this.getIds('status', 'active') // ["uuid1", "uuid2", ...]
const roleIds = await this.getIds('role', 'admin') // ["uuid2", "uuid3", ...]
// 2. JavaScript intersection (SLOW!)
return statusIds.filter(id => roleIds.includes(id)) // O(n*m) array filtering
}
// After: Roaring bitmap intersection
async getIdsForMultipleFields(pairs: [{field, value}, ...]): Promise<string[]> {
// 1. Fetch roaring bitmaps (integers, not UUIDs)
const bitmaps: RoaringBitmap32[] = []
for (const {field, value} of pairs) {
const bitmap = await this.getBitmapFromChunks(field, value)
if (!bitmap) return [] // Short-circuit if any field has no matches
bitmaps.push(bitmap)
}
// 2. Hardware-accelerated intersection (FAST! AVX2/SSE4.2 SIMD)
const result = RoaringBitmap32.and(...bitmaps) // O(1) hardware operation!
// 3. Convert final bitmap to UUIDs (once, not per-field)
return this.idMapper.intsIterableToUuids(result)
}
```
**Performance Impact**:
- Multi-field intersection: **1.4x average speedup**, up to 3.3x on 10K entities
- Memory usage: **90% reduction** (17.17 MB → 2.01 MB for 100K entities)
- Hardware acceleration: SIMD instructions make bitmap operations nearly free
**Benchmark Results** — example output from a single run of `tests/performance/roaring-bitmap-benchmark.ts` (1,000 queries per size, one machine; absolute times vary by hardware, the relative speedup and memory savings are the durable signal):
| Dataset Size | Operation | Set Time | Roaring Time | Speedup | Memory Savings |
|--------------|-----------|----------|--------------|---------|----------------|
| 10,000 entities | 3-field intersection | 3.74ms | 1.14ms | **3.3x faster** | 90% |
| 100,000 entities | 3-field intersection | 2.60ms | 1.78ms | **1.5x faster** | 88% |
**Implementation**: See `src/utils/entityIdMapper.ts` and benchmark at `tests/performance/roaring-bitmap-benchmark.ts`
#### Bloom Filter (Probabilistic Membership Testing)
```typescript
class BloomFilter {
bits: Uint8Array // Bit array
size: number // Total bits
hashCount: number // Number of hash functions (FNV-1a, DJB2)
mightContain(value): boolean // ~1% false positive, 0% false negative
}
```
**Use case**: Quickly skip chunks that definitely don't contain a value
#### Zone Map (Range Query Optimization)
```typescript
interface ZoneMap {
min: any | null // Minimum value in chunk
max: any | null // Maximum value in chunk
count: number // Number of entries
hasNulls: boolean // Whether chunk contains null values
}
```
**Use case**: Skip entire chunks during range queries (ClickHouse-inspired)
#### Type-Field Affinity
```typescript
// Tracks which fields are commonly used with which types
// Example:
// typeFieldAffinity.get('character') → {
// 'name': 127, // 127 characters have a 'name' field
// 'age': 89, // 89 characters have an 'age' field
// 'alignment': 45 // 45 characters have an 'alignment' field
// }
```
**Use case**: Enables NLP to understand "find characters named John" → knows 'name' is a character field
#### Word Index (`__words__`) -
```typescript
// Special field for text/keyword search
// Entity text content is tokenized and indexed as word hashes
// Tokenization:
// "David Smith is a software engineer" → ["david", "smith", "is", "software", "engineer"]
// Word Hashing (FNV-1a):
// "david" → hashWord("david") → 1234567 (int32)
// "smith" → hashWord("smith") → 9876543 (int32)
// Index structure (same as other fields):
// __words__ → 1234567 → RoaringBitmap{entity1, entity5, ...}
// __words__ → 9876543 → RoaringBitmap{entity1, entity3, ...}
```
**Design Decisions**:
- **Max 50 words per entity**: Prevents index bloat for large documents
- **FNV-1a hashing**: Fast, low collision rate, int32 output
- **Min word length 2 chars**: Filters out noise words
- **Lowercase normalization**: Case-insensitive matching
- **Automatic integration**: Words extracted via `extractIndexableFields()`
**Hybrid Search**: Text results combined with vector results using Reciprocal Rank Fusion (RRF):
```typescript
// RRF formula: score(d) = sum(1 / (k + rank(d)))
// where k = 60 (standard constant)
// alpha = weight for semantic (0 = text only, 1 = semantic only)
```
### Query Algorithm
**Exact Match Query**:
```typescript
async getIds(field: string, value: any): Promise<string[]> {
// 1. Load sparse index for field
const sparseIndex = await this.loadSparseIndex(field)
// 2. Find candidate chunks using bloom filters
const candidateChunks = sparseIndex.findChunksForValue(value)
// → Bloom filter checks all chunks (~1ms)
// → Returns only chunks that *might* contain value
// 3. Load candidate chunks and collect IDs
const results = []
for (const chunkId of candidateChunks) {
const chunk = await this.chunkManager.loadChunk(field, chunkId)
const ids = chunk.entries.get(value)
if (ids) results.push(...ids)
}
return results
}
```
**Range Query**:
```typescript
async getIdsForRange(field: string, min: any, max: any): Promise<string[]> {
// 1. Load sparse index for field
const sparseIndex = await this.loadSparseIndex(field)
// 2. Find candidate chunks using zone maps
const candidateChunks = sparseIndex.findChunksForRange(min, max)
// → Check zoneMap.min and zoneMap.max for each chunk
// → Skip chunks where max < min or min > max
// 3. Load chunks and filter values
const results = []
for (const chunkId of candidateChunks) {
const chunk = await this.chunkManager.loadChunk(field, chunkId)
for (const [value, ids] of chunk.entries) {
if (value >= min && value <= max) {
results.push(...ids)
}
}
}
return results
}
```
**Benefits**:
- Bloom filters: Skip 99% of irrelevant chunks (exact match)
- Zone maps: Skip entire chunks that fall outside range
- Adaptive chunking: ~50 values per chunk optimizes I/O
- Immediate flushing: No need for dirty tracking or batch writes
### Temporal Bucketing
**Problem Solved**: High-cardinality timestamp fields created massive file pollution.
- Example: 575 entities with unique timestamps → 358,407 index files (98.7% pollution!)
**Solution**: Automatic bucketing of temporal fields to 1-minute intervals.
```typescript
// In normalizeValue(value, field):
if (field && typeof value === 'number') {
const fieldLower = field.toLowerCase()
const isTemporal = fieldLower.includes('time') ||
fieldLower.includes('date') ||
fieldLower.includes('accessed') ||
fieldLower.includes('modified') ||
fieldLower.includes('created') ||
fieldLower.includes('updated')
if (isTemporal) {
// Bucket to 1-minute intervals
const bucketSize = 60000 // milliseconds
const bucketed = Math.floor(value / bucketSize) * bucketSize
return bucketed.toString()
}
}
```
**Benefits**:
- ✅ Reduces 575 unique timestamps → ~10 buckets
- ✅ File count: 358,407 → ~4,600 (98.7% reduction)
- ✅ Zero configuration - automatic field name detection
- ✅ Still enables range queries (not excluded like before)
- ✅ 1-minute precision sufficient for most use cases
**Field Name Detection**: Automatically buckets fields with these keywords:
- `time`, `date`, `accessed`, `modified`, `created`, `updated`
- Examples: `timestamp`, `createdAt`, `lastModified`, `birthdate`, `eventTime`
### Operations
```typescript
// Add to index (src/brainy.ts:387)
await this.metadataIndex.addToIndex(id, metadata)
// Query exact match
const ids = await this.metadataIndex.getIds('status', 'active')
// Query range
const ids = await this.metadataIndex.getIdsForFilter({
publishDate: { greaterThan: 1640995200000 }
})
// Filter discovery (what values exist for a field)
const values = await this.metadataIndex.getFilterValues('status')
// → ['active', 'archived', 'draft']
// Statistics (O(1))
const totalEntities = this.metadataIndex.getTotalEntityCount()
const typeBreakdown = this.metadataIndex.getAllEntityCounts()
// → Map { 'character': 127, 'item': 89, 'location': 45 }
```
### Excluded Fields
Some fields are excluded from indexing to prevent pollution:
```typescript
const DEFAULT_EXCLUDE_FIELDS = [
'id', // Primary key (redundant to index)
'uuid', // Alternative primary key
'vector', // High-dimensional data
'embedding', // Same as vector
'content', // Large text content
'description', // Large text content
'metadata', // Nested object (too large)
'data' // Generic nested object
]
```
**Note**: Timestamp fields like `modified`, `accessed`, `created` are NO LONGER excluded as of they are indexed with automatic bucketing.
## 2. Vector Index - Vector Similarity Search
**Purpose**: O(log n) semantic similarity search using vector embeddings.
The default JS implementation is `JsHnswVectorIndex`; an optional native acceleration package (`@soulcraft/cor`) can register a higher-performing `VectorIndexProvider` through the plugin system. The public API stays the same either way.
### Internal Architecture
```typescript
class JsHnswVectorIndex {
// Per-noun indexes for efficiency
private nouns: Map<string, HNSWNoun> = new Map()
// Global entry point for search
private entryPointId: string | null = null
private maxLevel = 0
// Shared memory management
private unifiedCache: UnifiedCache
private storage: BaseStorage | null = null
}
// Each noun has its own HNSW graph
class HNSWNoun {
noun: string
nodes: Map<string, HNSWNode>
entryPointId: string | null
maxLevel: number
}
// Each node in the graph
class HNSWNode {
id: string
vector: Vector | null // Lazy-loaded from storage
level: number
connections: Map<number, string[]> // level → neighbor IDs
}
```
### Hierarchical Graph Structure
The default vector index builds a multi-layered graph:
```
Layer 2: [entry] ←→ [node1] (sparse, long-range connections)
↓ ↓
Layer 1: [entry] ←→ [node1] ←→ [node2] ←→ [node3] (medium density)
↓ ↓ ↓ ↓
Layer 0: [entry] ←→ [node1] ←→ [node2] ←→ [node3] ←→ [node4] ←→ [node5] (dense, all nodes)
```
**Search Algorithm**:
1. Start at entry point in top layer
2. Greedy search for nearest neighbor in current layer
3. Move down to next layer with found neighbor
4. Repeat until reaching layer 0
5. Return k nearest neighbors
**Complexity**: O(log n) due to hierarchical structure
### Adaptive Vector Loading
Vectors are lazy-loaded on demand based on memory availability:
```typescript
private async getVectorSafe(noun: HNSWNoun): Promise<Vector> {
// Check UnifiedCache first
const cached = this.unifiedCache.get(noun.id)
if (cached) return cached
// Load from storage if memory available
if (this.unifiedCache.canCache()) {
const vector = await this.storage.loadVector(noun.id)
this.unifiedCache.set(noun.id, vector)
return vector
}
// Load transiently if memory pressure
return await this.storage.loadVector(noun.id)
}
```
### Operations
```typescript
// Add entity (src/brainy.ts:add)
await this.index.addEntity(id, vector, noun)
// Search for similar vectors
const results = await this.index.search(queryVector, k, threshold)
// Returns: Array<{id: string, similarity: number}>
// Rebuild from storage
await this.index.rebuild()
```
## 3. GraphAdjacencyIndex - O(1) Relationship Traversal
**Purpose**: Constant-time neighbor lookups regardless of graph size.
### Internal Architecture
```typescript
class GraphAdjacencyIndex {
// O(1) bidirectional lookups
private sourceIndex = new Map<string, Set<string>>() // sourceId → targetIds
private targetIndex = new Map<string, Set<string>>() // targetId → sourceIds
// Full relationship data
private verbIndex = new Map<string, GraphVerb>() // verbId → metadata
// Statistics
private relationshipCountsByType = new Map<string, number>()
// Shared memory
private unifiedCache: UnifiedCache
private storage: BaseStorage
}
```
### Key Innovation: Bidirectional Adjacency
**Core Insight**: Store BOTH directions of each relationship for O(1) lookups.
```typescript
// Example: Alice KNOWS Bob
// verbId = "verb-123"
// Source index: Alice → Bob
sourceIndex.set('alice', Set(['bob']))
// Target index: Bob ← Alice
targetIndex.set('bob', Set(['alice']))
// Full metadata
verbIndex.set('verb-123', {
id: 'verb-123',
verb: 'knows',
source: 'alice',
target: 'bob',
metadata: { since: 2020 }
})
```
**Result**: Finding Alice's friends OR Bob's friends is O(1) - just one Map lookup!
### Operations
```typescript
// Add relationship (src/brainy.ts:relate)
await this.graphIndex.addRelationship(verbId, sourceId, targetId, verb)
// Get neighbors (O(1) per hop)
const outgoing = await this.graphIndex.getNeighbors(id, 'out') // Who does id point to?
const incoming = await this.graphIndex.getNeighbors(id, 'in') // Who points to id?
const both = await this.graphIndex.getNeighbors(id, 'both') // All neighbors
// Get relationships
const verbs = await this.graphIndex.getRelationships(sourceId, targetId)
// Statistics (O(1))
const totalRelationships = this.graphIndex.getTotalRelationshipCount()
const byType = this.graphIndex.getRelationshipCountsByType()
// → Map { 'knows': 45, 'created': 23, 'located_at': 12 }
```
### Graph Traversal
The index supports multi-hop traversal:
```typescript
// Find all entities within 2 hops
const reachable = await this.graphIndex.traverse({
startId: 'alice',
depth: 2,
direction: 'out'
})
// Complexity: O(V + E) breadth-first search, but each neighbor lookup is O(1)
```
## Shared Memory Management: UnifiedCache
All three main indexes share a single **UnifiedCache** instance for coordinated memory management.
### Architecture
```typescript
class UnifiedCache {
private cache: Map<string, CachedItem> = new Map()
private maxSize: number
private currentSize: number = 0
private evictionPolicy: 'LRU' | 'LFU' = 'LRU'
}
// Each index gets the same cache instance
const unifiedCache = new UnifiedCache({ maxSize: 1000 })
this.metadataIndex = new MetadataIndexManager(storage, { unifiedCache })
this.vectorIndex = new JsHnswVectorIndex(storage, { unifiedCache })
this.graphIndex = new GraphAdjacencyIndex(storage, { unifiedCache })
```
### Benefits
1. **Fair Resource Allocation**: All indexes compete for the same memory pool
2. **Prevents Monopolization**: No single index can starve others of memory
3. **Coordinated Eviction**: LRU eviction across all cached items system-wide
4. **Memory Pressure Handling**: Automatic cache shrinking when memory is tight
5. **Adaptive Loading**: Indexes load data transiently under memory pressure
### Cache Key Patterns
Each index uses different key prefixes:
```typescript
// Metadata index
cache.set(`meta:${field}:${value}`, indexEntry)
// Vector index
cache.set(`vector:${id}`, vectorData)
// Graph index
cache.set(`graph:${sourceId}`, neighbors)
// Deleted items (no caching needed - uses Set)
```
## How Indexes Work Together
### 1. Entity Creation (`brainy.add()`)
```typescript
// src/brainy.ts:add()
async add(params: AddParams): Promise<string> {
const id = generateId()
const vector = await this.embedder(params.content)
// Add to metadata index (field filtering)
await this.metadataIndex.addToIndex(id, params.metadata)
// Add to vector index (vector search)
await this.index.addEntity(id, vector, params.noun)
// Relationships added via separate relate() calls
return id
}
```
### 2. Entity Search (`brainy.find()`)
```typescript
// src/brainy.ts:find()
async find(query: FindQuery): Promise<Result[]> {
let results: Result[] = []
// Step 1: Metadata filtering (fast pre-filter)
if (query.where) {
const filteredIds = await this.metadataIndex.getIdsForFilter(query.where)
results = await this.getEntitiesByIds(filteredIds)
}
// Step 2: Vector similarity search (semantic ranking)
if (query.like) {
const queryVector = await this.embedder(query.like)
const vectorResults = await this.index.search(queryVector, query.limit)
// Intersect or union with metadata results
results = this.combineResults(results, vectorResults)
}
// Step 3: Graph traversal (relationship filtering)
if (query.connected) {
const connectedIds = await this.graphIndex.traverse(query.connected)
results = results.filter(r => connectedIds.includes(r.id))
}
return results
}
```
### 3. Entity Update (`brainy.update()`)
```typescript
// src/brainy.ts:update()
async update(params: UpdateParams): Promise<void> {
const existing = await this.get(params.id)
// Update metadata index (remove old, add new)
await this.metadataIndex.removeFromIndex(params.id, existing.metadata)
await this.metadataIndex.addToIndex(params.id, params.metadata)
// Update vector index (re-embed if content changed)
if (params.content) {
const newVector = await this.embedder(params.content)
await this.index.updateEntity(params.id, newVector)
}
// Graph relationships unchanged (managed separately)
}
```
### 4. Statistics (`brainy.stats()`)
All indexes provide O(1) statistics:
```typescript
// src/brainy.ts:stats()
async stats(): Promise<Statistics> {
return {
// From metadata index
entities: this.metadataIndex.getTotalEntityCount(),
entityTypes: this.metadataIndex.getAllEntityCounts(),
// From graph index
relationships: this.graphIndex.getTotalRelationshipCount(),
relationshipTypes: this.graphIndex.getRelationshipCountsByType(),
// From vector index
vectorIndexSize: this.index.getSize()
}
}
```
### 5. Index Rebuilding (Lazy Loading Support)
**Two modes of index loading:**
#### Mode 1: Auto-Rebuild on init() (default)
```typescript
// src/brainy.ts:init()
async init(): Promise<void> {
// When disableAutoRebuild: false (default)
const metadataStats = await this.metadataIndex.getStats()
const vectorIndexSize = this.index.size()
const graphIndexSize = await this.graphIndex.size()
if (metadataStats.totalEntries === 0 ||
vectorIndexSize === 0 ||
graphIndexSize === 0) {
// Rebuild all indexes in parallel
await Promise.all([
metadataStats.totalEntries === 0 ? this.metadataIndex.rebuild() : Promise.resolve(),
vectorIndexSize === 0 ? this.index.rebuild() : Promise.resolve(),
graphIndexSize === 0 ? this.graphIndex.rebuild() : Promise.resolve()
])
}
}
```
#### Mode 2: Lazy Loading on First Query
```typescript
// When disableAutoRebuild: true
const brain = new Brainy({
storage: { type: 'filesystem' },
disableAutoRebuild: true // Enable lazy loading
})
await brain.init() // Returns instantly, indexes empty
// First query triggers lazy rebuild
const results = await brain.find({ limit: 10 })
// → Calls ensureIndexesLoaded() (line 4617)
// → Rebuilds all 3 main indexes with concurrency control
// → Subsequent queries are instant (0ms check)
```
**Performance:**
- First query with lazy loading: ~50-200ms rebuild (1K-10K entities)
- Concurrent queries: Wait for same rebuild (mutex prevents duplicates)
- Subsequent queries: 0ms check (instant)
See [initialization-and-rebuild.md](./initialization-and-rebuild.md) for detailed lazy loading implementation.
## Triple Intelligence Integration
The **TripleIntelligenceSystem** (`src/triple/TripleIntelligenceSystem.ts`) combines all three core indexes:
```typescript
class TripleIntelligenceSystem {
constructor(
private metadataIndex: MetadataIndexManager,
private vectorIndex: VectorIndexProvider,
private graphIndex: GraphAdjacencyIndex,
private embedder: EmbedderFunction,
private storage: BaseStorage
) {}
async query(nlpQuery: string): Promise<Result[]> {
// Parse natural language
const parsed = await this.parseQuery(nlpQuery)
// Execute across all three indexes
const [metadataResults, vectorResults, graphResults] = await Promise.all([
this.metadataIndex.getIdsForFilter(parsed.filters),
this.vectorIndex.search(parsed.vector, parsed.limit),
this.graphIndex.traverse(parsed.graphConstraints)
])
// Fuse results with weighted scoring
return this.fuseResults(metadataResults, vectorResults, graphResults)
}
}
```
## Performance Characteristics
### Operation Complexity by Index
| Operation | MetadataIndexManager | TypeAwareVectorIndex | GraphAdjacencyIndex |
|-----------|---------------------|-------------------|---------------------|
| **Add** | O(1) per field | O(log n) | O(1) |
| **Remove** | O(1) per field | O(log n) | O(1) |
| **Exact lookup** | O(1) | N/A | O(1) |
| **Range query** | O(log n) + O(k) | N/A | N/A |
| **Similarity search** | N/A | O(log n) | N/A |
| **Neighbor lookup** | N/A | N/A | O(1) |
| **Statistics** | O(1) | O(1) | O(1) |
| **Rebuild** | O(n) | O(n) | O(n) |
Where:
- n = total number of entities
- k = number of matching results
**Note**: All 3 main indexes have rebuild() methods that load persisted data (O(n)) rather than recomputing (which would be O(n log n) for the vector index).
### Memory Footprint
| Index | Per-Entity Memory | Notes |
|-------|-------------------|-------|
| **MetadataIndexManager** | ~100 bytes | Depends on field count and cardinality (RoaringBitmap32 compression) |
| **TypeAwareVectorIndex** | ~1.5 KB | Vector (384 dims × 4 bytes) + graph connections across 42 type-specific indexes |
| **GraphAdjacencyIndex** | ~50 bytes per relationship | Bidirectional verb-id references in 2 LSM-trees |
**Total overhead**: ~1.6 KB per entity + ~50 bytes per relationship
**Sub-index memory:**
- ChunkManager: ~20 bytes per chunk descriptor
- EntityIdMapper: ~32 bytes per UUID mapping (50-90% savings vs Set\<string\>)
- LSM-trees: ~200 bytes per relationship (SSTable storage)
### Scalability
All indexes scale gracefully. The cost of each stage is governed 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) |
**Key observations**:
- Graph queries stay O(1) regardless of scale
- Metadata filtering scales sub-linearly
- Vector search degrades gracefully due to the hierarchical index
- Combined queries remain fast even at scale
## Best Practices
### When to Use Each Index
**MetadataIndex**:
- Filtering by exact field values (status, type, category)
- Range queries on numeric/temporal fields (dates, prices, counts)
- Field discovery (what filters are available)
- Type-based querying (find all characters, all items)
**Vector Index**:
- Semantic similarity search ("find similar documents")
- Content-based retrieval ("find posts about AI")
- Fuzzy matching (when exact matches aren't required)
- Recommendation systems (find related items)
**GraphAdjacencyIndex**:
- Relationship queries ("who knows whom")
- Path finding ("how are these entities connected")
- Network analysis ("find communities")
- Multi-hop traversal ("friends of friends")
**Note**: Soft-delete functionality is not currently integrated. Brainy uses hard deletes via storage layer.
### Query Optimization
1. **Start with metadata filters** - They're fastest and most selective
2. **Use graph constraints** - O(1) lookups significantly reduce search space
3. **Vector search last** - Most expensive, best used on pre-filtered set
4. **Leverage temporal bucketing** - Timestamp range queries work efficiently
5. **Monitor statistics** - Use O(1) stats methods for cardinality estimation
### Memory Management
1. **Configure UnifiedCache appropriately** - Balance between speed and memory
2. **Use lazy loading** - Vector index loads vectors on-demand
3. **Monitor cache hit rates** - Adjust cache size if hit rate is low
4. **Consider storage adapter** - Memory = fastest, filesystem = persistent
## Related Documentation
- [Find System](../FIND_SYSTEM.md) - Query-centric view of index usage
- [Triple Intelligence](./triple-intelligence.md) - Advanced query system
- [Storage Architecture](./storage-architecture.md) - Storage layer details
- [Performance Guide](../PERFORMANCE.md) - Performance tuning
- [Overview](./overview.md) - High-level architecture
## Summary: Index Hierarchy
### Level 1: Main Indexes (3)
All have rebuild() methods and are covered by lazy loading:
1. **TypeAwareVectorIndex** - `src/hnsw/typeAwareHNSWIndex.ts:403`
2. **MetadataIndexManager** - `src/utils/metadataIndex.ts:2318`
3. **GraphAdjacencyIndex** - `src/graph/graphAdjacencyIndex.ts:389`
### Level 2: Sub-Indexes (~50+)
Automatically managed by parent rebuild():
- **42 type-specific vector indexes** (one per NounType)
- **6 metadata components** (ChunkManager, EntityIdMapper, FieldTypeInference, Field Sparse Indexes, Sorted Indexes)
- **2 LSM-trees** (lsmTreeVerbsBySource, lsmTreeVerbsByTarget — the verb set is the single adjacency source of truth; neighbor reads derive from live verbs so removals are honored)
- **In-memory graph structures** (sourceIndex, targetIndex, verbIndex)
### Lazy Loading
- **Mode 1**: Auto-rebuild on init() (default)
- **Mode 2**: Lazy rebuild on first query (when `disableAutoRebuild: true`)
- **Concurrency-safe**: Mutex prevents duplicate rebuilds
- **Performance**: First query ~50-200ms, subsequent queries instant
### Total Functional Index Count
- **3 main indexes** with independent rebuild() methods
- **~50+ sub-components** managed automatically
- **All covered** by rebuildIndexesIfNeeded() or built-in lazy initialization
## Version History
- **v5.7.7** (November 2025): Added production-scale lazy loading with concurrency control. Fixed critical bug where `disableAutoRebuild: true` left indexes empty forever. Added `ensureIndexesLoaded()` helper and `getIndexStatus()` diagnostic.
- **v3.43.0** (October 2025): Migrated from `roaring` (native C++) to `roaring-wasm` (WebAssembly) for universal compatibility. No API changes - maintains identical RoaringBitmap32 interface. Benefits: works in all environments (Node.js, browsers, serverless) without build tools, zero compilation errors, simpler developer experience. 90% memory savings and hardware-accelerated operations unchanged.
- **v3.42.0** (October 2025): Replaced flat file indexing with adaptive chunked sparse indexing. Bloom filters + zone maps for O(1) exact match and O(log n) range queries. 630x file reduction (560k → 89 files). Removed dual code paths.
- **v3.41.0** (October 2025): Added automatic temporal bucketing to MetadataIndex
- **v3.40.0** (October 2025): Enhanced batch processing for imports
- **v3.0.0** (September 2025): Introduced 3-tier index architecture with UnifiedCache

View file

@ -1,713 +0,0 @@
# Initialization and Rebuild Processes
This document explains how Brainy's four indexes (MetadataIndex, vector index, GraphAdjacencyIndex, DeletedItemsIndex) initialize and rebuild from persisted storage.
## Core Principle: All Indexes Are Disk-Based
**KEY INSIGHT**: All indexes in Brainy are already disk-based. There is no need for snapshots or separate backup mechanisms. Initialization simply loads the right amount of data from storage into memory based on available resources.
### What Gets Persisted
| Index | Persisted Data | Storage Method | Since Version |
|-------|---------------|----------------|---------------|
| **MetadataIndex** | Field registry + chunked sparse indices with bloom filters + zone maps | `storage.saveMetadata()` | v3.42.0 (chunks), v4.2.1 (registry) |
| **Vector Index** | Vector embeddings + graph connections | `storage.saveHNSWData()` + `storage.saveHNSWSystem()` | v3.35.0 |
| **GraphAdjacencyIndex** | Relationships via LSM-tree SSTables | LSM-tree auto-persistence | v3.44.0 |
| **DeletedItemsIndex** | Set of deleted IDs | `storage.saveDeletedItems()` | v3.0.0 |
#### MetadataIndex Persistence Details
The MetadataIndex now persists two components:
1. **Field Registry** (`__metadata_field_registry__`): Directory of indexed fields for O(1) discovery
- Size: ~4-8KB (50-200 fields typical)
- Enables instant cold starts by discovering persisted indices
- Auto-saved during every flush operation
2. **Sparse Indices** (`__sparse_index__<field>`): Per-field index directories
- Contains chunk metadata, zone maps, and bloom filters
- Lazy-loaded via UnifiedCache on first query
3. **Chunks** (`__metadata_chunk__<field>_<chunkId>`): Actual inverted index data
- Roaring bitmaps for compressed entity ID storage
- Loaded on-demand based on query patterns
All storage operations use the **StorageAdapter** interface, which works with FileSystem and Memory backends.
## Initialization Process
### 1. Lazy Initialization Pattern
All indexes use lazy initialization - they don't load data until first use:
```typescript
// Example: GraphAdjacencyIndex
class GraphAdjacencyIndex {
private initialized = false
private async ensureInitialized(): Promise<void> {
if (this.initialized) return
// Initialize LSM-trees from storage
await this.lsmTreeSource.init()
await this.lsmTreeTarget.init()
this.initialized = true
}
// Every public method calls ensureInitialized() first
async getNeighbors(id: string): Promise<string[]> {
await this.ensureInitialized() // Lazy init!
// ... actual logic
}
}
```
**Benefits**:
- Zero-cost abstraction: No initialization overhead if index not used
- Faster startup: Indexes initialize in parallel on first use
- Lower memory: Only used indexes consume memory
### 2. Brain Initialization Flow
When you create a `Brain` instance and call `init()`, behavior depends on the `disableAutoRebuild` configuration:
#### Mode 1: Auto-Rebuild on init() (Default)
```typescript
// src/brainy.ts (lines 192-237)
async init(): Promise<void> {
const initStartTime = Date.now()
// STEP 1: Initialize storage and unified cache
await this.storage.init()
// STEP 2: Check index sizes (lazy initialization triggers here)
const metadataStats = await this.metadataIndex.getStats()
const vectorIndexSize = this.index.size()
const graphIndexSize = await this.graphIndex.size()
// STEP 3: Rebuild empty indexes from storage in parallel
if (metadataStats.totalEntries === 0 ||
vectorIndexSize === 0 ||
graphIndexSize === 0) {
const rebuildStartTime = Date.now()
await Promise.all([
metadataStats.totalEntries === 0
? this.metadataIndex.rebuild()
: Promise.resolve(),
vectorIndexSize === 0
? this.index.rebuild()
: Promise.resolve(),
graphIndexSize === 0
? this.graphIndex.rebuild()
: Promise.resolve()
])
const rebuildDuration = Date.now() - rebuildStartTime
console.log(`✅ All indexes rebuilt in ${rebuildDuration}ms`)
}
// STEP 4: Log statistics
const stats = await this.stats()
console.log(`📊 Brain initialized with ${stats.entities} entities`)
}
```
**Timeline** (typical cold start with 10K entities):
- 0-50ms: Storage adapter initialization
- 50-100ms: Field registry loading (O(1) discovery of persisted indices)
- 100-200ms: Index lazy initialization (LSM-tree loading)
- 200-500ms: Cache warming (preload common fields)
- **No rebuild needed!** Registry discovers existing indices
- Total: ~0.5-1 second (instant cold starts)
**Timeline** (cold start WITHOUT field registry - first run only):
- 0-50ms: Storage adapter initialization
- 50-100ms: Index lazy initialization
- 100-2000ms: One-time rebuild to create indices
- Total: ~1-3 seconds (one time only)
#### Mode 2: Lazy Loading on First Query
When `disableAutoRebuild: true`, indexes remain empty after init() and rebuild on first query:
```typescript
// User code
const brain = new Brainy({
storage: { type: 'filesystem' },
disableAutoRebuild: true // Enable lazy loading
})
await brain.init() // Returns instantly (0-10ms)
// First query triggers lazy rebuild
const results = await brain.find({ limit: 10 })
// → Calls ensureIndexesLoaded() internally (brainy.ts:4617)
// → Rebuilds all 3 main indexes with concurrency control
// → Returns results (~50-200ms total for 1K-10K entities)
// Subsequent queries are instant
const more = await brain.find({ limit: 100 }) // 0ms check, instant
```
**ensureIndexesLoaded() Implementation** (brainy.ts:4617-4664):
```typescript
private async ensureIndexesLoaded(): Promise<void> {
// Fast path: Already loaded
if (this.lazyRebuildCompleted) {
return // 0ms
}
// Concurrency control: Wait for in-progress rebuild
if (this.lazyRebuildInProgress && this.lazyRebuildPromise) {
await this.lazyRebuildPromise // Wait for same rebuild
return
}
// Check if storage has data
const entities = await this.storage.getNouns({ pagination: { limit: 1 } })
const hasData = (entities.totalCount && entities.totalCount > 0) || entities.items.length > 0
if (!hasData) {
this.lazyRebuildCompleted = true
return
}
// Start lazy rebuild with mutex
this.lazyRebuildInProgress = true
this.lazyRebuildPromise = this.rebuildIndexesIfNeeded(true)
.then(() => {
this.lazyRebuildCompleted = true
})
.finally(() => {
this.lazyRebuildInProgress = false
this.lazyRebuildPromise = null
})
await this.lazyRebuildPromise
}
```
**Lazy Loading Performance:**
- First query: ~50-200ms (1K-10K entities) - triggers rebuild
- Concurrent queries: Wait for same rebuild (mutex prevents duplicates)
- Subsequent queries: 0ms check (instant)
- Zero-config: Works automatically, no code changes needed
**Use Cases for Lazy Loading:**
- **Serverless/Edge**: Minimize cold start time, indexes load on demand
- **Development**: Faster restarts during development
- **Large datasets**: Defer index loading until actually needed
- **Read-heavy workloads**: Write operations don't wait for index rebuild
## Rebuild Process
### What "Rebuild" Actually Means
**IMPORTANT**: "Rebuild" does NOT mean recomputing data. It means:
1. **Load persisted data** from storage (vector index connections, metadata chunks, LSM-tree SSTables)
2. **Populate in-memory structures** (Maps, Sets, graphs)
3. **Apply adaptive caching** (preload vectors if small dataset, lazy load if large)
**Complexity**: O(N) - linear scan through storage, NOT O(N log N) recomputation!
### 1. Vector Index Rebuild (Correct Pattern)
```typescript
// src/hnsw/hnswIndex.ts (lines 809-947)
public async rebuild(options: {
lazy?: boolean
batchSize?: number
onProgress?: (loaded: number, total: number) => void
} = {}): Promise<void> {
// STEP 1: Clear in-memory structures
this.clear()
// STEP 2: Load system data (entry point, max level)
const systemData = await this.storage.getHNSWSystem()
this.entryPointId = systemData.entryPointId
this.maxLevel = systemData.maxLevel
// STEP 3: Determine preloading strategy (adaptive caching)
const totalNouns = await this.storage.getNounCount()
const vectorMemory = totalNouns * 384 * 4 // 384 dims × 4 bytes
const availableCache = this.unifiedCache.getRemainingCapacity()
const shouldPreload = vectorMemory < availableCache * 0.3
// STEP 4: Load entities with persisted vector index connections
let hasMore = true
let cursor: string | undefined = undefined
while (hasMore) {
const result = await this.storage.getNouns({
pagination: { limit: 1000, cursor }
})
for (const nounData of result.items) {
// Load vector graph data from storage (NOT recomputed!)
const hnswData = await this.storage.getHNSWData(nounData.id)
// Create noun with restored connections
const noun: HNSWNoun = {
id: nounData.id,
vector: shouldPreload ? nounData.vector : [], // Adaptive!
connections: new Map(),
level: hnswData.level
}
// Restore connections from persisted data
for (const [levelStr, nounIds] of Object.entries(hnswData.connections)) {
const level = parseInt(levelStr, 10)
noun.connections.set(level, new Set<string>(nounIds))
}
// Just add to memory (no recomputation!)
this.nouns.set(nounData.id, noun)
}
hasMore = result.hasMore
cursor = result.nextCursor
}
}
```
**Key Points**:
- ✅ Loads vector index connections from storage via `getHNSWData()`
- ✅ Uses adaptive caching (preload vectors if < 30% of available cache)
- ✅ O(N) complexity - just loads existing data
- ❌ Does NOT call `addItem()` which would recompute connections (O(N log N))
### 2. TypeAwareVectorIndex Rebuild (Fixed in v3.45.0)
**Critical Architectural Fix**: The type-aware vector index previously had TWO major bugs:
1. **Bug #1**: Called `addItem()` during rebuild → O(N log N) recomputation instead of O(N) loading
2. **Bug #2**: Loaded ALL nouns 31 times in parallel (once per type) → O(31*N) complexity causing timeouts
Both were fixed in v3.45.0 by loading ALL nouns ONCE and routing to correct type indexes:
```typescript
// src/hnsw/typeAwareHNSWIndex.ts (lines 379-571)
public async rebuild(options?: {
lazy?: boolean
batchSize?: number
onProgress?: (loaded: number, total: number) => void
}): Promise<void> {
// STEP 1: Clear all type-specific indexes
for (const index of this.typeIndexes.values()) {
index.clear()
}
// STEP 2: Determine preloading strategy (same as vector index)
const totalNouns = await this.storage.getNounCount()
const vectorMemory = totalNouns * 384 * 4
const availableCache = this.unifiedCache.getRemainingCapacity()
const shouldPreload = vectorMemory < availableCache * 0.3
// STEP 3: Load entities grouped by type
for (const nounType of ALL_NOUN_TYPES) {
const index = this.getOrCreateIndex(nounType)
let hasMore = true
let cursor: string | undefined = undefined
while (hasMore) {
const result = await this.storage.getNouns({
type: nounType,
pagination: { limit: 1000, cursor }
})
for (const nounData of result.items) {
// CORRECT: Load persisted vector index data (not recomputed!)
const hnswData = await this.storage.getHNSWData(nounData.id)
const noun = {
id: nounData.id,
vector: shouldPreload ? nounData.vector : [],
connections: new Map(),
level: hnswData.level
}
// Restore connections from storage
for (const [levelStr, nounIds] of Object.entries(hnswData.connections)) {
const level = parseInt(levelStr, 10)
noun.connections.set(level, new Set<string>(nounIds))
}
// Add to in-memory index (no recomputation!)
index.nouns.set(nounData.id, noun)
}
hasMore = result.hasMore
cursor = result.nextCursor
}
}
}
```
**Bug Fix**: Changed from `index.addItem()` (recomputation) to direct `nouns.set()` (restoration).
**Performance Impact**: 200-600x speedup (5 minutes → 500ms for 10K entities)
**Correct Pattern**:
```typescript
// Load ALL nouns ONCE (not 31 times!)
while (hasMore) {
const result = await storage.getNounsWithPagination({ limit: 1000, cursor })
for (const noun of result.items) {
const type = noun.nounType || noun.metadata?.noun
const index = this.getIndexForType(type)
// Load persisted HNSW data
const hnswData = await storage.getHNSWData(noun.id)
// Restore connections (not recompute!)
const restoredNoun = {
id: noun.id,
vector: shouldPreload ? noun.vector : [],
connections: restoreConnections(hnswData),
level: hnswData.level
}
// Add to correct type index
index.nouns.set(noun.id, restoredNoun)
}
cursor = result.nextCursor
hasMore = result.hasMore
}
```
**Performance Improvements**:
- 31x speedup: Load nouns ONCE instead of 31 times (O(N) vs O(31*N))
- 200-600x speedup: Load from storage instead of recomputing (O(N) vs O(N log N))
- **Combined**: ~6000x speedup! (150 minutes → 1.5 seconds for 10K entities)
### 3. MetadataIndex Rebuild (v4.2.1+ with Field Registry)
**v4.2.1 Critical Fix**: Field registry persistence eliminates unnecessary rebuilds!
```typescript
// src/utils/metadataIndex.ts (lines 202-216)
async init(): Promise<void> {
// STEP 1: Load field registry to discover persisted indices
// This is THE KEY FIX - O(1) discovery of existing indices
await this.loadFieldRegistry()
// If registry found, fieldIndexes Map is now populated
// getStats() will return totalEntries > 0 → skips rebuild!
// STEP 2: Initialize EntityIdMapper
await this.idMapper.init()
// STEP 3: Warm cache with discovered fields
await this.warmCache()
}
async loadFieldRegistry(): Promise<void> {
const registry = await this.storage.getMetadata('__metadata_field_registry__')
if (registry?.fields) {
// Populate fieldIndexes Map from discovered fields
// Sparse indices are lazy-loaded when first accessed
for (const field of registry.fields) {
this.fieldIndexes.set(field, {
values: {},
lastUpdated: registry.lastUpdated
})
}
// Result: getStats() now returns totalEntries > 0
// → Brain skips rebuild, cold start in 2-3 seconds!
}
}
```
**Rebuild Only Happens If**:
1. **First run** (no field registry exists yet)
2. **Registry corruption** (rare)
3. **Explicit rebuild request** (manual operation)
```typescript
// Only runs if field registry not found
async rebuild(): Promise<void> {
// STEP 1: Clear in-memory structures
this.fieldIndexes.clear()
// STEP 2: Load all entity metadata and rebuild indices
// Sequential batching (25/batch) to prevent socket exhaustion
// After rebuild: Field registry saved during next flush()
// One-time cost: ~2-3 seconds for 1K entities
}
```
**Performance Comparison**:
| Version | Cold Start | Discovery Method | Rebuild Needed? |
|---------|------------|------------------|-----------------|
| v4.2.0 | 8-9 min | None (always rebuild) | Always |
| v4.2.1 | 2-3 sec | Field registry O(1) | First run only |
**Key Points**:
- ✅ Field registry enables O(1) discovery (4-8KB file)
- ✅ Sparse indices lazy-loaded on first query
- ✅ Bloom filters + zone maps loaded for fast filtering
- ✅ One-time rebuild on first run, then instant restarts forever
- ✅ Automatic: No configuration needed
### 4. GraphAdjacencyIndex Rebuild
```typescript
// src/graph/graphAdjacencyIndex.ts (lines 279-336)
async rebuild(): Promise<void> {
// STEP 1: Clear in-memory caches
this.verbIndex.clear()
this.relationshipCountsByType.clear()
// STEP 2: Load all verbs from storage
let hasMore = true
let cursor: string | undefined = undefined
while (hasMore) {
const result = await this.storage.getVerbs({
pagination: { limit: 1000, cursor }
})
for (const verb of result.items) {
// Add to index (which updates LSM-trees)
await this.addVerb(verb)
}
hasMore = result.hasMore
cursor = result.nextCursor
}
// Note: LSM-trees (lsmTreeSource, lsmTreeTarget) are already
// initialized from persisted SSTables during ensureInitialized()
}
```
**Key Points**:
- ✅ LSM-tree SSTables already loaded during `init()`
- ✅ Rebuild just repopulates verb cache
- ✅ O(E) complexity where E = number of edges
## Adaptive Memory Management
### Strategy: Preload vs Lazy Load
All indexes use the **UnifiedCache** to determine memory allocation:
```typescript
// Decision logic (in all indexes)
const totalDataSize = estimateDataSize()
const availableCache = unifiedCache.getRemainingCapacity()
if (totalDataSize < availableCache * 0.3) {
// PRELOAD: Dataset is small relative to available memory
// Load everything into memory for maximum performance
shouldPreload = true
} else {
// LAZY LOAD: Dataset is large
// Load on-demand with LRU eviction
shouldPreload = false
}
```
**Thresholds**:
- **< 30% of available cache**: Preload all vectors
- **> 30% of available cache**: Lazy load on demand
**Example** (default 100MB cache):
- 10K entities × 1.5KB = 15MB → **Preload** (15MB < 30MB)
- 100K entities × 1.5KB = 150MB → **Lazy load** (150MB > 30MB)
### UnifiedCache Integration
```typescript
// All indexes share the same cache
const unifiedCache = getGlobalCache() // Singleton, 100MB default
// MetadataIndex
this.unifiedCache = unifiedCache
// Vector index
this.unifiedCache = unifiedCache
// GraphAdjacencyIndex
this.unifiedCache = unifiedCache
```
**Benefits**:
- Fair resource allocation across indexes
- Prevents any single index from monopolizing memory
- Coordinated LRU eviction system-wide
## Performance Characteristics
### Rebuild Times (Typical Hardware)
| Dataset Size | Metadata | Vector | Graph | Total (Parallel) |
|--------------|----------|------|-------|------------------|
| 1K entities | 50ms | 100ms | 30ms | **150ms** |
| 10K entities | 200ms | 500ms | 150ms | **600ms** |
| 100K entities | 1s | 3s | 1s | **3.5s** |
| 1M entities | 8s | 25s | 10s | **28s** |
**Note**: Parallel rebuild means total time ≈ max(individual times), not sum.
### Memory Overhead
| Index | In-Memory Overhead | Disk Storage |
|-------|-------------------|--------------|
| **MetadataIndex** | ~100 bytes/entity | ~500 bytes/entity (chunks) |
| **Vector Index** | ~200 bytes/entity (no vectors) | ~1.5 KB/entity (vectors + connections) |
| **GraphAdjacencyIndex** | ~128 bytes/relationship | ~200 bytes/relationship (LSM-tree) |
| **DeletedItemsIndex** | ~40 bytes/deleted ID | ~50 bytes/deleted ID |
**Total overhead** (lazy loading):
- **In-memory**: ~300 bytes per entity + ~128 bytes per relationship
- **On-disk**: ~2 KB per entity + ~200 bytes per relationship
### O(N) vs O(N log N) Comparison
**Before fix** (TypeAwareVectorIndex bug):
```typescript
// BAD: Recomputes vector index connections during rebuild
for (const noun of nouns) {
await index.addItem(noun) // O(log N) per item → O(N log N) total
}
// 10K entities: ~5 minutes
```
**After fix** (correct pattern):
```typescript
// GOOD: Loads connections from storage
for (const noun of nouns) {
const hnswData = await storage.getHNSWData(noun.id) // O(1) per item
noun.connections = restoreConnections(hnswData) // O(1) per item
index.nouns.set(noun.id, noun) // O(1) per item
}
// 10K entities: ~500ms (600x faster!)
```
## Common Patterns
### Cold Start (Empty Storage)
```typescript
const brain = new Brain({ storage })
// First init: All indexes are empty
await brain.init()
// → No rebuild needed, indexes start empty
// Add data
await brain.add({ content: 'Hello', noun: 'message' })
// Second init: Indexes populated
const brain2 = new Brain({ storage })
await brain2.init()
// → Rebuilds all indexes from storage (~1-3s for 10K entities)
```
### Warm Start (Storage Already Populated)
```typescript
const brain = new Brain({ storage })
// Init with existing data
await brain.init()
// → Detects non-empty storage
// → Rebuilds indexes in parallel
// → Uses adaptive caching (preload if small, lazy if large)
```
### Manual Rebuild
```typescript
const brain = new Brain({ storage })
await brain.init()
// Force rebuild (e.g., after data corruption)
await brain.metadataIndex.rebuild()
await brain.index.rebuild()
await brain.graphIndex.rebuild()
```
## Troubleshooting
### Slow Rebuild Times
**Symptom**: Rebuild takes minutes instead of seconds
**Diagnosis**:
```typescript
// Check if rebuild is recomputing instead of loading
console.time('rebuild')
await brain.index.rebuild()
console.timeEnd('rebuild')
// For 10K entities:
// - Expected: 500-800ms (loading from storage)
// - Bug: 5-10 minutes (recomputing vector index connections)
```
**Solution**: Ensure index is loading from storage, not calling `addItem()` during rebuild.
### High Memory Usage
**Symptom**: Memory usage exceeds expectations
**Diagnosis**:
```typescript
// Check if vectors are being preloaded
const stats = brain.index.getStats()
console.log('Preloaded vectors:', stats.preloadedVectors)
// Expected:
// - Small dataset (< 30% cache): Most vectors preloaded
// - Large dataset (> 30% cache): Few vectors preloaded
```
**Solution**: Adjust `UnifiedCache` size or force lazy loading:
```typescript
const brain = new Brain({
storage,
cache: { maxSize: 50 * 1024 * 1024 } // 50MB cache
})
```
### Missing Data After Rebuild
**Symptom**: Entities disappear after restart
**Diagnosis**:
```typescript
// Check storage persistence
const nouns = await storage.getNouns({ pagination: { limit: 10 } })
console.log('Nouns in storage:', nouns.items.length)
// If empty: Storage not persisting
// If populated: Rebuild not loading correctly
```
**Solution**: Verify storage adapter is configured correctly (e.g., FileSystem path exists).
## Related Documentation
- [Index Architecture](./index-architecture.md) - Data structures and operations
- [Storage Architecture](./storage-architecture.md) - Storage layer details
- [Performance Guide](../PERFORMANCE.md) - Performance tuning
- [Scaling Guide](../SCALING.md) - Large dataset optimization
## Version History
- **v5.7.7** (November 2025): Added production-scale lazy loading with `ensureIndexesLoaded()` helper. Fixed critical bug where `disableAutoRebuild: true` left indexes empty forever. Added concurrency control (mutex) to prevent duplicate rebuilds from concurrent queries. Added `getIndexStatus()` diagnostic method. Zero-config operation - works automatically.
- **v3.45.0** (October 2025): Fixed type-aware vector index `rebuild()` to load from storage instead of recomputing. Removed all snapshot code (unnecessary with correct rebuild pattern). 200-600x speedup.
- **v3.44.0** (October 2025): GraphAdjacencyIndex migrated to LSM-tree storage for billion-scale relationships
- **v3.42.0** (October 2025): MetadataIndex migrated to chunked sparse indexing
- **v3.35.0** (August 2025): Vector index connections first persisted to storage
- **v3.0.0** (September 2025): Initial 3-tier index architecture

View file

@ -1,134 +0,0 @@
# Design note: multi-process storage mixin
**Status:** Proposed (future minor)
**Owner:** Brainy core
**Filed:** 2026-05-15
**Companion:** [`concepts/storage-adapters`](../concepts/storage-adapters.md)
## Context
Brainy 7.21 added seven storage-adapter methods to support multi-process
safety:
```
supportsMultiProcessLocking()
acquireWriterLock(opts)
releaseWriterLock()
readWriterLock()
startFlushRequestWatcher(cb)
stopFlushRequestWatcher()
requestFlushOverFilesystem(timeoutMs)
```
They live on `BaseStorage` as no-op defaults and are overridden on
`FileSystemStorage` with real implementations. Any adapter extending
`FileSystemStorage` (e.g. Cor's `MmapFileSystemStorage`) inherits the
real ones for free.
This works correctly today. The question is whether the methods *belong*
on `BaseStorage`.
## The case for moving them out
`BaseStorage` already mixes several concerns:
- entity / verb CRUD primitives
- generational record hooks (8.0 MVCC)
- type-statistics tracking
- count persistence
- multi-process safety (new)
Adapters that have no notion of multi-process semantics — `MemoryStorage`,
cloud adapters (S3, GCS, R2, Azure, OPFS) — still carry seven inherited
no-ops on their prototype chain. A reader can't tell from the class
declaration whether a given adapter participates in the locking protocol;
it has to call `supportsMultiProcessLocking()` and trust the answer.
A cleaner separation:
```typescript
interface MultiProcessSafeStorage {
supportsMultiProcessLocking(): boolean
acquireWriterLock(opts?: { force?: boolean }): Promise<WriterLockInfo | null>
releaseWriterLock(): Promise<void>
readWriterLock(): Promise<WriterLockInfo | null>
startFlushRequestWatcher(cb: () => Promise<void>): void
stopFlushRequestWatcher(): void
requestFlushOverFilesystem(timeoutMs: number): Promise<boolean>
}
function isMultiProcessSafe(s: BaseStorage): s is BaseStorage & MultiProcessSafeStorage {
return typeof (s as any).supportsMultiProcessLocking === 'function'
&& (s as any).supportsMultiProcessLocking()
}
```
Brainy's call sites become:
```typescript
if (this.config.mode !== 'reader' && isMultiProcessSafe(this.storage)) {
await this.storage.acquireWriterLock({ force: this.config.force })
// ... TypeScript narrows the rest correctly ...
}
```
Benefits:
- Type system enforces the capability — no more `(this.storage as any).X()`.
- Adapters that opt out (memory, cloud) are visibly clean.
- `hasStorageMethod()` defensive helper can stay (still guards
build/install artifacts) but doesn't carry the conceptual weight of
"did the plugin implement the interface."
- ADR-style trail for future capability additions: each new capability
gets its own interface, opted into explicitly.
## The case against doing it now
- Breaking change for any adapter that already overrides these methods.
`FileSystemStorage` is the only one in-tree, but Cor's
`MmapFileSystemStorage` inherits from it — interface relocation would
ripple through the plugin ecosystem.
- The current state works. The real failure modes seen in the field
were build/install artifacts, not type-system failures.
- 7.22.0 just shipped a clean fix. Stacking another refactor before
consumers absorb it adds churn without urgency.
- The `hasStorageMethod()` guard accomplishes the same runtime safety the
interface narrowing would in TypeScript-aware code.
## Recommendation
**Defer.** Keep the current architecture through the 7.x line. Revisit
when:
- A second multi-process capability lands (e.g. distributed-readers
coordination) and the natural surface area is more than seven
methods. Five+ becomes the moment a separate interface earns its
keep.
- A v8 major is on the table for unrelated reasons. Bundle the
interface extraction with that release so consumers absorb both
changes in one upgrade.
Until then:
- Document the inheritance contract (done — see
[`concepts/storage-adapters`](../concepts/storage-adapters.md)).
- Keep `hasStorageMethod()` as the runtime guard.
- Don't add new methods to `BaseStorage` defaults without re-evaluating
the surface-area boundary.
## Migration sketch (when we do it)
For reference, a clean migration path:
1. Add `MultiProcessSafeStorage` interface to `src/storage/coreTypes.ts`.
2. Move the seven method signatures from `BaseStorage` to the new
interface. Default implementations stay on `BaseStorage` but only as
private helpers consumed by `FileSystemStorage`'s explicit
implementations.
3. `FileSystemStorage implements MultiProcessSafeStorage` becomes
explicit; methods get the `public` modifier with full JSDoc.
4. Brainy call sites switch from `hasStorageMethod` to
`isMultiProcessSafe` type-guard. Keep `hasStorageMethod` for
build/install artifact protection.
5. Document the new contract in `concepts/storage-adapters.md`.
6. Major-version-bump the `@soulcraft/brainy` peerDep range expected by
plugins.
Estimated work: ~half a day of code, ~2 hours of doc/example updates,
ecosystem coordination via the platform handoff.

File diff suppressed because it is too large Load diff

View file

@ -4,16 +4,17 @@ Brainy is a multi-dimensional AI database that combines vector similarity, graph
## Core Components
### Brainy (Main Entry Point)
### BrainyData (Main Entry Point)
The central orchestrator that manages all subsystems:
- **4-Index Architecture**: MetadataIndex, vector index, GraphAdjacencyIndex, DeletedItemsIndex (see [Index Architecture](./index-architecture.md))
- **Storage System**: FileSystem and Memory adapters
- **HNSW Index**: O(log n) vector similarity search
- **Storage System**: Universal storage adapters (FileSystem, S3, OPFS, Memory)
- **Metadata Index**: O(1) field lookups with inverted indexing
- **Augmentation System**: Extensible plugin architecture
- **Triple Intelligence**: Unified query engine
### Triple Intelligence Engine
Brainy's revolutionary feature that unifies three types of search:
- **Vector Search**: Semantic similarity via the pluggable vector index
- **Vector Search**: Semantic similarity using HNSW indexing
- **Graph Traversal**: Relationship-based queries
- **Field Filtering**: Precise metadata filtering with O(1) performance
@ -39,16 +40,16 @@ brainy-data/
│ ├── __entity_registry__.json
│ └── __metadata_index__*.json
├── verbs/ # Relationship storage
├── wal/ # Write-Ahead Logging
└── locks/ # Concurrent access control
```
### Vector Index
Pluggable vector index (`VectorIndexProvider`) for efficient nearest-neighbor search. The default JS implementation, `JsHnswVectorIndex`, uses a hierarchical graph:
### HNSW Index
Hierarchical Navigable Small World index for efficient vector search:
- **Performance**: O(log n) search complexity
- **Configurable recall**: `fast` / `balanced` / `accurate` presets trade recall for latency
- **Scalable**: Handles millions of vectors per process
- **Memory Efficient**: Product quantization support
- **Scalable**: Handles millions of vectors
- **Persistent**: Serializable to storage
- **Swappable**: Replace with a native implementation (such as `@soulcraft/cor`) via the plugin system without changing application code
### Metadata Index Manager
High-performance field indexing system:
@ -60,7 +61,7 @@ High-performance field indexing system:
## Performance Characteristics
### Operation Complexity
- **Vector Search**: O(log n) via the vector index
- **Vector Search**: O(log n) via HNSW
- **Field Filtering**: O(1) via inverted indexes
- **Graph Traversal**: O(V + E) for breadth-first search
- **Add Operation**: O(log n) for index insertion
@ -82,18 +83,20 @@ High-performance field indexing system:
Brainy's extensible plugin architecture allows for powerful enhancements:
### Core Augmentations
- **WAL (Write-Ahead Logging)**: Durability and crash recovery
- **Entity Registry**: High-speed deduplication for streaming data
- **Batch Processing**: Optimized bulk operations
- **Connection Pool**: Efficient resource management
- **Request Deduplicator**: Prevents duplicate processing
### Creating Custom Augmentations
```typescript
class CustomAugmentation extends BrainyAugmentation {
async onInit(brain: Brainy): Promise<void> {
async onInit(brain: BrainyData): Promise<void> {
// Initialize augmentation
}
async onAdd(item: any, brain: Brainy): Promise<any> {
async onAdd(item: any, brain: BrainyData): Promise<any> {
// Process item before adding
return item
}
@ -111,14 +114,11 @@ Multi-layered caching for optimal performance:
## Integration Points
### Key Objects for Extensions
- `brain.index`: Access the vector index
- `brain.index`: Access HNSW vector index
- `brain.metadataIndex`: Access field indexing
- `brain.graphIndex`: Access graph adjacency index
- `brain.storage`: Access storage layer
- `brain.augmentations`: Access augmentation manager
For detailed information about each index, see [Index Architecture](./index-architecture.md).
### Event System
```typescript
brain.on('add', (item) => console.log('Item added:', item))
@ -144,7 +144,6 @@ brain.on('error', (error) => console.error('Error:', error))
## Next Steps
- [Index Architecture](./index-architecture.md) - Deep dive into the 4-index system
- [Storage Architecture](./storage-architecture.md) - Deep dive into storage system
- [Triple Intelligence](./triple-intelligence.md) - Advanced query system
- [API Reference](../api/README.md) - Complete API documentation

View file

@ -1,120 +1,86 @@
# Storage Architecture
> **Updated**: Metadata/vector separation, UUID-based sharding, on-disk artifact for operator-layer backup
Brainy implements a sophisticated, unified storage system that works across all environments (Node.js, Browser, Edge Workers) with enterprise-grade features like metadata indexing, entity registry, and write-ahead logging.
## Storage Structure
### Architecture: Metadata/Vector Separation
Entities and relationships are split into **2 separate files** for optimal performance at billion-entity scale:
```
brainy-data/
├── _system/ # System metadata (not sharded)
│ ├── statistics.json # Performance metrics
│ ├── __metadata_field_index__*.json # Field indexes
│ └── __metadata_sorted_index__*.json # Sorted indexes
├── entities/
│ ├── nouns/
│ │ ├── vectors/ # Vector graph data (sharded by UUID)
│ │ │ ├── 00/ # Shard 00 (first 2 hex digits)
│ │ │ │ ├── 00123456-....json # Vector + graph connections
│ │ │ │ └── 00abcdef-....json
│ │ │ ├── 01/ ... ff/ # 256 shards total
│ │ │
│ │ └── metadata/ # Business data (sharded by UUID)
│ │ ├── 00/
│ │ │ ├── 00123456-....json # Entity metadata only
│ │ │ └── 00abcdef-....json
│ │ ├── 01/ ... ff/
│ │
│ └── verbs/
│ ├── vectors/ # Relationship vectors (sharded)
│ │ ├── 00/ ... ff/
│ │
│ └── metadata/ # Relationship data (sharded)
│ ├── 00/ ... ff/
├── _system/ # System management
│ └── statistics.json # Performance metrics and statistics
├── nouns/ # Primary entity storage
│ └── {uuid}.json # Individual entity documents
├── metadata/ # Metadata and indexing system
│ ├── {uuid}.json # Entity metadata
│ ├── __entity_registry__.json # Entity deduplication registry
│ ├── __metadata_field_index__field_{field}.json # Field discovery
│ └── __metadata_index__{field}_{value}_chunk{n}.json # Value indexes
├── verbs/ # Relationship/action storage
│ └── {uuid}.json # Relationship documents
├── wal/ # Write-Ahead Logging
│ └── wal_{timestamp}_{id}.wal # Transaction logs
└── locks/ # Concurrent access control
└── {resource}.lock # Resource locks
```
### Why Split Metadata and Vectors?
**Performance at scale:**
- **Vector search operations**: Only load vectors (4KB) during search, not metadata (2-10KB)
- **Filtering**: Only load metadata during filtering, not vectors
- **Pagination**: Load metadata IDs first, fetch vectors/metadata on-demand
- **Result**: 60-70% reduction in I/O for typical queries at million-entity scale
### UUID-Based Sharding (256 Shards)
**How it works:**
```typescript
const uuid = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
const shard = uuid.substring(0, 2) // "3f"
// Vector path: entities/nouns/vectors/3f/3fa85f64-....json
// Metadata path: entities/nouns/metadata/3f/3fa85f64-....json
```
**Benefits:**
- **Uniform distribution**: ~3,900 entities per shard (at 1M scale)
- **Filesystem optimization**: avoids huge flat directories that bog down `readdir`
- **Parallel operations**: walk 256 shards in parallel
- **Predictable**: Deterministic shard assignment
## Storage Adapters
Brainy 8.0 ships two adapters, both implementing the same `StorageAdapter` interface:
Brainy provides multiple storage adapters with identical APIs:
### FileSystem Storage (Node.js, default)
### FileSystem Storage (Node.js)
```typescript
const brain = new Brainy({
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: './data'
}
})
```
- **Use case**: Server applications, CLI tools, single-node deployments
- **Use case**: Server applications, CLI tools
- **Performance**: Direct file I/O
- **Persistence**: Permanent on disk
- **Features**:
- **Batch Delete**: Efficient bulk deletion with retries
- **UUID Sharding**: Automatic 256-shard distribution
### S3 Compatible Storage
```typescript
const brain = new BrainyData({
storage: {
type: 's3',
bucket: 'my-brainy-data',
region: 'us-east-1',
credentials: {
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
}
})
```
- **Use case**: Distributed applications, cloud deployments
- **Performance**: Network dependent, with intelligent caching
- **Persistence**: Cloud storage durability
### Origin Private File System (Browser)
```typescript
const brain = new BrainyData({
storage: {
type: 'opfs'
}
})
```
- **Use case**: Browser applications, PWAs
- **Performance**: Near-native file system speed
- **Persistence**: Permanent in browser (with quota limits)
### Memory Storage
```typescript
const brain = new Brainy({
const brain = new BrainyData({
storage: {
type: 'memory'
}
})
```
- **Use case**: Tests, ephemeral workloads, single-process caches
- **Performance**: No I/O — all data lives in process memory
- **Persistence**: None — data is lost when the process exits
### Auto
```typescript
const brain = new Brainy({
storage: {
type: 'auto',
path: './data'
}
})
```
`'auto'` picks `'filesystem'` when running on Node.js with a writable `path`, and falls back to `'memory'` otherwise.
## Backup and Off-Site Replication
Brainy 8.0 does not embed cloud SDKs. The on-disk artifact at `path` is a plain directory tree of JSON files, so backup is an operator-layer concern. Typical patterns:
- `gsutil rsync -r ./data gs://my-bucket/brainy-data`
- `aws s3 sync ./data s3://my-bucket/brainy-data`
- `rclone sync ./data remote:brainy-data`
- Periodic `tar` snapshots to any object store
Run these from your scheduler (cron, systemd timer, k8s CronJob) — Brainy itself only reads and writes the local directory.
- **Use case**: Testing, temporary processing
- **Performance**: Fastest possible
- **Persistence**: Volatile (lost on restart)
## Metadata Indexing System
@ -178,39 +144,43 @@ High-performance deduplication system for streaming data:
- **Cache**: LRU with configurable TTL
- **Sync**: Periodic or on-demand
## Durability
## Write-Ahead Logging (WAL)
Brainy persists writes to disk through the filesystem adapter. Each save is a rename-based atomic write of a JSON file under the appropriate shard. Operators that need point-in-time recovery should snapshot `path` (see [Backup and Off-Site Replication](#backup-and-off-site-replication)).
Ensures durability and enables recovery:
### WAL Entry Format
```json
{
"timestamp": 1699564234567,
"operation": "add",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "...",
"metadata": {}
},
"checksum": "sha256:..."
}
```
### Recovery Process
1. On startup, check for WAL files
2. Replay operations from last checkpoint
3. Verify checksums for integrity
4. Clean up processed WAL files
## Storage Optimization
### 1. Batch Operations
### Compression
- **JSON**: Automatic minification
- **Vectors**: Float32 to Uint8 quantization option
- **Indexes**: Binary format for large datasets
### Caching Strategy
```typescript
// Efficient batch delete
await storage.batchDelete([
'entities/nouns/vectors/00/00123456-....json',
'entities/nouns/metadata/00/00123456-....json'
// ...
])
// Batch writes for performance
await brain.addBatch([
{ content: "item1", metadata: {} },
{ content: "item2", metadata: {} },
{ content: "item3", metadata: {} }
])
// Single transaction, optimized I/O
```
### 2. Caching Strategy
```typescript
// Configure caching
const brain = new Brainy({
// Configure caching per storage type
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: './data',
cache: {
enabled: true,
maxSize: 1000, // Maximum cached items
@ -221,6 +191,17 @@ const brain = new Brainy({
})
```
### Batch Operations
```typescript
// Batch writes for performance
await brain.addBatch([
{ content: "item1", metadata: {} },
{ content: "item2", metadata: {} },
{ content: "item3", metadata: {} }
])
// Single transaction, optimized I/O
```
## Concurrent Access
### Locking Mechanism
@ -239,39 +220,58 @@ await brain.storage.withLock('resource-id', async () => {
## Migration and Backup
Backup and restore go through the Db API — see
[Snapshots & Time Travel](../guides/snapshots-and-time-travel.md) for the full
recipe book.
### Snapshot (backup)
### Export Data
```typescript
// Instant, self-contained snapshot (hard links on filesystem storage)
const db = brain.now()
await db.persist('/backups/2026-06-11')
await db.release()
// Export entire database
const backup = await brain.export({
format: 'json',
includeVectors: true,
includeIndexes: false
})
```
### Restore
### Import Data
```typescript
// Replace the store's entire state from a snapshot (destructive — confirm required)
await brain.restore('/backups/2026-06-11', { confirm: true })
// Import from backup
await brain.import(backup, {
mode: 'merge', // or 'replace'
validateSchema: true
})
```
### Move to a new directory
### Storage Migration
```typescript
// A snapshot directory is a complete store: restore it into a fresh brain
const brain = new Brainy({ storage: { type: 'filesystem', path: './new' } })
await brain.init()
await brain.restore('/backups/2026-06-11', { confirm: true })
// Migrate between storage types
const oldBrain = new BrainyData({ storage: { type: 'filesystem' } })
const newBrain = new BrainyData({ storage: { type: 's3' } })
await oldBrain.init()
await newBrain.init()
// Transfer all data
const data = await oldBrain.export()
await newBrain.import(data)
```
## Performance Tuning
### FileSystem Optimizations
- **Directory sharding**: 256 shards spread files across subdirectories
### Storage-Specific Optimizations
#### FileSystem
- **Directory sharding**: Split files across subdirectories
- **Async I/O**: Non-blocking file operations
- **Buffer pooling**: Reuse buffers for efficiency
#### S3
- **Multipart uploads**: For large objects
- **Request batching**: Combine small operations
- **CDN integration**: Edge caching for reads
#### OPFS
- **Quota management**: Monitor and request increases
- **Worker offloading**: Heavy operations in workers
- **Transaction batching**: Group operations
### Monitoring
```typescript
@ -290,29 +290,23 @@ console.log(stats)
## Best Practices
### Choose the Right Adapter
1. **Development & tests**: `memory` for speed, `filesystem` when you need persistence
2. **Single-process production**: `filesystem` with off-site backup via `gsutil` / `aws s3 sync` / `rclone`
3. **Horizontal scaling**: Brainy runs in one process — there is no built-in cluster. Run independent instances behind a service layer and replicate the on-disk artifact with your operator tooling; or run many reader processes against one shared store with a single writer
1. **Development**: Memory or FileSystem
2. **Production Server**: FileSystem or S3
3. **Browser Apps**: OPFS or Memory
4. **Distributed**: S3 with caching
### Optimize for Your Use Case
1. **Read-heavy**: Enable caching and let the OS page cache do its job
2. **Write-heavy**: Batch operations and tune the cache `maxSize`
3. **Real-time**: FileSystem with periodic snapshots
4. **Archival**: Snapshot `path` to cold object storage on a schedule
5. **Large-scale**: Rely on metadata/vector separation + UUID sharding
1. **Read-heavy**: Enable aggressive caching
2. **Write-heavy**: Use WAL and batching
3. **Real-time**: Memory with periodic persistence
4. **Archival**: S3 with compression
### Monitor and Maintain
1. Regular statistics collection
2. Watch disk usage and shard balance
2. WAL cleanup scheduling
3. Index optimization
4. Cache tuning based on hit rates
5. Verify backup runs (test restore quarterly)
## API Reference
See the [Storage API](../api/storage.md) for complete method documentation.
---
**Last Updated**: 2026
**Key Features**: Metadata/vector separation, UUID sharding, filesystem-and-memory adapters, operator-layer backup

View file

@ -1,16 +1,3 @@
---
title: Triple Intelligence
slug: concepts/triple-intelligence
public: true
category: concepts
template: concept
order: 1
description: Unified vector similarity, graph traversal, and metadata filtering in one query. Auto-optimizes between parallel execution and progressive filtering.
next:
- concepts/noun-types
- api/reference
---
# Triple Intelligence System
The Triple Intelligence System is Brainy's revolutionary query engine that unifies vector similarity, graph relationships, and metadata filtering into a single, optimized query interface.
@ -23,37 +10,29 @@ Traditional databases force you to choose between vector search, graph traversal
### Unified Query Structure
`find()` accepts a single `FindParams` object (or a natural-language string). One
object combines all three intelligences:
```typescript
interface FindParams {
// Vector intelligence — semantic similarity
query?: string // Natural-language / semantic query (embedded, matched via HNSW + text index)
vector?: number[] // Pre-computed embedding for direct vector search
interface TripleQuery {
// Vector/Semantic search
like?: string | Vector | any
similar?: string | Vector | any
// Metadata intelligence — structured field filters
type?: NounType | NounType[] // Filter by entity type
subtype?: string | string[] // Filter by per-product subtype
where?: Record<string, any> // Field predicates with bare operators (gte, lt, in, contains, exists…)
// Graph intelligence — relationship traversal
// Graph/Relationship search
connected?: {
to?: string // Reachable to this entity
from?: string // Reachable from this entity
via?: VerbType | VerbType[] // Relationship type(s) to traverse (alias: type)
depth?: number // Max traversal depth (default: 1)
to?: string | string[]
from?: string | string[]
type?: string | string[]
depth?: number
direction?: 'in' | 'out' | 'both'
}
// Proximity — nearest neighbours of a known entity
near?: { id: string; threshold?: number }
// Field/Attribute search
where?: Record<string, any>
// Control
limit?: number // Max results (default: 10)
offset?: number // Skip N results
orderBy?: string // Field to sort by (e.g. 'createdAt')
order?: 'asc' | 'desc' // Sort direction
// Advanced options
limit?: number
boost?: 'recent' | 'popular' | 'verified' | string
explain?: boolean
threshold?: number
}
```
@ -76,16 +55,16 @@ const articles = await brain.find("verified articles by John Smith about machine
#### Simple Vector Search
```typescript
const results = await brain.find("machine learning concepts")
const results = await brain.search("machine learning concepts")
```
#### Combined Intelligence Query
```typescript
const results = await brain.find({
query: "neural networks",
like: "neural networks",
where: {
category: "research",
year: { gte: 2023 }
year: { $gte: 2023 }
},
connected: {
to: "deep-learning-team",
@ -117,8 +96,8 @@ All three search types execute simultaneously:
```typescript
// Parallel execution for balanced query
const results = await brain.find({
query: "AI research", // ~1000 potential matches
where: { kind: "paper" }, // ~500 potential matches
like: "AI research", // ~1000 potential matches
where: { type: "paper" }, // ~500 potential matches
connected: { to: "stanford" } // ~200 potential matches
})
// All three execute in parallel, results fused
@ -134,7 +113,7 @@ Operations chain for maximum efficiency:
// Progressive execution for selective query
const results = await brain.find({
where: { userId: "user123" }, // Very selective (1-10 matches)
query: "recent posts", // Applied to filtered set
like: "recent posts", // Applied to filtered set
limit: 5
})
// Metadata filter first, then vector search on results
@ -169,15 +148,15 @@ Brainy includes 220+ embedded patterns for natural language understanding:
```typescript
// Natural language automatically parsed
const results = await brain.find(
const results = await brain.search(
"show me recent AI papers from Stanford published this year"
)
// Automatically converts to:
// {
// query: "AI papers",
// like: "AI papers",
// where: {
// institution: "Stanford",
// published: { gte: "2024-01-01" }
// published: { $gte: "2024-01-01" }
// }
// }
```
@ -196,11 +175,11 @@ The NLP processor identifies query intent:
Successful execution plans are cached:
```typescript
// First call parses the natural-language query and builds an execution plan
await brain.find("machine learning papers")
// First query: 50ms (plan generation + execution)
await brain.search("machine learning papers")
// A structurally similar query reuses that plan, skipping plan generation
await brain.find("deep learning papers")
// Subsequent similar queries: 10ms (cached plan)
await brain.search("deep learning papers")
```
### Self-Optimization
@ -221,45 +200,50 @@ Triple Intelligence leverages all available indexes:
### Explain Mode
Diagnose how a query's `where` fields map to the index. Run `brain.explain()`
first whenever `find()` returns surprising or empty results:
```typescript
const plan = await brain.explain({
query: "quantum computing",
where: { category: "research" }
})
console.log(plan.fieldPlan)
// [
// { field: 'category', path: 'column-store', notes: '...' }
// ]
console.log(plan.warnings)
// e.g. ['Field "category" has no index entries. find() will return [] silently...']
```
### Result Ordering
Sort results by any stored field with `orderBy` / `order`:
Understand how your query was executed:
```typescript
const results = await brain.find({
query: "news articles",
where: { verified: true },
orderBy: 'createdAt', // Newest first
order: 'desc'
like: "quantum computing",
where: { category: "research" },
explain: true
})
console.log(results[0].explanation)
// {
// plan: "field-first-progressive",
// timing: {
// fieldFilter: 2,
// vectorSearch: 8,
// fusion: 1
// },
// selectivity: {
// field: 0.1,
// vector: 0.3
// }
// }
```
### Similarity Threshold
### Boosting
Find the nearest neighbours of a known entity and keep only close matches with
`near`:
Apply custom ranking boosts:
```typescript
const results = await brain.find({
near: { id: anchorId, threshold: 0.9 }, // Only results >= 0.9 similarity
like: "news articles",
boost: 'recent', // Boost recent items
where: { verified: true }
})
```
### Threshold Control
Set minimum similarity thresholds:
```typescript
const results = await brain.find({
like: "exact match needed",
threshold: 0.9, // Only very similar results
limit: 10
})
```
@ -286,7 +270,7 @@ const results = await brain.find({
```typescript
// Find similar content with constraints
const results = await brain.find({
query: searchText,
like: query,
where: {
status: 'published',
language: 'en'
@ -301,7 +285,7 @@ const results = await brain.find({
connected: {
to: itemId,
depth: 2,
via: VerbType.RelatedTo
type: 'similar'
},
limit: 20
})
@ -312,11 +296,10 @@ const results = await brain.find({
// Recent items matching criteria
const results = await brain.find({
where: {
timestamp: { gte: Date.now() - 86400000 }
timestamp: { $gte: Date.now() - 86400000 }
},
query: "trending topics",
orderBy: 'timestamp',
order: 'desc'
like: "trending topics",
boost: 'recent'
})
```

View file

@ -1,157 +1,769 @@
---
title: Zero Configuration
slug: concepts/zero-config
public: false
category: concepts
template: concept
order: 3
description: Brainy auto-detects storage, initializes embeddings, and builds indexes — no configuration required. Works in Node.js and Bun (server-only since 8.0).
next:
- getting-started/installation
- guides/storage-adapters
---
# Zero Configuration & Auto-Adaptation
> **"Zero config by default, fully tunable when you need it."** Construct a
> `Brainy()` with no options and it picks sensible, environment-aware defaults.
> Every default below is overridable through the constructor — see the
> [API Reference](../api/README.md#configuration).
> **Current Status**: Basic zero-config is fully functional. Advanced auto-adaptation features are in development.
## Overview
Brainy 8.0 is server-only (Node.js 22+ / Bun). With no configuration it:
Brainy is designed with **"Zero Config by Default, Infinite Tunability"** philosophy. It automatically detects your environment, adapts to available resources, learns from usage patterns, and optimizes itself for your specific workload—all without any configuration.
- selects a storage adapter from the runtime,
- initializes the embedding model (all-MiniLM-L6-v2, 384 dimensions),
- builds and maintains the metadata, graph, and vector indexes,
- sizes its caches and write buffers to the detected memory budget,
- chooses a persistence mode that matches the storage backend, and
- quiets its own logging when it detects a production environment.
## Zero Configuration Magic
There is no public config-generation function — adaptation happens inside the
constructor and `init()`.
## Instant Start
### Instant Start
```typescript
import { Brainy } from '@soulcraft/brainy'
import { BrainyData } from 'brainy'
// That's it. No config needed.
const brain = new Brainy()
const brain = new BrainyData()
await brain.init()
await brain.add({ data: 'First entity', type: 'concept' })
const results = await brain.find('first')
// Brainy automatically:
// ✓ Detects environment (Node.js, Browser, Edge, Deno)
// ✓ Chooses optimal storage (FileSystem, OPFS, Memory)
// ✓ Downloads required models (if needed)
// ✓ Configures vector dimensions (384 optimal)
// ✓ Sets up indexing strategies
// ✓ Enables appropriate augmentations
// ✓ Configures caching layers
// ✓ Optimizes for your hardware
```
## What Auto-Adaptation Covers
### Environment Detection ✅ Available
### 1. Storage auto-detection
With no `storage` option, Brainy uses `type: 'auto'`:
- **Filesystem** when running on a runtime with a writable Node filesystem and a
resolvable root directory. This is the default for typical Node/Bun servers and
persists across restarts.
- **In-memory** otherwise (no filesystem access, or an explicit memory request).
Fast, zero I/O, discarded on process exit — ideal for tests and ephemeral
caches.
8.0 ships exactly two storage adapters — `memory` and `filesystem` — plus the
`auto` selector that resolves to one of them. See
[Storage Adapters](../concepts/storage-adapters.md) for the full contract.
Brainy automatically detects and adapts to your runtime:
```typescript
// Explicit override when you want a specific root
const brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
// Brainy's environment detection
const environment = {
// Runtime detection
isNode: typeof process !== 'undefined',
isBrowser: typeof window !== 'undefined',
isDeno: typeof Deno !== 'undefined',
isEdge: typeof EdgeRuntime !== 'undefined',
isWebWorker: typeof WorkerGlobalScope !== 'undefined',
// Capability detection
hasFileSystem: /* auto-detected */,
hasIndexedDB: /* auto-detected */,
hasOPFS: /* auto-detected */,
hasWebGPU: /* auto-detected */,
hasWASM: /* auto-detected */,
// Resource detection
cpuCores: /* auto-detected */,
memory: /* auto-detected */,
storage: /* auto-detected */
}
```
### 2. HNSW quality from the `recall` preset
## Auto-Adaptive Storage ✅ Available
Vector-index quality comes from a single preset rather than hand-tuned graph
parameters. `config.vector.recall` accepts `'fast'`, `'balanced'`, or
`'accurate'` and defaults to `'balanced'`. The preset maps internally to the
HNSW construction and search parameters (`M` / `efConstruction` / `efSearch`),
so you trade recall against latency with one knob instead of three.
> **Current**: Brainy automatically selects the best storage adapter for your environment.
### Storage Selection Logic
```typescript
const brain = new Brainy({
vector: { recall: 'fast' } // favor latency over recall
})
// Brainy's intelligent storage selection
async function autoSelectStorage() {
// Server environments
if (environment.isNode) {
if (await hasWritePermission('./data')) {
return 'filesystem' // Best for servers
} else if (process.env.S3_BUCKET) {
return 's3' // Cloud deployment
} else {
return 'memory' // Fallback for restricted environments
}
}
// Browser environments
if (environment.isBrowser) {
if (await navigator.storage.estimate() > 1GB) {
return 'opfs' // Best for modern browsers
} else if (indexedDB) {
return 'indexeddb' // Fallback for older browsers
} else {
return 'memory' // In-memory for restricted contexts
}
}
// Edge environments
if (environment.isEdge) {
return 'kv' // Use edge KV stores (Cloudflare, Vercel)
}
}
```
The default JS index is `JsHnswVectorIndex`. An optional native acceleration
provider (the `@soulcraft/cor` package) can replace it with a
higher-performing implementation; the public knobs stay the same. Quantization
and other index-internal acceleration are the native provider's concern, not a
Brainy configuration option.
### Storage Migration
### 3. Persistence mode follows the backend
`config.vector.persistMode` accepts `'immediate'` or `'deferred'`. Left unset,
Brainy chooses for you:
- **Immediate** on filesystem storage, so the index file stays in lock-step with
the data and survives a crash.
- **Deferred** on in-memory storage, where there is nothing durable to sync to,
so writes are batched for throughput.
Brainy seamlessly migrates between storage types:
```typescript
const brain = new Brainy({
vector: { persistMode: 'deferred' } // batch persistence for write-heavy loads
// Start with memory storage (development)
const brain = new BrainyData() // Auto-selects memory
// Later, migrate to production storage
await brain.migrate({
to: 'filesystem',
path: './production-data'
})
// All data seamlessly transferred
```
### 4. Memory-aware cache and buffer sizing
## Learning & Optimization 🚧 Coming Soon
Brainy reads the container's memory budget — `CLOUD_RUN_MEMORY`, `MEMORY_LIMIT`,
or the cgroup memory limit when running in a container — and sizes its read
caches and write buffers to fit. On a small instance it stays conservative; on a
large one it uses more of the available headroom. Query-result limits are capped
against the same budget (roughly 25 KB per result) to keep a single oversized
query from exhausting memory.
> **Note**: These features are planned for Q2 2025. Currently, Brainy uses static optimizations.
You can pin the cache explicitly:
### Query Pattern Learning 🚧 Planned
Brainy learns from your query patterns and optimizes accordingly:
```typescript
const brain = new Brainy({
cache: { maxSize: 10000, ttl: 3_600_000 }
})
// Brainy observes query patterns
class QueryPatternLearner {
analyze(queries: Query[]) {
return {
// Frequency analysis
mostCommonFields: this.getTopFields(queries),
avgResultSize: this.getAvgSize(queries),
temporalPatterns: this.getTimePatterns(queries),
// Relationship analysis
commonTraversals: this.getGraphPatterns(queries),
typicalDepth: this.getAvgDepth(queries),
// Performance analysis
slowQueries: this.getSlowQueries(queries),
cacheability: this.getCacheability(queries)
}
}
}
// Automatic optimizations based on learning:
// - Creates indexes for frequently queried fields
// - Pre-computes common graph traversals
// - Adjusts cache sizes based on working set
// - Optimizes vector search parameters
```
### 5. Logging quiets in production
### Auto-Indexing 🚧 Planned
Brainy detects production-style environments (for example `NODE_ENV` set to a
non-development value) and reduces its own log verbosity automatically. This is
logging-only behavior — it does not change indexing, storage, or query results.
Brainy automatically creates indexes based on usage:
```typescript
// No manual index configuration needed
await brain.find({ where: { category: "tech" } }) // First query
// Brainy notices 'category' field usage
await brain.find({ where: { category: "science" } }) // Second query
// Pattern detected - auto-creates category index
await brain.find({ where: { category: "tech" } }) // Third query
// Now using index - 100x faster!
```
### Adaptive Caching 🚧 Planned
Cache strategies adapt to your access patterns:
```typescript
class AdaptiveCache {
async adapt(metrics: AccessMetrics) {
if (metrics.hitRate < 0.3) {
// Low hit rate - switch strategy
this.strategy = 'lfu' // Least Frequently Used
} else if (metrics.workingSet > this.size) {
// Working set too large - increase size
this.size = Math.min(metrics.workingSet * 1.5, maxMemory)
} else if (metrics.temporalLocality > 0.8) {
// High temporal locality - use time-based eviction
this.strategy = 'ttl'
this.ttl = metrics.avgAccessInterval * 2
}
}
}
```
## Performance Auto-Scaling 🚧 Coming Soon
### Dynamic Batch Sizing
Brainy adjusts batch sizes based on system load:
```typescript
class DynamicBatcher {
calculateOptimalBatch() {
const cpuUsage = process.cpuUsage()
const memoryUsage = process.memoryUsage()
if (cpuUsage < 30 && memoryUsage < 50) {
return 1000 // System idle - large batches
} else if (cpuUsage < 60 && memoryUsage < 70) {
return 100 // Moderate load - medium batches
} else {
return 10 // High load - small batches
}
}
}
// Automatically applied during bulk operations
for (const item of millionItems) {
await brain.addNoun(item) // Internally batched optimally
}
```
### Memory Management
Automatic memory pressure handling:
```typescript
class MemoryManager {
async handlePressure() {
const usage = process.memoryUsage()
const available = os.freemem()
if (available < 100 * 1024 * 1024) { // Less than 100MB free
// Emergency mode
await this.flushCaches()
await this.compactIndexes()
await this.offloadToDisk()
} else if (usage.heapUsed / usage.heapTotal > 0.9) {
// Preventive mode
await this.reduceCacheSizes()
await this.pauseBackgroundTasks()
}
}
}
```
### Connection Pooling
Automatic connection management for storage backends:
```typescript
class ConnectionPool {
async getOptimalPoolSize() {
// Adapts based on workload
const metrics = await this.getMetrics()
if (metrics.waitTime > 100) {
// Queries waiting - increase pool
this.size = Math.min(this.size * 1.5, this.maxSize)
} else if (metrics.idleConnections > this.size * 0.5) {
// Too many idle - decrease pool
this.size = Math.max(this.size * 0.7, this.minSize)
}
return this.size
}
}
```
## Model Auto-Selection
### Embedding Model Selection
Brainy chooses the best embedding model for your use case:
```typescript
async function autoSelectModel(data: Sample[]) {
const analysis = {
languages: detectLanguages(data),
domainSpecific: detectDomain(data),
averageLength: getAvgLength(data),
requiresMultilingual: languages.length > 1
}
if (analysis.requiresMultilingual) {
return 'multilingual-e5-base' // Handles 100+ languages
} else if (analysis.domainSpecific === 'code') {
return 'codebert-base' // Optimized for code
} else if (analysis.averageLength > 512) {
return 'all-mpnet-base-v2' // Better for long text
} else {
return 'all-MiniLM-L6-v2' // Fast and efficient default
}
}
```
### Model Downloading
Models are automatically downloaded when needed:
```typescript
// First use - model auto-downloads
const brain = new BrainyData()
await brain.init() // Downloads model if not cached
// Intelligent model caching
const modelCache = {
location: process.env.MODEL_CACHE || '~/.brainy/models',
maxSize: 5 * 1024 * 1024 * 1024, // 5GB max
strategy: 'lru', // Least recently used eviction
// CDN selection based on location
cdn: await selectFastestCDN([
'https://cdn.brainy.io',
'https://brainy.b-cdn.net',
'https://models.huggingface.co'
])
}
```
## Workload Detection
### Pattern Recognition
Brainy identifies your workload type and optimizes:
```typescript
enum WorkloadType {
OLTP = 'oltp', // Many small transactions
OLAP = 'olap', // Analytical queries
STREAMING = 'streaming', // Real-time ingestion
BATCH = 'batch', // Bulk processing
HYBRID = 'hybrid' // Mixed workload
}
class WorkloadDetector {
detect(metrics: OperationMetrics): WorkloadType {
if (metrics.writesPerSecond > 1000 && metrics.avgWriteSize < 1024) {
return WorkloadType.STREAMING
} else if (metrics.avgQueryComplexity > 0.8 && metrics.avgResultSize > 10000) {
return WorkloadType.OLAP
} else if (metrics.batchOperations > metrics.singleOperations) {
return WorkloadType.BATCH
} else if (metrics.writeReadRatio > 0.3 && metrics.writeReadRatio < 0.7) {
return WorkloadType.HYBRID
} else {
return WorkloadType.OLTP
}
}
}
```
### Optimization Strategies
Different optimizations for different workloads:
```typescript
class WorkloadOptimizer {
optimize(workload: WorkloadType) {
switch (workload) {
case WorkloadType.STREAMING:
return {
entityRegistry: true, // Deduplication
batchSize: 1000,
walEnabled: true,
cacheSize: 'small',
indexStrategy: 'lazy'
}
case WorkloadType.OLAP:
return {
entityRegistry: false,
batchSize: 10000,
walEnabled: false,
cacheSize: 'large',
indexStrategy: 'eager',
parallelQueries: true
}
case WorkloadType.BATCH:
return {
entityRegistry: false,
batchSize: 50000,
walEnabled: false,
cacheSize: 'minimal',
indexStrategy: 'deferred'
}
default:
return this.defaultConfig
}
}
}
```
## Hardware Adaptation 🚧 Coming Soon
> **Note**: GPU acceleration and hardware optimization planned for Q3 2025.
### CPU Optimization
Adapts to available CPU resources:
```typescript
class CPUAdapter {
async optimize() {
const cores = os.cpus().length
const type = os.cpus()[0].model
// Parallel processing based on cores
this.parallelism = Math.max(1, cores - 1) // Leave one core free
// SIMD detection for vector operations
if (type.includes('Intel') || type.includes('AMD')) {
this.enableSIMD = await checkSIMDSupport()
}
// Thread pool sizing
this.threadPoolSize = cores * 2 // Optimal for I/O bound
// Vector search optimization
if (cores >= 8) {
this.hnswConstruction = 200 // Higher quality index
this.hnswSearch = 100 // More accurate search
} else {
this.hnswConstruction = 100 // Balanced
this.hnswSearch = 50 // Faster search
}
}
}
```
### Memory Adaptation
Intelligent memory allocation:
```typescript
class MemoryAdapter {
async configure() {
const totalMemory = os.totalmem()
const availableMemory = os.freemem()
// Allocate based on available memory
const allocation = {
cache: Math.min(availableMemory * 0.25, 2 * GB),
vectors: Math.min(availableMemory * 0.30, 4 * GB),
indexes: Math.min(availableMemory * 0.20, 2 * GB),
working: Math.min(availableMemory * 0.25, 2 * GB)
}
// Adjust for low memory systems
if (totalMemory < 4 * GB) {
allocation.cache *= 0.5
allocation.vectors *= 0.7
this.enableSwapping = true
}
return allocation
}
}
```
### GPU Acceleration
Automatic GPU detection and utilization:
```typescript
class GPUAdapter {
async detect() {
// WebGPU in browsers
if (navigator?.gpu) {
const adapter = await navigator.gpu.requestAdapter()
return {
available: true,
type: 'webgpu',
memory: adapter.limits.maxBufferSize,
compute: adapter.limits.maxComputeWorkgroupsPerDimension
}
}
// CUDA in Node.js
if (process.platform === 'linux' || process.platform === 'win32') {
const hasCuda = await checkCudaSupport()
if (hasCuda) {
return {
available: true,
type: 'cuda',
memory: await getCudaMemory(),
compute: await getCudaCores()
}
}
}
return { available: false }
}
async optimize(gpu: GPUInfo) {
if (gpu.available) {
// Offload vector operations to GPU
this.vectorOps = 'gpu'
this.embeddingGeneration = 'gpu'
this.matrixMultiplication = 'gpu'
// Larger batch sizes for GPU
this.batchSize = gpu.memory > 8 * GB ? 10000 : 1000
}
}
}
```
## Network Adaptation
### Bandwidth Detection
Optimizes for available network bandwidth:
```typescript
class NetworkAdapter {
async measureBandwidth() {
const testSize = 1 * MB
const start = Date.now()
await this.transfer(testSize)
const duration = Date.now() - start
const bandwidth = (testSize / duration) * 1000 // bytes/sec
if (bandwidth < 1 * MB) {
// Low bandwidth - optimize
this.compression = 'aggressive'
this.batchTransfers = true
this.cacheRemote = true
} else if (bandwidth > 100 * MB) {
// High bandwidth
this.compression = 'minimal'
this.parallelTransfers = true
}
}
}
```
### Latency Optimization
Adapts to network latency:
```typescript
class LatencyOptimizer {
async optimize() {
const latency = await this.measureLatency()
if (latency > 100) { // High latency
// Batch operations
this.minBatchSize = 100
// Aggressive prefetching
this.prefetchDepth = 3
// Local caching
this.cacheStrategy = 'aggressive'
// Connection pooling
this.connectionPool = Math.min(latency / 10, 50)
}
}
}
```
## Cloud Provider Detection 🚧 Coming Soon
> **Note**: Cloud provider auto-detection planned for Q3 2025.
### Automatic Cloud Optimization
Detects and optimizes for cloud providers:
```typescript
class CloudDetector {
async detect() {
// AWS Detection
if (process.env.AWS_REGION || await canReachMetadata('169.254.169.254')) {
return {
provider: 'aws',
instance: await getEC2InstanceType(),
region: process.env.AWS_REGION,
services: {
storage: 's3',
cache: 'elasticache',
compute: 'lambda'
}
}
}
// Google Cloud Detection
if (process.env.GOOGLE_CLOUD_PROJECT || await canReachMetadata('metadata.google.internal')) {
return {
provider: 'gcp',
instance: await getGCEInstanceType(),
region: process.env.GOOGLE_CLOUD_REGION,
services: {
storage: 'gcs',
cache: 'memorystore',
compute: 'cloud-run'
}
}
}
// Vercel Edge Detection
if (process.env.VERCEL) {
return {
provider: 'vercel',
region: process.env.VERCEL_REGION,
services: {
storage: 'vercel-kv',
cache: 'edge-config',
compute: 'edge-runtime'
}
}
}
}
}
```
## Development vs Production
### Automatic Environment Detection
```typescript
class EnvironmentDetector {
detect() {
const indicators = {
// Development indicators
isDevelopment:
process.env.NODE_ENV === 'development' ||
process.env.DEBUG ||
process.argv.includes('--dev') ||
isLocalhost() ||
hasDevTools(),
// Test indicators
isTest:
process.env.NODE_ENV === 'test' ||
process.env.CI ||
isTestRunner(),
// Production indicators
isProduction:
process.env.NODE_ENV === 'production' ||
process.env.VERCEL ||
process.env.NETLIFY ||
!isLocalhost()
}
return indicators
}
}
// Different defaults for different environments
const config = environment.isProduction ? {
storage: 'filesystem',
wal: true,
monitoring: true,
compression: true,
caching: 'aggressive'
} : {
storage: 'memory',
wal: false,
monitoring: false,
compression: false,
caching: 'minimal'
}
```
## Error Recovery
### Automatic Fallbacks
Brainy automatically recovers from errors:
```typescript
class AutoRecovery {
async handleStorageFailure() {
try {
await this.primaryStorage.write(data)
} catch (error) {
console.warn('Primary storage failed, trying fallback')
// Try secondary storage
if (this.secondaryStorage) {
await this.secondaryStorage.write(data)
} else {
// Fall back to memory
await this.memoryStorage.write(data)
// Schedule retry
this.scheduleRetry(data)
}
}
}
async handleModelFailure() {
try {
return await this.primaryModel.embed(text)
} catch (error) {
// Fall back to simpler model
return await this.fallbackModel.embed(text)
}
}
}
```
## Configuration Override
Zero-config is the default, not a ceiling. Every adaptive decision above has an
explicit constructor option:
While zero-config is default, you can override when needed:
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
vector: {
recall: 'accurate',
persistMode: 'immediate'
// Explicit configuration when needed
const brain = new BrainyData({
// Override auto-detection
storage: {
type: 'filesystem',
path: '/custom/path'
},
cache: { maxSize: 50000, ttl: 600_000 }
})
await brain.init()
// Override auto-optimization
optimization: {
autoIndex: false,
autoCache: false,
autoBatch: false
},
// Override auto-scaling
scaling: {
maxMemory: 2 * GB,
maxConnections: 100,
maxBatchSize: 1000
}
})
```
See the [API Reference](../api/README.md#configuration) for the complete option
list.
## Monitoring Auto-Adaptation
Brainy provides visibility into its auto-adaptation:
```typescript
brain.on('adaptation', (event) => {
console.log(`Brainy adapted: ${event.type}`)
console.log(`Reason: ${event.reason}`)
console.log(`Before: ${JSON.stringify(event.before)}`)
console.log(`After: ${JSON.stringify(event.after)}`)
})
// Example events:
// - Index created for frequently queried field
// - Cache strategy changed due to low hit rate
// - Batch size increased due to high throughput
// - Storage migrated due to space constraints
// - Model switched due to multilingual content
```
## Conclusion
Brainy's zero-configuration and auto-adaptation capabilities mean you can focus on your application logic while Brainy handles:
- Environment detection and optimization
- Storage selection and migration
- Performance tuning and scaling
- Resource management
- Error recovery
- Workload optimization
Just create a Brainy instance and start using it. Brainy will learn, adapt, and optimize itself for your specific use case—no configuration required.
## See Also
- [Architecture Overview](./overview.md)
- [Storage Adapters](../concepts/storage-adapters.md)
- [Scaling Guide](../SCALING.md)
- [API Reference](../api/README.md)
- [Storage Architecture](./storage.md)
- [Performance Guide](../guides/performance.md)
- [Augmentations System](./augmentations.md)

View file

@ -0,0 +1,421 @@
# 🔌 Brainy 2.0 Augmentations Complete Reference
> **All 27 augmentations that power Brainy's extensibility - with locations, usage, and examples**
## Quick Start
```typescript
import { BrainyData } from '@soulcraft/brainy'
const brain = new BrainyData({
// Augmentations auto-configure based on environment
storage: 'auto', // Storage augmentation
cache: true, // Cache augmentation
index: true // Index augmentation
})
await brain.init() // Augmentations initialize automatically
```
## Core Concepts
### What are Augmentations?
Augmentations are modular extensions that add functionality to Brainy without cluttering the core API. They follow a unified interface and can be:
- **Auto-enabled**: Based on configuration (cache, index, storage)
- **Manually registered**: For custom functionality
- **Chained**: Multiple augmentations work together seamlessly
### Augmentation Lifecycle
1. **Registration**: Augmentations register before init()
2. **Initialization**: Two-phase init (storage first, then others)
3. **Execution**: Hook into operations (before/after/both)
4. **Shutdown**: Clean teardown on brain.shutdown()
---
## Storage Augmentations (8 total)
### MemoryStorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Auto-enabled**: When `storage: 'memory'` or in test environments
**Purpose**: In-memory storage for testing and temporary data
```typescript
const brain = new BrainyData({ storage: 'memory' })
```
### FileSystemStorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Auto-enabled**: When `storage: 'filesystem'` or Node.js detected
**Purpose**: Persistent file-based storage for Node.js applications
```typescript
const brain = new BrainyData({
storage: { type: 'filesystem', path: './data' }
})
```
### OPFSStorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Auto-enabled**: When `storage: 'opfs'` or browser with OPFS support
**Purpose**: Browser-based persistent storage using Origin Private File System
```typescript
const brain = new BrainyData({ storage: 'opfs' })
```
### S3StorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Manual**: Requires AWS credentials
**Purpose**: AWS S3-compatible cloud storage
```typescript
const brain = new BrainyData({
storage: {
type: 's3',
bucket: 'my-bucket',
region: 'us-east-1',
credentials: { accessKeyId, secretAccessKey }
}
})
```
### R2StorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Manual**: Requires Cloudflare credentials
**Purpose**: Cloudflare R2 storage (S3-compatible)
```typescript
const brain = new BrainyData({
storage: {
type: 'r2',
accountId: 'xxx',
bucket: 'my-bucket',
credentials: { accessKeyId, secretAccessKey }
}
})
```
### GCSStorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Manual**: Requires Google Cloud credentials
**Purpose**: Google Cloud Storage
```typescript
const brain = new BrainyData({
storage: {
type: 'gcs',
bucket: 'my-bucket',
projectId: 'my-project'
}
})
```
### StorageAugmentation (base)
**Location**: `src/augmentations/storageAugmentation.ts`
**Purpose**: Base class for custom storage implementations
### DynamicStorageAugmentation
**Location**: `src/augmentations/storageAugmentation.ts`
**Purpose**: Runtime storage adapter switching
---
## Performance Augmentations (7 total)
### CacheAugmentation
**Location**: `src/augmentations/cacheAugmentation.ts`
**Auto-enabled**: When `cache: true` (default)
**Purpose**: LRU cache for search results and frequent queries
```typescript
brain.clearCache() // Exposed via API
brain.getCacheStats() // Cache hit/miss statistics
```
### IndexAugmentation
**Location**: `src/augmentations/indexAugmentation.ts`
**Auto-enabled**: When `index: true` (default)
**Purpose**: Metadata indexing for O(1) field lookups
```typescript
brain.rebuildMetadataIndex() // Exposed via API
// Enables fast where queries:
brain.find({ where: { category: 'tech' } })
```
### MetricsAugmentation
**Location**: `src/augmentations/metricsAugmentation.ts`
**Auto-enabled**: Always active
**Purpose**: Performance metrics and statistics collection
```typescript
brain.getStatistics() // Comprehensive metrics
```
### MonitoringAugmentation
**Location**: `src/augmentations/monitoringAugmentation.ts`
**Manual**: Register for detailed monitoring
**Purpose**: Real-time performance monitoring and alerts
### BatchProcessingAugmentation
**Location**: `src/augmentations/batchProcessingAugmentation.ts`
**Auto-enabled**: For batch operations
**Purpose**: Optimizes bulk add/update/delete operations
```typescript
brain.addNouns([...]) // Automatically batched
```
### RequestDeduplicatorAugmentation
**Location**: `src/augmentations/requestDeduplicatorAugmentation.ts`
**Auto-enabled**: Always active
**Purpose**: Prevents duplicate concurrent operations
### ConnectionPoolAugmentation
**Location**: `src/augmentations/connectionPoolAugmentation.ts`
**Auto-enabled**: For network storage
**Purpose**: Connection pooling for cloud storage adapters
---
## Data Integrity Augmentations (3 total)
### WALAugmentation
**Location**: `src/augmentations/walAugmentation.ts`
**Auto-enabled**: When `wal: true`
**Purpose**: Write-ahead logging for crash recovery
```typescript
const brain = new BrainyData({ wal: true })
// Automatic recovery on restart after crash
```
### EntityRegistryAugmentation
**Location**: `src/augmentations/entityRegistryAugmentation.ts`
**Auto-enabled**: For streaming operations
**Purpose**: High-speed deduplication for real-time data
```typescript
// Prevents duplicate entities in streaming scenarios
brain.addNoun(data) // Automatically deduplicated
```
### AutoRegisterEntitiesAugmentation
**Location**: `src/augmentations/entityRegistryAugmentation.ts`
**Manual**: For automatic entity discovery
**Purpose**: Auto-discovers and registers entities from data
---
## Intelligence Augmentations (2 total)
### NeuralImportAugmentation
**Location**: `src/augmentations/neuralImport.ts`
**Manual**: Via `brain.neuralImport()`
**Purpose**: AI-powered smart data import
```typescript
const result = await brain.neuralImport(data, {
confidenceThreshold: 0.7,
autoApply: true
})
// Automatically detects entities and relationships
```
### IntelligentVerbScoringAugmentation
**Location**: `src/augmentations/intelligentVerbScoringAugmentation.ts`
**Auto-enabled**: When verbs are used
**Purpose**: ML-based relationship strength scoring
```typescript
brain.verbScoring.train(feedback)
brain.verbScoring.getScore(verbId)
```
---
## Communication Augmentations (4 total)
### APIServerAugmentation
**Location**: `src/augmentations/apiServerAugmentation.ts`
**Manual**: For server deployments
**Purpose**: REST/WebSocket/MCP API server
```typescript
const augmentation = new APIServerAugmentation()
await brain.registerAugmentation(augmentation)
// Exposes full Brainy API over network
```
### WebSocketConduitAugmentation
**Location**: `src/augmentations/conduitAugmentations.ts`
**Manual**: For Brainy-to-Brainy sync
**Purpose**: Real-time sync between Brainy instances
```typescript
const conduit = new WebSocketConduitAugmentation()
await conduit.establishConnection('ws://other-brain')
```
### ServerSearchConduitAugmentation
**Location**: `src/augmentations/serverSearchAugmentations.ts`
**Manual**: For client-server search
**Purpose**: Search remote Brainy instance, cache locally
### ServerSearchActivationAugmentation
**Location**: `src/augmentations/serverSearchAugmentations.ts`
**Manual**: Works with ServerSearchConduit
**Purpose**: Triggers and manages server search operations
---
## External Integration (2 total)
### SynapseAugmentation (base)
**Location**: `src/augmentations/synapseAugmentation.ts`
**Purpose**: Base class for external platform integrations
```typescript
// Example: NotionSynapse, SlackSynapse, etc.
class NotionSynapse extends SynapseAugmentation {
async fetchData() { /* Notion API calls */ }
async pushData() { /* Sync to Notion */ }
}
```
### ExampleFileSystemSynapse
**Location**: `src/augmentations/synapseAugmentation.ts`
**Purpose**: Example implementation for file system sync
---
## Augmentation Configuration
### Auto-Configuration
```typescript
const brain = new BrainyData({
// These auto-register augmentations:
storage: 'auto', // Storage augmentation
cache: true, // Cache augmentation
index: true, // Index augmentation
wal: true, // WAL augmentation
metrics: true // Metrics augmentation
})
```
### Manual Registration
```typescript
const brain = new BrainyData()
// Register before init()
const customAug = new MyCustomAugmentation()
await brain.registerAugmentation(customAug)
await brain.init()
```
### Creating Custom Augmentations
```typescript
import { BaseAugmentation } from '@soulcraft/brainy'
class MyAugmentation extends BaseAugmentation {
readonly name = 'my-augmentation'
readonly timing = 'after' // before | after | both
readonly operations = ['addNoun', 'search'] // Which ops to hook
readonly priority = 10 // Execution order (lower = earlier)
protected async onInit(): Promise<void> {
// Initialize your augmentation
}
async execute<T>(
operation: string,
params: any,
context?: AugmentationContext
): Promise<T | void> {
// Your augmentation logic
if (operation === 'addNoun') {
console.log('Noun added:', params)
}
}
protected async onShutdown(): Promise<void> {
// Cleanup
}
}
```
---
## Augmentation Timing & Priority
### Timing Options
- **`before`**: Runs before the operation (can modify params)
- **`after`**: Runs after the operation (can see results)
- **`both`**: Runs before AND after
### Priority (lower = earlier)
1. Storage augmentations (priority: 0)
2. Cache/Index augmentations (priority: 5-10)
3. Monitoring/Metrics (priority: 15-20)
4. Conduits/Synapses (priority: 20-30)
---
## Key Integration Points
### Where Augmentations Hook In
**BrainyData Constructor**:
- Storage augmentations register based on config
- Cache/Index augmentations auto-register if enabled
**brain.init()**:
- Two-phase initialization (storage first, then others)
- Augmentations can access brain instance via context
**Operations** (addNoun, search, etc.):
- Augmentations execute based on timing and operations filter
- Can modify params (before) or see results (after)
**brain.shutdown()**:
- All augmentations cleaned up in reverse order
---
## Performance Impact
Most augmentations have minimal overhead:
- **Cache**: ~1ms per search (saves 10-100ms on hits)
- **Index**: ~1ms per operation (saves 100ms+ on queries)
- **Metrics**: <1ms per operation
- **Storage**: Varies by adapter (memory: 0ms, S3: 50-200ms)
---
## Best Practices
1. **Let auto-configuration work**: Most apps need zero manual config
2. **Storage first**: Always configure storage before other augmentations
3. **Use built-in augmentations**: They're optimized and battle-tested
4. **Custom augmentations**: Extend BaseAugmentation for consistency
5. **Respect timing**: Use 'before' to modify, 'after' to observe
6. **Mind priority**: Lower numbers execute first
---
## Troubleshooting
### Augmentation not working?
```typescript
// Check if registered
brain.listAugmentations()
// Check if enabled
brain.isAugmentationEnabled('cache')
// Enable/disable at runtime
brain.enableAugmentation('cache')
brain.disableAugmentation('cache')
```
### Performance issues?
```typescript
// Check augmentation overhead
const stats = brain.getStatistics()
console.log(stats.augmentations)
// Disable non-critical augmentations
brain.disableAugmentation('monitoring')
```
---
---
*Augmentations make Brainy infinitely extensible while keeping the core API clean and simple!*

View file

@ -0,0 +1,477 @@
# 🛠️ Brainy Augmentation Developer Guide
> **How to create, test, and use augmentations in Brainy 2.0**
## Quick Start: Your First Augmentation
```typescript
import { BaseAugmentation, BrainyAugmentation, AugmentationContext } from '@soulcraft/brainy'
export class MyFirstAugmentation extends BaseAugmentation {
readonly name = 'my-first-augmentation'
readonly timing = 'after' as const // When to run: before | after | both
readonly operations = ['addNoun'] as const // Which operations to hook
readonly priority = 10 // Execution order (lower = first)
protected async onInit(): Promise<void> {
// Initialize your augmentation
console.log('MyFirstAugmentation initialized!')
}
async execute<T = any>(
operation: string,
params: any,
context?: AugmentationContext
): Promise<T | void> {
// Your augmentation logic
if (operation === 'addNoun') {
console.log('Noun added:', params.noun)
// You can access the brain instance
const stats = await context?.brain.getStatistics()
console.log('Total nouns:', stats.totalNouns)
}
}
protected async onShutdown(): Promise<void> {
// Cleanup
console.log('MyFirstAugmentation shutting down')
}
}
```
## Using Your Augmentation
```typescript
import { BrainyData } from '@soulcraft/brainy'
import { MyFirstAugmentation } from './my-first-augmentation'
const brain = new BrainyData()
// Register before init()
brain.augmentations.register(new MyFirstAugmentation())
await brain.init()
// Now your augmentation runs automatically!
await brain.addNoun('Hello World')
// Console: "Noun added: { id: '...', vector: [...], metadata: {} }"
```
---
## Augmentation Lifecycle
### 1. Registration Phase
```typescript
const aug = new MyAugmentation()
brain.augmentations.register(aug) // Before brain.init()!
```
### 2. Initialization Phase
```typescript
await brain.init() // Calls aug.initialize() internally
// Your onInit() method runs here
```
### 3. Execution Phase
```typescript
await brain.addNoun('data') // Your execute() method runs
```
### 4. Shutdown Phase
```typescript
await brain.shutdown() // Your onShutdown() method runs
```
---
## Timing Options
### `before` - Modify Input
```typescript
class ValidationAugmentation extends BaseAugmentation {
readonly timing = 'before' as const
async execute<T>(operation: string, params: any): Promise<any> {
if (operation === 'addNoun') {
// Validate and/or modify params
if (!params.content) {
throw new Error('Content required')
}
// Return modified params
return { ...params, validated: true }
}
}
}
```
### `after` - React to Results
```typescript
class LoggingAugmentation extends BaseAugmentation {
readonly timing = 'after' as const
async execute<T>(operation: string, params: any): Promise<void> {
if (operation === 'search') {
console.log(`Search for "${params.query}" returned ${params.result.length} results`)
}
// Don't return anything - just observe
}
}
```
### `both` - Before AND After
```typescript
class TimingAugmentation extends BaseAugmentation {
readonly timing = 'both' as const
private startTime?: number
async execute<T>(operation: string, params: any, context?: AugmentationContext): Promise<void> {
if (!this.startTime) {
// Before execution
this.startTime = Date.now()
} else {
// After execution
const duration = Date.now() - this.startTime
console.log(`${operation} took ${duration}ms`)
this.startTime = undefined
}
}
}
```
---
## Operation Hooks
### Core Operations You Can Hook
```typescript
readonly operations = [
'addNoun', // Adding data
'updateNoun', // Updating data
'deleteNoun', // Deleting data
'getNoun', // Retrieving data
'search', // Searching
'find', // Triple Intelligence queries
'addVerb', // Adding relationships
'deleteVerb', // Removing relationships
'clear', // Clearing data
'all' // Hook ALL operations
] as const
```
### Example: Multi-Operation Hook
```typescript
class AuditAugmentation extends BaseAugmentation {
readonly operations = ['addNoun', 'updateNoun', 'deleteNoun'] as const
async execute<T>(operation: string, params: any): Promise<void> {
// Log all data modifications
await this.logToAuditTrail(operation, params)
}
}
```
---
## Accessing Brain Context
```typescript
class ContextAwareAugmentation extends BaseAugmentation {
async execute<T>(
operation: string,
params: any,
context?: AugmentationContext
): Promise<void> {
// Access the brain instance
const brain = context?.brain
if (!brain) return
// Use any brain method
const stats = await brain.getStatistics()
const size = await brain.size()
const results = await brain.search('query')
// Access other augmentations
const cache = brain.augmentations.get('cache')
if (cache) {
await cache.clear()
}
}
}
```
---
## Real-World Examples
### 1. Backup Augmentation
```typescript
class BackupAugmentation extends BaseAugmentation {
readonly name = 'backup'
readonly timing = 'after' as const
readonly operations = ['addNoun', 'updateNoun', 'deleteNoun'] as const
readonly priority = 5
private changes = 0
private readonly backupThreshold = 100
async execute<T>(operation: string, params: any, context?: AugmentationContext): Promise<void> {
this.changes++
if (this.changes >= this.backupThreshold) {
await this.performBackup(context?.brain)
this.changes = 0
}
}
private async performBackup(brain?: any): Promise<void> {
if (!brain) return
const backup = await brain.backup()
await this.saveToCloud(backup)
console.log('Automatic backup completed')
}
}
```
### 2. Rate Limiting Augmentation
```typescript
class RateLimitAugmentation extends BaseAugmentation {
readonly name = 'rate-limit'
readonly timing = 'before' as const
readonly operations = ['search', 'find'] as const
readonly priority = 100 // High priority - run first
private requests = new Map<string, number[]>()
private readonly limit = 100 // 100 requests
private readonly window = 60000 // per minute
async execute<T>(operation: string, params: any): Promise<void> {
const now = Date.now()
const key = params.userId || 'anonymous'
// Get request timestamps
const timestamps = this.requests.get(key) || []
// Remove old timestamps
const recent = timestamps.filter(t => now - t < this.window)
// Check limit
if (recent.length >= this.limit) {
throw new Error('Rate limit exceeded')
}
// Add current request
recent.push(now)
this.requests.set(key, recent)
}
}
```
### 3. Encryption Augmentation
```typescript
class EncryptionAugmentation extends BaseAugmentation {
readonly name = 'encryption'
readonly timing = 'both' as const
readonly operations = ['addNoun', 'getNoun'] as const
readonly priority = 90 // Run early
async execute<T>(operation: string, params: any): Promise<any> {
if (operation === 'addNoun') {
// Encrypt before storing
if (params.metadata?.sensitive) {
params.content = await this.encrypt(params.content)
params.encrypted = true
}
return params
}
if (operation === 'getNoun' && params.result?.encrypted) {
// Decrypt after retrieval
params.result.content = await this.decrypt(params.result.content)
delete params.result.encrypted
return params.result
}
}
}
```
---
## Testing Your Augmentation
```typescript
import { describe, it, expect } from 'vitest'
import { BrainyData } from '@soulcraft/brainy'
import { MyAugmentation } from './my-augmentation'
describe('MyAugmentation', () => {
it('should hook into addNoun', async () => {
const brain = new BrainyData({ storage: 'memory' })
const aug = new MyAugmentation()
// Spy on the execute method
const executeSpy = vi.spyOn(aug, 'execute')
brain.augmentations.register(aug)
await brain.init()
// Trigger the augmentation
await brain.addNoun('test data')
// Verify it was called
expect(executeSpy).toHaveBeenCalledWith(
'addNoun',
expect.objectContaining({ content: 'test data' }),
expect.any(Object)
)
})
})
```
---
## Best Practices
### 1. Use Proper Timing
- `before`: Validation, modification, rate limiting
- `after`: Logging, metrics, side effects
- `both`: Timing, tracing, wrapping
### 2. Set Appropriate Priority
```typescript
// Priority guidelines
100: Critical (auth, rate limiting)
50: Important (validation, transformation)
10: Normal (logging, metrics)
1: Optional (debugging, tracing)
```
### 3. Handle Errors Gracefully
```typescript
async execute<T>(operation: string, params: any): Promise<void> {
try {
await this.riskyOperation()
} catch (error) {
// Log but don't break the main operation
console.error(`Augmentation error in ${this.name}:`, error)
// Optionally report to monitoring
this.reportError(error)
}
}
```
### 4. Be Performance Conscious
```typescript
class CachedAugmentation extends BaseAugmentation {
private cache = new Map<string, any>()
async execute<T>(operation: string, params: any): Promise<any> {
const key = this.getCacheKey(params)
// Check cache first
if (this.cache.has(key)) {
return this.cache.get(key)
}
// Expensive operation
const result = await this.expensiveOperation(params)
this.cache.set(key, result)
return result
}
}
```
### 5. Clean Up Resources
```typescript
protected async onShutdown(): Promise<void> {
// Close connections
await this.connection?.close()
// Clear intervals
clearInterval(this.interval)
// Flush buffers
await this.flush()
// Clear caches
this.cache.clear()
}
```
---
## Publishing Your Augmentation (Future)
### Package Structure
```
my-augmentation/
├── src/
│ └── index.ts # Your augmentation
├── dist/ # Built output
├── tests/
│ └── augmentation.test.ts
├── package.json
├── tsconfig.json
└── README.md
```
### package.json
```json
{
"name": "@mycompany/brainy-custom-augmentation",
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"keywords": ["brainy-augmentation"],
"peerDependencies": {
"@soulcraft/brainy": ">=2.0.0"
},
"brainy": {
"type": "augmentation",
"class": "CustomAugmentation",
"timing": "after",
"operations": ["addNoun"],
"priority": 10
}
}
```
### Future: Brain Cloud Registry
```bash
# Coming in 2.1+
npm run build
npm test
brainy publish # Publishes to brain-cloud registry
```
---
## FAQ
### Q: Can I modify the operation result?
**A**: Yes, if `timing: 'before'`, return modified params. If `timing: 'after'`, you can see but not modify results.
### Q: Can augmentations communicate?
**A**: Yes, through the context: `context.brain.augmentations.get('other-augmentation')`
### Q: What if my augmentation fails?
**A**: Handle errors internally. Don't break the main operation unless critical.
### Q: Can I use async operations?
**A**: Yes, everything is async-friendly.
### Q: How do I access storage directly?
**A**: Through context: `context.brain.storage` (but prefer using brain methods)
---
## Get Help
- **GitHub**: [github.com/soulcraft/brainy](https://github.com/soulcraft/brainy)
- **Discord**: [discord.gg/brainy](https://discord.gg/brainy)
- **Examples**: See `/examples/augmentations/` in the repo
---
*Start building your augmentation today! The marketplace is coming in 2.1 🚀*

View file

@ -0,0 +1,206 @@
# Brainy Augmentations
Augmentations are the core extensibility mechanism in Brainy. They allow you to modify, enhance, and extend Brainy's behavior without changing the core code.
## Core Principle: One Interface, Infinite Possibilities
Every augmentation implements the same simple `BrainyAugmentation` interface:
```typescript
interface BrainyAugmentation {
name: string
timing: 'before' | 'after' | 'around' | 'replace'
operations: string[]
priority: number
initialize(context): Promise<void>
execute(operation, params, next): Promise<any>
}
```
This single interface can handle EVERYTHING - from adding AI capabilities to exposing APIs to replacing storage backends.
## Available Augmentations
### 🧠 Data Processing
Augmentations that enhance how data is processed and stored.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **NeuralImportAugmentation** | AI-powered entity and relationship extraction | `before` | ✅ Production |
| **EntityRegistryAugmentation** | High-performance entity deduplication | `before` | ✅ Production |
| **BatchProcessingAugmentation** | Optimizes bulk operations | `around` | ✅ Production |
| **IntelligentVerbScoringAugmentation** | Learns relationship importance over time | `after` | ✅ Production |
### 🔌 External Connections (Synapses)
Connect Brainy to external services and data sources.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **NotionSynapse** | Sync with Notion databases | `after` | 📝 Example |
| **SalesforceSynapse** | Connect to Salesforce CRM | `after` | 📝 Example |
| **SlackSynapse** | Import Slack conversations | `after` | 📝 Example |
| **GoogleDriveSynapse** | Sync Google Drive documents | `after` | 📝 Example |
### 🌐 API Exposure
Expose Brainy through various protocols.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **APIServerAugmentation** | REST, WebSocket, and MCP server | `after` | ✅ Production |
| **GraphQLAugmentation** | GraphQL API endpoint | `after` | 🚧 Planned |
| **gRPCAugmentation** | gRPC service | `after` | 🚧 Planned |
### 💾 Storage Backends
Replace or enhance the storage layer.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **WALAugmentation** | Write-ahead logging for durability | `around` | ✅ Production |
| **S3StorageAugmentation** | Use S3 as storage backend | `replace` | 📝 Example |
| **RedisAugmentation** | Redis caching layer | `around` | 📝 Example |
| **PostgresAugmentation** | PostgreSQL persistence | `replace` | 📝 Example |
### 🔄 Real-time & Sync
Handle real-time updates and synchronization.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **WebSocketConduitAugmentation** | WebSocket client connections | `after` | ⚠️ Legacy |
| **ServerSearchAugmentation** | Connect to remote Brainy servers | `after` | ⚠️ Legacy |
| **TeamCoordinationAugmentation** | Multi-agent synchronization | `after` | 📝 Example |
### 🛡️ Infrastructure
Core infrastructure and reliability features.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **ConnectionPoolAugmentation** | Optimize cloud storage connections | `before` | ✅ Production |
| **RequestDeduplicatorAugmentation** | Prevent duplicate concurrent requests | `before` | ✅ Production |
| **TransactionAugmentation** | ACID transaction support | `around` | 🚧 Planned |
| **CacheAugmentation** | Multi-level caching | `around` | ✅ Production |
### 📊 Monitoring & Analytics
Track and analyze Brainy's behavior.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **MetricsAugmentation** | Prometheus metrics | `after` | 📝 Example |
| **LoggingAugmentation** | Structured logging | `after` | 📝 Example |
| **TracingAugmentation** | Distributed tracing | `around` | 🚧 Planned |
### 🤖 AI & Chat
AI-powered interfaces and chat capabilities.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **ChatInterfaceAugmentation** | Natural language interface | `before` | 📝 Example |
| **MCPAgentMemoryAugmentation** | AI agent memory via MCP | `after` | 📝 Example |
| **LLMQueryAugmentation** | LLM-enhanced queries | `before` | 📝 Example |
### 📈 Visualization
Visual representations of data.
| Augmentation | Description | Timing | Status |
|-------------|-------------|--------|--------|
| **GraphVisualizationAugmentation** | Real-time graph visualization | `after` | 📝 Example |
| **DashboardAugmentation** | Web-based dashboard | `after` | 🚧 Planned |
## Status Legend
- ✅ **Production**: Fully implemented and tested
- 📝 **Example**: Example implementation available
- 🚧 **Planned**: On the roadmap
- ⚠️ **Legacy**: Being replaced by newer augmentations
## Using Augmentations
### Zero-Config Approach
```typescript
const brain = new BrainyData()
// Just register augmentations - they work automatically!
brain.augmentations.register(new WALAugmentation())
brain.augmentations.register(new EntityRegistryAugmentation())
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
```
### With Configuration
```typescript
const brain = new BrainyData()
brain.augmentations.register(
new APIServerAugmentation({
port: 8080,
auth: { required: true }
})
)
await brain.init()
```
## Creating Custom Augmentations
See [Creating Custom Augmentations](./creating-augmentations.md) for a complete guide.
Quick example:
```typescript
class MyAugmentation extends BaseAugmentation {
readonly name = 'my-augmentation'
readonly timing = 'after'
readonly operations = ['add', 'search']
readonly priority = 50
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
console.log(`Before ${operation}`)
const result = await next()
console.log(`After ${operation}`)
return result
}
}
```
## Augmentation Timing
### `before`
Executes before the main operation. Used for:
- Input validation
- Data transformation
- Authentication checks
### `after`
Executes after the main operation. Used for:
- Broadcasting updates
- Syncing to external services
- Logging and metrics
### `around`
Wraps the main operation. Used for:
- Transactions
- Caching
- Error handling
### `replace`
Completely replaces the main operation. Used for:
- Alternative storage backends
- Mock implementations
- Proxy operations
## Priority System
Higher numbers execute first:
- **100**: Critical system operations
- **50**: Performance optimizations
- **10**: Enhancement features
- **1**: Optional features
## Related Documentation
- [API Server Augmentation](./api-server.md) - Complete API server documentation
- [Creating Augmentations](./creating-augmentations.md) - How to build your own
- [Augmentation Examples](../AUGMENTATION-EXAMPLES.md) - Real-world examples
- [Architecture Overview](../COMPLETE-ARCHITECTURE-VISION.md) - System architecture

View file

@ -0,0 +1,404 @@
# API Server Augmentation
## Overview
The `APIServerAugmentation` is a powerful augmentation that exposes your Brainy instance through REST, WebSocket, and MCP (Model Context Protocol) APIs. It transforms Brainy into a full-featured API server with zero configuration required.
## Features
### 🌐 REST API
Complete CRUD operations and advanced queries through HTTP endpoints.
### 🔌 WebSocket Server
Real-time bidirectional communication with automatic operation broadcasting.
### 🧠 MCP Integration
Built-in Model Context Protocol support for AI agent communication.
### 📊 Operation Broadcasting
Automatically broadcasts all Brainy operations to subscribed WebSocket clients.
### 🔒 Optional Security
Built-in authentication and rate limiting when needed.
## Installation
The APIServerAugmentation is included in Brainy core. No additional installation required.
For Node.js environments, you may want to install optional dependencies:
```bash
npm install express cors ws
```
## Zero-Config Usage
```typescript
import { BrainyData } from 'brainy'
import { APIServerAugmentation } from 'brainy/augmentations'
const brain = new BrainyData()
// Register the API server augmentation
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
// Server is now running at http://localhost:3000
console.log('API Server ready!')
console.log('REST: http://localhost:3000/api/*')
console.log('WebSocket: ws://localhost:3000/ws')
console.log('MCP: http://localhost:3000/api/mcp')
```
## Configuration Options
While zero-config works great, you can customize the server:
```typescript
const apiServer = new APIServerAugmentation({
enabled: true, // Enable/disable the server
port: 3000, // HTTP port
host: '0.0.0.0', // Bind address
cors: {
origin: '*', // CORS allowed origins
credentials: true // Allow credentials
},
auth: {
required: false, // Require authentication
apiKeys: [], // Valid API keys
bearerTokens: [] // Valid bearer tokens
},
rateLimit: {
windowMs: 60000, // Rate limit window (ms)
max: 100 // Max requests per window
}
})
```
## REST API Endpoints
### Health Check
```http
GET /health
```
Returns server status and basic metrics.
### Search
```http
POST /api/search
Content-Type: application/json
{
"query": "search text",
"limit": 10,
"options": {}
}
```
### Add Data
```http
POST /api/add
Content-Type: application/json
{
"content": "data to add",
"metadata": {
"key": "value"
}
}
```
### Get by ID
```http
GET /api/get/:id
```
### Delete
```http
DELETE /api/delete/:id
```
### Create Relationship
```http
POST /api/relate
Content-Type: application/json
{
"source": "id1",
"target": "id2",
"verb": "relates_to",
"metadata": {}
}
```
### Complex Queries
```http
POST /api/find
Content-Type: application/json
{
"where": { "type": "document" },
"like": "machine learning",
"limit": 10
}
```
### Clustering
```http
POST /api/cluster
Content-Type: application/json
{
"algorithm": "kmeans",
"options": {
"k": 5
}
}
```
### Statistics
```http
GET /api/stats
```
### Operation History
```http
GET /api/history
```
## WebSocket API
### Connection
```javascript
const ws = new WebSocket('ws://localhost:3000/ws')
ws.onopen = () => {
console.log('Connected to Brainy WebSocket')
}
ws.onmessage = (event) => {
const msg = JSON.parse(event.data)
console.log('Received:', msg)
}
```
### Subscribe to Operations
```javascript
ws.send(JSON.stringify({
type: 'subscribe',
operations: ['all'] // or specific: ['add', 'search', 'delete']
}))
```
### Search via WebSocket
```javascript
ws.send(JSON.stringify({
type: 'search',
query: 'your search',
limit: 10,
requestId: 'unique-id'
}))
```
### Add Data via WebSocket
```javascript
ws.send(JSON.stringify({
type: 'add',
content: 'data to add',
metadata: {},
requestId: 'unique-id'
}))
```
### Operation Broadcasts
When subscribed, you'll receive real-time updates:
```javascript
{
"type": "operation",
"operation": "add",
"params": { /* sanitized parameters */ },
"timestamp": 1234567890,
"duration": 15
}
```
## MCP (Model Context Protocol)
The MCP endpoint allows AI agents to interact with Brainy:
```http
POST /api/mcp
Content-Type: application/json
{
"method": "search",
"params": {
"query": "find documents about AI"
}
}
```
## Authentication
When authentication is enabled:
### API Key
```http
GET /api/stats
X-API-Key: your-api-key
```
### Bearer Token
```http
GET /api/stats
Authorization: Bearer your-token
```
## Environment Support
### Node.js ✅
Full support with Express, WebSocket, and all features.
### Deno 🚧
Planned support using Deno.serve() or oak framework.
### Browser/Service Worker 🚧
Planned support for intercepting fetch() calls locally.
## How It Works
The APIServerAugmentation hooks into Brainy's augmentation pipeline:
1. **Timing**: Executes `after` operations complete
2. **Operations**: Monitors `all` operations
3. **Broadcasting**: Sends operation details to subscribed clients
4. **History**: Maintains operation history (last 1000 operations)
## Example: Multi-Client Sync
```typescript
// Server
const brain = new BrainyData()
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
// Client 1 - WebSocket subscriber
const ws1 = new WebSocket('ws://localhost:3000/ws')
ws1.onopen = () => {
ws1.send(JSON.stringify({
type: 'subscribe',
operations: ['add', 'delete']
}))
}
ws1.onmessage = (e) => {
console.log('Client 1 received update:', JSON.parse(e.data))
}
// Client 2 - REST API user
fetch('http://localhost:3000/api/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
content: 'New data',
metadata: { source: 'client2' }
})
})
// Client 1 automatically receives notification!
```
## Performance Considerations
- **Operation History**: Limited to last 1000 operations
- **WebSocket Heartbeat**: Every 30 seconds
- **Client Timeout**: 60 seconds of inactivity
- **Parameter Sanitization**: Sensitive fields removed, large content truncated
- **Rate Limiting**: In-memory tracking (use Redis in production)
## Security Notes
1. **Default Configuration**: No auth, open CORS - suitable for development
2. **Production**: Enable auth, configure CORS, use HTTPS
3. **Sensitive Data**: Parameters are sanitized before broadcasting
4. **Rate Limiting**: Basic in-memory implementation included
## Comparison with Previous Implementations
The APIServerAugmentation unifies and replaces:
- `BrainyMCPBroadcast` - Node-specific WebSocket/HTTP server
- `WebSocketConduitAugmentation` - WebSocket client functionality
- `ServerSearchAugmentations` - Remote Brainy connections
Benefits of the unified approach:
- Single augmentation for all API needs
- Consistent interface across protocols
- Automatic operation broadcasting
- Environment-aware implementation
- Zero-configuration philosophy
## Advanced Usage
### Custom Operation Filtering
```typescript
class FilteredAPIServer extends APIServerAugmentation {
shouldExecute(operation: string, params: any): boolean {
// Don't broadcast sensitive operations
if (operation === 'delete' && params.sensitive) {
return false
}
return true
}
}
```
### Integration with Other Augmentations
```typescript
const brain = new BrainyData()
// Stack augmentations for complete system
brain.augmentations.register(new WALAugmentation()) // Durability
brain.augmentations.register(new EntityRegistryAugmentation()) // Dedup
brain.augmentations.register(new APIServerAugmentation()) // API
await brain.init()
// All augmentations work together seamlessly!
```
## Troubleshooting
### Server won't start
- Check if port is already in use
- Verify Node.js dependencies are installed: `npm install express cors ws`
- Check console for error messages
### WebSocket connections drop
- Ensure heartbeat responses are handled
- Check for proxy/firewall issues
- Verify CORS configuration
### Authentication not working
- Ensure `auth.required` is set to `true`
- Verify API keys or bearer tokens are correctly configured
- Check request headers are properly formatted
## Future Enhancements
- [ ] Deno server implementation
- [ ] Service Worker implementation
- [ ] GraphQL endpoint
- [ ] gRPC support
- [ ] Built-in SSL/TLS
- [ ] Redis-based rate limiting
- [ ] Prometheus metrics endpoint
- [ ] OpenAPI/Swagger documentation
## Related Documentation
- [Augmentation System Overview](../AUGMENTATION-SYSTEM.md)
- [BrainyAugmentation Interface](./brainy-augmentation.md)
- [MCP Integration](../mcp/README.md)
- [Zero-Config Philosophy](../ZERO-CONFIG.md)

View file

@ -1,386 +0,0 @@
---
title: Consistency Model
slug: concepts/consistency-model
public: true
category: concepts
template: concept
order: 4
description: The exact guarantees behind Brainy's Db API — snapshot isolation, atomic transactions, two levels of compare-and-swap, time travel, retention, snapshots, crash recovery, and the reserved-field contract.
next:
- guides/snapshots-and-time-travel
- guides/optimistic-concurrency
---
# Consistency Model
Brainy 8.0's consistency story rests on one mechanism: **generational MVCC**
— multi-version concurrency control over immutable, generation-stamped
records. It is exposed through a single value type, the **`Db`**: an
immutable, point-in-time view of the whole store that you query like the
live brain.
```typescript
const db = brain.now() // pin the current state — O(1), no I/O
await brain.transact([
{ op: 'update', id: invoiceId, metadata: { status: 'paid' } }
])
await db.get(invoiceId) // still 'pending' — pinned, forever
await brain.get(invoiceId) // 'paid' — live
await db.release() // unpin when done
```
This page states the guarantees precisely — what is promised, what it costs,
and where the honest limits are. The design record is
[ADR-001](../ADR-001-generational-mvcc.md); every guarantee below is proven
by a dedicated test in `tests/integration/db-mvcc.test.ts`.
## The generation clock
A **monotonic 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`/…).
- `brain.generation()` reads it; it is persisted in the data directory and
**never reissued** — not across restarts, and not across `restore()`
(the counter is floored at its pre-restore value).
Every `Db` is pinned at one generation. `db.generation` and `db.timestamp`
identify the view; `newerDb.since(olderDb)` returns exactly the entity and
relationship ids that committed transactions touched between two views.
## Snapshot isolation for reads
**Guarantee:** a `Db` reads exactly the state at its pinned generation, no
matter what commits afterwards — including deletes. There are no torn reads,
no partially applied batches, and no drift over time.
- `brain.now()` pins the current generation in O(1).
- `brain.transact()` returns a `Db` pinned at the freshly committed
generation.
- `brain.asOf(generation | Date | snapshotPath)` pins past state.
While nothing has committed past the pin, reads delegate to the live fast
paths — pinning is free until history actually moves. Once later
transactions commit, the view keeps serving the **full query surface** at
its generation (see "Reading the past" below).
Writers are never blocked by readers and readers never block writers: a
pinned view stays valid because nothing overwrites the immutable records it
resolves from (the LMDB reader-pin model).
## Transaction atomicity
`brain.transact(ops)` executes a declarative batch — `add`, `update`,
`remove`, `relate`, `unrelate`**atomically as exactly one generation**:
```typescript
const db = await brain.transact([
{ op: 'add', id: orderId, type: NounType.Document, subtype: 'order', data: 'Order #1042' },
{ op: 'add', id: itemId, type: NounType.Thing, subtype: 'line-item', data: 'Widget x3' },
{ op: 'relate', from: orderId, to: itemId, type: VerbType.Contains, subtype: 'order-line' }
], { meta: { author: 'order-service', requestId: 'req-9f2' } })
db.receipt.ids // resolved id per operation, in input order
```
Either every operation applies, or none do and the store is byte-identical
to its pre-transaction state. Operation semantics mirror the corresponding
single-operation methods — validation, subtype enforcement, relationship
deduplication, delete cascades — and later operations may reference ids
created earlier in the same batch.
**The commit point is one atomic rename.** The durability protocol:
1. Before-images of every touched id are staged into an immutable
generation directory and **fsynced**.
2. The batch executes through the transaction manager (which has its own
operation-level rollback for non-crash failures).
3. The store manifest is replaced via atomic temp-file rename and fsynced.
**The rename is the commit** — a generation is committed if and only if
the manifest says so.
**Crash recovery:** on the next open, any staged generation above the
manifest watermark is an uncommitted transaction; its before-images are
restored (idempotently — recovery can itself crash and rerun) and derived
indexes never observe the rolled-back state. A crash anywhere before the
rename rolls back to the exact pre-transaction bytes; a crash after it keeps
the transaction.
Transaction metadata (`meta`) is reified Datomic-style: recorded in an
append-only transaction log readable via `brain.transactionLog()` — audit
fields live in the database, not in commit messages.
## Two levels of compare-and-swap
Concurrent `transact()` calls commit serially (snapshot-isolated batches).
For lost-update protection across a readmodifywrite cycle, Brainy offers
CAS at two granularities:
| Granularity | Mechanism | Conflict error | Use when |
|---|---|---|---|
| **Per entity** | `_rev` + `{ op: 'update', ifRev }` (also on `brain.update()`) | `RevisionConflictError` | "This entity must not have changed since I read it." |
| **Whole store** | `transact(ops, { ifAtGeneration })` | `GenerationConflictError` | "*Nothing* may have committed since I read." |
```typescript
const view = brain.now()
const order = await view.get(orderId)
try {
await brain.transact(
[{ op: 'update', id: orderId, metadata: { total: recompute(order) }, ifRev: order._rev }],
{ ifAtGeneration: view.generation }
)
} catch (err) {
if (err instanceof GenerationConflictError) {
// Something committed since the pin — re-read and retry.
}
} finally {
await view.release()
}
```
An `ifRev` conflict on any operation rejects the **whole batch**; an
`ifAtGeneration` conflict is detected before anything is staged. Both leave
the store untouched and the generation counter unchanged. See
[Optimistic concurrency with `_rev`](../guides/optimistic-concurrency.md)
for the per-entity pattern in depth.
## Reading the past
`brain.asOf()` accepts a generation number, a `Date` (resolved through the
transaction log to the newest generation committed at or before it), or a
snapshot directory path. Historical views serve the **full query surface**
`get()`, `find()` in every mode, semantic search, graph traversal,
cursors, aggregation — through two complementary paths:
- **Record path** (free): `get()`, metadata-level `find()`, and
filter-based `related()` resolve directly through the immutable record
layer. Ids untouched since the pin still ride the live fast paths.
- **Index path** (paid once): index-accelerated queries — semantic/vector
search, graph traversal, cursors, aggregation — are served by an
**at-generation index materialization** built lazily on first use:
Brainy reconstructs in-memory indexes over the exact record set at that
generation. This costs O(n at the pinned generation) time and memory,
**once per `Db`**, cached until `release()`. That is the open-core price
of historical index queries, stated plainly.
A native index provider implementing the optional
`VersionedIndexProvider` plugin capability serves the same historical reads
from its retained index segments **without any rebuild** — the materializer
is the correctness baseline, the provider is the accelerator. Semantics are
identical on both paths.
### History granularity — the honest limit
Generation *records* are written per `transact()` batch only.
Single-operation writes (`add`/`update`/`remove`/`relate`/… outside
`transact()`) advance the generation counter — so watermarks and CAS stay
sound — but do **not** stage before-images: they remain visible through
earlier pins and are not reported by `db.since()`. Code that needs pinned
isolation across its own writes uses `transact()`. This is the documented
8.0 contract, not an accident.
## Speculative writes: `db.with()`
`db.with(ops)` returns a new `Db` whose reads see the operations applied
**in memory, on top of the view** — Datomic's `with`. Nothing touches disk,
the generation counter, or index providers:
```typescript
const current = brain.now()
const whatIf = await current.with([
{ op: 'update', id: employeeId, metadata: { team: 'platform' } }
])
await whatIf.find({ where: { team: 'platform' } }) // sees the change
await brain.get(employeeId) // unchanged — nothing committed
```
**The one boundary:** overlay entities carry no embeddings (`with()` never
invokes the embedder), so index-accelerated queries and `persist()` on a
speculative view throw `SpeculativeOverlayError` rather than returning
silently incomplete results. `get()`, metadata-filter `find()`, and
filter-based `related()` work fully on overlays. To get the full surface,
commit the same operations with `brain.transact()`.
## Retention and compaction
Historical records cost disk space, so retention is explicit:
- Every live `Db` holds a refcounted **pin**; a record-set is never
reclaimed while any pin could need it — pinned reads stay correct across
compaction, always.
- The **`retention`** knob governs auto-compaction (on every `flush()`/
`close()`): unset → ADAPTIVE (disk/RAM-pressure, zero-config) · `'all'`
unbounded · `{ maxGenerations?, maxAge?, maxBytes? }` → explicit caps.
`brain.compactHistory({ maxGenerations?, maxAge?, maxBytes? })` reclaims
manually on the same caps, and records the **horizon**`asOf()` below it
throws `GenerationCompactedError`, explicitly, never partial data.
- To keep a state readable forever, `persist()` it first: snapshots are
self-contained and unaffected by compaction of the source store.
Release `Db` values you do not keep (including the ones `transact()`
returns). A `FinalizationRegistry` backstop releases leaked pins at garbage
collection, but explicit `release()` is what makes compaction
deterministic.
## Durability: snapshots and restore
`db.persist(path)` cuts a **self-contained snapshot** under the store's
commit mutex, so no commit or compaction can interleave. On filesystem
storage it is built from **hard links**: because every data file is
immutable-by-rename, linking is safe — the snapshot is created without
copying entity data, shares disk space with the source, and later writes to
the source can never alter it (rewrites swap inodes; the snapshot keeps the
old bytes). Cross-device targets fall back to byte copies; in-memory stores
serialize to the same directory layout, producing a real, durable store.
Two rules keep snapshots honest:
- `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 `GenerationConflictError` instead of persisting the
wrong state.
- `brain.restore(path, { confirm: true })` replaces the store's entire
state from a snapshot via byte copy (never links — the snapshot stays
independent), rebuilds all indexes, and floors the generation counter so
observed generation numbers are never reissued. Live pins do not survive
a restore — release them first (a warning is logged when any exist).
`Brainy.load(path)` (or `brain.asOf(path)`) opens a snapshot as a
self-contained **read-only** store with the full query surface, including
vector search.
## Reserved fields
Some field names belong to Brainy, not to your metadata. They live at **top
level** on every entity and relationship, have dedicated write paths, and may
never appear inside a `metadata` bag:
| Entities (nouns) | Relationships (verbs) | Canonical write path |
|---|---|---|
| `noun` | `verb` | the `type` param of `add()` / `relate()` |
| `subtype` | `subtype` | the `subtype` param |
| `visibility` | `visibility` | the `visibility` param (`'public'` \| `'internal'`) |
| `confidence` | `confidence` | the `confidence` param |
| `weight` | `weight` | the `weight` param |
| `service` | `service` | the `service` param (fixed at create time) |
| `data` | `data` | the `data` param |
| `createdBy` | `createdBy` | the `createdBy` param of `add()` (system-managed on verbs) |
| `createdAt`, `updatedAt`, `_rev` | `createdAt`, `updatedAt`, `_rev` | system-managed (`ifRev` for CAS) |
The canonical machine-readable lists are exported as
`RESERVED_ENTITY_FIELDS` and `RESERVED_RELATION_FIELDS` (defined in
`src/types/reservedFields.ts`, the single source of truth). Three layers
enforce the contract:
1. **Compile time** — every `metadata` param (`add`, `update`, `relate`,
`updateRelation`, and the matching `transact()` operations) rejects a
literal reserved key as a TypeScript error.
2. **Write time** — untyped (JavaScript) callers that pass one anyway are
normalized: user-settable fields (`confidence`, `weight`, `subtype`, and
`service`/`createdBy` at create time) are remapped to their dedicated
param — **top-level wins** when both are supplied — and system-managed
fields are dropped with a one-shot warning naming the correct write path.
`update({ metadata: { confidence: 0.9 } })` therefore behaves exactly
like `update({ confidence: 0.9 })`.
3. **Read time** — every read path (`get`, `find`, `search`,
`related`, batch reads, and historical `asOf()` materialization)
surfaces reserved fields **only at top level**: `entity.metadata` and
`relation.metadata` contain only your custom fields, always.
```typescript
const id = await brain.add({
type: 'document', subtype: 'invoice',
data: 'Invoice #42', confidence: 0.95, // reserved → top-level params
metadata: { customer: 'acme', total: 129.5 } // custom fields only
})
const entity = await brain.get(id)
entity.confidence // 0.95 — top level
entity.metadata // { customer: 'acme', total: 129.5 }
```
### Visibility — `public` / `internal` / `system`
`visibility` is a reserved tier that controls whether an entity or relationship
surfaces on Brainy's **default** user-facing reads. The absence of the field is
exactly equivalent to `'public'`.
| Tier | Counted in `getNounCount()` / `stats()`? | Returned by default `find()` / `related()`? | Opt-in |
|---|---|---|---|
| `'public'` (default, or field absent) | yes | yes | — |
| `'internal'` | no | no | `find({ includeInternal: true })` / `related({ includeInternal: true })` |
| `'system'` | no | no | `find({ includeSystem: true })` / `related({ includeSystem: true })` |
- **`'public'`** — normal data. Counted and returned everywhere. Stored lean:
the field is omitted on disk for public records, so existing data needs no
migration.
- **`'internal'`** — your app's own bookkeeping (audit trails, derived caches,
scratch entities) that should not pollute default queries, counts, or
`stats()`, yet must stay retrievable on demand. Set it via the `visibility`
param; read it back with the `includeInternal` opt-in.
- **`'system'`** — Brainy's own plumbing (for example the Virtual File System
root entity). Hidden everywhere by default — even when `includeInternal` is
set — and surfaced only with the explicit `includeSystem` opt-in. The
`'system'` tier is **not** part of the public `add()` / `relate()` param type
(`'public' | 'internal'`); only internal Brainy code assigns it.
The opt-ins are applied as a **hard candidate filter** — hidden entities are
removed before `limit` / `offset` are applied, so a default `find({ limit: 10 })`
always returns ten *visible* results when that many exist, never a short page.
```typescript
// App-internal scratch entity: present, retrievable, but out of the way.
await brain.add({ type: 'task', data: 'reindex job', visibility: 'internal' })
await brain.getNounCount() // unchanged — internal not counted
await brain.find({ type: 'task' }) // [] — hidden by default
await brain.find({ type: 'task', includeInternal: true }) // includes it
// A brand-new brain reports zero user entities even though the VFS root exists:
const fresh = new Brainy()
await fresh.init()
await fresh.getNounCount() // 0 — the root is visibility:'system'
```
> **Note (8.0):** the structural `Contains` edges the VFS creates between
> directories and files are left at the default (`public`) visibility for now —
> only the VFS *root entity* is `'system'`. Marking those edges system requires
> companion changes to VFS traversal and is out of scope for this change.
## What is not guaranteed
Stated plainly, so nothing surprises you in production:
- **Single-writer.** Brainy is a single-writer, many-reader database
([multi-process model](./multi-process.md)). Transactions are atomic
within one writer process — there is no distributed or cross-process
transaction coordination.
- **History granularity.** Every write is its own immutable generation —
`transact()` batches AND single-operation `add`/`update`/`remove`/`relate`.
A pin always freezes against later writes, and every write is addressable
via `asOf()`. (`transact()` groups several operations into ONE atomic
generation; durability of single-op history is batched via async
group-commit — a hard crash can lose only the last un-flushed window's
*history*, never live data.)
- **Compacted history is gone.** `asOf()` below the compaction horizon
fails explicitly; persist what you must keep.
- **Counter persistence is coalesced for single-operation writes.** Durable
artifacts (records, manifests, snapshots) always persist the counter at
their own commit points, so a crash inside the coalescing window can lose
only counter values nothing durable ever referenced.
- **Speculative overlays are metadata-only readers.** Index-accelerated
queries on `with()` views throw rather than guess.
## Where to go next
- [Snapshots & Time Travel](../guides/snapshots-and-time-travel.md) — the
recipes: backup, restore, time-travel debugging, what-if analysis, audit
trails.
- [Optimistic concurrency with `_rev`](../guides/optimistic-concurrency.md)
— the per-entity CAS pattern.
- [ADR-001: Generational MVCC](../ADR-001-generational-mvcc.md) — the full
design record, including the persisted layout and the proof table.

View file

@ -1,117 +0,0 @@
---
title: The Generation Fact Log
slug: concepts/generation-fact-log
public: true
category: concepts
template: concept
order: 6
description: Every committed write also appends a self-verifying "fact" — an after-image commit record — to an append-only log. What facts are, the crash-safety model, the scanFacts() streaming surface, family stamps, and how index providers consume the log for sequential heals.
next:
- concepts/consistency-model
- guides/snapshots-and-time-travel
---
# The Generation Fact Log
Since 8.4.0, every committed generation also appends a **fact** — a compact record of what each
touched entity or relationship *became* — to an append-only, checksummed log under
`_generations/facts/`. Where the generational history answers *"what did things look like
before?"* (before-images, powering `asOf()` and rollback), the fact log answers *"what happened,
in order?"* — one sequential, self-verifying stream of the store's present being written.
Nothing about querying changes. The fact log exists for three consumers:
1. **Index heals and rebuilds** — one sequential read in commit order replaces a per-entity
directory walk over millions of files.
2. **Incremental catch-up** — a derived index that knows which generation it reflects reads *just
the gap*, instead of rebuilding from scratch.
3. **Replay and audit tooling** — anything that wants the store's committed timeline as a stream.
## What a fact is
One fact per committed generation:
- **`generation`** and **`timestamp`** — which commit, when.
- **`ops`** — every write in that commit: `{ kind: 'noun' | 'verb', id, record }` where `record`
holds the entity's full after-image (both stored legs), or **`null` for a tombstone** — a
removal carries no body, by design.
- **`meta`** — the transaction metadata `transact()` was submitted with, when present.
- **`blobHashes`** — content-blob references, for exact reclamation accounting.
Facts accumulate **from the first write after upgrading** — pre-existing history is not
retroactively converted, and consumers fall back to the enumeration walk when no log exists.
## Crash safety, in one paragraph
Facts are appended and fsynced **inside the same durability window as the commit itself**, before
the commit point — so after a crash, the log can only ever be *ahead* of committed truth, never
behind it with a hole. On open, the store reconciles the log back to the committed watermark:
torn tails are detected by per-record checksums and cut; whole records beyond the watermark are
truncated. The invariant every reader can rely on: **an absent generation was never committed; a
present fact was.** `transact()` facts are durable the moment `transact()` returns; single-op
facts share the same group-commit flush as the rest of their generation, so a hard kill loses the
fact and the generation *together* — never a torn state.
## Reading the log
```typescript
const scan = brain.scanFacts({ fromGeneration: 1 })
if (scan) {
// Telemetry up front — progress bars get a denominator from second zero.
console.log(scan.headGeneration, scan.segmentCount, scan.approxFactCount)
for await (const batch of scan.batches()) {
// Each batch: { facts, firstGeneration, lastGeneration, factCount, byteSize, segmentId }
for (const fact of batch.facts) {
for (const op of fact.ops) {
if (op.record === null) {
// a tombstone: op.id was removed in this generation
}
}
}
}
console.log(scan.summary()) // { factsYielded, segmentsRead } — the cross-check
}
```
- `scanFacts()` returns `null` when the store hosts no fact log (older store, or a storage adapter
without binary append support) — fall back to enumerating entities.
- Scans run against a **snapshot**: facts appended after the scan opens never bleed in, each fact
is yielded exactly once, and a detected gap aborts loudly — never a silent skip.
- `brain.factSegmentPaths()` returns the immutable, *sealed* segment files for zero-copy consumers
(the append-mutable tail is excluded — read it through `scanFacts()`).
## Family stamps: how a projection proves it's current
Anything derived from the store — an index, the entity file tree itself — carries a **family
stamp**: a small JSON record of *which committed generation the projection reflects*
(`sourceGeneration`) plus the invariants that verify it whole (exact per-file byte sizes for
bounded families; rollup invariants like entity counts for unbounded ones). At open, coherence is
a **comparison**, not a walk:
- stamp equals the committed watermark and invariants hold → serve;
- stamp is behind → the projection reads just the gap from the fact log;
- invariants fail → loud, named divergence — `brain.repairIndex()` rebuilds from canonical and
re-stamps.
The verifier is exported (`verifyFamilyStamp`) so every projection — TypeScript or native — runs
literally the same check.
## For plugin authors: the storage capability
Index providers receive the storage adapter, not the brain — so the host wires the log onto it.
Feature-detect and prefer the stream; fall back to enumeration:
```typescript
const scan = storage.scanFacts?.({ fromGeneration: stamp.sourceGeneration + 1 })
if (scan) {
// sequential catch-up from the log
} else {
// enumeration walk (older store or adapter)
}
const committed = storage.committedGeneration?.() // the watermark stamps compare against
```
Providers must never construct their own reader over the log's files — the open path belongs to
the single writer (it reconciles the log at open); the capability is the sanctioned seam.

View file

@ -1,153 +0,0 @@
---
title: Multi-Process Model
slug: concepts/multi-process
public: true
category: concepts
template: concept
order: 5
description: How Brainy coordinates a single writer with any number of readers on a filesystem data directory — and how to safely inspect a live store.
next:
- guides/inspection
---
# Multi-Process Model
Brainy is a **single-writer, many-reader** database when backed by filesystem
storage. This page explains the model, the guarantees, and the safe ways to
inspect a live store from a second process.
## The rule
For one data directory:
- **One writer** at a time. The writer acquires an exclusive lock on the
directory at `init()` and releases it on `close()`.
- **Any number of readers**, concurrent with each other and with the writer.
Readers open via `Brainy.openReadOnly()` — they never touch the writer
lock.
Any attempt to open a second writer on the same directory throws:
```
BrainyError: Another writer holds this Brainy directory.
PID: 1774431 on host app-host-1
Started: 2026-05-15T14:22:11Z
Heartbeat: 2026-05-15T14:22:34Z
Version: 7.21.0
Directory: /data/brain
```
This is intentional. Two writers sharing a directory would silently corrupt
in-memory indexes and produce wrong query results — the worst possible default
for an operations tool.
## Why a lock?
Brainy keeps its primary indexes (HNSW, metadata, graph adjacency) in memory.
On disk, those indexes are persisted incrementally as writes flush. A second
process opening the same directory:
- Loads the *persisted* state into a fresh in-memory copy.
- Has no awareness of writes the first process buffered but hasn't flushed.
- Will overwrite the persisted state on its own next flush, racing the first
process and corrupting whichever wins.
The fix is the lock: refuse to open a second writer. SQLite has done the same
since the late 1990s (`SQLITE_BUSY`).
## What about Cor?
Brainy + Cor compose cleanly under this model:
- Cor stores its column-index segments inside the same `rootDir` (under
`indexes/_column_index/{field}/`).
- Segments (`*.cidx` files) are **immutable** once written. Cor mmaps them
read-only.
- The `MANIFEST.json` per field is updated via atomic rename — readers see
either the old or new manifest, never a torn file.
A reader process can safely mmap Cor segments alongside a live writer
without coordination. The single Brainy writer lock at
`<rootDir>/locks/_writer.lock` covers Cor too, because Cor segment
writes happen on the writer's side.
## Stale-lock detection
If a writer crashes or is forcibly killed, its lock file is left behind. To
avoid a permanently-jammed directory, Brainy treats a lock as stale when:
1. The recorded `hostname` equals the current host (cross-host PID checks
are unsafe), AND
2. The recorded `pid` is no longer alive (`process.kill(pid, 0)` returns
`ESRCH`), OR the `lastHeartbeat` field is older than 60 seconds.
A live writer rewrites `lastHeartbeat` every 10 seconds, so a hung writer
that's missed several heartbeats is treated as dead. Stale locks are
overwritten with a warning.
If stale detection cannot prove the existing lock is dead — for example, a
crashed writer on a different host writing to a shared filesystem — pass
`{ force: true }` to override. A warning is logged either way.
## Heartbeat and shutdown
The heartbeat interval rewrites the lock file every 10 seconds. The timer
is unref'd, so it does not keep the event loop alive on its own.
On normal shutdown the writer releases the lock in `close()`. The shutdown
hooks Brainy registers for `SIGTERM`, `SIGINT`, and `beforeExit` also
release the lock so a container restart doesn't strand the directory.
## How to inspect a live writer
Use `Brainy.openReadOnly()`. It does not acquire the writer lock, so it
coexists with whatever the writer is doing:
```typescript
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', path: '/data/brain' }
})
const stats = await reader.stats()
const bookings = await reader.find({ where: { entityType: 'booking' } })
await reader.close()
```
What the reader sees reflects the writer's most recent **flush** to disk. If
you need fresher state, ask the writer to flush before opening:
```typescript
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', path: '/data/brain' }
})
const acked = await reader.requestFlush({ timeoutMs: 5000 })
if (!acked) {
console.warn('Writer did not respond; results reflect last natural flush.')
}
const fresh = await reader.find({ where: { entityType: 'booking' } })
```
The CLI `brainy inspect` subcommands all do this for you by default
(`--no-fresh` to opt out).
## What's not enforced (yet)
- **Non-filesystem backends** are out of scope in 8.0, which ships only the
filesystem and memory adapters. A custom `BaseStorage` subclass that is not
filesystem-backed does not enforce multi-process locking by default: two
processes can both succeed at `init()` in writer mode and clobber each
other's writes. A best-effort warning is logged in writer mode against a
non-filesystem backend.
- **Long-running readers** do not automatically pick up new Cor segments
the writer publishes. One-shot inspector calls re-open the store and see
fresh segments; a reader that stays open for hours sees its column store
as-of the time it opened.
## Reading material
- `Brainy.openReadOnly()` — [API reference](../api/brainy.md)
- `brainy inspect` — [inspection guide](../guides/inspection.md)
- Cor columnar storage — see `node_modules/@soulcraft/cor/README.md`

View file

@ -1,189 +0,0 @@
---
title: Storage Adapter Inheritance Contract
slug: concepts/storage-adapters
public: true
category: concepts
template: concept
order: 6
description: How storage adapters extend Brainy's BaseStorage and FileSystemStorage to inherit multi-process safety, what plugin authors need to override, and the contract Brainy promises to keep stable.
next:
- concepts/multi-process
- guides/inspection
---
# Storage Adapter Inheritance Contract
Brainy's storage layer is designed for plugins to extend cleanly. A plugin
that subclasses `BaseStorage` or `FileSystemStorage` inherits new behavior
Brainy adds over time without code changes — provided the import resolution
brings in the right Brainy version at runtime.
This page documents what plugin authors can rely on, what they need to
override, and the install-time failure modes that the defensive
`hasStorageMethod()` guard exists to handle.
## The class hierarchy
```
BaseStorageAdapter (counts, batch ops, multi-tenancy hooks)
BaseStorage (type-statistics, lifecycle helpers, generation
hooks, default no-op multi-process methods)
FileSystemStorage (real filesystem I/O, writer-lock implementation,
flush-request watcher, atomic writes)
<your plugin's storage> (Cor's MmapFileSystemStorage, etc.)
```
When you `extend FileSystemStorage`, your adapter inherits every method on
the chain — including the ones Brainy adds in a later release — for free.
JavaScript's prototype chain resolves method lookups dynamically; nothing
about the inheritance is baked in at class-definition time.
## What you inherit for free
A plugin whose storage class extends `FileSystemStorage` automatically gets
the full multi-process safety surface:
| Method | What it does | Override? |
|---|---|---|
| `acquireWriterLock(opts)` | Write `_writer.lock`, start heartbeat, throw on conflict | No |
| `releaseWriterLock()` | Clean up lock file + heartbeat timer | No |
| `readWriterLock()` | Read lock file as `WriterLockInfo` | No |
| `startFlushRequestWatcher(cb)` | Poll `_flush_requests/` and invoke `cb` | No |
| `stopFlushRequestWatcher()` | Stop the polling timer | No |
| `requestFlushOverFilesystem(timeoutMs)` | Drop a `.req`, await `.ack` | No |
| `supportsMultiProcessLocking()` | Return `false` (default) | **Yes — override to `true`** |
The only required override is the capability flag. Returning `true` from
`supportsMultiProcessLocking()` is the signal Brainy uses to decide whether
to call `acquireWriterLock()` at init.
```typescript
import { FileSystemStorage } from '@soulcraft/brainy'
export class MmapFileSystemStorage extends FileSystemStorage {
public supportsMultiProcessLocking(): boolean {
return true
}
// ... your mmap-specific overrides ...
}
```
That's the full ceremony for inheriting multi-process safety.
## When NOT to extend FileSystemStorage
If your storage is **not filesystem-backed** (a custom
network backend), extend `BaseStorage` directly:
```typescript
import { BaseStorage } from '@soulcraft/brainy'
export class MyCloudStorage extends BaseStorage {
// BaseStorage's default no-op implementations of the multi-process
// methods stay in effect. `supportsMultiProcessLocking()` returns false
// by default — keep it that way unless you've implemented an object-
// versioned lease or similar cross-process synchronization for your
// backend.
}
```
Brainy treats cloud backends as not-multi-process-safe by default and logs a
one-line warning at init. That's the correct behavior until cloud locking
ships (currently out of scope — see
[`concepts/multi-process`](./multi-process.md)).
## What `hasStorageMethod()` actually guards against
The defensive check at every new-storage-method call site (`brainy.ts`,
`hasStorageMethod(name)`) does **not** exist to handle "plugin bundles a
stale BaseStorage." Plugins ship a dist that preserves the dynamic ESM
import (verify in your plugin's `dist/`: `import { FileSystemStorage } from
'@soulcraft/brainy'` is not rewritten to a vendored copy). The prototype
chain at runtime resolves to whatever Brainy version your consumer has
installed.
`hasStorageMethod()` protects against **build/install artifacts** that break
the prototype chain at the consumer-app level:
- **Stale `node_modules`** — a lingering install from before the consumer
upgraded Brainy. The package.json says `@soulcraft/brainy@7.22.0` but
`node_modules/@soulcraft/brainy` is still 7.20.x.
- **Lockfile drift**`bun.lockb` / `package-lock.json` pins a brainy
version older than the package.json range, and `bun install` honors the
lockfile.
- **Docker layer cache** — the image reuses a `node_modules` from an
earlier build that predates the brainy bump.
- **Bundler quirks** — some bundlers (esbuild, webpack) flatten the
prototype chain at build time and lose later prototype mutations. Brainy
doesn't mutate prototypes at runtime, but bundler behavior can still
cause method lookups to fail in non-Node environments.
In any of those, calling `storage.acquireWriterLock(...)` unconditionally
throws `TypeError: storage.acquireWriterLock is not a function`. The guard
turns that into a logged warning + graceful no-op so the app still boots,
and the warning names the adapter class plus a remediation hint:
```
[brainy] Storage adapter `MmapFileSystemStorage` is missing the multi-process
methods on its prototype chain. Writer locking and the flush-request RPC are
disabled for this directory. Likely fix: clean install (`rm -rf node_modules
bun.lockb && bun install`) or rebuild your container image to refresh
`@soulcraft/brainy` to ≥7.21. See docs/concepts/storage-adapters.md.
```
## Authoring a new storage adapter — minimum checklist
1. **Extend the right base class.**
- Filesystem-backed → `FileSystemStorage`.
- Cloud / network / custom → `BaseStorage`.
2. **Override the capability flag.** If filesystem-backed:
```typescript
public supportsMultiProcessLocking(): boolean { return true }
```
3. **Don't `super.X()`-wrap the multi-process methods**. They're inherited;
leaving them inherited means `hasStorageMethod()` finds them on the
prototype chain. Re-declaring them as `super.X()` wrappers makes the
helper resolve to your wrapper, which can fool the guard if your
constructor runs before the super class initializes.
4. **Do override `init()` / `flush()` / `close()`** as needed. Always call
`super.init()` / `super.flush()` / `super.close()` first so the
filesystem prep, writer-lock acquisition, and lock release happen in the
expected order.
5. **Verify the inheritance.** A one-line smoke test in your plugin's
`__tests__/`:
```typescript
const s = new MyStorage(rootDir)
await s.init()
assert(typeof s.acquireWriterLock === 'function')
assert(s.supportsMultiProcessLocking() === true)
```
If `acquireWriterLock` is undefined the prototype chain is broken at
install time — fix install, not your plugin.
6. **Pin your peer dep generously.** `"peerDependencies": {
"@soulcraft/brainy": "^7.21.0" }` accepts any compatible 7.x. Don't pin
to an exact patch unless you're tracking a known regression.
## Future direction
The 7 multi-process methods are currently defaults on `BaseStorage`. A
future refactor may extract them into a `MultiProcessSafeStorage`
interface/mixin for cleaner separation — only adapters that opt in would
expose them. This would require a minor bump and is tracked as an internal
follow-up; consumers don't need to anticipate the change.
## Reading material
- [`concepts/multi-process`](./multi-process.md) — the writer-lock model,
heartbeat semantics, what the lock protects.
- [`guides/inspection`](../guides/inspection.md) — `brainy inspect` and the
read-only mode.
- `node_modules/@soulcraft/brainy/dist/storage/baseStorage.d.ts` — the
authoritative type signatures for every method this page references.

View file

@ -1,155 +0,0 @@
---
title: What is Brainy?
slug: getting-started/what-is-brainy
public: true
category: getting-started
template: guide
order: 0
description: Plain-language guide covering what Brainy does, how it compares to other tools, and what you can build with it. No jargon, no code — just clear analogies.
next:
- getting-started/installation
- getting-started/quick-start
---
# Brainy and Cor — Explained Simply
*A plain-language guide for anyone who wants to understand what this thing actually does.*
---
## What is Brainy?
Imagine you have the world's smartest librarian.
You walk up and say *"I'm looking for something about climate change — but only books published after 2020, and only ones written by authors I've already read."* A normal library would make you dig through a card catalogue, then cross-reference a list of authors, then scan the shelves yourself. That takes a while.
Your smart librarian does all three at the same time — in less than the time it takes to blink.
That's Brainy. It's a knowledge database that can search by **meaning**, follow **connections**, and filter by **labels** — all at once, in a single question.
---
## The Three Superpowers
### 1. Meaning Search (the "fuzzy" superpower)
When you search for "automobile," Brainy also finds results about "car," "vehicle," and "sedan" — because it understands what words *mean*, not just how they're spelled. It reads your data the way a person would, not the way a search box does.
Think of it like the librarian who finds books on "heartbreak" when you ask for something about "loneliness."
### 2. Relationship Walking (the "follow the thread" superpower)
Every piece of information can be connected to other pieces. A Person *works at* a Company. A Project *depends on* a Tool. A Recipe *contains* Ingredients.
Brainy can follow these connections across many hops in one step. Ask for "everything connected to this author, two steps out" and Brainy returns the author's books, the books' publishers, the publishers' other authors — without you needing to chain four separate lookups yourself.
Think of it like the librarian who not only hands you the book you asked for, but also knows which shelf it came from, who donated it, and what other books arrived in the same donation.
### 3. Label Filtering (the "narrow it down" superpower)
Sometimes meaning and connections aren't enough — you need precision. "Only recipes with fewer than 500 calories." "Only events from last week." "Only documents tagged as urgent."
Brainy can narrow any result set down by exact labels or ranges in the same breath as the other two searches. No extra steps.
---
## What Else Can It Do?
- **Virtual file cabinet.** Brainy includes a full filesystem you can use to store, organize, and semantically search files — PDFs, documents, anything — the same way you search everything else.
- **Live dashboards.** You can define running totals that Brainy keeps updated automatically — things like "total sales this month by region" or "average response time per service." Every time new data comes in, the numbers stay current with no manual recalculation.
- **Time travel.** Every committed change becomes part of the database's history. You can pin the current state as a frozen view, see the whole knowledge base exactly as it was last week, try out changes in a scratch copy that never touches the real data, and take instant backups.
- **Universal vocabulary.** Brainy ships with a shared language of 42 kinds of things (Person, Document, Task, Concept, Event…) and 127 kinds of connections (Contains, DependsOn, Creates, RelatedTo…). This means data from different sources speaks the same language without you having to translate.
---
## What is Cor?
Cor is a turbocharger for Brainy.
Same car. Same controls. Same fuel. You just swap in a faster engine under the hood, and everything that used to take a moment now happens instantly.
Technically, Cor is an optional plugin written in Rust — a lower-level language that runs much closer to the raw metal of your processor. It plugs into Brainy and takes over the most compute-intensive work: the distance calculations that power meaning search, the number-crunching behind live aggregates, and the set operations that drive label filtering.
You install it with one line, register it with one call, and Brainy automatically uses it everywhere it can help.
---
## How Much Faster?
Plain language:
- **Searches** go from "the blink of an eye" to "faster than a blink." The overall speedup is **5.2× on average** across all operations.
- **Live aggregates** are rebuilt using all CPU cores in parallel, so re-indexing large datasets takes a fraction of the time.
- **Analytics** that aren't even possible in pure JavaScript — real-time anomaly detection, streaming percentile estimates, approximate unique counts — become available because Cor brings the native capabilities required to run them efficiently.
If Brainy is what makes knowledge fast, Cor is what makes Brainy feel instant.
---
## What Does Brainy Replace?
Most applications that need to store and search knowledge end up stitching together several specialized tools. Brainy replaces all of them with one — a single free, open-source library in place of multiple paid services.
### Before and After
**Before Brainy** — a pile of services:
- Pinecone (vectors) + Neo4j (graph) + MongoDB (docs)
- Algolia (search) + Redis (cache) + PostgreSQL + pgvector
- Plus glue code, sync jobs, ETL pipelines, and 3am incidents
**After Brainy** — one thing:
Search, graph, filter, files, time travel, and imports — unified in a single library.
### What Each Tool Is Missing
| Tool | Search | Graph | Filter | VFS | Time travel | Import |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| **Brainy** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| *— Vector databases —* | | | | | | |
| Pinecone | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Weaviate | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Qdrant | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Chroma | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| *— Graph databases —* | | | | | | |
| Neo4j | ❌ | ✅ | ✅ | ❌ | ❌ | ❌ |
| *— Document stores —* | | | | | | |
| MongoDB | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Firestore | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| DynamoDB | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| *— Relational + vector —* | | | | | | |
| PostgreSQL + pgvector | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| MySQL | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| SQLite | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| *— Search engines —* | | | | | | |
| Elasticsearch | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Algolia | ✅ | ❌ | ✅ | ❌ | ❌ | ❌ |
| *— Cache —* | | | | | | |
| Redis | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
Brainy is the only row with every box checked. And it runs all of them in a single query — no stitching services together.
### One library, any scale
Brainy scales from a quick experiment to serious production datasets without changing a line of code. Small datasets live entirely in memory. Larger ones spill to disk, where Brainy shards and compresses everything automatically. Need a backup or a copy? Snapshots are instant — the same API the whole way.
Add Cor and you also unlock memory-mapped storage — aggregate state lives directly in the operating system's memory with zero serialization overhead, as fast as the hardware allows.
---
## What Can You Build?
### Common applications
- **AI agents with persistent memory** — Give any AI an always-on, self-organizing knowledge graph that persists between sessions and across agents.
- **Searchable knowledge bases** — Build institutional memory that links documents automatically and surfaces answers across the full web of related information.
- **Semantic document search** — Index PDFs, code, or media and find them by meaning, not just keywords.
- **Relationship-aware recommendations** — Power product catalogs or content platforms where every recommendation understands what connects to what.
- **Safe experiments** — Test risky changes against a scratch copy of the knowledge base, audit exactly what changed and when, and roll back to any snapshot instantly.
- **Unified business platforms** — Combine booking, CRM, inventory, and analytics in one queryable knowledge graph with no sync pipeline.
### What Brainy is good at
Brainy is the engine underneath production systems that need to combine semantic search, structured filtering, and graph traversal in a single query — agent memory, knowledge-base platforms, business operations consoles, multi-agent coordination, and more. The combination of vector + graph + metadata search in one indexed call is what differentiates it from running three engines side by side.

View file

@ -0,0 +1,415 @@
# 🚀 Brainy 2.0 - Complete Feature List
> **The Truth**: Brainy is MORE powerful than previously documented! This is the complete list of ALL implemented features.
## 🧠 Core Intelligence Engine
### Triple Intelligence System ✅
Unified query system that automatically combines:
- **Vector Search**: HNSW-indexed semantic similarity (O(log n) performance)
- **Graph Traversal**: Relationship-based discovery
- **Field Filtering**: Metadata and attribute queries
- **Auto-optimization**: Queries are automatically optimized based on data patterns
```typescript
// All three intelligences work together automatically
const results = await brain.find({
like: 'AI research', // Vector search
where: { year: 2024 }, // Metadata filtering
connected: { to: authorId } // Graph traversal
})
```
### Neural Query Understanding ✅
- **220+ embedded patterns** for query intent detection
- Natural language query processing
- Automatic query type detection
- Query rewriting and optimization
## 🔧 12+ Production Augmentations
### 1. WAL (Write-Ahead Logging) ✅
```typescript
import { WALAugmentation } from 'brainy'
// Full crash recovery, checkpointing, replay
```
### 2. Entity Registry ✅
```typescript
import { EntityRegistryAugmentation } from 'brainy'
// Bloom filter-based deduplication for streaming data
// Handles millions of entities with minimal memory
```
### 3. Auto-Register Entities ✅
```typescript
import { AutoRegisterEntitiesAugmentation } from 'brainy'
// Automatically extracts and registers entities from text
```
### 4. Intelligent Verb Scoring ✅
```typescript
import { IntelligentVerbScoringAugmentation } from 'brainy'
// Multi-factor relationship strength:
// - Semantic similarity
// - Temporal decay
// - Frequency amplification
// - Context awareness
```
### 5. Batch Processing ✅
```typescript
import { BatchProcessingAugmentation } from 'brainy'
// Adaptive batching with backpressure
// Dynamically adjusts batch size based on load
```
### 6. Connection Pool ✅
```typescript
import { ConnectionPoolAugmentation } from 'brainy'
// Auto-scaling connection management
// Optimized for distributed operations
```
### 7. Request Deduplicator ✅
```typescript
import { RequestDeduplicatorAugmentation } from 'brainy'
// In-flight request deduplication
// 3x performance boost for concurrent operations
```
### 8. WebSocket Conduit ✅
```typescript
import { WebSocketConduitAugmentation } from 'brainy'
// Real-time bidirectional streaming
// Auto-reconnection and heartbeat
```
### 9. WebRTC Conduit ✅
```typescript
import { WebRTCConduitAugmentation } from 'brainy'
// Peer-to-peer data channels
// Direct browser-to-browser communication
```
### 10. Memory Storage Optimization ✅
```typescript
import { MemoryStorageAugmentation } from 'brainy'
// Memory-specific optimizations
// Circular buffers, compression
```
### 11. Server Search Conduit ✅
```typescript
import { ServerSearchConduitAugmentation } from 'brainy'
// Distributed query execution
// Load balancing across nodes
```
### 12. Neural Import ✅
```typescript
import { NeuralImportAugmentation } from 'brainy'
// AI-powered data understanding
// Automatic entity detection and classification
// Relationship discovery
```
## 🤖 Neural Import Capabilities (FULLY IMPLEMENTED!)
```typescript
const neuralImport = new NeuralImport(brain)
// ALL of these work TODAY:
await neuralImport.neuralImport('data.csv')
await neuralImport.detectEntitiesWithNeuralAnalysis(data)
await neuralImport.detectNounType(entity)
await neuralImport.detectRelationships(entities)
await neuralImport.generateInsights(data)
```
### Features:
- **Auto-detects file format** (CSV, JSON, XML, etc.)
- **Identifies entity types** using AI
- **Discovers relationships** between entities
- **Generates insights** about the data
- **Creates optimal graph structure** automatically
## 🎯 Zero-Config Model Loading Cascade
Brainy automatically loads models with ZERO configuration required:
```typescript
const brain = new BrainyData() // That's it!
await brain.init()
// Models load automatically from best available source
```
### Loading Priority:
1. **Local Cache** (./models) - Instant, no network
2. **CDN** (models.soulcraft.com) - Fast, global [Coming Soon]
3. **GitHub Releases** - Reliable backup
4. **HuggingFace** - Ultimate fallback
### Key Features:
- **Automatic fallback** if sources fail
- **Model verification** with checksums
- **Offline support** with bundled models
- **No environment variables needed**
- **Works in all environments** (Node, Browser, Workers)
## 🏢 Distributed Operation Modes
### Reader Mode ✅
```typescript
const brain = new BrainyData({ mode: 'reader' })
// Optimized for read-heavy workloads
// 80% cache ratio, aggressive prefetch
// 1 hour TTL, minimal writes
```
### Writer Mode ✅
```typescript
const brain = new BrainyData({ mode: 'writer' })
// Optimized for write-heavy workloads
// Large write buffers, batch writes
// Minimal caching, fast ingestion
```
### Hybrid Mode ✅
```typescript
const brain = new BrainyData({ mode: 'hybrid' })
// Balanced for mixed workloads
// Adaptive caching and batching
```
## 💾 Advanced Caching System
### 3-Level Cache Architecture ✅
```typescript
const cacheConfig = {
hotCache: {
size: 1000, // L1 - RAM
ttl: 60000 // 1 minute
},
warmCache: {
size: 10000, // L2 - Fast storage
ttl: 300000 // 5 minutes
},
coldCache: {
size: 100000, // L3 - Persistent
ttl: null // No expiry
}
}
```
### Cache Features:
- **Automatic promotion/demotion** between levels
- **LRU eviction** within each level
- **Compression** for cold cache
- **Statistics tracking** for optimization
## 📊 Comprehensive Statistics
```typescript
const stats = await brain.getStatistics()
// Returns detailed metrics:
{
nouns: {
count, created, updated, deleted,
size, avgSize
},
verbs: {
count, created, types,
weights: { min, max, avg }
},
vectors: {
dimensions: 384,
indexSize, partitions,
avgSearchTime
},
cache: {
hits, misses, evictions,
hitRate, sizes
},
performance: {
operations, avgTimes,
p95Latency, p99Latency
},
storage: {
used, available,
compression, files
},
throttling: {
delays, rateLimited,
backoffMs, retries
}
}
```
## 🚀 GPU Acceleration Support
```typescript
// Automatic GPU detection
const device = await detectBestDevice()
// Returns: 'cpu' | 'webgpu' | 'cuda'
// WebGPU in browser (when available)
if (device === 'webgpu') {
// Transformer models use WebGPU automatically
}
// CUDA in Node.js (requires ONNX Runtime GPU)
if (device === 'cuda') {
// Automatically uses GPU for embeddings
}
```
## 🔄 Adaptive Systems
### Adaptive Backpressure ✅
```typescript
// Automatically adjusts flow based on system load
// Prevents OOM and maintains throughput
```
### Adaptive Socket Manager ✅
```typescript
// Dynamic connection pooling
// Scales connections based on traffic patterns
```
### Cache Auto-Configuration ✅
```typescript
// Sizes cache based on available memory
// Adjusts strategies based on usage patterns
```
### S3 Throttling Protection ✅
```typescript
// Built-in exponential backoff
// Rate limit detection and adaptation
// Automatic retry with jitter
```
## 🛠️ Storage Adapters
All included, auto-selected based on environment:
### FileSystem Storage ✅
- Default for Node.js
- Efficient file-based storage
- Automatic directory management
### Memory Storage ✅
- Ultra-fast in-memory operations
- Perfect for testing and temporary data
- Circular buffer support
### OPFS Storage ✅
- Browser persistent storage
- Survives page refreshes
- Quota management
### S3 Storage ✅
- AWS S3 compatible
- Automatic multipart uploads
- Throttling protection
- Batch operations
## 🎨 Natural Language Processing
### Built-in Patterns (220+)
- Question types (what, why, how, when, where)
- Temporal queries (yesterday, last week, 2024)
- Comparative queries (better than, similar to)
- Aggregations (count, sum, average)
- Filters (only, except, without)
- Relationships (related to, connected with)
### Coverage: 94-98% of typical queries!
## 🔐 Security Features
### Built-in Security ✅
- Automatic input sanitization
- SQL injection prevention
- XSS protection for web contexts
- Rate limiting support
### Encryption Ready ✅
```typescript
import { crypto } from 'brainy/utils'
// AES-256-GCM encryption utilities
// Key derivation functions
// Secure random generation
```
## 🎯 Key Design Principles
### 1. Zero Configuration
```typescript
const brain = new BrainyData()
await brain.init()
// Everything else is automatic!
```
### 2. Fixed Dimensions (384)
- **ALWAYS** uses all-MiniLM-L6-v2 model
- **ALWAYS** 384 dimensions
- **NOT** configurable (by design)
- Ensures everything works together
### 3. Progressive Enhancement
- Starts simple, scales automatically
- Adapts to workload patterns
- Optimizes based on usage
### 4. Universal Compatibility
- Works in Node.js 18+
- Works in modern browsers
- Works in Web Workers
- Works in Edge environments
## 📦 What Ships in Core (MIT Licensed)
**EVERYTHING** is included in the core package:
- ✅ All engines (vector, graph, field, neural)
- ✅ All augmentations (12+)
- ✅ All storage adapters
- ✅ All distributed modes
- ✅ Complete statistics
- ✅ GPU support
- ✅ No feature limitations
- ✅ No premium tiers
- ✅ 100% MIT licensed
## 🚀 Quick Start
```typescript
import { BrainyData } from 'brainy'
// Zero config required!
const brain = new BrainyData()
await brain.init()
// Add data (auto-detects type)
await brain.addNoun('Content here')
// Search with natural language
const results = await brain.find('related content from last week')
// Everything else is automatic!
```
## 📈 Performance Characteristics
- **Vector Search**: O(log n) with HNSW indexing
- **Graph Traversal**: O(k) for k-hop queries
- **Field Filtering**: O(1) with metadata index
- **Memory Usage**: ~100MB base + data
- **Embedding Speed**: ~100ms for batch of 10
- **Query Speed**: <10ms for most queries
## 🎉 Summary
Brainy 2.0 is a **complete**, **production-ready** AI database that requires **ZERO configuration**. Every feature listed here is **implemented and working** today. No configuration, no setup, no complexity - just powerful AI capabilities that work out of the box!

View file

@ -1,230 +0,0 @@
# Migrating to v5.11.1
## Overview
v5.11.1 introduces a **breaking change** with **massive performance benefits**:
- `brain.get()` now loads **metadata-only by default** (76-81% faster!)
- Vector embeddings require **explicit opt-in**: `{ includeVectors: true }`
**Impact**: Only ~6% of codebases need changes (code that computes similarity on retrieved entities).
## What Changed
### Before (v5.11.0 and earlier)
```typescript
const entity = await brain.get(id)
// entity.vector was ALWAYS loaded (384 dimensions, 6KB)
console.log(entity.vector.length) // 384
```
### After (v5.11.1)
```typescript
// DEFAULT: Metadata-only (76-81% faster)
const entity = await brain.get(id)
console.log(entity.vector) // [] (empty array - not loaded)
// EXPLICIT: Full entity with vectors
const entity = await brain.get(id, { includeVectors: true })
console.log(entity.vector.length) // 384
```
## Who Needs to Update?
### ✅ NO CHANGES NEEDED (94% of code)
If you use `brain.get()` for:
- **VFS operations** (readFile, stat, readdir)
- **Existence checks**: `if (await brain.get(id))`
- **Metadata access**: `entity.data`, `entity.type`, `entity.metadata`
- **Relationship traversal**
- **Admin tools**, import utilities, data APIs
→ **Zero changes needed, automatic 76-81% speedup!**
### ⚠️ REQUIRES UPDATE (~6% of code)
If you use `brain.get()` AND then compute similarity on the returned entity:
```typescript
// ❌ BEFORE (v5.11.0) - will break in v5.11.1
const entity = await brain.get(id)
const similar = await brain.similar({ to: entity.vector }) // entity.vector is [] !
// ✅ AFTER (v5.11.1) - add includeVectors
const entity = await brain.get(id, { includeVectors: true })
const similar = await brain.similar({ to: entity.vector }) // Works!
```
**Note**: `brain.similar({ to: entityId })` (using ID) still works - no changes needed!
## Migration Steps
### Step 1: Find Affected Code
Search your codebase for patterns that use vectors from `brain.get()`:
```bash
# Find brain.get() calls that access .vector
grep -r "await brain.get(" --include="*.ts" --include="*.js" | \
grep -E "(\.vector|entity\.vector)"
```
### Step 2: Update Pattern-by-Pattern
#### Pattern 1: Similarity Using Retrieved Entity Vector
```typescript
// ❌ BEFORE
const entity = await brain.get(id)
const similar = await brain.similar({ to: entity.vector })
// ✅ AFTER - Option A: Add includeVectors
const entity = await brain.get(id, { includeVectors: true })
const similar = await brain.similar({ to: entity.vector })
// ✅ AFTER - Option B: Use ID directly (recommended)
const similar = await brain.similar({ to: id })
```
#### Pattern 2: Manual Vector Operations
```typescript
// ❌ BEFORE
const entity = await brain.get(id)
const magnitude = Math.sqrt(entity.vector.reduce((sum, v) => sum + v*v, 0))
// ✅ AFTER
const entity = await brain.get(id, { includeVectors: true })
const magnitude = Math.sqrt(entity.vector.reduce((sum, v) => sum + v*v, 0))
```
#### Pattern 3: Vector Assertions in Tests
```typescript
// ❌ BEFORE
const entity = await brain.get(id)
expect(entity.vector).toBeDefined()
expect(entity.vector.length).toBe(384)
// ✅ AFTER
const entity = await brain.get(id, { includeVectors: true })
expect(entity.vector).toBeDefined()
expect(entity.vector.length).toBe(384)
```
### Step 3: Verify Migration
Run your test suite to catch any remaining issues:
```bash
npm test
```
Look for errors like:
- `entity.vector is empty` or `entity.vector.length is 0`
- `Cannot compute similarity on empty vector`
Add `{ includeVectors: true }` wherever these errors occur.
## Performance Impact
### Before Migration
```
brain.get(): 43ms, 6KB per call
VFS readFile(): 53ms per file
VFS readdir(100 files): 5.3s
```
### After Migration
```
brain.get(): 10ms, 300 bytes per call (76-81% faster) ✨
brain.get({ includeVectors: true }): 43ms, 6KB (unchanged)
VFS readFile(): ~13ms per file (75% faster) ✨
VFS readdir(100 files): ~1.3s (75% faster) ✨
```
**Result**:
- VFS operations: **75% faster**
- Metadata access: **76-81% faster**
- Vector similarity: **Unchanged** (still fast when needed)
## TypeScript Support
The new `GetOptions` interface is fully typed:
```typescript
interface GetOptions {
/**
* Include 384-dimensional vector embeddings in the response
*
* Default: false (metadata-only for 76-81% speedup)
*/
includeVectors?: boolean
}
// TypeScript will autocomplete and validate
const entity = await brain.get(id, { includeVectors: true })
```
## Rollback Plan
If you encounter issues, you can temporarily force full entity loading everywhere:
```typescript
// Temporary wrapper (NOT RECOMMENDED - defeats optimization)
async function getLegacy(id: string) {
return brain.get(id, { includeVectors: true })
}
// Use throughout codebase while migrating
const entity = await getLegacy(id)
```
**Important**: This defeats the 76-81% performance improvement. Only use temporarily while fixing affected code.
## FAQ
### Q: Why did you make this a breaking change?
**A**: The performance gains are massive (76-81% speedup, 95% less bandwidth) and affect 94% of code positively. Only ~6% of code needs updates. The net benefit is enormous.
### Q: Do I need to update my VFS code?
**A**: No! VFS automatically benefits from the optimization with zero code changes. Your VFS operations are now 75% faster automatically.
### Q: Will brain.similar() still work?
**A**: Yes! `brain.similar({ to: entityId })` works exactly as before. Only `brain.similar({ to: entity.vector })` requires the entity to be loaded with `{ includeVectors: true }`.
### Q: What about backward compatibility?
**A**: Entities returned without vectors have `vector: []` (empty array), which is type-safe. Code that doesn't use vectors continues to work. Only code that explicitly uses `entity.vector` needs updating.
### Q: Can I check if vectors are loaded?
**A**: Yes! Check `entity.vector.length > 0` to detect if vectors were loaded.
```typescript
const entity = await brain.get(id)
if (entity.vector.length > 0) {
// Vectors are loaded
} else {
// Metadata-only
}
```
## Support
If you encounter migration issues:
1. Check the [VFS Performance Guide](../vfs/VFS_PERFORMANCE.md)
2. Review [API Reference](../api/README.md)
3. See [Performance Documentation](../PERFORMANCE.md)
4. File an issue: https://github.com/soulcraft/brainy/issues
## Changelog
See [CHANGELOG.md](../../CHANGELOG.md) for complete v5.11.1 release notes.

View file

@ -1,593 +0,0 @@
# Aggregation Guide
> Real-time analytics on your entity data with incremental running totals
## Overview
Brainy's aggregation engine computes running totals at write time, so reading aggregate results is always O(1) regardless of dataset size. Define an aggregate once, and every `add()`, `update()`, and `delete()` automatically updates the running metrics.
No batch jobs. No scheduled recalculations. Aggregates stay current with every write.
**Defining over existing data:** if you define an aggregate on a store that already holds
matching entities, Brainy backfills it from those entities on the first query (a one-time scan,
then purely incremental). So `defineAggregate()` behaves the same whether you define it before
or after the data exists.
**Reopening a persisted brain:** aggregate state persists across restarts. Re-defining the
same aggregate at boot (the normal declarative pattern) adopts the persisted state directly —
no rescan. A backfill scan runs only when the definition actually changed, when no persisted
state exists, or when the state failed to load; and however many aggregates need backfilling,
they share a single scan.
## Quick Start
```typescript
import { Brainy, NounType } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// 1. Define an aggregate
brain.defineAggregate({
name: 'sales_by_category',
source: { type: NounType.Event },
groupBy: ['category'],
metrics: {
revenue: { op: 'sum', field: 'amount' },
count: { op: 'count' },
average: { op: 'avg', field: 'amount' }
}
})
// 2. Add entities — aggregates update automatically
await brain.add({
data: 'Coffee purchase',
type: NounType.Event,
metadata: { category: 'food', amount: 5.50 }
})
await brain.add({
data: 'Laptop purchase',
type: NounType.Event,
metadata: { category: 'electronics', amount: 1200 }
})
await brain.add({
data: 'Lunch purchase',
type: NounType.Event,
metadata: { category: 'food', amount: 12.00 }
})
// 3. Query results
const results = await brain.find({ aggregate: 'sales_by_category' })
// Results:
// [
// { groupKey: { category: 'food' }, metrics: { revenue: 17.50, count: 2, average: 8.75 } },
// { groupKey: { category: 'electronics' }, metrics: { revenue: 1200, count: 1, average: 1200 } }
// ]
```
## Aggregation Operations
Brainy supports 7 aggregation operations:
### `sum` — Running Total
Adds up all values of a numeric field.
```typescript
metrics: {
total_revenue: { op: 'sum', field: 'amount' }
}
```
### `count` — Entity Count
Counts the number of matching entities. No `field` required.
```typescript
metrics: {
order_count: { op: 'count' }
}
```
### `avg` — Running Average
Computes `sum / count` incrementally.
```typescript
metrics: {
average_price: { op: 'avg', field: 'price' }
}
```
### `min` — Minimum Value
Tracks the minimum value across all entities in each group.
```typescript
metrics: {
lowest_price: { op: 'min', field: 'price' }
}
```
### `max` — Maximum Value
Tracks the maximum value across all entities in each group.
```typescript
metrics: {
highest_price: { op: 'max', field: 'price' }
}
```
### `stddev` — Sample Standard Deviation
Computes the sample standard deviation using Welford's numerically stable online algorithm. Updates incrementally without storing individual values.
```typescript
metrics: {
price_spread: { op: 'stddev', field: 'price' }
}
```
### `variance` — Sample Variance
Computes the sample variance (square of standard deviation) using Welford's online algorithm.
```typescript
metrics: {
price_variance: { op: 'variance', field: 'price' }
}
```
## GROUP BY Dimensions
Every aggregate requires at least one `groupBy` dimension. Results are grouped by the unique combinations of dimension values.
### Plain Fields
Group by a metadata field value:
```typescript
groupBy: ['category']
// Produces groups: { category: 'food' }, { category: 'electronics' }, ...
```
### Multiple Fields
Group by multiple fields for composite keys:
```typescript
groupBy: ['category', 'region']
// Produces groups: { category: 'food', region: 'US' }, { category: 'food', region: 'EU' }, ...
```
### Time Windows
Group by a timestamp field bucketed into time periods:
```typescript
groupBy: [{ field: 'date', window: 'month' }]
// Produces groups: { date: '2024-01' }, { date: '2024-02' }, ...
```
Available time window granularities:
| Window | Format | Example |
|--------|--------|---------|
| `hour` | `YYYY-MM-DDThh` | `2024-01-15T14` |
| `day` | `YYYY-MM-DD` | `2024-01-15` |
| `week` | `YYYY-Wnn` | `2024-W03` |
| `month` | `YYYY-MM` | `2024-01` |
| `quarter` | `YYYY-Qn` | `2024-Q1` |
| `year` | `YYYY` | `2024` |
| `{ seconds: N }` | ISO 8601 | Custom interval |
### Combined Dimensions
Mix plain fields and time windows:
```typescript
brain.defineAggregate({
name: 'monthly_sales',
source: { type: NounType.Event },
groupBy: ['region', { field: 'date', window: 'month' }],
metrics: {
revenue: { op: 'sum', field: 'amount' },
count: { op: 'count' }
}
})
// Produces groups like:
// { region: 'US', date: '2024-01' }
// { region: 'US', date: '2024-02' }
// { region: 'EU', date: '2024-01' }
```
### Array Fields (Unnest)
Group by **each element** of an array-valued field — for tag frequencies, label counts, and
faceted breakdowns. Mark the dimension `{ field, unnest: true }`:
```typescript
brain.defineAggregate({
name: 'tag_frequency',
source: { type: NounType.Document },
groupBy: [{ field: 'tags', unnest: true }],
metrics: { count: { op: 'count' } }
})
// A document tagged ['ml', 'ai'] contributes once to the 'ml' group and once to 'ai'.
// Duplicate tags on one entity count once; an entity with no tags joins no group.
const top = await brain.queryAggregate('tag_frequency', { orderBy: 'count', order: 'desc' })
// [ { groupKey: { tags: 'ai' }, metrics: { count: 3 }, count: 3 }, ... ]
```
## Querying Aggregates
Aggregate results are queried through the standard `find()` method.
### Basic Query
```typescript
const results = await brain.find({ aggregate: 'sales_by_category' })
```
### Filter by Group Key
Use `where` to filter on group key values:
```typescript
const foodOnly = await brain.find({
aggregate: 'sales_by_category',
where: { category: 'food' }
})
```
### Filter by Metric Value (HAVING)
Use `having` to filter groups by their **computed metric values** — the analytics equivalent of
SQL `HAVING`. (`where` filters group *keys*; `having` filters *metrics*.)
```typescript
const bigCategories = await brain.find({
aggregate: 'sales_by_category',
having: { revenue: { greaterThan: 1000 } }
})
```
`having` accepts the same operators as `where`, applied to each group's metric results plus
`count`. It is evaluated per group — **O(groups), independent of entity count** — before sorting
and pagination, so it stays cheap even over billions of entities.
### Sort and Paginate
Sort by any metric or group key field:
```typescript
const topCategories = await brain.find({
aggregate: {
name: 'sales_by_category',
orderBy: 'revenue',
order: 'desc',
limit: 10
}
})
```
### Combined Parameters
`where`, `orderBy`, `limit`, and `offset` from the outer `find()` call merge automatically with the aggregate query:
```typescript
const recentTopSpenders = await brain.find({
aggregate: 'monthly_sales',
where: { region: 'US' },
orderBy: 'revenue',
order: 'desc',
limit: 12,
offset: 0
})
```
### Result Format
`find({ aggregate })` returns `Result<T>` rows (for uniformity with the rest of `find()`),
with the aggregate fields surfaced **both** at the top level and, for backward compatibility,
flattened into `metadata`:
```typescript
{
id: string,
score: 1.0,
type: NounType.Measurement,
groupKey: { category: 'food' }, // top-level — the group key values
metrics: { revenue: 17.50, count: 2, average: 8.75 }, // top-level — computed metrics
count: 2, // top-level — entities in the group
metadata: { // legacy mirror of the same data
__aggregate: 'sales_by_category',
category: 'food',
revenue: 17.50, count: 2, average: 8.75
},
entity: Entity
}
```
### `queryAggregate()` — the report-friendly view
For dashboards and reports, prefer `brain.queryAggregate(name, params)`. It returns the clean
`AggregateResult[]` shape directly — no search-result wrapper:
```typescript
const rows = await brain.queryAggregate('sales_by_category', {
orderBy: 'revenue',
order: 'desc',
limit: 10
})
// [
// { groupKey: { category: 'electronics' }, metrics: { revenue: 1200, count: 1, average: 1200 }, count: 1 },
// { groupKey: { category: 'food' }, metrics: { revenue: 17.50, count: 2, average: 8.75 }, count: 2 }
// ]
```
It accepts the same `where` / `having` / `orderBy` / `order` / `limit` / `offset` params as the
`find({ aggregate })` form.
## Source Filtering
Control which entities feed into an aggregate with the `source` property.
### Filter by Entity Type
```typescript
brain.defineAggregate({
name: 'event_stats',
source: { type: NounType.Event },
groupBy: ['category'],
metrics: { count: { op: 'count' } }
})
```
### Filter by Multiple Types
```typescript
source: { type: [NounType.Event, NounType.Document] }
```
### Filter by Metadata
Use the same `where` syntax as `find()`:
```typescript
source: {
type: NounType.Event,
where: { domain: 'financial', subtype: 'transaction' }
}
```
### Filter by Service
For multi-tenant deployments:
```typescript
source: { service: 'tenant-123' }
```
Entities that don't match the source filter are silently skipped during incremental updates.
## Incremental Updates
The aggregation engine hooks into every write operation:
### On `add()`
When a new entity matches an aggregate's source filter:
1. The group key is computed from the entity's metadata
2. Each metric in the matching group is incremented
3. New groups are created automatically
### On `update()`
When an existing entity is updated:
1. The old entity's contribution is reversed from its group
2. The new entity's contribution is applied to its (potentially different) group
3. Handles group key changes — an entity moving from category "food" to "drink" updates both groups
### On `delete()`
When an entity is deleted:
1. The entity's contribution is reversed from its group
2. If a group becomes empty (all metric counts reach zero), it's removed
### Aggregate Entity Exclusion
Materialized `NounType.Measurement` entities are automatically excluded from all source matching, preventing infinite feedback loops. Entities with `service: 'brainy:aggregation'` or `metadata.__aggregate` are always skipped.
## Materialization
Materialization writes aggregate results as `NounType.Measurement` entities, making them automatically available through OData, Google Sheets, SSE, and webhook integrations.
```typescript
brain.defineAggregate({
name: 'daily_metrics',
source: { type: NounType.Event },
groupBy: [{ field: 'date', window: 'day' }],
metrics: {
total: { op: 'sum', field: 'amount' },
count: { op: 'count' }
},
materialize: true
})
```
### Debounce Configuration
During high-throughput ingestion, materialization is debounced to avoid excessive writes:
```typescript
materialize: {
debounceMs: 2000, // Wait 2 seconds after last update before writing
trackSources: true // Track which entities contributed
}
```
The default debounce interval is 1000ms.
## Multiple Aggregates
Define multiple aggregates that process the same entities:
```typescript
// Revenue by category
brain.defineAggregate({
name: 'category_revenue',
source: { type: NounType.Event },
groupBy: ['category'],
metrics: { total: { op: 'sum', field: 'amount' } }
})
// Monthly trends
brain.defineAggregate({
name: 'monthly_trends',
source: { type: NounType.Event },
groupBy: [{ field: 'date', window: 'month' }],
metrics: {
revenue: { op: 'sum', field: 'amount' },
count: { op: 'count' },
avg_order: { op: 'avg', field: 'amount' }
}
})
// Regional breakdown with statistical analysis
brain.defineAggregate({
name: 'regional_analysis',
source: { type: NounType.Event },
groupBy: ['region'],
metrics: {
revenue: { op: 'sum', field: 'amount' },
spread: { op: 'stddev', field: 'amount' },
variance: { op: 'variance', field: 'amount' }
}
})
```
Each `add()` call updates all matching aggregates automatically.
## Removing Aggregates
Remove an aggregate and clean up its state:
```typescript
brain.removeAggregate('category_revenue')
```
## Persistence
Aggregate definitions and running state are automatically persisted:
- **On `flush()`/`close()`**: All dirty aggregate state is written to storage
- **On `init()`**: Definitions and state are restored from storage
- **Change detection**: Definition changes are detected via FNV-1a hashing — only changed aggregates reset their state on restart
## Native Acceleration
When [Cor](https://www.npmjs.com/package/@soulcraft/cor) is installed as a plugin, the aggregation engine automatically uses Rust-accelerated computation:
- Incremental updates run in Rust with BTreeMap-backed precise MIN/MAX
- Welford's online stddev/variance computed natively
- Rebuild uses Rayon parallel iterators across CPU cores (above 1,000 entities)
- Time window bucketing uses integer arithmetic without `Date` object allocation
```typescript
const brain = new Brainy({
plugins: ['@soulcraft/cor']
})
await brain.init()
// Aggregation automatically uses native engine
brain.defineAggregate({ ... })
```
Verify native acceleration is active:
```typescript
const diag = brain.diagnostics()
console.log(diag.providers.aggregation)
// { source: 'plugin' }
```
## Common Patterns
### Financial Analytics
```typescript
brain.defineAggregate({
name: 'monthly_spending',
source: {
type: NounType.Event,
where: { domain: 'financial', subtype: 'transaction' }
},
groupBy: [
'category',
{ field: 'date', window: 'month' }
],
metrics: {
total: { op: 'sum', field: 'amount' },
count: { op: 'count' },
average: { op: 'avg', field: 'amount' },
highest: { op: 'max', field: 'amount' },
lowest: { op: 'min', field: 'amount' }
},
materialize: true
})
```
### Time-Series Monitoring
```typescript
brain.defineAggregate({
name: 'hourly_metrics',
source: { type: NounType.Event, where: { domain: 'monitoring' } },
groupBy: [
'service',
{ field: 'timestamp', window: 'hour' }
],
metrics: {
request_count: { op: 'count' },
avg_latency: { op: 'avg', field: 'latency_ms' },
max_latency: { op: 'max', field: 'latency_ms' },
error_count: { op: 'sum', field: 'is_error' },
latency_spread: { op: 'stddev', field: 'latency_ms' }
}
})
```
### Content Analytics
```typescript
brain.defineAggregate({
name: 'content_stats',
source: { type: NounType.Document },
groupBy: ['author', { field: 'publishedAt', window: 'month' }],
metrics: {
articles: { op: 'count' },
total_words: { op: 'sum', field: 'wordCount' },
avg_words: { op: 'avg', field: 'wordCount' }
}
})
```
## Performance
Aggregation complexity per write is O(A x G x M) where A = matching aggregates, G = groupBy dimensions, M = metrics. For typical configurations (2-5 aggregates, 1-3 dimensions, 3-5 metrics), this is effectively O(1).
With Cor native acceleration:
| Operation | Throughput | Latency |
|-----------|-----------|---------|
| Incremental update (1K entities) | 809 ops/s | 1.2 ms |
| Rebuild (10K entities) | 475 ops/s | 2.1 ms |
| Rebuild (100K entities, Rayon) | 66 ops/s | 15.2 ms |
| Query (1K groups, sort + paginate) | 986 ops/s | 1.0 ms |

View file

@ -21,7 +21,7 @@ Enterprise features on our roadmap.
**Everyone gets bank-level security features:**
```typescript
const brain = new Brainy({
const brain = new BrainyData({
security: {
encryption: 'aes-256-gcm', // Military-grade encryption
keyRotation: true, // Automatic key rotation
@ -46,9 +46,11 @@ const brain = new Brainy({
**Everyone gets mission-critical reliability:**
```typescript
import { WALAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new WALAugmentation({
enabled: true, // Write-ahead logging
redundancy: 3, // Triple redundancy
checkpointInterval: 1000, // Frequent checkpoints
@ -102,7 +104,7 @@ const performance = {
```typescript
import { MonitoringAugmentation } from 'brainy'
const brain = new Brainy({
const brain = new BrainyData({
augmentations: [
new MonitoringAugmentation({
metrics: 'all', // Complete metrics
@ -165,35 +167,39 @@ await brain.syncWith({
- **Webhook support**: React to changes
- **API generation**: Auto-generate REST/GraphQL APIs
### 🌍 Scale
### 🌍 Enterprise Scale 🚧 Coming Soon
**Everyone gets the same scale model:**
**Everyone gets planetary scale:**
```typescript
// Pure JS by default; install the optional native provider for billions of vectors
const brain = new Brainy()
// Same architecture Netflix uses, free for you
const brain = new BrainyData({
clustering: {
enabled: true, // Distributed mode
sharding: 'automatic', // Auto-sharding
replication: 3, // Triple replication
consensus: 'raft', // Strong consistency
geoDistribution: true // Multi-region support
}
})
// 1 → ~1M vectors: pure-JS HNSW, zero extra setup
// 1M → 10B+ vectors: install @soulcraft/cor for the native DiskANN provider
// Handles everything from 1 to 1 billion entities
```
**Scaling model:**
- **Single process, no cluster**: Brainy runs in one process — no coordinator,
no peer discovery, no consensus to operate
- **Optional native provider**: install `@soulcraft/cor` to back the index with
on-disk DiskANN that scales to 10B+ vectors on one machine
- **Per-tenant pools**: isolate tenants by giving each its own Brainy instance and
storage directory
- **Horizontal read scaling**: run many reader processes against one shared on-disk
store (single writer, many readers); replicate the artifact with your operator
tooling
**Scaling features:**
- **Horizontal scaling**: Add nodes as needed
- **Auto-sharding**: Distributes data automatically
- **Multi-region**: Global distribution
- **Load balancing**: Automatic request distribution
- **Zero-downtime upgrades**: Rolling updates
- **Infinite scale**: No upper limits
### 🛡️ Enterprise Compliance 🚧 Coming Soon
**Everyone gets compliance tools:**
```typescript
const brain = new Brainy({
const brain = new BrainyData({
compliance: {
gdpr: {
rightToDelete: true, // Automatic PII deletion
@ -231,7 +237,7 @@ const brain = new Brainy({
```typescript
// Advanced AI capabilities for everyone
const brain = new Brainy({
const brain = new BrainyData({
ai: {
embeddings: 'state-of-the-art', // Best models available
dimensions: 1536, // High-precision vectors
@ -263,7 +269,7 @@ const anomalies = await brain.detectAnomalies()
```typescript
// CI/CD and DevOps features
const brain = new Brainy({
const brain = new BrainyData({
operations: {
blueGreen: true, // Zero-downtime deployments
canary: true, // Gradual rollouts
@ -312,7 +318,7 @@ Your hobby project today might be tomorrow's unicorn startup. With Brainy, you w
### Startups
```typescript
// A 2-person startup gets the same features as Amazon
const startup = new Brainy()
const startup = new BrainyData()
// ✓ Full durability
// ✓ Complete security
// ✓ Unlimited scale
@ -322,7 +328,7 @@ const startup = new Brainy()
### Education
```typescript
// Students learn with production-grade tools
const classroom = new Brainy()
const classroom = new BrainyData()
// ✓ No feature restrictions
// ✓ Real enterprise experience
// ✓ Free forever
@ -331,7 +337,7 @@ const classroom = new Brainy()
### Non-Profits
```typescript
// NGOs get enterprise features without enterprise costs
const nonprofit = new Brainy()
const nonprofit = new BrainyData()
// ✓ Compliance tools
// ✓ Security features
// ✓ Scale for impact
@ -341,7 +347,7 @@ const nonprofit = new Brainy()
### Enterprises
```typescript
// Enterprises get everything plus peace of mind
const enterprise = new Brainy()
const enterprise = new BrainyData()
// ✓ Proven at scale
// ✓ Community tested
// ✓ No vendor lock-in
@ -393,14 +399,14 @@ npm install brainy
```
```typescript
import { Brainy } from 'brainy'
import { BrainyData } from 'brainy'
// Create your enterprise-grade database
const brain = new Brainy()
const brain = new BrainyData()
await brain.init()
// You're now running the same tech as Fortune 500 companies
await brain.add("Your data is enterprise-grade", {
await brain.addNoun("Your data is enterprise-grade", {
secure: true,
durable: true,
scalable: true,
@ -438,4 +444,4 @@ Brainy is more than software—it's a movement to democratize enterprise technol
- [Zero Configuration](../architecture/zero-config.md)
- [Augmentations System](../architecture/augmentations.md)
- [Architecture Overview](../architecture/overview.md)
- [API Reference](../api/README.md)
- [Getting Started](./getting-started.md)

View file

@ -1,181 +0,0 @@
---
title: Export & Import (portable graph)
slug: guides/export-and-import
public: true
category: guides
template: guide
order: 9
description: Export part or all of a brain to a portable, versioned PortableGraph document and import it back — by id, collection, connected neighbourhood, VFS subtree, predicate, or the whole brain. export() lives on the immutable Db, so asOf()/with() give time-travel and what-if exports.
next:
- guides/subtypes-and-facets
- api/README
---
# Export & Import (portable graph)
Brainy serializes part or all of a brain — an item, a collection, a connected
neighbourhood, a VFS subtree, a predicate match, or the whole brain — into a single
versioned JSON document (`PortableGraph`), and restores it.
```typescript
const graph = await brain.export() // whole brain → PortableGraph
await brain.import(graph) // restore (merge by id, re-embed if no vectors)
```
It is **portable** (human-readable JSON), **versioned** (`formatVersion`, so a document
written by 7.x imports cleanly into 8.0), and **current-state** (the entities and edges as
they are now — no generation history). Use it for portable artifacts, partial exports,
cross-environment moves, and version upgrades.
`export()` is a method on the **immutable `Db` value**, so it composes with every way of
obtaining one:
```typescript
brain.export(sel) // = brain.now().export(sel)
;(await brain.asOf(gen)).export(sel) // time-travel export (a past generation)
brain.now().with(ops).export(sel) // what-if export (a speculative state)
```
## When to use which
| You want… | Use |
|-----------|-----|
| A portable, partial-or-whole, cross-version graph document | **`brain.export()` / `brain.import()`** (this guide) |
| A whole-brain snapshot **with generation history** | `brain.now().persist(path)` / `Brainy.load(path)` (native) |
| To ingest a CSV / PDF / Excel / JSON **file** as new entities | `brain.import(file)` — see [Import Anything](./import-anything.md) |
`import()` is **polymorphic**: hand it a `PortableGraph` and it does the graph round-trip;
hand it a file/buffer and it does foreign-file ingestion (dispatched on the document's
`format: 'brainy-portable-graph'` tag).
## Exporting
```typescript
brain.export(selector?, options?): Promise<PortableGraph>
// (also on any Db: brain.now().export(...), (await brain.asOf(g)).export(...))
```
### Selectors — *what* to export
Omit the selector to export the whole brain. Otherwise pick a node set:
| Scenario | Selector |
|----------|----------|
| Just an item (or items) | `{ ids: ['a', 'b'] }` |
| A collection + its children | `{ collection: collectionId }` (alias `memberOf`) |
| A connected neighbourhood | `{ connected: { from: id, depth: 2, verbs?, direction? } }` |
| A VFS directory / file (+ subtree) | `{ vfsPath: '/docs', recursive?: true }` |
| Everything matching a predicate | `{ type, subtype, where, service, visibility }` |
| The whole brain | *(omit)* |
The selector reuses `find()`'s grammar — *"export what `find()` would match, minus ranking
and limit."* Structural and predicate selectors **compose**:
```typescript
// Members of a collection whose status is "open"
await brain.export({ collection: collectionId, where: { status: 'open' } })
// Already have find() results? Export exactly those with the ids selector
const hits = await brain.find({ type: NounType.Document })
await brain.export({ ids: hits.map(r => r.id) })
```
### Options — *how* to serialize
| Option | Default | Effect |
|--------|---------|--------|
| `includeVectors` | `false` | Carry embedding vectors verbatim. Off ⇒ `import()` re-embeds from `data`. |
| `includeContent` | `false` | Include VFS file bytes in `blobs` so files round-trip byte-identically. |
| `includeSystem` | `false` | Include `visibility:'system'` entities such as the VFS root. |
| `edges` | `'induced'` | `'induced'` (both endpoints in the set), `'incident'` (also dangling edges, recorded in `danglingIds`), or `'none'` (nodes only). |
## Importing
```typescript
brain.import(graph, options?): Promise<ImportResult>
```
The whole graph is applied as **one atomic transaction** — it advances the brain exactly
one generation, or none on failure.
```typescript
const result = await brain.import(graph, { onConflict: 'merge' })
// → { imported, merged, skipped, reembedded, blobsWritten, errors }
```
| Option | Default | Effect |
|--------|---------|--------|
| `onConflict` | `'merge'` | `'merge'` (update existing id in place — assemble many exported graphs), `'replace'` (delete + recreate), or `'skip'`. |
| `reembed` | `'auto'` | `'auto'` (use the carried vector, else re-embed from `data`) or `'never'` (require a carried vector; record an error if absent). |
| `remapIds` | — | Rewrite every id on the way in, e.g. to clone a template subgraph under fresh ids. |
| `meta` | — | Transaction metadata recorded in the tx-log alongside the new generation. |
The default `onConflict: 'merge'` lets you assemble one working graph from many exported
documents that share entity ids — re-importing an id merges rather than duplicates.
## The `PortableGraph` format
```jsonc
{
"format": "brainy-portable-graph", // identifies the document type
"formatVersion": 1, // import gates on this (cross-version migration)
"brainyVersion": "8.0.0",
"createdAt": "2026-06-16T…Z",
"embedding": { "model": "all-MiniLM-L6-v2", "dimensions": 384 },
"selector": { … }, // echoes what was exported (provenance)
"entities": [
{
"id": "…", "type": "Document", "subtype": "invoice", "visibility": "public",
"data": "…", // the embedding source
"confidence": 1, "weight": 1, "service": "…",
"vector": [ … ], // only with includeVectors
"metadata": { … } // custom fields only (reserved fields are top-level)
}
],
"relations": [
{ "id":"…", "from":"…", "to":"…", "type":"Contains", "subtype":"…",
"weight":1, "confidence":1, "metadata": { … } }
],
"blobs": { "<sha256>": "<base64>" }, // only with includeContent
"danglingIds": [ "…" ], // only with edges:'incident'
"stats": { "entityCount": 0, "relationCount": 0, "blobCount": 0, "vectorDimensions": 384 }
}
```
Standard fields (`subtype`, `visibility`, `data`, `confidence`, `weight`, `service`) sit at
the top level of each entity; `metadata` holds **only** custom user fields — mirroring the
in-memory `Entity` shape, so `import()` maps each field to its dedicated parameter. The
TypeScript types (`PortableGraph`, `PortableGraphEntity`, `PortableGraphRelation`, `ExportSelector`,
`ExportOptions`, `ImportOptions`, `ImportResult`) are exported from the package root.
## Generations & time-travel
The portable document is **current-state** — it never embeds generation history (that keeps
it cross-version-portable). History lives where it's queryable:
- **During a session:** `brain.asOf(g)` / `brain.now().with(ops)` on the live brain. Because
`export()` is on the `Db`, `(await brain.asOf(g)).export()` serializes a *past* generation
and `brain.now().with(ops).export()` serializes a *speculative* one.
- **A whole-brain snapshot with history:** `brain.now().persist(path)` / `Brainy.load(path)`
(native, generation-preserving) — a separate facility from this portable format.
Note: only `transact()` (and the write shortcuts that commit through it) advances a
generation, so time-travel export differs across transaction boundaries.
## Cross-version (7.x → 8.0)
Because the document is shared and versioned, a PortableGraph written by 7.x imports into 8.0:
`formatVersion` is read forward, `subtype` is carried so 8.0 re-types correctly, and the
same 384-dimension model on both lines means `includeVectors:false` re-embeds identically
(or `true` carries vectors verbatim).
## VFS
VFS directories are `Collection` entities and files are entities linked by `Contains`, so
the whole filesystem (or any subtree) exports through the `vfsPath` selector:
```typescript
await brain.export({ vfsPath: '/' }, { includeContent: true }) // all VFS + bytes
await brain.export({ vfsPath: '/docs' }, { includeContent: true }) // one directory
await brain.export({ vfsPath: '/a/b.txt' }, { includeContent: true }) // one file
```

View file

@ -1,99 +0,0 @@
---
title: External Backups & Sparse Storage
slug: guides/external-backups
public: true
category: guides
template: guide
order: 10
description: How to back up a brain directory with external tools (tar, rsync, cp) without exploding sparse files — why a store can show 100+ GB "apparent" size on a small disk, which files are sparse, and how persist()/restore() handle it for you.
next:
- guides/snapshots-and-time-travel
- concepts/storage-adapters
---
# External Backups & Sparse Storage
The built-in snapshot path — [`db.persist()` and `brain.restore()`](/docs/guides/snapshots-and-time-travel) —
already handles everything on this page for you. Read this when you back up a brain directory with
**external tools**: `tar`, `rsync`, `cp`, `scp`, or a filesystem-level backup agent.
## The one-sentence rule
> **Always use the sparse-aware flag**: `tar czSf` (capital `S`), `rsync --sparse`,
> `cp --sparse=always`. A naive copy can turn a 2 GB store into a 100+ GB one — or fail
> the disk entirely.
## Why: some files are sparse
When a native accelerator plugin is active, parts of the index live in **memory-mapped files**
created at a large fixed virtual size — the file's *apparent* size — while the filesystem only
allocates blocks that were actually written. A brand-new id-mapper file can report tens of
gigabytes in `ls -l` while occupying a few megabytes on disk.
Check the difference yourself:
```bash
ls -lh brain-data/_id_mapper/ # APPARENT size (can be huge)
du -sh brain-data/ # ALLOCATED size (the real footprint)
```
The sparse candidates in a brain directory:
| Path | What it is |
|---|---|
| `_id_mapper/` | The native id-mapper's mmap files (large fixed virtual size) |
| `_blobs/` | Native index files (vector base, segments) — may be mmap-backed |
Everything else (entities, `_system`, `_generations`, `_cas` content blobs) is ordinary dense data.
## Doing it right
**tar** — the `S` flag detects holes and stores only real data:
```bash
tar czSf brain-backup.tgz /data/brain
# restore preserves the holes:
tar xzSf brain-backup.tgz -C /data/
```
**rsync**:
```bash
rsync -a --sparse /data/brain/ backup-host:/backups/brain/
```
**cp**:
```bash
cp -a --sparse=always /data/brain /backups/brain
```
**What goes wrong without the flag:** the copy *materializes* every hole as real zero bytes.
A store whose apparent size exceeds the target disk fails with `ENOSPC` partway through — and a
copy that *does* fit silently costs the full apparent size in storage and transfer time.
## What the built-in paths do (so you don't have to)
- **`db.persist(path)`** snapshots via **hard links** — instant and space-shared, since every data
file is immutable-by-rename. The handful of append-in-place files (the transaction log, the
commit fact log's tail segment) and mmap-mutated directories (`_id_mapper/`) are **byte-copied**
instead, so a post-snapshot write can never reach through a shared inode into your backup.
- **`brain.restore(path, { confirm: true })`** is **non-destructive and sparse-aware**: the snapshot
is copied into a staging area *before* any live data is touched (all-zero blocks stay holes), and
only after the copy fully succeeds does an atomic swap move it into place. A failed copy —
including `ENOSPC` — leaves the live store exactly as it was. A crash mid-swap completes forward
on the next open.
## Live-store caveats for external tools
1. **Prefer snapshotting a `persist()` output, not the live directory.** `persist()` produces a
crash-consistent, immutable snapshot; running `tar` against a live, actively-written directory
can capture a torn mid-write state. If you must archive live, stop writes first (or accept that
the archive is only as consistent as the moment's flush state).
2. **Never prune or "clean up" files inside a brain directory.** Index files that look stale or
redundant are load-bearing; the store protects its declared index families from in-process
deletion, but an external `rm` bypasses that fence. If space is the concern, `du -sh` first —
the allocated size is usually far smaller than it looks.
3. **Verify restores by opening them.** `Brainy.load(path)` opens any snapshot or restored
directory read-only — the store verifies its own coherence at open and reports loudly if
anything is missing or torn.

View file

@ -1,153 +0,0 @@
---
title: Query Limits & Pagination
slug: guides/find-limits
public: true
category: guides
template: guide
order: 8
description: How Brainy caps `find({ limit })` to prevent OOM, the three escape valves when the cap is too tight, and why pagination is the future-proof pattern.
next:
- guides/aggregation
- api/reference
---
# Query Limits & Pagination
Brainy's `find()` returns entities into a JavaScript array. The size of that array is bounded by an auto-configured cap so a single query can never run the host out of memory. This guide explains the cap, the three ways to raise it when your use case justifies it, and the one pattern that scales no matter what cap is in effect: pagination.
## Why the cap exists
Every entity Brainy returns carries:
- A 384-dim float32 embedding vector (1.5 KB)
- Standard fields: `id`, `type`, `subtype`, timestamps, confidence, weight (~200 bytes)
- User metadata (variable — typical 5-10 KB, can spike to 20+ KB)
Conservative budget: **25 KB per result**. A `find({ limit: 100_000 })` against a brain with rich metadata can claim ~2.5 GB before Brainy's iteration starts. JavaScript's GC + V8's heap targets can't absorb that swing without paging or OOM in production.
The cap is a safety net. It's not the only reason your query might be slow — graph traversal and HNSW search have their own perf characteristics — but it's the one that turns a slow query into a sudden runtime error.
## The auto-configured cap (7.30.2+)
Brainy picks `maxLimit` from the first of these that's available:
| Priority | Source | Formula |
|---|---|---|
| 1 | Constructor option `maxQueryLimit` | Hard cap at supplied value, max 100 000 |
| 2 | Constructor option `reservedQueryMemory` | `floor(reservedQueryMemory / 25 KB)` capped at 100 000 |
| 3 | Detected container memory limit (Cloud Run, Kubernetes, cgroups v1/v2) | `floor(containerLimit × 0.25 / 25 KB)` capped at 100 000 |
| 4 | Free system memory | `floor(availableMemory / 25 KB)` capped at 100 000 |
Worked example: a 4 GB Cloud Run container picks priority 3 → `floor(4 GB × 0.25 / 25 KB) = floor(40 960) = 40 000` results. A 900 MB free-memory box on priority 4 gets `floor(900 MB / 25 KB) = ~36 000`.
The cap is fixed at construction and never changes at runtime. Query timing is recorded
for diagnostics only — a burst of slow queries cannot silently shrink the cap, and the
auto-detected tiers (3 and 4) never go below a floor of 10 000.
> **Calibration note.** Pre-7.30.2 used 100 KB per result instead of 25 KB, which produced caps that were 4× too tight for typical workloads (an 8 KB / result reality). 7.30.2 recalibrated to match observed entity sizes; existing `limit: 10_000` safety patterns now pass silently on any reasonably-sized box.
## What happens when you exceed the cap
`find({ limit })` enforces in **two tiers**:
### Soft tier: `maxLimit < limit ≤ 2 × maxLimit`
You get a one-time warning per call site:
```
[Brainy] find({ limit: 50000 }) exceeds the auto-configured query limit of
40000 (basis: detected container memory limit). Choose one:
• Increase the cap: new Brainy({ maxQueryLimit: 50000 })
• Reserve more memory: new Brainy({ reservedQueryMemory: 1310720000 })
• Paginate: split the query with { limit, offset } pages
at YourService.loadDashboard (/app/src/dashboard.ts:142:18)
Docs: https://soulcraft.com/docs/guides/find-limits
```
**The query proceeds.** Brainy returns the result set you asked for; the warning is a teaching signal, not a block. Existing code that relied on the cap silently allowing safety-cap limits (`limit: 10_000` against a 9 K-cap box) keeps working — the warning shows you the recipe so you can fix it intentionally.
### Hard tier: `limit > 2 × maxLimit`
Same message, but thrown as an error. This is real OOM territory; the cap stops being a recommendation and becomes a guardrail.
## The three escape valves
### 1. Raise the cap at construction — `maxQueryLimit`
When the auto-config is wrong for your workload (e.g. you know your entities are smaller than 25 KB average and you need bigger result sets), set an explicit cap:
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' },
maxQueryLimit: 50_000 // raises the cap; still hard-clamped at 100 000
})
```
This is the right answer when:
- Your entity metadata is genuinely small (e.g. 1-2 KB) and 25 KB per result is over-conservative
- You're running on a box with lots of headroom and 25% of memory underestimates what you can spare for queries
- You need a known-good limit that doesn't change when the box's free-memory wiggles at startup
### 2. Reserve more memory for queries — `reservedQueryMemory`
When you want the cap to be memory-derived but more generous than the default 25% slice:
```typescript
const brain = new Brainy({
reservedQueryMemory: 1024 * 1024 * 1024 // 1 GB → ~40 000 result cap
})
```
This is the right answer when:
- Your host's memory budget for queries is known and stable, regardless of free-memory at startup
- You want the formula to scale with the documented per-result size (25 KB) instead of a hard number
### 3. Paginate — the future-proof pattern
If your query genuinely needs to walk all matches in a category, don't fight the cap — walk in pages:
```typescript
async function findAll<T>(params: FindParams<T>, pageSize = 1000): Promise<Result<T>[]> {
const all: Result<T>[] = []
let offset = 0
while (true) {
const page = await brain.find({ ...params, limit: pageSize, offset })
all.push(...page)
if (page.length < pageSize) break
offset += page.length
}
return all
}
// Use it just like find():
const allEvents = await findAll({ type: NounType.Event, where: { status: 'open' } })
```
For very large brains, prefer the streaming API which avoids holding the full result set in memory at all:
```typescript
for await (const entity of brain.streaming.entities({ type: NounType.Event })) {
// process one entity at a time
}
```
## When to use which
| Situation | Recommended valve |
|---|---|
| The cap is unreasonably low for your known entity size | `maxQueryLimit` |
| You want a memory-derived cap but more generous than 25% | `reservedQueryMemory` |
| Your query needs ALL matches in a category | Pagination or `brain.streaming.entities()` |
| You hit the cap once during a one-off migration | `maxQueryLimit` or `migrateField` (which already paginates internally) |
| You're hitting the cap on a recurring user-facing query | Pagination — the cap will get tighter in 8.0, not looser |
## A note on Brainy 8.0
8.0's Datomic-style `Db` API may make per-call limits stricter to keep snapshot semantics cheap. **Pagination is the only pattern that's guaranteed to keep working unchanged.** Code that paginates today doesn't need to revisit when 8.0 ships.
## Reference
- `BrainyConfig.maxQueryLimit?: number` — explicit cap override (max 100 000)
- `BrainyConfig.reservedQueryMemory?: number` — memory budget for queries (bytes)
- `find({ limit, offset })` — paginated find
- `brain.streaming.entities(filter)` — streaming alternative for very large traversals

View file

@ -1,545 +0,0 @@
# Framework Integration Guide
Brainy is **framework-friendly** - designed to drop into the server side of any modern JavaScript framework. This guide shows you how to integrate Brainy into framework-based apps.
> **Runtime**: Brainy 8.0 runs on Node.js 22+ and Bun (server-side only). It is not a browser library. In framework apps, run Brainy in API routes, server components, server actions, loaders, or a dedicated backend service - never in client-side bundles.
## 🎯 Why Server-Side?
Brainy embeds an HNSW vector index, a graph engine, and a filesystem-backed persistence layer. These belong on the server:
- **Zero configuration**: Just `import { Brainy } from '@soulcraft/brainy'`
- **Auto storage detection**: `new Brainy()` auto-selects filesystem persistence on Node
- **Cleaner code**: No browser polyfills, no conditional client/server imports
- **Better DX**: One instance shared across your server routes
## 🚀 Quick Start
### Install Brainy
```bash
npm install @soulcraft/brainy
```
### Basic Integration
```javascript
import { Brainy } from '@soulcraft/brainy'
// Run on the server (API route, server component, backend service)
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
// Add data
await brain.add({
data: "Framework integration is awesome!",
type: "concept",
metadata: { framework: "any" }
})
// Search
const results = await brain.find("framework integration")
```
## ⚛️ React Integration
Brainy runs on the server, so a React client component talks to it through an API endpoint (see the Next.js API route below). The hook fetches results; it never instantiates Brainy in the browser.
### Basic Hook Pattern
```jsx
import { useState, useCallback } from 'react'
function useBrainySearch(endpoint = '/api/search') {
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
const search = useCallback(async (query) => {
if (!query) return
setLoading(true)
try {
const res = await fetch(endpoint, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
})
const { results } = await res.json()
setResults(results)
} finally {
setLoading(false)
}
}, [endpoint])
return { results, loading, search }
}
// Usage in component
function SearchComponent() {
const { results, loading, search } = useBrainySearch()
return (
<div>
<input
type="text"
placeholder="Search..."
onChange={(e) => search(e.target.value)}
/>
{loading && <div>Searching...</div>}
<div>
{results.map(result => (
<div key={result.id}>
<h3>{result.data}</h3>
<p>Score: {(result.score * 100).toFixed(1)}%</p>
</div>
))}
</div>
</div>
)
}
```
### Shared Server Instance
On the server, create one Brainy instance and reuse it across requests. This module is imported only by server code (API routes, server components), never by client components:
```javascript
// lib/brain.server.js
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
}
return brainPromise
}
```
## 🟢 Vue.js Integration
Vue components call a server endpoint (see the Nuxt server route in the [Vue.js Integration Guide](vue-integration.md)); Brainy itself runs on the server.
### Composition API (client component)
```vue
<template>
<div>
<input v-model="query" @input="search" placeholder="Search..." />
<div v-for="result in results" :key="result.id">
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue'
const query = ref('')
const results = ref([])
const search = async () => {
if (!query.value) return
const res = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: query.value })
})
results.value = (await res.json()).results
}
</script>
```
### Shared Server Instance
On the server, create one Brainy instance and reuse it across requests:
```javascript
// server/brain.js (server-only module)
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
}
return brainPromise
}
```
## 🅰️ Angular Integration
The Angular service calls your backend over HTTP; Brainy lives in that backend, not in the browser.
### Service Pattern (calls the backend)
```typescript
// brainy.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
@Injectable({
providedIn: 'root'
})
export class BrainyService {
constructor(private http: HttpClient) {}
search(query: string): Observable<{ results: any[] }> {
return this.http.post<{ results: any[] }>('/api/search', { query })
}
add(data: any, type: string, metadata?: any): Observable<{ id: string }> {
return this.http.post<{ id: string }>('/api/add', { data, type, metadata })
}
}
```
```typescript
// search.component.ts
import { Component } from '@angular/core'
import { BrainyService } from './brainy.service'
@Component({
selector: 'app-search',
template: `
<div>
<input
[(ngModel)]="query"
(input)="search()"
placeholder="Search..."
/>
<div *ngFor="let result of results">
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
</div>
</div>
`
})
export class SearchComponent {
query = ''
results: any[] = []
constructor(private brainyService: BrainyService) {}
search() {
if (!this.query) return
this.brainyService.search(this.query).subscribe(({ results }) => {
this.results = results
})
}
}
```
The matching backend endpoint uses Brainy directly (Node/Bun):
```typescript
// server: api/search
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy() // auto-detects filesystem persistence on Node
await brain.init()
export async function handleSearch(query: string) {
return await brain.find(query)
}
```
## 🚀 Next.js Integration
In Next.js, Brainy lives in server code only: API routes, server components, or server actions. Create one shared instance in a server-only module.
### Shared Server Instance
```javascript
// lib/brain.server.js (imported only by server code)
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
})
await brain.init()
return brain
})()
}
return brainPromise
}
```
### API Routes
```javascript
// app/api/search/route.js
import { getBrain } from '@/lib/brain.server'
export async function POST(request) {
const { query } = await request.json()
const brain = await getBrain()
const results = await brain.find(query)
return Response.json({ results })
}
```
### Server Action
```javascript
// app/actions.js
'use server'
import { getBrain } from '@/lib/brain.server'
export async function search(query) {
const brain = await getBrain()
return await brain.find(query)
}
```
## 🔷 SvelteKit Integration
Brainy runs in a server-only module (`*.server.js`); the component fetches results from an endpoint.
```javascript
// src/lib/server/brain.js (server-only — note the .server suffix)
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
const brain = new Brainy() // auto-detects filesystem persistence
await brain.init()
return brain
})()
}
return brainPromise
}
```
```javascript
// src/routes/api/search/+server.js
import { json } from '@sveltejs/kit'
import { getBrain } from '$lib/server/brain'
export async function POST({ request }) {
const { query } = await request.json()
const brain = await getBrain()
return json({ results: await brain.find(query) })
}
```
```svelte
<!-- SearchComponent.svelte -->
<script>
let query = ''
let results = []
async function search() {
if (!query) return
const res = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
})
results = (await res.json()).results
}
</script>
<div>
<input bind:value={query} on:input={search} placeholder="Search..." />
{#each results as result}
<div>
<h3>{result.data}</h3>
<p>Score: {(result.score * 100).toFixed(1)}%</p>
</div>
{/each}
</div>
```
## 🌟 Solid.js Integration
The component calls a server endpoint (use SolidStart server routes, or any backend, to host Brainy):
```jsx
import { createSignal } from 'solid-js'
function SearchComponent() {
const [query, setQuery] = createSignal('')
const [results, setResults] = createSignal([])
const search = async () => {
if (!query()) return
const res = await fetch('/api/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query: query() })
})
setResults((await res.json()).results)
}
return (
<div>
<input
value={query()}
onInput={(e) => {
setQuery(e.target.value)
search()
}}
placeholder="Search..."
/>
<For each={results()}>
{(result) => (
<div>
<h3>{result.data}</h3>
<p>Score: {(result.score * 100).toFixed(1)}%</p>
</div>
)}
</For>
</div>
)
}
```
## 📦 Bundler Configuration
Brainy is a server-side dependency, so keep it out of client bundles. Import it only from server-only modules (`*.server.js`, API routes, server components, server actions). If your bundler ever tries to pull Brainy into a client bundle, that's a sign it's being imported from a client component — move the import to a server module.
For server builds, mark Brainy as external so the bundler doesn't inline it:
```javascript
// vite.config.js (SSR build)
import { defineConfig } from 'vite'
export default defineConfig({
ssr: {
external: ['@soulcraft/brainy']
}
})
```
```javascript
// rollup.config.js (server bundle)
export default {
external: ['@soulcraft/brainy', 'node:fs', 'node:path', 'node:crypto']
}
```
## 🌐 SSR/SSG Considerations
### Server-Side Rendering
Instantiate Brainy on the server and feed its results into the rendered page. Never construct it in client code.
```javascript
// Server-side data loading (framework loader / getServerSideProps / load fn)
import { getBrain } from './brain.server'
export async function load({ url }) {
const brain = await getBrain()
const query = url.searchParams.get('q') ?? ''
const results = query ? await brain.find(query) : []
return { results }
}
```
### Static Site Generation
```javascript
// For build-time usage (runs in Node during the build)
import { Brainy } from '@soulcraft/brainy'
export async function generateStaticProps() {
const brain = new Brainy({
storage: { type: 'filesystem', path: './content' }
})
await brain.init()
// Build search index (paginate with { limit, offset } for larger stores)
const allContent = await brain.find({ limit: 1000 })
return {
props: { searchIndex: allContent }
}
}
```
## 🔧 Framework-Specific Tips
### React
- Keep components client-side and call a Brainy-backed API route
- Use `useCallback` for fetch handlers to prevent re-renders
- Debounce keystroke-driven searches before hitting the endpoint
### Vue
- Components call an endpoint; the shared instance lives in a server module
- Consider Pinia for caching results client-side
- Debounce reactive search queries
### Angular
- Use `HttpClient` and RxJS to call the backend
- Hold the shared Brainy instance in your Node backend, not the app
- Consider lazy loading search features in feature modules
### Next.js
- Put Brainy in server-only modules (`*.server.js`), API routes, or server actions
- Reuse one shared instance across requests
- Implement proper error boundaries for failed fetches
## 🚨 Common Issues & Solutions
### Issue: "fs module not found" / "crypto is not defined" in the browser
**Cause**: Brainy was imported into a client bundle. Brainy 8.0 is a server-side library (Node 22+/Bun) and uses Node built-ins like `fs` and `crypto`.
**Solution**: Import Brainy only from server code — server-only modules (`*.server.js`), API routes, server components, or server actions. From client components, call those endpoints instead.
### Issue: Large client bundle size
**Cause**: A client module is pulling in Brainy.
**Solution**: Move the `import { Brainy } from '@soulcraft/brainy'` into a server-only module so it never reaches the browser bundle.
### Issue: SSR hydration mismatch
**Solution**: Run the search on the server (loader / server action / API route) and pass the results down as props, so server and client render the same markup.
## 🎯 Best Practices
1. **Initialize Once**: Create one shared Brainy instance per server process, not per request
2. **Server-Only**: Import Brainy only from server modules — never from client components
3. **Endpoint Boundary**: Expose search/add through API routes or server actions
4. **Handle Loading**: Show loading states in the client while the fetch is in flight
5. **Error Handling**: Catch and surface failed endpoint calls gracefully
6. **Storage**: Use `filesystem` for persistence (the default on Node) or `memory` for ephemeral/tests
## 📚 Next Steps
- [Next.js Integration Guide](nextjs-integration.md) - Detailed Next.js examples
- [Vue.js Integration Guide](vue-integration.md) - Complete Vue.js patterns
- [API Reference](../api/README.md) - Complete API documentation
- [Production Deployment](../deployment/CLOUD_DEPLOYMENT_GUIDE.md) - Deploy to production
## 🤝 Community Examples
Check out community examples in the [examples repository](https://github.com/soulcraftlabs/brainy-examples):
- React + TypeScript starter
- Vue 3 + Composition API
- Next.js full-stack app
- Svelte SPA with search
- Angular enterprise app

View file

@ -0,0 +1,333 @@
# Getting Started with Brainy
This guide will help you get up and running with Brainy, the multi-dimensional AI database that combines vector similarity, graph relationships, and metadata filtering.
## Installation
```bash
npm install brainy
```
## Basic Setup
### Simple Initialization
```typescript
import { BrainyData } from 'brainy'
// Create a new Brainy instance with defaults
const brain = new BrainyData()
// Initialize (downloads models if needed)
await brain.init()
// You're ready to go!
```
### Custom Configuration
```typescript
const brain = new BrainyData({
// Storage configuration
storage: {
type: 'filesystem', // or 's3', 'opfs', 'memory'
path: './my-data'
},
// Vector configuration
vectors: {
dimensions: 384,
model: 'all-MiniLM-L6-v2'
},
// Performance tuning
cache: {
enabled: true,
maxSize: 1000
}
})
await brain.init()
```
## Your First Operations
### Adding Data
```typescript
// Add entities (nouns) with automatic embedding generation
const id = await brain.addNoun("The quick brown fox jumps over the lazy dog", {
category: "demo",
timestamp: Date.now()
})
console.log(`Added noun with ID: ${id}`)
// Add relationships (verbs) between entities
const sourceId = await brain.addNoun("John Smith")
const targetId = await brain.addNoun("TechCorp")
await brain.addVerb(sourceId, targetId, "works_at", {
position: "Engineer",
since: "2024"
})
```
### Searching
```typescript
// Simple semantic search
const results = await brain.search("fast animals")
results.forEach(result => {
console.log(`Found: ${result.content} (score: ${result.score})`)
})
```
### Advanced Queries with find()
```typescript
// Natural language queries - Brainy understands intent!
const results = await brain.find("show me technology articles about AI from 2023")
// Automatically interprets: topic, category, and time range
// Structured queries with vector similarity and metadata filtering
const structured = await brain.find({
like: "artificial intelligence",
where: {
category: "technology",
year: { $gte: 2023 }
},
limit: 10
})
// Complex natural language with multiple filters
const complex = await brain.find("financial reports from Q3 2024 with revenue over 1M")
// Automatically extracts: document type, date range, numeric filters
```
## Common Use Cases
### 1. Semantic Search Engine
```typescript
// Index documents
const documents = [
{ title: "Introduction to AI", content: "AI is transforming..." },
{ title: "Machine Learning Basics", content: "ML algorithms..." },
{ title: "Deep Learning", content: "Neural networks..." }
]
for (const doc of documents) {
await brain.addNoun(doc.content, {
title: doc.title,
type: "document"
})
}
// Search semantically
const results = await brain.search("how do neural networks work")
```
### 2. Recommendation System
```typescript
// Add user interactions as nouns
const interactionId = await brain.addNoun("user viewed product", {
userId: "user123",
productId: "product456",
action: "view",
timestamp: Date.now()
})
// Create relationships between users and products
const userId = await brain.addNoun("user123")
const productId = await brain.addNoun("product456")
await brain.addVerb(userId, productId, "viewed", {
timestamp: Date.now()
})
// Natural language query for recommendations
const recommendations = await brain.find("products similar to what user123 viewed recently")
// Or structured query for similar users
const similar = await brain.find({
like: "user123 interests",
where: { action: "view" },
limit: 5
})
```
### 3. Knowledge Graph
```typescript
// Add entities (nouns) to the knowledge graph
const personId = await brain.addNoun("John Smith, Software Engineer", {
type: "person",
role: "engineer"
})
const companyId = await brain.addNoun("TechCorp, Innovation Leader", {
type: "company",
industry: "technology"
})
// Create relationship
await brain.addVerb(personId, companyId, "works_at", {
since: "2020",
position: "Senior Engineer"
})
// Natural language query for relationships
const colleagues = await brain.find("people who work at TechCorp")
// Or structured query for specific relationships
const results = await brain.find({
connected: {
from: personId,
type: "works_at"
}
})
```
### 4. Real-time Data Processing
```typescript
// Configure for streaming
const brain = new BrainyData({
augmentations: [
new EntityRegistryAugmentation(), // Deduplication
new BatchProcessingAugmentation({ batchSize: 100 }) // Batching
]
})
// Process streaming data
async function processStream(item) {
// Entity registry prevents duplicate nouns
const id = await brain.addNoun(item.content, {
externalId: item.id,
timestamp: item.timestamp
})
// Real-time natural language queries
if (item.urgent) {
const related = await brain.find(`urgent items similar to ${item.content}`)
// Process related items...
}
}
```
## Storage Options
### Development (Memory)
```typescript
const brain = new BrainyData({
storage: { type: 'memory' }
})
// Fast, temporary, perfect for testing
```
### Production (FileSystem)
```typescript
const brain = new BrainyData({
storage: {
type: 'filesystem',
path: '/var/lib/brainy'
}
})
// Persistent, efficient, server-ready
```
### Cloud (S3)
```typescript
const brain = new BrainyData({
storage: {
type: 's3',
bucket: 'my-brainy-data',
region: 'us-east-1'
}
})
// Scalable, distributed, cloud-native
```
### Browser (OPFS)
```typescript
const brain = new BrainyData({
storage: { type: 'opfs' }
})
// Browser-native, persistent, offline-capable
```
## Performance Tips
### 1. Use Batch Operations
```typescript
// Good - batch operations for nouns
const items = ["item1", "item2", "item3"]
for (const item of items) {
await brain.addNoun(item, { batch: true })
}
// Create relationships efficiently
const relationships = [
{ source: id1, target: id2, type: "related" },
{ source: id2, target: id3, type: "similar" }
]
for (const rel of relationships) {
await brain.addVerb(rel.source, rel.target, rel.type)
}
```
### 2. Enable Caching
```typescript
const brain = new BrainyData({
cache: {
enabled: true,
maxSize: 1000,
ttl: 300000 // 5 minutes
}
})
```
### 3. Use Appropriate Limits
```typescript
// Always specify reasonable limits
const results = await brain.search("query", {
limit: 20 // Don't fetch more than needed
})
```
### 4. Index Frequently Queried Fields
```typescript
const brain = new BrainyData({
indexedFields: ['category', 'userId', 'timestamp']
})
```
## Error Handling
```typescript
try {
await brain.addNoun("content", metadata)
} catch (error) {
if (error.code === 'STORAGE_FULL') {
console.error('Storage is full')
} else if (error.code === 'INVALID_INPUT') {
console.error('Invalid input:', error.message)
} else {
console.error('Unexpected error:', error)
}
}
```
## Next Steps
- [Architecture Overview](../architecture/overview.md) - Understand the system design
- [Triple Intelligence](../architecture/triple-intelligence.md) - Advanced query capabilities
- [API Reference](../api/README.md) - Complete API documentation
- [Examples](https://github.com/brainy-org/brainy/tree/main/examples) - More code examples
## Getting Help
- **Issues**: [GitHub Issues](https://github.com/brainy-org/brainy/issues)
- **Discussions**: [GitHub Discussions](https://github.com/brainy-org/brainy/discussions)
- **Examples**: Check the `/examples` directory

View file

@ -1,400 +0,0 @@
# Import Anything - ONE Method, Infinite Intelligence 🚀
Brainy's import is **ONE magical method** that understands EVERYTHING:
- 📊 Data (objects, arrays, strings)
- 📁 Files (auto-detects by path)
- 🌐 URLs (auto-fetches with authentication support)
- 📄 Formats (JSON, CSV, Excel, PDF, YAML, DOCX, Markdown - all auto-detected)
## The Ultimate Simplicity
```javascript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// ONE method for EVERYTHING:
await brain.import(anything)
```
## Import Examples - It Just Works™
### 📊 Import JSON Data
```javascript
// Array of objects? No problem.
const people = [
{ name: 'Alice', role: 'Engineer', company: 'TechCorp' },
{ name: 'Bob', role: 'Designer', company: 'TechCorp' }
]
await brain.import(people)
// ✨ Automatically detected as Person entities with Organization relationships!
```
### 📄 Import CSV - File or String
```javascript
// From file? Just pass the path!
await brain.import('customers.csv')
// ✨ Auto-detects encoding, delimiter, types - creates entities!
// Or pass CSV content directly
const csv = `name,age,city
John,30,NYC
Jane,25,SF`
await brain.import(csv, { format: 'csv' })
// ✨ Smart CSV parsing handles quotes, escapes, everything!
```
### 📊 Import Excel - Multi-Sheet Support
```javascript
// Import entire Excel workbook — every sheet is processed automatically
await brain.import('sales-report.xlsx')
// ✨ Processes all sheets, preserves structure, infers types!
// Mirror the workbook into the VFS, grouped by sheet
await brain.import('data.xlsx', {
vfsPath: '/imports/data',
groupBy: 'sheet'
})
// ✨ Multi-sheet data becomes interconnected entities!
```
### 📑 Import PDF - Text & Tables
```javascript
// Import PDF documents — text and tables are extracted automatically
await brain.import('research-paper.pdf')
// ✨ Extracts text, detects tables, preserves metadata!
```
### 📝 Import YAML - File or String
```javascript
// From file? Auto-detected!
await brain.import('config.yaml')
// ✨ Knows it's a file, reads it, parses YAML!
// Or directly:
const yaml = `
project: AI Assistant
team:
- name: Alice
role: Lead
- name: Bob
role: Dev
`
await brain.import(yaml, { format: 'yaml' })
// ✨ Hierarchical data becomes a connected graph!
```
### 📄 Import Word Documents (DOCX) -
```javascript
// From file path
await brain.import('research-paper.docx')
// ✨ Extracts text, headings, tables, and metadata!
// Or from buffer
const buffer = fs.readFileSync('document.docx')
await brain.import(buffer, { format: 'docx' })
// ✨ Uses heading hierarchy for entity organization!
// With neural extraction
await brain.import('report.docx', {
enableNeuralExtraction: true,
enableHierarchicalRelationships: true
})
// ✨ Extracts entities from paragraphs and creates relationships within sections!
```
### 🌐 Import from URLs - Auto-Detected!
```javascript
// Just pass the URL - it knows!
await brain.import('https://api.example.com/data.json')
// ✨ Auto-detects URL, fetches, parses, processes!
// Works with any URL
await brain.import('https://data.gov/census.csv')
// ✨ Fetches CSV from web, parses, imports!
// With authentication
await brain.import({
type: 'url',
data: 'https://api.example.com/private/data.xlsx',
auth: {
username: 'user',
password: 'pass'
}
})
// ✨ Supports basic authentication for protected resources!
// With custom headers
await brain.import({
type: 'url',
data: 'https://api.example.com/data.json',
headers: {
'Authorization': 'Bearer TOKEN',
'X-API-Key': 'your-key'
}
})
// ✨ Full HTTP header customization support!
```
### 📖 Import Plain Text
```javascript
// Even unstructured text works
const article = `Artificial Intelligence is transforming industries.
Machine learning enables predictive analytics.
Natural language processing powers chatbots.`
await brain.import(article, { format: 'text' })
// ✨ Extracts concepts, creates semantic connections!
```
## The Magic Behind the Scenes
When you import data, Brainy:
1. **Auto-detects format** - CSV, Excel, PDF, JSON, YAML, DOCX, Markdown, or by file extension
2. **Intelligent parsing** - CSV (encoding/delimiter), Excel (multi-sheet), PDF (text/tables), DOCX (headings/paragraphs)
3. **Identifies entity types** - Uses AI to classify as Person, Document, Product, etc. (31 types!)
4. **Finds relationships** - Detects connections like "belongsTo", "createdBy", "references" (40 types!)
5. **Scores confidence & weight** - Every entity and relationship gets quality metrics
6. **Creates embeddings** - Makes everything semantically searchable
7. **Indexes metadata** - Enables lightning-fast filtering with range queries
## Intelligent Type Detection
Brainy automatically detects what TYPE of data you're importing:
```javascript
// This becomes a Person entity
{ name: 'John', email: 'john@example.com' }
// This becomes an Organization
{ companyName: 'Acme', employees: 500 }
// This becomes a Document
{ title: 'Report', content: '...', author: 'Jane' }
// This becomes a Location
{ latitude: 37.7, longitude: -122.4, city: 'SF' }
```
**42 noun types and 127 verb types** cover EVERYTHING!
## Relationship Detection
Brainy finds connections in your data:
```javascript
const data = [
{ id: 'u1', name: 'Alice', managerId: 'u2' },
{ id: 'u2', name: 'Bob', departmentId: 'd1' },
{ id: 'd1', name: 'Engineering' }
]
await brain.import(data)
// ✨ Automatically creates:
// - Alice "reportsTo" Bob
// - Bob "memberOf" Engineering
```
## Confidence & Weight Scoring -
Every entity and relationship gets confidence and weight scores:
```javascript
// Import with confidence threshold
await brain.import(data, {
confidenceThreshold: 0.8 // Only extract entities with >80% confidence
})
// Query high-confidence entities using range queries
const highConfidence = await brain.find({
where: {
confidence: { gte: 0.8 } // Get entities with confidence >= 0.8
}
})
// Range query operators: gt, gte, lt, lte, between
const mediumConfidence = await brain.find({
where: {
confidence: { between: [0.6, 0.8] }
}
})
```
**What do confidence scores mean?**
- **High (>0.8)**: Very confident entity classification
- **Medium (0.6-0.8)**: Reasonable confidence
- **Low (<0.6)**: Uncertain classification (filtered by default)
**Weights** indicate importance/relevance within the document context.
## Per-Sheet Excel Extraction -
Excel files with multiple sheets can be organized by sheet:
```javascript
// Group entities by sheet in VFS
await brain.import('multi-sheet-data.xlsx', {
groupBy: 'sheet' // Creates separate directories for each sheet
})
// Result VFS structure:
// /imports/data/
// ├── Sheet1/
// │ ├── entity1.json
// │ └── entity2.json
// └── Sheet2/
// ├── entity3.json
// └── entity4.json
// Other groupBy options:
// - 'type': Group by entity type (Person, Place, etc.)
// - 'flat': All entities in one directory
// - 'custom': Use custom grouping function
```
## Query Your Imported Data
Once imported, use Triple Intelligence to query:
```javascript
// Vector search
const similar = await brain.find('engineers')
// Natural language
const results = await brain.find('people in engineering who joined this year')
// Graph traversal + filters
const connected = await brain.find({
like: 'Alice',
connected: { depth: 2 },
where: { department: 'Engineering' }
})
```
## Import Options (Optional!)
Everything works with zero config, but you can customize:
```javascript
await brain.import(data, {
// Format detection
format: 'excel', // Force specific format (auto-detected if not specified)
// VFS & Organization
vfsPath: '/imports/my-data', // Where to store in VFS (auto-generated if not specified)
groupBy: 'type', // Group entities by: 'type' | 'sheet' | 'flat' | 'custom'
preserveSource: true, // Keep original source file in VFS (default: true)
// Entity & Relationship Creation
createEntities: true, // Create entities in knowledge graph (default: true)
createRelationships: true, // Create relationships in knowledge graph (default: true)
// Neural Intelligence
enableNeuralExtraction: true, // Use AI to extract entities (default: true)
enableRelationshipInference: true, // Use AI to infer relationships (default: true)
enableConceptExtraction: true, // Extract concepts from text (default: true)
confidenceThreshold: 0.6, // Minimum confidence for entities (0-1, default: 0.6)
// Deduplication
enableDeduplication: true, // Check for duplicate entities (default: true)
deduplicationThreshold: 0.85, // Similarity threshold for duplicates (0-1, default: 0.85)
// Notes: false disables BOTH the inline merge and the background pass that
// runs ~5 min after the last import (merged duplicates are deleted).
// The inline pass auto-disables for imports >100 entities (O(n²) cost);
// the background pass still covers those unless the flag is false.
// Performance
chunkSize: 100, // Batch size for processing (default: varies by operation)
// History & Progress
enableHistory: true, // Track import history (default: true)
onProgress: (progress) => { // Progress callback
console.log(progress.stage, progress.message)
}
})
```
## Error Handling
Import continues even if some items fail:
```javascript
const results = await brain.import(problematicData)
// Returns IDs of successful imports
// Logs warnings for failures
// Never crashes your app!
```
## Performance
- **Parallel processing** - Fast imports with concurrent operations
- **Batch operations** - Memory efficient chunk processing
- **Lazy loading** - Import system loads only when needed
- **Smart caching** - Type detection and format parsing results cached
## Use Cases
### 🏢 Business Data
```javascript
// Import ANY source - ONE method!
await brain.import('customers.csv') // File
await brain.import('https://api.co/orders') // URL
await brain.import(productsArray) // Data
// Now query across all of it!
await brain.find('customers who bought products in Q4')
```
### 🔬 Research Data
```javascript
// Import research papers
await brain.import(papers)
// Import citations
await brain.import(citations)
// Find connections
await brain.find('papers citing machine learning from 2024')
```
### 📱 Application Data
```javascript
// Import users
await brain.import(users)
// Import posts
await brain.import(posts)
// Import comments
await brain.import(comments)
// Query the social graph
await brain.find('posts by users following Alice with >10 comments')
```
## The Philosophy
**Zero Configuration**: Works perfectly out of the box
**Maximum Intelligence**: AI understands your data's meaning
**Universal Protocol**: 42 nouns × 127 verbs = ANY data model
**Delightful DX**: Simple, clean, modern API
## The ONE Method Philosophy
```javascript
// ONE method that understands EVERYTHING:
await brain.import(data) // Objects, arrays, strings
await brain.import('file.csv') // Files (auto-detected)
await brain.import('http://..') // URLs (auto-fetched)
// It ALWAYS knows what to do! ✨
```
**Why ONE method?**
- 🎯 **Simpler** - No need to remember different methods
- 🧠 **Smarter** - Auto-detects what you're importing
- ✨ **Magical** - It just works, every time
That's the power of the Universal Knowledge Protocol™ - infinite intelligence, zero complexity!

File diff suppressed because it is too large Load diff

View file

@ -1,370 +0,0 @@
# Import Progress - Usage Examples
**How to Use Progress Tracking in Your Applications**
Brainy provides real-time progress tracking for **all 7 supported file formats** (CSV, PDF, Excel, JSON, Markdown, YAML, DOCX).
> **⚠️ KEY FEATURE:** The progress API is **100% standardized**. Write your progress handler ONCE and it works for ALL formats with zero format-specific code! See [Standard Import Progress API](./standard-import-progress.md) for the complete interface documentation.
---
## 🚀 Quick Start
### Basic Progress Tracking
```typescript
import { Brainy } from '@soulcraft/brainy'
import * as fs from 'fs'
const brain = await Brainy.create()
// Import with progress tracking
const result = await brain.import(fs.readFileSync('large-file.xlsx'), {
onProgress: (progress) => {
console.log(`Progress: ${progress.stage}`)
console.log(` Message: ${progress.message}`)
console.log(` Entities: ${progress.entities || 0}`)
console.log(` Relationships: ${progress.relationships || 0}`)
}
})
console.log(`Import complete: ${result.entities.length} entities created`)
```
**Expected Output:**
```
Progress: detecting
Message: Detecting format...
Entities: 0
Relationships: 0
Progress: extracting
Message: Loading Excel workbook...
Entities: 0
Relationships: 0
Progress: extracting
Message: Reading sheet: Sales (1/3)
Entities: 0
Relationships: 0
Progress: extracting
Message: Parsing Excel (33%)
Entities: 0
Relationships: 0
Progress: extracting
Message: Reading sheet: Products (2/3)
Entities: 0
Relationships: 0
... (more progress updates)
Progress: complete
Message: Import complete
Entities: 1523
Relationships: 892
Import complete: 1523 entities created
```
---
## 🎯 Universal Progress Handler (Works for ALL Formats)
The examples below show format-specific messages, but **you don't need format-specific code**! The `ImportProgress` interface is the same for all formats:
```typescript
// ONE HANDLER FOR ALL FORMATS!
function universalProgressHandler(progress) {
console.log(`[${progress.stage}] ${progress.message}`)
if (progress.processed && progress.total) {
console.log(` Progress: ${progress.processed}/${progress.total}`)
}
if (progress.entities || progress.relationships) {
console.log(` Extracted: ${progress.entities || 0} entities, ${progress.relationships || 0} relationships`)
}
if (progress.throughput && progress.eta) {
console.log(` Rate: ${progress.throughput.toFixed(1)}/sec, ETA: ${Math.round(progress.eta/1000)}s`)
}
}
// Use it for ANY format!
await brain.import(csvBuffer, { onProgress: universalProgressHandler })
await brain.import(pdfBuffer, { onProgress: universalProgressHandler })
await brain.import(excelBuffer, { onProgress: universalProgressHandler })
await brain.import(jsonBuffer, { onProgress: universalProgressHandler })
await brain.import(markdownString, { onProgress: universalProgressHandler })
await brain.import(yamlBuffer, { onProgress: universalProgressHandler })
await brain.import(docxBuffer, { onProgress: universalProgressHandler })
```
---
## 📊 What Different Formats Look Like (Same Handler!)
The examples below show **what messages look like** for different formats using the **same universal handler** above.
### CSV Import (Row-by-Row Progress)
```typescript
await brain.import(csvBuffer, {
format: 'csv',
onProgress: (progress) => {
if (progress.stage === 'extracting') {
// CSV reports: "Parsing CSV (45%)", "Extracted 1000 rows", etc.
console.log(progress.message)
}
}
})
```
**CSV Progress Messages:**
- ✅ "Detecting CSV encoding and delimiter..."
- ✅ "Parsing CSV rows (delimiter: ",")"
- ✅ "Parsed 75%" (via bytes processed)
- ✅ "Extracted 1000 rows"
- ✅ "Converting types: 5000/10000 rows..."
- ✅ "CSV processing complete: 10000 rows"
---
### PDF Import (Page-by-Page Progress)
```typescript
await brain.import(pdfBuffer, {
format: 'pdf',
onProgress: (progress) => {
// PDF reports exact page numbers
console.log(progress.message)
// Example: "Processing page 5 of 23"
}
})
```
**PDF Progress Messages:**
- ✅ "Loading PDF document..."
- ✅ "Processing 23 pages..."
- ✅ "Processing page 5 of 23"
- ✅ "Parsed 22%" (via bytes processed)
- ✅ "Extracted 156 items from PDF"
- ✅ "PDF complete: 23 pages, 156 items extracted"
---
### Excel Import (Sheet-by-Sheet Progress)
```typescript
await brain.import(excelBuffer, {
format: 'excel',
onProgress: (progress) => {
// Excel reports sheet names
console.log(progress.message)
// Example: "Reading sheet: Q2 Sales (2/5)"
}
})
```
**Excel Progress Messages:**
- ✅ "Loading Excel workbook..."
- ✅ "Processing 3 sheets..."
- ✅ "Reading sheet: Sales (1/3)"
- ✅ "Parsing Excel (33%)" (via bytes processed)
- ✅ "Extracted 5234 rows from Excel"
- ✅ "Excel complete: 3 sheets, 5234 rows"
---
### JSON Import (Node Traversal)
```typescript
await brain.import(jsonBuffer, {
format: 'json',
onProgress: (progress) => {
// JSON reports every 10 nodes
console.log(`Processed ${progress.processed} nodes, found ${progress.entities} entities`)
}
})
```
---
### Markdown Import (Section-by-Section)
```typescript
await brain.import(markdownString, {
format: 'markdown',
onProgress: (progress) => {
console.log(`Section ${progress.processed}/${progress.total}`)
}
})
```
---
## 🎯 Building Progress UI Components
### React Progress Bar
```typescript
function ImportProgress({ file }: { file: File }) {
const [progress, setProgress] = useState({
stage: 'idle',
message: '',
percent: 0,
entities: 0,
relationships: 0
})
const handleImport = async () => {
const buffer = await file.arrayBuffer()
await brain.import(Buffer.from(buffer), {
onProgress: (p) => {
setProgress({
stage: p.stage,
message: p.message,
// Estimate percentage from stage
percent: {
detecting: 10,
extracting: 50,
'storing-vfs': 80,
'storing-graph': 90,
complete: 100
}[p.stage] || 0,
entities: p.entities || 0,
relationships: p.relationships || 0
})
}
})
}
return (
<div>
<ProgressBar value={progress.percent} />
<p>{progress.message}</p>
<p>Entities: {progress.entities} | Relationships: {progress.relationships}</p>
</div>
)
}
```
---
### CLI Progress Spinner
```typescript
import ora from 'ora'
const spinner = ora('Starting import...').start()
await brain.import(buffer, {
onProgress: (progress) => {
spinner.text = progress.message
if (progress.stage === 'complete') {
spinner.succeed(`Import complete: ${progress.entities} entities`)
}
}
})
```
**CLI Output:**
```
⠋ Detecting format...
⠙ Loading Excel workbook...
⠹ Reading sheet: Sales (1/3)
⠸ Parsing Excel (33%)
⠼ Reading sheet: Products (2/3)
...
✔ Import complete: 1523 entities
```
---
### Progress Dashboard with ETA
```typescript
let startTime = Date.now()
let lastUpdate = startTime
await brain.import(buffer, {
onProgress: (progress) => {
const elapsed = Date.now() - startTime
const rate = progress.entities / (elapsed / 1000) // entities/sec
console.clear()
console.log('Import Progress Dashboard')
console.log('========================')
console.log(`Stage: ${progress.stage}`)
console.log(`Status: ${progress.message}`)
console.log(`Entities: ${progress.entities}`)
console.log(`Relationships: ${progress.relationships}`)
console.log(`Rate: ${rate.toFixed(1)} entities/sec`)
console.log(`Elapsed: ${(elapsed / 1000).toFixed(1)}s`)
}
})
```
---
## 🔧 Advanced: Format-Specific Optimization
### Detecting Format to Show Appropriate Progress
```typescript
const formatMessages = {
csv: (p) => `CSV: ${p.message}`,
pdf: (p) => `PDF: ${p.message}`,
excel: (p) => `Excel: ${p.message}`,
json: (p) => `JSON: ${p.processed} nodes, ${p.entities} entities`,
markdown: (p) => `Markdown: Section ${p.processed}/${p.total}`,
yaml: (p) => `YAML: ${p.processed} nodes`,
docx: (p) => `DOCX: ${p.processed} paragraphs`
}
await brain.import(buffer, {
onProgress: (progress) => {
// Format is available in progress.stage metadata
const message = formatMessages[detectedFormat]?.(progress) || progress.message
console.log(message)
}
})
```
---
## ⚡ Performance Tips
### Throttle UI Updates
```typescript
let lastUIUpdate = 0
const THROTTLE_MS = 100 // Update UI max once per 100ms
await brain.import(buffer, {
onProgress: (progress) => {
const now = Date.now()
if (now - lastUIUpdate < THROTTLE_MS && progress.stage !== 'complete') {
return // Skip this update
}
lastUIUpdate = now
updateUI(progress) // Only update every 100ms
}
})
```
**Note:** Brainy already throttles progress callbacks internally, but additional UI throttling can help with heavy rendering.
---
## 📝 Summary
**All 7 formats** have consistent progress reporting
**Real-time updates** during long imports (no more "0%" hangs)
**Contextual messages** show exactly what's happening
**Build reliable tools** with standardized progress callbacks
**Problem SOLVED** - users see progress throughout import
**Files Modified:**
- 3 handlers: `csvHandler.ts`, `pdfHandler.ts`, `excelHandler.ts`
- 7 importers: `SmartCSVImporter.ts`, `SmartPDFImporter.ts`, `SmartExcelImporter.ts`, `SmartJSONImporter.ts`, `SmartMarkdownImporter.ts`, `SmartYAMLImporter.ts`, `SmartDOCXImporter.ts`
**Result:** Comprehensive, consistent progress tracking across ALL import formats!

View file

@ -1,734 +0,0 @@
# Import Progress Implementation Guide
**For Developers: How to Add Progress Tracking to ANY File Handler**
> This guide shows the **standard pattern** for implementing rich progress tracking in Brainy import handlers. Follow this template for **all 7 supported formats** (CSV, PDF, Excel, JSON, Markdown, YAML, DOCX) or any future file format.
---
## 📊 Supported Formats & Consistent Progress Reporting
> **⚠️ IMPORTANT FOR DEVELOPERS:** The public API (`ImportProgress`) is 100% standardized across all formats. You can build ONE progress handler that works for CSV, PDF, Excel, JSON, Markdown, YAML, and DOCX with **zero format-specific code**. See [Standard Import Progress API](./standard-import-progress.md) for details.
**ALL 7 formats now have consistent, standardized progress reporting** for building reliable import tools:
| Format | Category | Progress Points | File Location | Status |
|--------|----------|-----------------|---------------|--------|
| **CSV** | Tabular | Parsing → Row extraction → Type conversion → Complete | `handlers/csvHandler.ts` + `SmartCSVImporter.ts` | ✅ Complete |
| **PDF** | Document | Loading → Page-by-page → Item extraction → Complete | `handlers/pdfHandler.ts` + `SmartPDFImporter.ts` | ✅ Complete |
| **Excel** | Tabular | Loading → Sheet-by-sheet → Row extraction → Type conversion → Complete | `handlers/excelHandler.ts` + `SmartExcelImporter.ts` | ✅ Complete |
| **JSON** | Structured | Parsing → Node traversal (every 10 nodes) → Complete | `SmartJSONImporter.ts` | ✅ Complete |
| **Markdown** | Document | Parsing → Section-by-section → Complete | `SmartMarkdownImporter.ts` | ✅ Complete |
| **YAML** | Structured | Parsing → Node traversal (every 10 nodes) → Complete | `SmartYAMLImporter.ts` | ✅ Complete |
| **DOCX** | Document | Parsing → Paragraph-by-paragraph (every 10) → Complete | `SmartDOCXImporter.ts` | ✅ Complete |
### The Standard Public API
**Developers calling `brain.import()` see ONE standardized interface** regardless of format:
```typescript
// THE PUBLIC API - Same for ALL 7 formats!
brain.import(buffer, {
onProgress: (progress: ImportProgress) => {
// These fields work for CSV, PDF, Excel, JSON, Markdown, YAML, DOCX
progress.stage // 'detecting' | 'extracting' | 'storing-vfs' | 'storing-graph' | 'complete'
progress.message // Human-readable status (varies by format, always readable)
progress.processed // Items processed (optional)
progress.total // Total items (optional)
progress.entities // Entities extracted (optional)
progress.relationships // Relationships inferred (optional)
progress.throughput // Items/sec (optional, during extraction)
progress.eta // Time remaining in ms (optional)
}
})
```
**Internal Implementation** (for developers adding new format handlers):
The table below shows how formats implement progress *internally*. Normal developers don't need to know this - they just use the standard `ImportProgress` interface above!
```typescript
// Internal: Binary formats use handler hooks (you added these!)
interface FormatHandlerProgressHooks {
onBytesProcessed?: (bytes: number) => void
onCurrentItem?: (message: string) => void
onDataExtracted?: (count: number, total?: number) => void
}
// Internal: Text formats use importer callbacks
interface ImporterProgressCallback {
onProgress?: (stats: { processed, total, entities, relationships }) => void
}
// Both are converted to ImportProgress by ImportCoordinator!
```
### Developer Benefits
**Consistent API** - Same pattern across all 7 formats
**Throttled Updates** - Progress reported every 10-1000 items (no spam)
**Contextual Messages** - "Processing page 5 of 23", "Reading sheet: Sales (2/5)"
**Real-time Estimates** - Users see progress during long imports
**Build Monitoring Tools** - Reliable progress data for UIs, dashboards, CLI tools
---
## 🎯 Overview
Brainy supports comprehensive, multi-dimensional progress tracking for imports:
- **Bytes processed** (always available, most deterministic)
- **Entities extracted** (AI extraction phase)
- **Stage-specific metrics** (parsing: MB/s, extraction: entities/s)
- **Time estimates** (remaining time, total time)
- **Context information** ("Processing page 5 of 23")
All handlers follow a simple, consistent pattern using **progress hooks**.
---
## 📋 The Progress Hooks Pattern
### 1. Progress Hooks Interface
```typescript
export interface FormatHandlerProgressHooks {
/**
* Report bytes processed
* Call this as you read/parse the file
*/
onBytesProcessed?: (bytes: number) => void
/**
* Set current processing context
* Examples: "Processing page 5", "Reading sheet: Q2 Sales"
*/
onCurrentItem?: (item: string) => void
/**
* Report structured data extraction progress
* Examples: "Extracted 100 rows", "Parsed 50 paragraphs"
*/
onDataExtracted?: (count: number, total?: number) => void
}
```
### 2. Handler Options (Automatic)
Progress hooks are automatically passed to your handler via `FormatHandlerOptions`:
```typescript
export interface FormatHandlerOptions {
// ... existing options ...
/**
* Progress hooks
* Handlers call these to report progress during processing
*/
progressHooks?: FormatHandlerProgressHooks
/**
* Total file size in bytes
* Used for progress percentage calculation
*/
totalBytes?: number
}
```
**You don't need to modify FormatHandlerOptions** - it's already done!
### 3. Standard Implementation Pattern
Every handler follows these 5 steps:
```typescript
async process(data: Buffer | string, options: FormatHandlerOptions): Promise<ProcessedData> {
const progressHooks = options.progressHooks // Step 1: Get hooks
// Step 2: Report initial progress
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem('Starting import...')
}
// Step 3: Report bytes as you process
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(0) // Start
}
// ... do parsing ...
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(buffer.length) // Complete
}
// Step 4: Report data extraction
if (progressHooks?.onDataExtracted) {
progressHooks.onDataExtracted(data.length, data.length)
}
// Step 5: Report completion
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`Complete: ${data.length} items processed`)
}
return { format, data, metadata }
}
```
---
## 📚 Complete Example: CSV Handler
Here's the **ACTUAL implementation** from CSV handler showing all the key progress points:
```typescript
async process(data: Buffer | string, options: FormatHandlerOptions): Promise<ProcessedData> {
const startTime = Date.now()
const progressHooks = options.progressHooks // ✅ Step 1
// Convert to buffer if string
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf-8')
const totalBytes = buffer.length
// ✅ Step 2: Report start
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(0)
}
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem('Detecting CSV encoding and delimiter...')
}
// Detect encoding
const detectedEncoding = options.encoding || this.detectEncodingSafe(buffer)
const text = buffer.toString(detectedEncoding as BufferEncoding)
// Detect delimiter
const delimiter = options.csvDelimiter || this.detectDelimiter(text)
// ✅ Progress update: Parsing phase
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`Parsing CSV rows (delimiter: "${delimiter}")...`)
}
// Parse CSV
const records = parse(text, { /* options */ })
// ✅ Step 3: Report bytes processed (entire file parsed)
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(totalBytes)
}
const data = Array.isArray(records) ? records : [records]
// ✅ Step 4: Report data extraction
if (progressHooks?.onDataExtracted) {
progressHooks.onDataExtracted(data.length, data.length)
}
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`Extracted ${data.length} rows, inferring types...`)
}
// Type inference and conversion
const fields = data.length > 0 ? Object.keys(data[0]) : []
const types = this.inferFieldTypes(data)
const convertedData = data.map((row, index) => {
const converted = this.convertRow(row, types)
// ✅ Progress update every 1000 rows (avoid spam)
if (progressHooks?.onCurrentItem && index > 0 && index % 1000 === 0) {
progressHooks.onCurrentItem(`Converting types: ${index}/${data.length} rows...`)
}
return converted
})
// ✅ Step 5: Report completion
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`CSV processing complete: ${convertedData.length} rows`)
}
return {
format: this.format,
data: convertedData,
metadata: { /* ... */ }
}
}
```
### Key Progress Points in CSV Handler
| Progress Point | Hook Used | Message Example |
|----------------|-----------|-----------------|
| **Start** | `onCurrentItem` | "Detecting CSV encoding and delimiter..." |
| **Start bytes** | `onBytesProcessed(0)` | 0 bytes |
| **Parsing** | `onCurrentItem` | "Parsing CSV rows (delimiter: \",\")..." |
| **Bytes complete** | `onBytesProcessed(totalBytes)` | All bytes read |
| **Data extracted** | `onDataExtracted(count, total)` | Number of rows extracted |
| **Type conversion** | `onCurrentItem` (every 1000 rows) | "Converting types: 5000/10000 rows..." |
| **Complete** | `onCurrentItem` | "CSV processing complete: 10000 rows" |
---
## 📖 Implementation Guide by File Type
### Supported Formats
Brainy supports **7 file formats** with full progress tracking:
**Binary Formats** (use handlers):
1. **CSV** - Row-by-row parsing with type inference
2. **PDF** - Page-by-page extraction with table detection
3. **Excel** - Sheet-by-sheet processing with formula evaluation
**Text/Structured Formats** (parse inline):
4. **JSON** - Recursive traversal of nested structures
5. **Markdown** - Section-by-section with heading extraction
6. **YAML** - Hierarchical traversal with relationship inference
7. **DOCX** - Paragraph-by-paragraph with structure analysis
---
### PDF Handler (Multi-Page)
```typescript
async process(data: Buffer, options: FormatHandlerOptions): Promise<ProcessedData> {
const progressHooks = options.progressHooks
const totalBytes = data.length
// Report start
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem('Loading PDF document...')
}
const pdfDoc = await loadPDF(data)
const totalPages = pdfDoc.numPages
const extractedData: any[] = []
let bytesProcessed = 0
for (let pageNum = 1; pageNum <= totalPages; pageNum++) {
// ✅ Report current page
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`Processing page ${pageNum} of ${totalPages}`)
}
const page = await pdfDoc.getPage(pageNum)
const text = await page.getTextContent()
extractedData.push(this.processPageText(text))
// ✅ Estimate bytes processed (pages are sequential)
bytesProcessed = Math.floor((pageNum / totalPages) * totalBytes)
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(bytesProcessed)
}
// ✅ Report extraction progress
if (progressHooks?.onDataExtracted) {
progressHooks.onDataExtracted(pageNum, totalPages)
}
}
// Final progress
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(totalBytes)
}
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`PDF complete: ${totalPages} pages processed`)
}
return { format: 'pdf', data: extractedData, metadata: { /* ... */ } }
}
```
### Excel Handler (Multi-Sheet)
```typescript
async process(data: Buffer, options: FormatHandlerOptions): Promise<ProcessedData> {
const progressHooks = options.progressHooks
const totalBytes = data.length
// Load workbook
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem('Loading Excel workbook...')
}
const workbook = XLSX.read(data)
const sheetNames = options.excelSheets === 'all'
? workbook.SheetNames
: (options.excelSheets || [workbook.SheetNames[0]])
const allData: any[] = []
let bytesProcessed = 0
for (let i = 0; i < sheetNames.length; i++) {
const sheetName = sheetNames[i]
// ✅ Report current sheet
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`Reading sheet: ${sheetName} (${i + 1}/${sheetNames.length})`)
}
const sheet = workbook.Sheets[sheetName]
const sheetData = XLSX.utils.sheet_to_json(sheet)
allData.push(...sheetData)
// ✅ Estimate bytes processed (sheets processed sequentially)
bytesProcessed = Math.floor(((i + 1) / sheetNames.length) * totalBytes)
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(bytesProcessed)
}
// ✅ Report data extraction
if (progressHooks?.onDataExtracted) {
progressHooks.onDataExtracted(allData.length, undefined) // Total unknown until done
}
}
// Final progress
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(totalBytes)
}
if (progressHooks?.onCurrentItem) {
progressHooks.onCurrentItem(`Excel complete: ${sheetNames.length} sheets, ${allData.length} rows`)
}
return { format: 'xlsx', data: allData, metadata: { /* ... */ } }
}
```
### JSON Importer (Recursive Traversal)
```typescript
async extract(data: any, options: SmartJSONOptions = {}): Promise<SmartJSONResult> {
// ✅ Report parsing start
options.onProgress?.({ processed: 0, entities: 0, relationships: 0 })
// Parse JSON if string
let jsonData = typeof data === 'string' ? JSON.parse(data) : data
// ✅ Report parsing complete
options.onProgress?.({ processed: 0, entities: 0, relationships: 0 })
// Traverse and extract (reports progress every 10 nodes)
const entities: ExtractedJSONEntity[] = []
const relationships: ExtractedJSONRelationship[] = []
let nodesProcessed = 0
await this.traverseJSON(
jsonData,
entities,
relationships,
() => {
nodesProcessed++
if (nodesProcessed % 10 === 0) {
options.onProgress?.({
processed: nodesProcessed,
entities: entities.length,
relationships: relationships.length
})
}
}
)
// ✅ Report completion
options.onProgress?.({
processed: nodesProcessed,
entities: entities.length,
relationships: relationships.length
})
return { nodesProcessed, entitiesExtracted: entities.length, ... }
}
```
### Markdown Importer (Section-Based)
```typescript
async extract(markdown: string, options: SmartMarkdownOptions = {}): Promise<SmartMarkdownResult> {
// ✅ Report parsing start
options.onProgress?.({ processed: 0, total: 0, entities: 0, relationships: 0 })
// Parse markdown into sections
const parsedSections = this.parseMarkdown(markdown, options)
// ✅ Report parsing complete
options.onProgress?.({ processed: 0, total: parsedSections.length, entities: 0, relationships: 0 })
// Process each section (reports progress after each section)
const sections: MarkdownSection[] = []
for (let i = 0; i < parsedSections.length; i++) {
const section = await this.processSection(parsedSections[i], options)
sections.push(section)
options.onProgress?.({
processed: i + 1,
total: parsedSections.length,
entities: sections.reduce((sum, s) => sum + s.entities.length, 0),
relationships: sections.reduce((sum, s) => sum + s.relationships.length, 0)
})
}
// ✅ Report completion
options.onProgress?.({
processed: sections.length,
total: sections.length,
entities: sections.reduce((sum, s) => sum + s.entities.length, 0),
relationships: sections.reduce((sum, s) => sum + s.relationships.length, 0)
})
return { sectionsProcessed: sections.length, ... }
}
```
### YAML Importer (Hierarchical)
```typescript
async extract(yamlContent: string | Buffer, options: SmartYAMLOptions = {}): Promise<SmartYAMLResult> {
// ✅ Report parsing start
options.onProgress?.({ processed: 0, entities: 0, relationships: 0 })
// Parse YAML
const yamlString = typeof yamlContent === 'string' ? yamlContent : yamlContent.toString('utf-8')
const data = yaml.load(yamlString)
// ✅ Report parsing complete
options.onProgress?.({ processed: 0, entities: 0, relationships: 0 })
// Traverse YAML structure (reports progress every 10 nodes)
// ... similar to JSON traversal ...
// ✅ Report completion (already implemented)
options.onProgress?.({
processed: nodesProcessed,
entities: entities.length,
relationships: relationships.length
})
return { nodesProcessed, entitiesExtracted: entities.length, ... }
}
```
### DOCX Importer (Paragraph-Based)
```typescript
async extract(buffer: Buffer, options: SmartDOCXOptions = {}): Promise<SmartDOCXResult> {
// ✅ Report parsing start
options.onProgress?.({ processed: 0, entities: 0, relationships: 0 })
// Extract text and HTML using Mammoth
const textResult = await mammoth.extractRawText({ buffer })
const htmlResult = await mammoth.convertToHtml({ buffer })
// ✅ Report parsing complete
options.onProgress?.({ processed: 0, entities: 0, relationships: 0 })
// Process paragraphs (reports progress every 10 paragraphs)
const paragraphs = textResult.value.split(/\n\n+/).filter(p => p.trim().length >= minLength)
for (let i = 0; i < paragraphs.length; i++) {
await this.processParagraph(paragraphs[i])
if (i % 10 === 0) {
options.onProgress?.({
processed: i + 1,
entities: entities.length,
relationships: relationships.length
})
}
}
// ✅ Report completion (already implemented)
options.onProgress?.({
processed: paragraphs.length,
entities: entities.length,
relationships: relationships.length
})
return { paragraphsProcessed: paragraphs.length, ... }
}
```
---
## 🎯 Best Practices
### 1. Always Check if Hooks Exist
Progress hooks are **optional**. Always check before calling:
```typescript
// ✅ Good - safe
if (progressHooks?.onBytesProcessed) {
progressHooks.onBytesProcessed(bytes)
}
// ❌ Bad - will crash if hooks undefined
progressHooks.onBytesProcessed(bytes) // TypeError!
```
### 2. Report Bytes at Start and End
```typescript
// ✅ Good - clear start and end
progressHooks?.onBytesProcessed(0) // Start
// ... processing ...
progressHooks?.onBytesProcessed(totalBytes) // End
// ❌ Bad - no clear boundaries
// ... just start processing without reporting start
```
### 3. Throttle Frequent Updates
```typescript
// ✅ Good - report every 1000 items
for (let i = 0; i < items.length; i++) {
processItem(items[i])
if (i > 0 && i % 1000 === 0) {
progressHooks?.onCurrentItem(`Processing: ${i}/${items.length}`)
}
}
// ❌ Bad - report EVERY item (spam!)
for (let i = 0; i < items.length; i++) {
processItem(items[i])
progressHooks?.onCurrentItem(`Processing: ${i}/${items.length}`) // 1M callbacks!
}
```
### 4. Provide Contextual Messages
```typescript
// ✅ Good - specific and helpful
progressHooks?.onCurrentItem('Parsing CSV rows (delimiter: ",")')
progressHooks?.onCurrentItem('Processing page 5 of 23')
progressHooks?.onCurrentItem('Reading sheet: Q2 Sales Data')
// ❌ Bad - vague
progressHooks?.onCurrentItem('Processing...')
progressHooks?.onCurrentItem('Working...')
```
### 5. Report Data Extraction with Totals (if known)
```typescript
// ✅ Good - total known
progressHooks?.onDataExtracted(100, 1000) // 100 of 1000 rows
// ✅ Also good - total unknown (streaming)
progressHooks?.onDataExtracted(100, undefined) // 100 rows so far
// ✅ Also good - complete
progressHooks?.onDataExtracted(1000, 1000) // All 1000 rows
```
---
## 🔧 Testing Your Handler
### Manual Test
```typescript
import { CSVHandler } from './csvHandler.js'
import * as fs from 'fs'
const handler = new CSVHandler()
const data = fs.readFileSync('./test.csv')
const result = await handler.process(data, {
filename: 'test.csv',
progressHooks: {
onBytesProcessed: (bytes) => {
console.log(`Bytes: ${bytes}`)
},
onCurrentItem: (item) => {
console.log(`Status: ${item}`)
},
onDataExtracted: (count, total) => {
console.log(`Extracted: ${count}${total ? `/${total}` : ''}`)
}
}
})
console.log(`Complete: ${result.data.length} rows`)
```
### Expected Output
```
Status: Detecting CSV encoding and delimiter...
Bytes: 0
Status: Parsing CSV rows (delimiter: ",")...
Bytes: 52438
Extracted: 1000/1000
Status: Extracted 1000 rows, inferring types...
Status: CSV processing complete: 1000 rows
Complete: 1000 rows
```
---
## 📊 Progress Flow Diagram
```
User Imports File
ImportManager
Creates ProgressTracker
Calls Handler.process() with progressHooks
Handler Reports Progress:
├─ onBytesProcessed(0) → ProgressTracker → overall_progress calculated
├─ onCurrentItem("Parsing...") → ProgressTracker → stage_message updated
├─ onBytesProcessed(bytes) → ProgressTracker → bytes_per_second calculated
├─ onDataExtracted(count) → ProgressTracker → entities_extracted updated
└─ onCurrentItem("Complete") → ProgressTracker → final progress
ProgressTracker emits to callback (throttled 100ms)
User sees:
"Overall: 45% | PARSING | 12.5 MB/s | Parsing CSV rows..."
```
---
## ✅ Checklist for New Handlers
When implementing a new file format handler:
- [ ] Get `progressHooks` from `options`
- [ ] Get `totalBytes` (if available)
- [ ] Report `onBytesProcessed(0)` at start
- [ ] Report `onCurrentItem()` for key stages
- [ ] Report `onBytesProcessed()` as you process
- [ ] Report `onDataExtracted()` when you extract data
- [ ] Throttle frequent updates (every 1000 items max)
- [ ] Report `onBytesProcessed(totalBytes)` at end
- [ ] Report final `onCurrentItem()` with summary
- [ ] Test with progress callback to verify output
---
## 🎓 Summary
**The Pattern (5 Steps)**:
1. Get `progressHooks` from options
2. Report start (`onBytesProcessed(0)`, `onCurrentItem("Starting...")`)
3. Report progress as you process (`onBytesProcessed(bytes)`, `onCurrentItem("Page 5...")`)
4. Report data extraction (`onDataExtracted(count, total)`)
5. Report completion (`onBytesProcessed(totalBytes)`, `onCurrentItem("Complete")`)
**Always Check**: `progressHooks?.method()`
**Throttle**: Report every N items, not every single item
**Context**: Provide specific, helpful messages
**Testing**: Use manual test with console.log callbacks
---
**This pattern makes it trivial to add progress tracking to ANY file format. Copy this template and adapt for your handler!**

View file

@ -1,461 +0,0 @@
# 📥 Import Quick Reference
> **Quick guide to importing data into Brainy**
---
## Basic Import
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Import from file path
await brain.import('/path/to/data.xlsx')
// Import from buffer
const buffer = fs.readFileSync('data.csv')
await brain.import(buffer)
// Import from object
const jsonData = { items: [...] }
await brain.import(jsonData)
```
---
## Supported Formats
| Format | Extensions | Auto-Detect |
|--------|------------|-------------|
| **Excel** | `.xlsx`, `.xls` | ✅ Yes |
| **CSV** | `.csv` | ✅ Yes |
| **JSON** | `.json` | ✅ Yes |
| **Markdown** | `.md` | ✅ Yes |
| **PDF** | `.pdf` | ✅ Yes |
| **YAML** | `.yaml`, `.yml` | ✅ Yes |
| **DOCX** | `.docx` | ✅ Yes |
---
## Common Options
### Basic Options
```typescript
await brain.import(file, {
// Specify format (optional - auto-detects by default)
format: 'excel',
// VFS destination path
vfsPath: '/imports/products',
// Enable/disable features
createEntities: true, // Create graph entities (default: true)
createRelationships: true, // Create relationships (default: true)
preserveSource: true, // Keep original file (default: true)
// Progress tracking
onProgress: (progress) => {
console.log(`${progress.processed}/${progress.total}`)
}
})
```
### Neural Intelligence
```typescript
await brain.import(file, {
// Entity type classification
enableNeuralExtraction: true, // Auto-classify entity types (default: true)
// Relationship type inference
enableRelationshipInference: true, // Auto-infer relationship types (default: true)
// Concept extraction
enableConceptExtraction: true, // Extract key concepts (default: true)
// Confidence threshold
confidenceThreshold: 0.6 // Min confidence for extraction (default: 0.6)
})
```
### Deduplication
```typescript
await brain.import(file, {
enableDeduplication: true, // Check for duplicates (default: true)
deduplicationThreshold: 0.85 // Similarity threshold (default: 0.85)
})
```
Deduplication merges entities judged duplicates — the non-primary records are
**deleted**. Set `enableDeduplication: false` to disable it entirely: the flag
gates both the inline merge during import and the background pass that runs
about 5 minutes after the last import.
```typescript
await brain.import(file, {
enableDeduplication: false // No merging, inline or background
})
```
### Import Tracking
Track and organize imports by project:
```typescript
await brain.import(file, {
projectId: 'worldbuilding', // Group related imports
importId: 'import-001', // Custom ID (auto-generated if not provided)
customMetadata: { // Additional metadata
campaign: 'fall-2024',
author: 'gamemaster'
}
})
// Query all entities in a project
const entities = await brain.find({
where: { projectId: 'worldbuilding' }
})
// Query entities from specific import
const importedEntities = await brain.find({
where: { importIds: { $includes: 'import-001' } }
})
// Exclude a project from search
const results = await brain.find({
query: 'dragon',
where: { projectId: { $ne: 'archived-project' } }
})
```
**All created items (entities, relationships, VFS files) are automatically tagged with:**
- `importIds: string[]` - Import operation IDs
- `projectId: string` - Project identifier
- `importedAt: number` - Timestamp
- `importFormat: string` - Format type ('excel', 'csv', etc.)
- `importSource: string` - Source filename/URL
### VFS Organization
```typescript
await brain.import(file, {
vfsPath: '/imports/catalog',
// Grouping strategy
groupBy: 'type', // Group by entity type (default)
// OR
groupBy: 'sheet', // Group by Excel sheet name
// OR
groupBy: 'flat', // All entities in root directory
// OR
groupBy: 'custom',
customGrouping: (entity) => {
return `/by-category/${entity.category}`
}
})
```
### Always-On Streaming
All imports use streaming with adaptive flush intervals. Query data as it's imported:
```typescript
await brain.import(file, {
onProgress: async (progress) => {
// Query data during import
if (progress.queryable) {
const products = await brain.find({ type: 'product', limit: 10000 })
console.log(`${products.length} products imported so far`)
}
}
})
```
**Progressive intervals** (automatic):
- 0-999 entities: Flush every 100 (frequent early updates)
- 1K-9.9K: Flush every 1000 (balanced)
- 10K+: Flush every 5000 (minimal overhead)
- Adjusts dynamically as import grows
---
## Complete Example
```typescript
import { Brainy } from '@soulcraft/brainy'
import * as fs from 'fs'
async function importCatalog() {
const brain = new Brainy({
storage: {
type: 'gcs',
bucket: 'my-bucket',
prefix: 'brainy/'
}
})
await brain.init()
const buffer = fs.readFileSync('catalog.xlsx')
const result = await brain.import(buffer, {
format: 'excel',
vfsPath: '/imports/product-catalog',
groupBy: 'type',
// Neural intelligence
enableNeuralExtraction: true,
enableRelationshipInference: true,
confidenceThreshold: 0.7,
// Deduplication
enableDeduplication: true,
deduplicationThreshold: 0.85,
// Progress tracking (streaming always enabled)
onProgress: async (progress) => {
console.log(`Stage: ${progress.stage}`)
console.log(`Progress: ${progress.processed}/${progress.total}`)
// Query live data (available after each flush)
if (progress.queryable) {
const products = await brain.find({ type: 'product', limit: 100000 })
const people = await brain.find({ type: 'person', limit: 100000 })
const all = await brain.find({ limit: 100000 })
const stats = {
products: products.length,
people: people.length,
total: all.length
}
console.log('Current counts:', stats)
}
}
})
console.log('Import complete!')
console.log(`Entities: ${result.entities.length}`)
console.log(`Relationships: ${result.relationships.length}`)
console.log(`VFS path: ${result.vfs.rootPath}`)
console.log(`Processing time: ${result.stats.processingTime}ms`)
return result
}
importCatalog()
```
---
## Import Result
```typescript
interface ImportResult {
importId: string
format: string
formatConfidence: number
vfs: {
rootPath: string
directories: string[]
files: Array<{
path: string
entityId?: string
type: 'entity' | 'metadata' | 'source' | 'relationships'
}>
}
entities: Array<{
id: string
name: string
type: NounType
vfsPath?: string
}>
relationships: Array<{
id: string
from: string
to: string
type: VerbType
}>
stats: {
entitiesExtracted: number
relationshipsInferred: number
vfsFilesCreated: number
graphNodesCreated: number
graphEdgesCreated: number
entitiesMerged: number // From deduplication
entitiesNew: number // Newly created
processingTime: number // In milliseconds
}
}
```
---
## Progress Callback
```typescript
interface ImportProgress {
stage: 'detecting' | 'extracting' | 'storing-vfs' | 'storing-graph' | 'complete'
message: string
processed?: number // Current item number
total?: number // Total items
entities?: number // Entities extracted
relationships?: number // Relationships inferred
throughput?: number // Rows per second
eta?: number // Estimated time remaining (ms)
queryable?: boolean // Data queryable now (streaming mode)
}
```
---
## Tips & Best Practices
### Performance
```typescript
// Streaming is always on with adaptive intervals (zero config)
// - Small imports (<1K): Flush every 100 entities
// - Medium (1K-10K): Flush every 1000 entities
// - Large (>10K): Flush every 5000 entities
// Disable features you don't need for faster imports
await brain.import(file, {
enableNeuralExtraction: false, // 10x faster
enableRelationshipInference: false, // 5x faster
enableConceptExtraction: false // 2x faster
})
```
### Error Handling
```typescript
try {
const result = await brain.import(file, {
vfsPath: '/imports/data',
onProgress: (p) => console.log(p.message)
})
console.log('Success:', result.stats)
} catch (error) {
console.error('Import failed:', error.message)
// Check partial results in VFS
const files = await brain.vfs().readdir('/imports')
console.log('Partial files:', files)
}
```
### Querying Imported Data
```typescript
// After import completes
const result = await brain.import(file)
// Find entities by type
const products = await brain.find({ type: 'Product' })
// Get entity relationships
const relations = await brain.related(products[0].id)
// Search VFS
const vfsFiles = await brain.vfs().find(result.vfs.rootPath + '/**/*.json')
// Read entity from VFS
const entity = await brain.vfs().readJSON(vfsFiles[0].path)
```
---
## Excel-Specific Tips
### Column Detection
Brainy auto-detects columns with flexible matching:
| Your Column | Matches Pattern |
|-------------|-----------------|
| `Name` | term\|name\|title\|concept |
| `Description` | definition\|description\|desc\|details |
| `Type` | type\|category\|kind\|class |
| `Related` | related\|see also\|links\|references |
### Multiple Sheets
All sheets are processed automatically:
```typescript
// catalog.xlsx with 3 sheets: Products, People, Places
const result = await brain.import('catalog.xlsx', {
groupBy: 'sheet' // Creates /Products/, /People/, /Places/
})
```
---
## CSV-Specific Tips
### Headers
First row is treated as headers. Ensure headers exist:
```csv
Term,Definition,Type
Product A,Description A,Product
Product B,Description B,Product
```
### Large CSVs
For large CSV files (>100K rows), streaming is automatic:
```typescript
await brain.import(largeCsv, {
// Automatically flushes every 5000 entities (adaptive)
enableNeuralExtraction: false // Faster for large imports
})
```
---
## JSON-Specific Tips
### Supported Structures
```javascript
// Array of objects
[
{ name: "Item 1", type: "Product" },
{ name: "Item 2", type: "Product" }
]
// Nested objects (creates hierarchical relationships)
{
"company": {
"name": "Acme Corp",
"products": [
{ "name": "Widget", "price": 9.99 }
]
}
}
```
---
## Further Reading
- [Import Flow Guide](./import-flow.md) - Deep dive into how imports work
- [Streaming Imports](./streaming-imports.md) - Progressive imports for large files
- [VFS Guide](./vfs-guide.md) - Working with the virtual file system
- [Type Classification](./type-classification.md) - How entity types are inferred
- [Relationship Inference](./relationship-inference.md) - How relationships are classified
---
**Questions?** Check the [FAQ](../faq.md) or [open an issue](https://github.com/soulcraft/brainy/issues)!

View file

@ -1,213 +0,0 @@
---
title: Inspecting a Live Brainy
slug: guides/inspection
public: true
category: guides
template: guide
order: 30
description: Operator recipes for diagnosing a running Brainy data directory — counts, queries, query plans, health checks, and snapshots — without stopping the live writer.
next:
- concepts/multi-process
---
# Inspecting a Live Brainy
When something is wrong in production, you need to see what's actually in the
store. This guide covers the safe ways to query a running Brainy directory.
## The cardinal rule
**Never open a second writer on the same directory.** Filesystem storage will throw, and any other write path will corrupt the live writer's state. Use `Brainy.openReadOnly()` or the `brainy inspect` CLI instead.
## The CLI is the fastest path
```bash
# What's in this brain?
brainy inspect stats /data/brain
# Find specific entities
brainy inspect find /data/brain --type Event --where '{"status":"paid"}' --limit 20
# Single entity by ID
brainy inspect get /data/brain 0b7a9...
# Why is this query returning empty?
brainy inspect explain /data/brain --where '{"entityType":"booking"}'
# Quick invariants
brainy inspect health /data/brain
# Random sample (no query needed)
brainy inspect sample /data/brain --type Event --n 20
# Tail new writes as they happen
brainy inspect watch /data/brain --type Event
# Save a snapshot
brainy inspect backup /data/brain /backups/brain-$(date +%Y%m%d).tar
```
Every subcommand internally:
1. Asks the live writer to flush via the cross-process RPC (skip with `--no-fresh`).
2. Opens the data directory via `Brainy.openReadOnly()`.
3. Runs the query.
4. Closes cleanly.
Results are JSON by default. Add `--pretty` for indented output.
## When a query returns surprising results
If `find()` returns `0` for a query you expect to match: run `inspect
explain` first. It shows which index path will serve each `where` clause:
```bash
$ brainy inspect explain /data/brain --where '{"entityType":"booking","status":"paid"}'
{
"query": { "where": { "entityType": "booking", "status": "paid" } },
"fieldPlan": [
{ "field": "entityType", "path": "none", "notes": "No index entries for field..." },
{ "field": "status", "path": "column-store", "notes": "O(log n) binary search..." }
],
"warnings": [
"Field \"entityType\" has no index entries. find() will return [] silently."
]
}
```
The `"path": "none"` is the smoking gun. It means the field has no column
store manifest and no sparse chunked index — so `find()` will return `[]`
regardless of what's actually on disk. Likely causes:
- The writer registered the field in memory but hasn't flushed. Run
`brain.requestFlush()` from the writer side, or use `brainy inspect
--fresh` (default).
- The field name has a typo or wrong casing.
- The field is genuinely absent from every entity.
## Health checks
`inspect health` runs a fixed battery of cheap invariant checks:
```bash
$ brainy inspect health /data/brain
{
"overall": "warn",
"checks": [
{ "name": "index-parity", "status": "pass", "message": "Vector (1851) and metadata (1851) agree." },
{ "name": "field-registry", "status": "pass", "message": "23 fields registered for 1851 entities." },
{ "name": "seeded-records", "status": "warn", "message": "15 entities tagged _seeded:true." },
{ "name": "writer-heartbeat", "status": "pass", "message": "Writer healthy (PID 1774431...)." }
]
}
```
Each check returns `pass`, `warn`, or `fail`. The exit code is `2` when any
check fails — useful for piping into monitoring or CI.
## Programmatic inspection
```typescript
import { Brainy } from '@soulcraft/brainy'
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', path: '/data/brain' }
})
// Force the writer to flush before reading
await reader.requestFlush({ timeoutMs: 5000 })
// What's in there?
const stats = await reader.stats()
console.log(`${stats.entityCount} entities`, stats.entitiesByType)
// Why is this query empty?
const plan = await reader.explain({ where: { entityType: 'booking' } })
for (const f of plan.fieldPlan) {
console.log(`${f.field} -> ${f.path}`)
}
// Run invariants
const health = await reader.health()
console.log(health.overall)
await reader.close()
```
Every mutation method (`add`, `update`, `remove`, `relate`, `transact`,
`restore`, ...) throws on a read-only instance with a clear message.
## Backups
`brainy inspect backup` asks the writer to flush first, then tars the
directory. The snapshot reflects the writer's state at the moment of the
flush:
```bash
brainy inspect backup /data/brain /backups/brain-2026-05-15.tar
```
For periodic backups (hourly, daily), schedule this via cron or your
container scheduler. For point-in-time recovery, use the Db API's
`db.persist(path)` — a self-contained hard-link snapshot that later writes
can never alter, restorable with `brain.restore(path, { confirm: true })`.
See [Snapshots & Time Travel](./snapshots-and-time-travel.md).
## Comparing two stores
`brainy inspect diff` returns a JSON summary of counts and a sample of
entity IDs present in one but not the other. Useful when debugging
replication or migrations:
```bash
brainy inspect diff /data/brain-prod /data/brain-staging
```
Sample-based — for a full diff, dump both with `inspect dump` and compare
the JSONL.
## Auditing graph-read truth
`brain.auditGraph()` (8.6.0+) proves — or disproves — that relationship reads
return canonical truth on a given brain, without mutating anything. It walks
every stored relationship record, asks the same read path your application
uses (`related()`, VFS `readdir`) with every visibility tier included, and
classifies every discrepancy:
```typescript
const report = await brain.auditGraph()
report.coherent // true = related()/readdir can be trusted on this brain
report.missingFromReadsCount // records the read path omits — stale index
report.danglingEndpointsCount // relationships whose endpoint entity is gone
report.readOnlyCount // read-path edges with NO stored record — ghosts
report.visibilityHiddenCount // internal/system edges hidden by design (not a fault)
```
Counts are always exact; the example lists (`missingFromReads`,
`danglingEndpoints`, `readOnlyVerbIds`) are capped at `maxExamples`
(default 100) and `truncatedExamples` says so when they are.
Run it after any engine upgrade, restore, or migration. If it reports
discrepancies, run `brain.repairIndex()` and audit again — a `coherent`
report after the repair is the verified statement that the heal worked.
Cost: one relationship-record walk plus one indexed read per distinct
source entity — safe on a live brain.
## Repairing a corrupted store
If invariants fail and you suspect index corruption, `inspect repair`
opens the store in writer mode and rebuilds all indexes from raw storage.
**Stop the live writer first** — `repair` will throw if another writer
holds the lock. Add `--force` only if you have personally verified the
existing lock is stale.
```bash
brainy inspect repair /data/brain
```
## Multi-process safety summary
See [concepts/multi-process](../concepts/multi-process.md) for the lock
semantics, heartbeat behavior, and what's not yet enforced on cloud
backends.

View file

@ -1,89 +0,0 @@
---
title: Installation
slug: getting-started/installation
public: true
category: getting-started
template: guide
order: 1
description: Install Brainy with npm, bun, yarn, or pnpm. Works in Node.js 22+ and Bun 1.0+ (server-only since 8.0). TypeScript included.
next:
- getting-started/quick-start
- guides/storage-adapters
---
# Installation
## Requirements
- **Node.js 22+** or **Bun 1.0+**
- TypeScript is optional — Brainy ships with full type definitions
## Install
```bash
npm install @soulcraft/brainy
```
Or with your preferred package manager:
```bash
bun add @soulcraft/brainy
yarn add @soulcraft/brainy
pnpm add @soulcraft/brainy
```
## Verify
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
console.log('Brainy ready.')
```
## Native Acceleration (Optional)
For production workloads, add Cor for Rust-accelerated SIMD distance calculations and native embeddings:
```bash
npm install @soulcraft/cor
```
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({ plugins: ['@soulcraft/cor'] })
await brain.init() // native providers registered during init
```
Cor registers native (Rust/SIMD) vector, metadata, and graph engines behind the same Brainy `find()` API — no code changes, an optional dependency for production-scale workloads.
## Server-only since 8.0
Brainy 8.0 runs on Node.js 22+ and Bun 1.0+. Browser support (OPFS storage,
Web Workers, in-browser WASM embeddings) was removed in 8.0 — the 7.x line
remains available on npm if you need it.
## TypeScript
Brainy ships with full TypeScript types. No `@types/` package needed:
```typescript
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
const id = await brain.add({
data: 'Hello, Brainy',
type: NounType.Concept,
metadata: { created: Date.now() }
})
```
## Next Steps
- [Quick Start](/docs/getting-started/quick-start) — build your first knowledge graph in 60 seconds
- [Storage Adapters](/docs/guides/storage-adapters) — choose the right storage for your deployment

View file

@ -1,491 +0,0 @@
# Migrating from Brainy v3.x to v4.x
**Brainy v4.0.0** introduces breaking changes to the import API for improved clarity, better defaults, and more powerful features.
This guide will help you migrate your code quickly and painlessly.
---
## 🎯 Quick Migration Checklist
If you just want to fix your code fast, here's what to do:
- [ ] Replace `extractRelationships` with `enableRelationshipInference`
- [ ] Remove `autoDetect` (auto-detection is now always enabled)
- [ ] Replace `createFileStructure: true` with `vfsPath: '/your/path'`
- [ ] Remove `excelSheets` (all sheets are now processed automatically)
- [ ] Remove `pdfExtractTables` (table extraction is now automatic)
- [ ] Add `enableNeuralExtraction: true` to enable AI entity extraction
- [ ] Add `preserveSource: true` if you want to keep the original file
---
## 📋 Option Name Changes
### Complete Mapping Table
| v3.x Option | v4.x Option | Action Required |
|-------------|-------------|-----------------|
| `extractRelationships` | `enableRelationshipInference` | **Rename option** |
| `autoDetect` | *(removed)* | **Delete option** (always enabled) |
| `createFileStructure` | `vfsPath` | **Replace** with VFS directory path |
| `excelSheets` | *(removed)* | **Delete option** (all sheets processed) |
| `pdfExtractTables` | *(removed)* | **Delete option** (always enabled) |
| - | `enableNeuralExtraction` | **Add option** (new in v4.x) |
| - | `enableConceptExtraction` | **Add option** (new in v4.x) |
| - | `preserveSource` | **Add option** (new in v4.x) |
---
## 🔄 Migration Examples
### Example 1: Basic Excel Import
**Before (v3.x):**
```typescript
const result = await brain.import('./glossary.xlsx', {
extractRelationships: true,
createFileStructure: true,
groupBy: 'type'
})
```
**After (v4.x):**
```typescript
const result = await brain.import('./glossary.xlsx', {
enableRelationshipInference: true, // ✅ Renamed
vfsPath: '/imports/glossary', // ✅ Replaced createFileStructure
groupBy: 'type' // ✅ No change
})
```
---
### Example 2: Full-Featured Import
**Before (v3.x):**
```typescript
const result = await brain.import('./data.xlsx', {
extractRelationships: true,
autoDetect: true,
createFileStructure: true,
groupBy: 'type',
enableDeduplication: true
})
```
**After (v4.x):**
```typescript
const result = await brain.import('./data.xlsx', {
// AI features
enableNeuralExtraction: true, // ✅ NEW - Extract entity names
enableRelationshipInference: true, // ✅ Renamed from extractRelationships
enableConceptExtraction: true, // ✅ NEW - Extract entity types
// VFS features
vfsPath: '/imports/data', // ✅ Replaced createFileStructure
groupBy: 'type', // ✅ No change
preserveSource: true, // ✅ NEW - Save original file
// Performance
enableDeduplication: true // ✅ No change
})
```
---
### Example 3: Simple Import (Defaults)
**Before (v3.x):**
```typescript
const result = await brain.import('./data.csv', {
autoDetect: true,
extractRelationships: true
})
```
**After (v4.x):**
```typescript
// Auto-detection is always enabled now
// Just enable the features you want
const result = await brain.import('./data.csv', {
enableRelationshipInference: true
})
// Or use all defaults (AI features enabled)
const result = await brain.import('./data.csv')
```
---
### Example 4: PDF Import
**Before (v3.x):**
```typescript
const result = await brain.import('./document.pdf', {
pdfExtractTables: true,
extractRelationships: true,
createFileStructure: true
})
```
**After (v4.x):**
```typescript
const result = await brain.import('./document.pdf', {
// pdfExtractTables removed - always enabled
enableRelationshipInference: true,
vfsPath: '/imports/documents'
})
```
---
## 💡 Why These Changes?
### Clearer Option Names
**v3.x naming was ambiguous:**
- `extractRelationships` → Could mean "create relationships" or "infer relationships"
- `createFileStructure` → Doesn't explain what structure or where
**v4.x naming is explicit:**
- `enableRelationshipInference` → Clearly means "use AI to infer semantic relationships"
- `vfsPath` → Explicitly sets the virtual filesystem directory path
- `enableNeuralExtraction` → Clearly indicates AI-powered entity extraction
### Separation of Concerns
**v4.x separates import features into clear categories:**
1. **Neural/AI Features:**
- `enableNeuralExtraction` - Extract entity names and metadata
- `enableRelationshipInference` - Infer semantic relationships
- `enableConceptExtraction` - Extract entity types and concepts
2. **VFS Features:**
- `vfsPath` - Virtual filesystem directory
- `groupBy` - Grouping strategy
- `preserveSource` - Keep original file
3. **Performance Features:**
- `enableDeduplication` - Merge similar entities
- `confidenceThreshold` - AI confidence threshold
- `onProgress` - Progress callbacks
### Better Defaults
**v3.x required explicit enabling:**
```typescript
// Had to enable everything manually
await brain.import(file, {
autoDetect: true,
extractRelationships: true,
createFileStructure: true
})
```
**v4.x has smart defaults:**
```typescript
// Auto-detection and AI features enabled by default
await brain.import(file)
// Or customize specific features
await brain.import(file, {
vfsPath: '/my/data',
confidenceThreshold: 0.8
})
```
---
## 🆕 New Features in v4.x
### Neural Entity Extraction
Extract entity names, types, and metadata using AI:
```typescript
const result = await brain.import('./glossary.xlsx', {
enableNeuralExtraction: true, // Extract entity names from "Term" column
enableConceptExtraction: true, // Detect entity types (Place, Person, etc.)
confidenceThreshold: 0.7 // Minimum AI confidence (0-1)
})
// Result includes rich entity metadata
result.entities.forEach(entity => {
console.log(`${entity.name} (${entity.type})`)
console.log(`Confidence: ${entity.confidence}`)
})
```
### VFS Integration
Imported data is organized in a virtual filesystem:
```typescript
const result = await brain.import('./data.xlsx', {
vfsPath: '/projects/myproject/data',
groupBy: 'type', // Group by entity type
preserveSource: true // Save original .xlsx file
})
// Access via VFS
const vfs = brain.vfs()
const files = await vfs.readdir('/projects/myproject/data')
// ['Places/', 'Characters/', 'Concepts/', '_source.xlsx', '_metadata.json']
// Read entity file
const content = await vfs.readFile('/projects/myproject/data/Places/Talifar.json')
```
### Semantic Relationship Inference
AI infers relationship types from context:
```typescript
const result = await brain.import('./glossary.xlsx', {
enableRelationshipInference: true
})
// Instead of generic "contains" relationships,
// you get semantic verbs like:
// - "capital_of"
// - "located_in"
// - "guards"
// - "part_of"
// - "related_to"
const relations = await brain.related({ limit: 100 })
const types = new Set(relations.map(r => r.label))
console.log(types)
// Set { 'capital_of', 'guards', 'located_in', 'related_to' }
```
---
## 🔍 What Breaks & How to Fix It
### Error: "Invalid import options: 'extractRelationships'"
**Cause:** Using v3.x option name
**Fix:**
```typescript
// Before
await brain.import(file, { extractRelationships: true })
// After
await brain.import(file, { enableRelationshipInference: true })
```
---
### Error: "Invalid import options: 'autoDetect'"
**Cause:** Using v3.x option that's been removed
**Fix:**
```typescript
// Before
await brain.import(file, { autoDetect: true })
// After - just remove it (auto-detection always enabled)
await brain.import(file)
```
---
### Error: "Invalid import options: 'createFileStructure'"
**Cause:** Using v3.x option name
**Fix:**
```typescript
// Before
await brain.import(file, { createFileStructure: true })
// After - specify VFS path explicitly
await brain.import(file, { vfsPath: '/imports/mydata' })
```
---
### Issue: Import succeeds but entities have generic names like "Entity_144"
**Cause:** Neural extraction is disabled
**Fix:**
```typescript
// Ensure AI features are enabled
await brain.import(file, {
enableNeuralExtraction: true, // ✅ Extract entity names
enableRelationshipInference: true, // ✅ Infer relationships
enableConceptExtraction: true // ✅ Extract types
})
```
---
### Issue: All relationships are type "contains"
**Cause:** Relationship inference is disabled
**Fix:**
```typescript
// Enable relationship inference
await brain.import(file, {
enableRelationshipInference: true // ✅ Use AI to detect semantic relationships
})
```
---
### Issue: VFS directory doesn't exist in filesystem
**This is NORMAL!** VFS is virtual - it uses Brainy entities, not physical files.
**How to access VFS:**
```typescript
// DON'T do this:
// ls brainy-data/vfs/ ❌ Won't work
// DO this instead:
const vfs = brain.vfs()
await vfs.init()
const files = await vfs.readdir('/imports') // ✅ Correct
```
---
## 📦 TypeScript Users
### Compile-Time Errors
If you're using TypeScript, you'll get compile-time errors when using deprecated options:
```typescript
// TypeScript will show error:
// "Type 'true' is not assignable to type 'never'"
await brain.import(file, {
extractRelationships: true // ❌ Type error
})
// Fix: Use correct option name
await brain.import(file, {
enableRelationshipInference: true // ✅ Type correct
})
```
### IDE Autocomplete
Your IDE will show deprecation warnings and suggest the correct option names:
```typescript
await brain.import(file, {
extract... // IDE suggests: enableNeuralExtraction, enableRelationshipInference
})
```
---
## 🎓 Best Practices for v4.x
### 1. Enable All AI Features by Default
```typescript
// Good: Enable all intelligent features
await brain.import('./data.xlsx', {
enableNeuralExtraction: true,
enableRelationshipInference: true,
enableConceptExtraction: true,
vfsPath: '/imports/data'
})
```
### 2. Use VFS for Organization
```typescript
// Good: Organize by project
await brain.import('./project-A.xlsx', {
vfsPath: '/projects/project-a/data'
})
await brain.import('./project-B.csv', {
vfsPath: '/projects/project-b/data'
})
```
### 3. Preserve Source Files
```typescript
// Good: Keep original files for reference
await brain.import('./important-data.xlsx', {
preserveSource: true, // Saves original .xlsx in VFS
vfsPath: '/archives/2025'
})
```
### 4. Tune Confidence Threshold
```typescript
// For high-quality data: Lower threshold
await brain.import('./curated-glossary.xlsx', {
confidenceThreshold: 0.5 // Extract more entities
})
// For noisy data: Higher threshold
await brain.import('./scraped-data.csv', {
confidenceThreshold: 0.8 // Only high-confidence entities
})
```
### 5. Disable Deduplication for Large Imports
```typescript
// For small imports: Keep deduplication
await brain.import('./small-data.xlsx', {
enableDeduplication: true
})
// For large imports (>1000 rows): Disable for performance
await brain.import('./huge-database.csv', {
enableDeduplication: false // Much faster
})
```
---
## 🚀 Migration Automation (Future)
We're working on an automated migration tool:
```bash
# Coming soon
npx @soulcraft/brainy-migrate
# Will scan your code and automatically update:
# - Option names
# - TypeScript types
# - Import patterns
```
---
## 📚 Additional Resources
- **API Documentation:** [https://brainy.dev/docs/api/import](https://brainy.dev/docs/api/import)
- **Examples:** [examples/import-excel/](../../examples/import-excel/)
- **Changelog:** [CHANGELOG.md](../../CHANGELOG.md)
- **Support:** [GitHub Issues](https://github.com/soulcraft/brainy/issues)
---
## 💬 Need Help?
If you're stuck migrating:
1. Check the error message - it includes migration hints
2. Review the examples in this guide
3. Open an issue on GitHub with your use case
4. Join our Discord community for real-time help
---
**Happy migrating! 🎉**

View file

@ -1,386 +0,0 @@
# Migration Guide: v3.36.0
## Overview
Brainy v3.36.0 introduces **enterprise-grade adaptive memory sizing** and **sync fast path optimizations** for production-scale deployments. These are **internal optimizations** that improve performance and resource efficiency with **zero breaking changes** to your existing code.
**TL;DR**: Your code continues to work exactly as before. These improvements are automatic and require no migration.
---
## What's New in v3.36.0
### 1. Adaptive Memory Sizing
**Automatic resource-aware cache allocation from 2GB to 128GB+ systems.**
**Before v3.36.0:**
```typescript
// Fixed cache sizes, manual tuning required
const brain = new Brainy()
// Cache size: ~512MB (hardcoded default)
```
**After v3.36.0:**
```typescript
// Automatic adaptive sizing - no code changes needed!
const brain = new Brainy()
// Cache adapts:
// - 2GB system → 400MB cache (after 150MB model reservation)
// - 16GB system → 4GB cache
// - 128GB system → 32GB+ cache (logarithmic scaling)
```
**Features:**
- ✅ Container-aware (Docker/K8s cgroups v1/v2 detection)
- ✅ Environment-smart (dev 25%, container 40%, production 50%)
- ✅ Model memory accounting (150MB Q8, 250MB FP32)
- ✅ Memory pressure monitoring with actionable warnings
### 2. Sync Fast Path Optimization
**Zero async overhead when vectors are in memory.**
**Before v3.36.0:**
```typescript
// Every distance calculation was async (overhead even when cached)
const results = await brain.search("query") // Always async
```
**After v3.36.0:**
```typescript
// Same API, but internally optimized
const results = await brain.search("query")
// - Sync path: Vector in UnifiedCache → zero overhead
// - Async path: Vector needs loading → minimal overhead
// Your code: Unchanged! ✅
```
**Performance Impact:**
- 🚀 Hot paths (cached vectors): **30-50% faster** (no async overhead)
- 🔥 Cold paths (storage loading): Same as before (async when needed)
- 📊 Production workloads: **15-25% overall speedup** (assuming 70%+ cache hit rate)
### 3. Production Monitoring
**New diagnostics for capacity planning and performance tuning.**
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// NEW: Comprehensive cache performance statistics
const stats = brain.hnsw.getCacheStats()
console.log(`
Caching Strategy: ${stats.cachingStrategy}
Cache Hit Rate: ${stats.unifiedCache.hitRatePercent}%
Memory: ${stats.hnswCache.estimatedMemoryMB}MB HNSW cache
Recommendations: ${stats.recommendations.join(', ')}
`)
// Example output:
// Caching Strategy: on-demand
// Cache Hit Rate: 89.2%
// Memory: 245.3MB HNSW cache
// Recommendations: All metrics healthy - no action needed
```
---
## Breaking Changes
### ✅ Zero Breaking Changes
**All changes are internal optimizations.** Your existing code continues to work without modification.
**Public API:**
- ✅ `brain.add()` - Unchanged
- ✅ `brain.search()` - Unchanged
- ✅ `brain.find()` - Unchanged
- ✅ `brain.relate()` - Unchanged
- ✅ All storage adapters - Unchanged
**The only visible change:** Better performance and automatic memory sizing.
---
## Upgrading
### Step 1: Update Package
```bash
npm install @soulcraft/brainy@latest
```
### Step 2: Restart Your Application
```bash
# Development
npm run dev
# Production
npm run start
```
**That's it!** No code changes required.
---
## Verification
### Check Adaptive Sizing is Working
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Check UnifiedCache allocation
const cacheStats = brain.hnsw.unifiedCache.getStats()
console.log(`Cache Size: ${cacheStats.maxSize / 1024 / 1024} MB`)
console.log(`Environment: ${cacheStats.memory.environment}`)
console.log(`Allocation Ratio: ${(cacheStats.memory.allocationRatio * 100).toFixed(0)}%`)
// Example output (2GB system):
// Cache Size: 400 MB
// Environment: development
// Allocation Ratio: 25%
// Example output (16GB production):
// Cache Size: 4000 MB
// Environment: production
// Allocation Ratio: 50%
```
### Monitor Performance Improvements
```typescript
// Before: Track baseline performance
console.time('search')
const results = await brain.search("query", { limit: 10 })
console.timeEnd('search')
// Before v3.36.0: ~15ms (with async overhead)
// After v3.36.0: ~10ms (sync fast path when cached)
```
### Check Cache Performance Stats
```typescript
const stats = brain.hnsw.getCacheStats()
console.log('Cache Performance Stats:')
console.log(` Strategy: ${stats.cachingStrategy}`)
console.log(` Entity Count: ${stats.autoDetection.entityCount.toLocaleString()}`)
console.log(` Cache Hit Rate: ${stats.unifiedCache.hitRatePercent}%`)
console.log(` HNSW Memory: ${stats.hnswCache.estimatedMemoryMB}MB`)
console.log(` Fairness: ${stats.fairness.fairnessViolation ? 'VIOLATION' : 'OK'}`)
console.log(` Recommendations:`)
stats.recommendations.forEach(r => console.log(` - ${r}`))
```
---
## Configuration (Optional)
### Manual Cache Sizing
If you need to override adaptive sizing:
```typescript
const brain = new Brainy({
cache: {
maxSize: 1024 * 1024 * 1024 // Force 1GB cache
}
})
```
**Note:** Adaptive sizing is recommended. Manual sizing should only be used for specific deployment constraints.
### Disable Sync Fast Path (Not Recommended)
For debugging or compatibility testing:
```typescript
// Internal feature flag (not exposed in public API)
// Contact support if you need to disable sync fast path
```
**Why not recommended:** Sync fast path has zero breaking changes and significant performance benefits.
---
## Rollback
If you need to rollback to v3.35.0:
```bash
npm install @soulcraft/brainy@3.35.0
```
**Note:** We don't anticipate any issues, but rollback is straightforward if needed.
---
## Performance Tuning
### Scenario 1: Low Memory Environment (2GB-4GB)
```typescript
// Adaptive sizing automatically allocates 25% in development
const brain = new Brainy()
await brain.init()
// Monitor memory pressure
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(memoryInfo.currentPressure)
// { pressure: 'moderate', warnings: [...] }
```
**Recommendation:**
- Let adaptive sizing handle allocation
- Monitor `getCacheStats()` for cache hit rate
- If hit rate < 50%, consider increasing available RAM
### Scenario 2: High Memory Environment (32GB-128GB+)
```typescript
// Adaptive sizing uses logarithmic scaling to prevent over-allocation
const brain = new Brainy()
await brain.init()
// Check allocation
const stats = brain.hnsw.unifiedCache.getStats()
console.log(`Allocated: ${stats.maxSize / 1024 / 1024 / 1024} GB`)
// 64GB system → ~32GB cache (50% production allocation)
// 128GB system → ~40GB cache (logarithmic scaling prevents waste)
```
**Recommendation:**
- Adaptive sizing prevents over-allocation on large systems
- Monitor fairness metrics to ensure HNSW doesn't dominate cache
- Use `getCacheStats()` to verify cache efficiency
### Scenario 3: Container Deployments (Docker/K8s)
```typescript
// Adaptive sizing detects cgroup limits automatically
const brain = new Brainy()
await brain.init()
// Verify container detection
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(`Container: ${memoryInfo.memoryInfo.isContainer}`)
console.log(`Source: ${memoryInfo.memoryInfo.source}`) // 'cgroup-v2' or 'cgroup-v1'
console.log(`Available: ${memoryInfo.memoryInfo.available / 1024 / 1024} MB`)
```
**Recommendation:**
- Set explicit memory limits in Docker/K8s (don't use unlimited)
- Adaptive sizing allocates 40% in container environments (vs 50% bare metal)
- Monitor warnings for container memory limit detection
---
## Troubleshooting
### Cache Size Too Small
**Symptom:** On-demand caching active but cache hit rate < 50%
**Solution:**
```typescript
const stats = brain.hnsw.getCacheStats()
console.log(stats.recommendations)
// Recommendation: "Low cache hit rate (42.3%). Consider increasing UnifiedCache size for better performance"
```
**Action:** Increase available system memory or reduce entity count.
### Memory Pressure Warnings
**Symptom:** Log warnings about memory utilization > 85%
**Solution:**
```typescript
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(memoryInfo.currentPressure)
// { pressure: 'high', warnings: ['HIGH: Memory utilization at 87.2%...'] }
```
**Action:** Either:
1. Increase available system memory
2. Reduce cache size manually
3. Reduce dataset size (system automatically uses on-demand caching for large datasets)
### Fairness Violations
**Symptom:** HNSW using >90% of cache with <10% access
**Solution:**
```typescript
const stats = brain.hnsw.getCacheStats()
if (stats.fairness.fairnessViolation) {
console.log(`HNSW cache: ${stats.fairness.hnswAccessPercent}% access`)
console.log(`HNSW size: ${stats.hnswCache.estimatedMemoryMB}MB`)
}
```
**Action:** This indicates cache eviction policies need tuning. Contact support or file an issue.
---
## FAQ
### Q: Do I need to change my code?
**A:** No. All changes are internal optimizations. Your existing code works unchanged.
### Q: Will my application use more memory?
**A:** No. Adaptive sizing respects available system resources. On small systems (2GB), it allocates *less* than before (400MB vs 512MB) because it now accounts for model memory (150MB Q8).
### Q: What if I'm in a container with memory limits?
**A:** Adaptive sizing automatically detects Docker/K8s cgroup limits (v1 and v2) and allocates appropriately (40% vs 50% on bare metal).
### Q: Can I disable adaptive sizing?
**A:** Yes, set manual cache size in config. But adaptive sizing is recommended for production - it handles edge cases and automatically scales.
### Q: Will sync fast path break anything?
**A:** No. Public API remains async. Internally, it's sync when possible, async when needed. Your `await` statements work identically.
### Q: How do I know what caching strategy is being used?
**A:** Check `brain.hnsw.getCacheStats().cachingStrategy` (returns 'preloaded' or 'on-demand') or watch initialization logs.
### Q: What's the performance impact?
**A:** **15-25% overall speedup** in production workloads (assuming 70%+ cache hit rate). Hot paths (cached vectors) see **30-50% improvement**.
---
## Next Steps
1. ✅ **Upgrade:** `npm install @soulcraft/brainy@latest`
2. 📊 **Monitor:** Use `getCacheStats()` to verify performance improvements
3. 🎯 **Tune:** Adjust based on recommendations (if needed)
4. 📖 **Read:** [Operations Guide](../operations/capacity-planning.md) for capacity planning
---
## Support
**Issues or questions?**
- 📖 [Operations Guide](../operations/capacity-planning.md)
- 🐛 [GitHub Issues](https://github.com/soulcraftlabs/brainy/issues)
- 💬 [Discord Community](https://discord.gg/brainy)
---
**Built with ❤️ for production scale** | v3.36.0 | [Full Changelog](../../CHANGELOG.md)

View file

@ -1,238 +1,358 @@
# Model Loading Guide
# 🤖 Model Loading Guide
Brainy uses AI embedding models to understand and process your data. With the Candle WASM engine, the model is **embedded at compile time** - no downloads, no configuration, no external dependencies.
Brainy uses AI embedding models to understand and process your data. This guide explains how model loading works and how to handle different scenarios.
## Zero Configuration (Default)
## 🚀 Zero Configuration (Default)
**For all developers, no configuration is needed:**
**For most developers, no configuration is needed:**
```typescript
const brain = new Brainy()
await brain.init() // Model is already embedded - nothing to download!
const brain = new BrainyData()
await brain.init() // Models load automatically
```
**What happens automatically:**
1. Candle WASM module loads (~90MB, includes model weights)
2. Model initializes in ~200ms
3. Ready to use immediately
1. Checks for local models in `./models/`
2. Downloads All-MiniLM-L6-v2 if needed (384 dimensions)
3. Configures optimal settings for your environment
4. Ready to use immediately
**No downloads. No CDN. No configuration. Just works.**
## 📦 Model Loading Cascade
## How It Works
The all-MiniLM-L6-v2 model is embedded in the WASM binary using Rust's `include_bytes!` macro:
Brainy tries multiple sources in this order:
```
candle_embeddings_bg.wasm (~90MB)
├── Candle ML Runtime (~3MB)
├── Model Weights (safetensors format, ~87MB)
└── Tokenizer (HuggingFace tokenizers, ~450KB)
1. LOCAL CACHE (./models/)
↓ (if not found)
2. CDN DOWNLOAD (fast mirrors)
↓ (if fails)
3. GITHUB RELEASES (github.com/xenova/transformers.js)
↓ (if fails)
4. HUGGINGFACE HUB (huggingface.co)
↓ (if fails)
5. FALLBACK STRATEGIES (different model variants)
```
This single WASM file contains everything needed for sentence embeddings.
## Environments
### Bun (Recommended)
```bash
# Bun as a runtime — supported and recommended
bun add @soulcraft/brainy
bun run server.ts
```
Brainy is pure WebAssembly with no native binaries, so the module graph stays
bundler-friendly. Single-binary `bun build --compile` is **not a supported
target** at present: Bun 1.3.10 has a `--compile` codegen regression
(`__promiseAll is not defined`) triggered by top-level `await` in the bundled
graph. Run Brainy under the Bun runtime (above) instead.
### Node.js
```typescript
// Standard Node.js
node dist/server.js
// Runs identically to Bun
```
## 🌍 Environment-Specific Behavior
### Browser
```typescript
// Model loads via WASM (single file, no additional assets)
const brain = new Brainy()
await brain.init()
// Automatically configured for browsers
const brain = new BrainyData() // Works in React, Vue, vanilla JS
await brain.init() // Downloads models via CDN
```
### Node.js Development
```typescript
// Zero config - downloads to ./models/
const brain = new BrainyData()
await brain.init() // Downloads once, cached forever
```
### Production Server
```typescript
// Preload models during build/deployment
const brain = new BrainyData()
await brain.init() // Uses cached local models
```
### Docker/Kubernetes
```dockerfile
FROM oven/bun:1.1
# Dockerfile - preload models
RUN npm run download-models
ENV BRAINY_ALLOW_REMOTE_MODELS=false
```
## 🛠️ Manual Model Management
### Pre-download Models
```bash
# Download models during build/deployment
npm run download-models
# Custom location
BRAINY_MODELS_PATH=./my-models npm run download-models
```
### Verify Models
```bash
# Check if models exist
ls ./models/Xenova/all-MiniLM-L6-v2/
# Should see:
# - config.json
# - tokenizer.json
# - onnx/model.onnx
```
### Custom Model Path
```typescript
const brain = new BrainyData({
embedding: {
cacheDir: './custom-models'
}
})
```
## 🔒 Offline & Air-Gapped Environments
### Complete Offline Setup
```bash
# 1. Download models on connected machine
npm run download-models
# 2. Copy models to offline machine
cp -r ./models /path/to/offline/project/
# 3. Force local-only mode
export BRAINY_ALLOW_REMOTE_MODELS=false
```
### Container/Server Deployment
```dockerfile
FROM node:18
WORKDIR /app
COPY package*.json ./
RUN bun install
RUN npm ci
# Download models during build
RUN npm run download-models
# Force local-only in production
ENV BRAINY_ALLOW_REMOTE_MODELS=false
COPY . .
EXPOSE 3000
CMD ["bun", "run", "server.ts"]
# That's it! No model download step needed.
# Model is embedded in the npm package.
CMD ["npm", "start"]
```
## Model Information
## ⚙️ Environment Variables
### all-MiniLM-L6-v2 (Embedded)
- **Dimensions**: 384 (fixed)
- **Format**: Safetensors (FP32)
- **Size**: ~87MB (embedded in WASM)
- **Total WASM Size**: ~90MB
- **Language**: English-optimized, works with all languages
- **Inference**: ~2-10ms per embedding
- **Initialization**: ~200ms
### BRAINY_ALLOW_REMOTE_MODELS
Controls whether remote model downloads are allowed:
### Memory Usage
- **Loaded WASM**: ~90MB
- **Inference peak**: ~140MB total
- **Steady state**: ~100MB
```bash
# Allow remote downloads (default in most environments)
export BRAINY_ALLOW_REMOTE_MODELS=true
## Comparing to Previous Architecture
# Force local-only (recommended for production)
export BRAINY_ALLOW_REMOTE_MODELS=false
```
| Feature | Before (ONNX) | Now (Candle WASM) |
|---------|--------------|-------------------|
| Model downloads | Required on first use | None - embedded |
| External dependencies | onnxruntime-web | None |
| Model files | model.onnx, tokenizer.json | Embedded in WASM |
| Offline support | Required setup | Works by default |
| Bun compile | Broken | Works |
| Configuration | Environment variables | None needed |
### BRAINY_MODELS_PATH
Custom model storage location:
## Troubleshooting
```bash
# Custom model path
export BRAINY_MODELS_PATH=/opt/brainy/models
### "Failed to initialize Candle Embedding Engine"
# Relative path
export BRAINY_MODELS_PATH=./my-custom-models
```
**Cause**: WASM loading issue.
## 🚨 Troubleshooting
### "Failed to load embedding model" Error
**Cause**: Models not found locally and remote download blocked/failed.
**Solutions**:
```bash
# Rebuild the WASM
npm run build:candle
# Option 1: Allow remote downloads
export BRAINY_ALLOW_REMOTE_MODELS=true
# Verify WASM exists
ls dist/embeddings/wasm/pkg/candle_embeddings_bg.wasm
# Should be ~90MB
# Option 2: Download models manually
npm run download-models
# Option 3: Check internet connectivity
ping huggingface.co
# Option 4: Use custom model path
export BRAINY_MODELS_PATH=/path/to/existing/models
```
### Out of Memory
### Models Download Very Slowly
**Cause**: Container/environment has less than 256MB RAM.
**Cause**: Network issues or regional restrictions.
**Solutions**:
```bash
# Pre-download during build/CI
npm run download-models
# Use faster mirrors (automatic in newer versions)
# No action needed - Brainy tries multiple CDNs
```
### Container Out of Memory During Model Load
**Cause**: Limited container memory during model initialization.
**Solutions**:
```dockerfile
# Increase memory limit (recommended: 512MB+)
docker run -m 512m my-app
# Increase memory limit
docker run -m 2g my-app
# Use quantized models (default)
ENV BRAINY_MODEL_DTYPE=q8
# Pre-load models at build time (recommended)
RUN npm run download-models
```
### Slow Initialization (>500ms)
### Permission Denied Creating Model Cache
**Cause**: Cold start, large WASM parsing.
**Cause**: Write permissions for model cache directory.
**Solutions**:
```typescript
// Initialize once at startup, not per-request
await brain.init() // Do this once
// Then reuse for all requests
app.get('/api', async (req, res) => {
const results = await brain.find(req.query)
res.json(results)
})
```
## Migration from Previous Versions
### From v6.x (ONNX)
No changes needed for most users:
```typescript
// Same API - just upgrade
const brain = new Brainy()
await brain.init()
```
**What's removed:**
- `BRAINY_ALLOW_REMOTE_MODELS` - no downloads
- `BRAINY_MODELS_PATH` - no external model files
- `npm run download-models` - no longer needed
**What's new:**
- Faster initialization
- Bundler-friendly (pure WASM, no native binaries)
- No network requirements
### From Custom Embedding Functions
If you provided a custom embedding function, it still works:
```typescript
const brain = new Brainy({
embeddingFunction: myCustomEmbedder // Still supported
})
```
## Advanced: Building Custom WASM
For contributors who want to modify the embedding engine:
```bash
# Navigate to Candle WASM source
cd src/embeddings/candle-wasm
# Make directory writable
chmod 755 ./models
# Build with wasm-pack
wasm-pack build --target web --release
# Use custom writable path
export BRAINY_MODELS_PATH=/tmp/brainy-models
# Copy to pkg folder
cp pkg/* ../wasm/pkg/
# Build TypeScript
npm run build
# Or use memory-only storage
const brain = new BrainyData({
storage: { forceMemoryStorage: true }
})
```
## Best Practices
## 🎯 Best Practices
### Development
```typescript
// Just works - no setup
const brain = new Brainy()
// ✅ Zero config - just works
const brain = new BrainyData()
await brain.init()
```
### Production
```typescript
// Initialize once at startup
const brain = new Brainy()
await brain.init()
```dockerfile
# ✅ Pre-download models
RUN npm run download-models
// Singleton pattern recommended
export { brain }
# ✅ Force local-only
ENV BRAINY_ALLOW_REMOTE_MODELS=false
# ✅ Verify models exist
RUN test -f ./models/Xenova/all-MiniLM-L6-v2/onnx/model.onnx
```
### Deployment
```bash
# Option 1: Bun runtime
bun run server.ts
### CI/CD Pipeline
```yaml
# .github/workflows/build.yml
- name: Download AI Models
run: npm run download-models
# Option 2: Docker
docker build -t my-app .
docker run -p 3000:3000 my-app
- name: Verify Models
run: |
test -f ./models/Xenova/all-MiniLM-L6-v2/onnx/model.onnx
echo "✅ Models verified"
- name: Test Offline Mode
env:
BRAINY_ALLOW_REMOTE_MODELS: false
run: npm test
```
### Lambda/Serverless
```typescript
// ✅ Models in deployment package
const brain = new BrainyData({
embedding: {
localFilesOnly: true, // No downloads in lambda
cacheDir: './models' // Bundled with deployment
}
})
```
## 📊 Model Information
### All-MiniLM-L6-v2 (Default)
- **Dimensions**: 384 (fixed)
- **Size**: ~80MB compressed, ~330MB uncompressed
- **Language**: English (optimized)
- **Speed**: Very fast inference
- **Quality**: High quality for most use cases
### Model Files Structure
```
models/
└── Xenova/
└── all-MiniLM-L6-v2/
├── config.json # Model configuration
├── tokenizer.json # Text tokenizer
├── tokenizer_config.json
└── onnx/
├── model.onnx # Main model file
└── model_quantized.onnx # Optimized version
```
## 🔄 Migration from Other Embedding Solutions
### From OpenAI Embeddings
```typescript
// Before: OpenAI API calls
const response = await openai.embeddings.create({
model: "text-embedding-ada-002",
input: "Your text"
})
// After: Local Brainy embeddings
const brain = new BrainyData()
await brain.init() // One-time setup
const id = await brain.add("Your text") // Embedded automatically
```
### From Sentence Transformers
```python
# Before: Python sentence-transformers
from sentence_transformers import SentenceTransformer
model = SentenceTransformer('all-MiniLM-L6-v2')
# After: JavaScript Brainy (same model!)
const brain = new BrainyData() // Uses same all-MiniLM-L6-v2
await brain.init()
```
## 🚀 Advanced Configuration
### Custom Embedding Options
```typescript
const brain = new BrainyData({
embedding: {
model: 'Xenova/all-MiniLM-L6-v2', // Default
dtype: 'q8', // Quantized for speed
device: 'cpu', // CPU inference
localFilesOnly: false, // Allow downloads
verbose: true // Debug logging
}
})
```
### Multiple Model Support (Advanced)
```typescript
// Use custom embedding function
import { createEmbeddingFunction } from 'brainy'
const customEmbedder = createEmbeddingFunction({
model: 'Xenova/all-MiniLM-L12-v2', // Larger model
dtype: 'fp32' // Higher precision
})
const brain = new BrainyData({
embeddingFunction: customEmbedder
})
```
---
## Additional Resources
## 📚 Additional Resources
- [Production Service Architecture](../PRODUCTION_SERVICE_ARCHITECTURE.md)
- [Zero Configuration Guide](../architecture/zero-config.md)
- [Zero Configuration Guide](./zero-config.md)
- [Enterprise Deployment](./enterprise-deployment.md)
- [Troubleshooting Guide](../troubleshooting.md)
- [API Reference](../api/README.md)
**Need help?** [Open an issue](https://github.com/soulcraftlabs/brainy/issues)
**Need help?** Check our [troubleshooting guide](../troubleshooting.md) or [open an issue](https://github.com/your-repo/brainy/issues).

View file

@ -21,9 +21,9 @@ The current NLP implementation supports:
## Basic Usage
```typescript
import { Brainy } from 'brainy'
import { BrainyData } from 'brainy'
const brain = new Brainy()
const brain = new BrainyData()
await brain.init()
// Simply ask in natural language
@ -281,3 +281,4 @@ While powerful, the NLP system has some limitations:
- [Triple Intelligence Architecture](../architecture/triple-intelligence.md)
- [API Reference](../api/README.md)
- [Getting Started Guide](./getting-started.md)

View file

@ -1,930 +0,0 @@
# Next.js Integration Guide
Complete guide to integrating Brainy with Next.js applications, covering App Router, Pages Router, API routes, and deployment strategies.
## 🚀 Quick Start
### Installation
```bash
npx create-next-app@latest my-brainy-app
cd my-brainy-app
npm install @soulcraft/brainy
```
### Basic Setup
```jsx
// app/components/BrainyProvider.jsx
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { Brainy } from '@soulcraft/brainy'
const BrainyContext = createContext()
export function BrainyProvider({ children }) {
const [brain, setBrain] = useState(null)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
const initBrain = async () => {
const newBrain = new Brainy({
storage: { type: 'opfs' } // Browser storage for client-side
})
await newBrain.init()
setBrain(newBrain)
setIsReady(true)
}
initBrain()
}, [])
return (
<BrainyContext.Provider value={{ brain, isReady }}>
{children}
</BrainyContext.Provider>
)
}
export const useBrainy = () => {
const context = useContext(BrainyContext)
if (!context) {
throw new Error('useBrainy must be used within BrainyProvider')
}
return context
}
```
## 📱 App Router (Next.js 13+)
### Root Layout Setup
```jsx
// app/layout.jsx
import { BrainyProvider } from './components/BrainyProvider'
import './globals.css'
export const metadata = {
title: 'My Brainy App',
description: 'AI-powered search with Brainy'
}
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<BrainyProvider>
{children}
</BrainyProvider>
</body>
</html>
)
}
```
### Search Component
```jsx
// app/components/Search.jsx
'use client'
import { useState, useCallback } from 'react'
import { useBrainy } from './BrainyProvider'
export function Search() {
const { brain, isReady } = useBrainy()
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
const handleSearch = useCallback(async (searchQuery) => {
if (!isReady || !searchQuery.trim()) {
setResults([])
return
}
setLoading(true)
try {
const searchResults = await brain.find(searchQuery)
setResults(searchResults)
} catch (error) {
console.error('Search error:', error)
setResults([])
} finally {
setLoading(false)
}
}, [brain, isReady])
if (!isReady) {
return (
<div className="flex items-center justify-center p-4">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
<span className="ml-2">Initializing AI...</span>
</div>
)
}
return (
<div className="max-w-4xl mx-auto p-6">
<div className="mb-6">
<input
type="text"
value={query}
onChange={(e) => {
setQuery(e.target.value)
handleSearch(e.target.value)
}}
placeholder="Search with AI..."
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
</div>
{loading && (
<div className="text-center py-4">
<span className="text-gray-600">Searching...</span>
</div>
)}
<div className="space-y-4">
{results.map((result, index) => (
<div key={result.id || index} className="bg-white p-4 rounded-lg shadow border">
<h3 className="font-semibold text-lg mb-2">{result.data}</h3>
<div className="flex justify-between items-center text-sm text-gray-600">
<span>Score: {(result.score * 100).toFixed(1)}%</span>
{result.metadata && (
<span>Type: {result.metadata.type || 'Unknown'}</span>
)}
</div>
</div>
))}
</div>
{query && !loading && results.length === 0 && (
<div className="text-center py-8 text-gray-500">
No results found for "{query}"
</div>
)}
</div>
)
}
```
### Main Page
```jsx
// app/page.jsx
import { Search } from './components/Search'
export default function HomePage() {
return (
<main className="min-h-screen bg-gray-50">
<div className="container mx-auto py-8">
<h1 className="text-3xl font-bold text-center mb-8">
AI-Powered Search with Brainy
</h1>
<Search />
</div>
</main>
)
}
```
## 🗂️ Pages Router
### _app.jsx Setup
```jsx
// pages/_app.jsx
import { BrainyProvider } from '../components/BrainyProvider'
import '../styles/globals.css'
export default function App({ Component, pageProps }) {
return (
<BrainyProvider>
<Component {...pageProps} />
</BrainyProvider>
)
}
```
### Search Page
```jsx
// pages/search.jsx
import { useState } from 'react'
import { useBrainy } from '../components/BrainyProvider'
export default function SearchPage() {
const { brain, isReady } = useBrainy()
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const handleSearch = async (e) => {
e.preventDefault()
if (!isReady || !query.trim()) return
const searchResults = await brain.find(query)
setResults(searchResults)
}
return (
<div className="container mx-auto p-6">
<h1 className="text-3xl font-bold mb-6">Search</h1>
<form onSubmit={handleSearch} className="mb-6">
<div className="flex gap-2">
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Search..."
className="flex-1 px-4 py-2 border rounded"
disabled={!isReady}
/>
<button
type="submit"
disabled={!isReady || !query.trim()}
className="px-6 py-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
Search
</button>
</div>
</form>
<div className="space-y-4">
{results.map((result, index) => (
<div key={index} className="p-4 border rounded">
<h3 className="font-semibold">{result.data}</h3>
<p className="text-sm text-gray-600">
Score: {(result.score * 100).toFixed(1)}%
</p>
</div>
))}
</div>
</div>
)
}
```
## 🔌 API Routes
### Search API Endpoint
```javascript
// app/api/search/route.js (App Router)
import { Brainy } from '@soulcraft/brainy'
let brain = null
async function initBrain() {
if (!brain) {
brain = new Brainy({
storage: {
type: 'filesystem',
path: process.env.BRAINY_DATA_PATH || './brainy-data'
}
})
await brain.init()
}
return brain
}
export async function POST(request) {
try {
const { query, options = {} } = await request.json()
if (!query) {
return Response.json({ error: 'Query is required' }, { status: 400 })
}
const brainInstance = await initBrain()
const results = await brainInstance.find(query, options)
return Response.json({ results, count: results.length })
} catch (error) {
console.error('Search API error:', error)
return Response.json(
{ error: 'Search failed', details: error.message },
{ status: 500 }
)
}
}
export async function GET() {
try {
const brainInstance = await initBrain()
const stats = await brainInstance.stats()
return Response.json({
status: 'ready',
stats: {
totalItems: stats.totalItems,
storageType: stats.storageType
}
})
} catch (error) {
return Response.json(
{ status: 'error', error: error.message },
{ status: 500 }
)
}
}
```
```javascript
// pages/api/search.js (Pages Router)
import { Brainy } from '@soulcraft/brainy'
let brain = null
async function initBrain() {
if (!brain) {
brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
await brain.init()
}
return brain
}
export default async function handler(req, res) {
if (req.method === 'POST') {
try {
const { query, options = {} } = req.body
if (!query) {
return res.status(400).json({ error: 'Query is required' })
}
const brainInstance = await initBrain()
const results = await brainInstance.find(query, options)
res.status(200).json({ results, count: results.length })
} catch (error) {
console.error('Search API error:', error)
res.status(500).json({ error: 'Search failed', details: error.message })
}
} else {
res.setHeader('Allow', ['POST'])
res.status(405).end(`Method ${req.method} Not Allowed`)
}
}
```
### Add Data API
```javascript
// app/api/data/route.js
import { Brainy } from '@soulcraft/brainy'
let brain = null
async function initBrain() {
if (!brain) {
brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
await brain.init()
}
return brain
}
export async function POST(request) {
try {
const { data, type, metadata } = await request.json()
if (!data || !type) {
return Response.json(
{ error: 'Data and type are required' },
{ status: 400 }
)
}
const brainInstance = await initBrain()
const id = await brainInstance.add({ data, type, metadata })
return Response.json({ id, success: true })
} catch (error) {
console.error('Add data API error:', error)
return Response.json(
{ error: 'Failed to add data', details: error.message },
{ status: 500 }
)
}
}
```
## 🔗 Server Actions (App Router)
```jsx
// app/actions/brainy.js
'use server'
import { Brainy } from '@soulcraft/brainy'
let brain = null
async function initBrain() {
if (!brain) {
brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
await brain.init()
}
return brain
}
export async function searchAction(query, options = {}) {
try {
const brainInstance = await initBrain()
const results = await brainInstance.find(query, options)
return { results, error: null }
} catch (error) {
console.error('Search action error:', error)
return { results: [], error: error.message }
}
}
export async function addDataAction(data, type, metadata) {
try {
const brainInstance = await initBrain()
const id = await brainInstance.add({ data, type, metadata })
return { id, error: null }
} catch (error) {
console.error('Add data action error:', error)
return { id: null, error: error.message }
}
}
```
## 📊 Data Management Features
### Admin Dashboard
```jsx
// app/admin/page.jsx
'use client'
import { useState, useEffect } from 'react'
import { useBrainy } from '../components/BrainyProvider'
export default function AdminPage() {
const { brain, isReady } = useBrainy()
const [stats, setStats] = useState(null)
const [newData, setNewData] = useState('')
const [newType, setNewType] = useState('concept')
useEffect(() => {
if (isReady) {
loadStats()
}
}, [isReady])
const loadStats = async () => {
try {
const brainStats = await brain.stats()
setStats(brainStats)
} catch (error) {
console.error('Failed to load stats:', error)
}
}
const handleAddData = async (e) => {
e.preventDefault()
if (!newData.trim()) return
try {
await brain.add({
data: newData,
type: newType,
metadata: { addedAt: new Date().toISOString() }
})
setNewData('')
loadStats() // Refresh stats
} catch (error) {
console.error('Failed to add data:', error)
}
}
if (!isReady) {
return <div>Loading admin panel...</div>
}
return (
<div className="container mx-auto p-6">
<h1 className="text-3xl font-bold mb-6">Admin Dashboard</h1>
{/* Stats */}
{stats && (
<div className="grid grid-cols-1 md:grid-cols-3 gap-4 mb-8">
<div className="bg-blue-100 p-4 rounded">
<h3 className="font-semibold">Total Items</h3>
<p className="text-2xl">{stats.totalItems}</p>
</div>
<div className="bg-green-100 p-4 rounded">
<h3 className="font-semibold">Storage Type</h3>
<p className="text-lg">{stats.storageType}</p>
</div>
<div className="bg-purple-100 p-4 rounded">
<h3 className="font-semibold">Memory Usage</h3>
<p className="text-lg">{stats.memoryUsage || 'N/A'}</p>
</div>
</div>
)}
{/* Add Data Form */}
<div className="bg-white p-6 rounded-lg shadow">
<h2 className="text-xl font-semibold mb-4">Add New Data</h2>
<form onSubmit={handleAddData} className="space-y-4">
<div>
<label className="block text-sm font-medium mb-1">Data</label>
<textarea
value={newData}
onChange={(e) => setNewData(e.target.value)}
placeholder="Enter data to add..."
className="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500"
rows={3}
required
/>
</div>
<div>
<label className="block text-sm font-medium mb-1">Type</label>
<select
value={newType}
onChange={(e) => setNewType(e.target.value)}
className="w-full px-3 py-2 border rounded focus:ring-2 focus:ring-blue-500"
>
<option value="concept">Concept</option>
<option value="document">Document</option>
<option value="person">Person</option>
<option value="project">Project</option>
<option value="task">Task</option>
</select>
</div>
<button
type="submit"
className="px-6 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
>
Add Data
</button>
</form>
</div>
</div>
)
}
```
## 🚀 Deployment
### Environment Variables
```bash
# .env.local
BRAINY_DATA_PATH=/app/brainy-data
NODE_ENV=production
```
### Vercel Deployment
```json
// vercel.json
{
"functions": {
"app/api/**/*.js": {
"maxDuration": 30
}
},
"env": {
"BRAINY_DATA_PATH": "/tmp/brainy-data"
}
}
```
### Docker Setup
```dockerfile
# Dockerfile
FROM node:22-alpine
WORKDIR /app
# Copy package files
COPY package*.json ./
RUN npm ci --only=production
# Copy app files
COPY . .
# Build the app
RUN npm run build
# Create data directory
RUN mkdir -p /app/brainy-data
EXPOSE 3000
CMD ["npm", "start"]
```
### next.config.js
```javascript
// next.config.js
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
serverComponentsExternalPackages: ['@soulcraft/brainy']
},
webpack: (config, { isServer }) => {
if (!isServer) {
config.resolve.fallback = {
...config.resolve.fallback,
fs: false,
path: false,
crypto: false
}
}
return config
}
}
module.exports = nextConfig
```
## ⚡ Performance Optimization
### Client-Side Optimization
```jsx
// app/hooks/useBrainCache.js
import { useState, useCallback, useMemo } from 'react'
export function useBrainCache() {
const [cache, setCache] = useState(new Map())
const getCachedResult = useCallback((query) => {
return cache.get(query)
}, [cache])
const setCachedResult = useCallback((query, result) => {
setCache(prev => {
const newCache = new Map(prev)
newCache.set(query, result)
// Keep only last 100 results
if (newCache.size > 100) {
const firstKey = newCache.keys().next().value
newCache.delete(firstKey)
}
return newCache
})
}, [])
return { getCachedResult, setCachedResult }
}
```
### Debounced Search
```jsx
// app/hooks/useDebounceSearch.js
import { useState, useEffect, useCallback } from 'react'
import { useBrainy } from '../components/BrainyProvider'
export function useDebounceSearch(delay = 300) {
const { brain, isReady } = useBrainy()
const [query, setQuery] = useState('')
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
const search = useCallback(async (searchQuery) => {
if (!isReady || !searchQuery.trim()) {
setResults([])
return
}
setLoading(true)
try {
const searchResults = await brain.find(searchQuery)
setResults(searchResults)
} catch (error) {
console.error('Search error:', error)
setResults([])
} finally {
setLoading(false)
}
}, [brain, isReady])
useEffect(() => {
const timer = setTimeout(() => {
search(query)
}, delay)
return () => clearTimeout(timer)
}, [query, delay, search])
return { query, setQuery, results, loading }
}
```
## 🔒 Security Best Practices
### Input Validation
```javascript
// app/utils/validation.js
export function validateSearchQuery(query) {
if (typeof query !== 'string') {
throw new Error('Query must be a string')
}
if (query.length > 1000) {
throw new Error('Query too long')
}
// Sanitize query
return query.trim()
}
export function validateDataInput(data, type, metadata) {
if (!data || !type) {
throw new Error('Data and type are required')
}
if (typeof data !== 'string') {
throw new Error('Data must be a string')
}
if (data.length > 10000) {
throw new Error('Data too long')
}
return { data: data.trim(), type, metadata }
}
```
### Rate Limiting
```javascript
// app/middleware/rateLimit.js
const requests = new Map()
export function rateLimit(req, limit = 100, window = 60000) {
const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress
const now = Date.now()
if (!requests.has(ip)) {
requests.set(ip, [])
}
const userRequests = requests.get(ip)
// Remove old requests
const validRequests = userRequests.filter(time => now - time < window)
if (validRequests.length >= limit) {
throw new Error('Rate limit exceeded')
}
validRequests.push(now)
requests.set(ip, validRequests)
return true
}
```
## 📚 Advanced Patterns
### Context + Reducer Pattern
```jsx
// app/contexts/BrainyContext.jsx
'use client'
import { createContext, useContext, useReducer, useEffect } from 'react'
import { Brainy } from '@soulcraft/brainy'
const BrainyContext = createContext()
const initialState = {
brain: null,
isReady: false,
error: null,
stats: null
}
function brainyReducer(state, action) {
switch (action.type) {
case 'INIT_START':
return { ...state, error: null }
case 'INIT_SUCCESS':
return { ...state, brain: action.brain, isReady: true, error: null }
case 'INIT_ERROR':
return { ...state, error: action.error, isReady: false }
case 'UPDATE_STATS':
return { ...state, stats: action.stats }
default:
return state
}
}
export function BrainyProvider({ children }) {
const [state, dispatch] = useReducer(brainyReducer, initialState)
useEffect(() => {
const initBrain = async () => {
dispatch({ type: 'INIT_START' })
try {
const brain = new Brainy()
await brain.init()
dispatch({ type: 'INIT_SUCCESS', brain })
// Load initial stats
const stats = await brain.stats()
dispatch({ type: 'UPDATE_STATS', stats })
} catch (error) {
console.error('Brain initialization failed:', error)
dispatch({ type: 'INIT_ERROR', error: error.message })
}
}
initBrain()
}, [])
return (
<BrainyContext.Provider value={{ state, dispatch }}>
{children}
</BrainyContext.Provider>
)
}
export const useBrainyContext = () => {
const context = useContext(BrainyContext)
if (!context) {
throw new Error('useBrainyContext must be used within BrainyProvider')
}
return context
}
```
## 🔍 Testing
### Unit Tests
```javascript
// __tests__/brainy.test.js
import { render, screen, waitFor } from '@testing-library/react'
import { BrainyProvider } from '../app/components/BrainyProvider'
import { Search } from '../app/components/Search'
// Mock Brainy
jest.mock('@soulcraft/brainy', () => ({
Brainy: jest.fn().mockImplementation(() => ({
init: jest.fn().mockResolvedValue(undefined),
find: jest.fn().mockResolvedValue([
{ id: '1', data: 'Test result', score: 0.9 }
])
}))
}))
describe('Search Component', () => {
it('renders search input', async () => {
render(
<BrainyProvider>
<Search />
</BrainyProvider>
)
await waitFor(() => {
expect(screen.getByPlaceholderText('Search with AI...')).toBeInTheDocument()
})
})
})
```
## 📖 Complete Example Project
Here's a complete mini-project structure:
```
my-brainy-app/
├── app/
│ ├── components/
│ │ ├── BrainyProvider.jsx
│ │ ├── Search.jsx
│ │ └── AdminPanel.jsx
│ ├── api/
│ │ ├── search/route.js
│ │ └── data/route.js
│ ├── admin/
│ │ └── page.jsx
│ ├── layout.jsx
│ └── page.jsx
├── next.config.js
├── package.json
└── README.md
```
This structure provides a complete, production-ready Next.js application with Brainy integration.
## 🎯 Next Steps
- [Vue.js Integration Guide](vue-integration.md) - Vue.js patterns
- [Production Deployment](../deployment/CLOUD_DEPLOYMENT_GUIDE.md) - Deploy to production
- [API Reference](../api/README.md) - Complete API documentation
- [Examples Repository](https://github.com/soulcraftlabs/brainy-examples) - More examples

View file

@ -1,214 +0,0 @@
---
title: Optimistic concurrency with _rev
slug: guides/optimistic-concurrency
public: true
category: guides
template: guide
order: 8
description: Use the per-entity `_rev` counter and `update({ ifRev })` to coordinate concurrent writes safely. Covers the lock pattern, idempotent inserts with `ifAbsent`, and recovery on conflict.
next:
- concepts/consistency-model
- guides/find-limits
---
# Optimistic concurrency with `_rev`
Brainy 7.31.0 adds a per-entity revision counter so multiple writers can coordinate without a global lock or external coordinator. The pattern is the same one CouchDB, PouchDB, and ETag-based HTTP caches use: read the current revision, do your work, write back with `ifRev: <whatRevYouSaw>`. If the revision moved, your write is rejected and you retry against the latest state.
## What gets added
| Surface | Behavior |
|---|---|
| `entity._rev: number` | Returned on every `get()`, `find()`, `search()`. Initialized to `1` on `add()`. Bumped by `1` on every successful `update()`. Pre-7.31.0 entities without `_rev` are surfaced as `1`. |
| `update({ id, ..., ifRev: number })` | If the persisted `_rev` does not equal `ifRev`, throws `RevisionConflictError`. Omitting `ifRev` keeps the prior (unconditional) update behavior. |
| `RevisionConflictError` | Carries `{ id, expected, actual }` for principled recovery. |
| `add({ id, ifAbsent: true })` | By-ID idempotent insert. Returns the existing `id` if one is already present; no throw, no overwrite. |
| `addMany({ items, ifAbsent: true })` | Applies `ifAbsent` to every item. Per-item `ifAbsent` overrides the batch flag. |
`_rev` is the **per-entity** counter. Its store-wide counterpart is the generation counter behind the [Db API](../concepts/consistency-model.md): `brain.transact(ops, { ifAtGeneration })` is CAS over the whole store, `update({ ifRev })` (and `ifRev` on `transact()` update operations) is CAS over one entity.
## The lock pattern
Every distributed-job scheduler eventually wants this exact loop:
```ts
import { Brainy, RevisionConflictError } from '@soulcraft/brainy'
const LOCK_ID = '...uuid for this job slot...'
// Bootstrap the lock document once (idempotent).
await brain.add({
id: LOCK_ID,
type: NounType.Document,
data: { owner: null, expiresAt: 0 },
ifAbsent: true
})
async function tryAcquireLock(workerId: string, ttlMs: number) {
const lock = await brain.get(LOCK_ID)
if (!lock) throw new Error('lock document missing')
const state = lock.data as { owner: string | null; expiresAt: number }
const now = Date.now()
// Only take the lock if it's free or expired.
if (state.owner && state.expiresAt > now) return false
try {
await brain.update({
id: LOCK_ID,
data: { owner: workerId, expiresAt: now + ttlMs },
ifRev: lock._rev
})
return true
} catch (err) {
if (err instanceof RevisionConflictError) {
// Another worker grabbed it between our read and write.
return false
}
throw err
}
}
```
No external lock service, no Redis SETNX, no Cloud Tasks. The CAS check is the lock.
## Read-modify-write with retry
The other common shape is "update a counter / config object" with bounded retries on conflict:
```ts
async function incrementCounter(id: string, by: number) {
for (let attempt = 0; attempt < 5; attempt++) {
const entity = await brain.get(id)
if (!entity) throw new Error('counter does not exist')
const current = (entity.data as { value: number }).value
try {
await brain.update({
id,
data: { value: current + by },
ifRev: entity._rev
})
return
} catch (err) {
if (err instanceof RevisionConflictError) continue // refetch + retry
throw err
}
}
throw new Error('counter update conflict after 5 attempts')
}
```
The retry bound matters — without one, two unlucky writers can ping-pong forever.
## Idempotent bootstrap with `ifAbsent`
For singletons (config rows, well-known seed entities, job-state documents) where the natural ID is deterministic:
```ts
await brain.add({
id: 'config:singleton',
type: NounType.Document,
data: { tenantQuota: 1000 },
ifAbsent: true
})
```
- First caller writes; gets back `'config:singleton'`.
- Every subsequent caller short-circuits at the pre-read; gets back the same `'config:singleton'` without touching the existing entity.
- `_rev` is **not** bumped on the no-op path (no write happened).
`ifAbsent` is only meaningful when you supply an `id`. With no `id`, Brainy generates a fresh UUID that can never collide, so the flag is silently ignored.
`addMany({ items, ifAbsent: true })` applies the flag to every item. Mixing per-item overrides with the batch flag works as you'd expect: per-item `ifAbsent: false` opts an individual row out, per-item `ifAbsent: true` opts it in.
### Why no `addIfMissing({ match, add })` for attribute-based dedup?
You may want "create if no entity with this email exists" (lookup by attribute, not by ID). That's a different operation:
```ts
// What we DID NOT ship in 7.31.0 — the attribute-based variant.
await brain.addIfMissing({ // ← not a real API
match: { type: 'Person', where: { email: 'x@y.com' } },
add: { data: '...', metadata: { email: 'x@y.com' } }
})
```
It's race-prone as a plain read-then-write: two concurrent imports both see "not found," both insert, you get duplicates. Without a unique-index primitive (which Brainy doesn't have today), close the race with whole-store CAS — read at a pinned generation, then commit only if nothing moved:
```ts
import { GenerationConflictError } from '@soulcraft/brainy'
async function addIfMissingByEmail(email: string, data: string) {
for (let attempt = 0; attempt < 5; attempt++) {
const db = brain.now()
try {
const existing = await db.find({
type: NounType.Person,
where: { email },
limit: 1
})
if (existing.length > 0) return existing[0].id
const committed = await brain.transact(
[{ op: 'add', type: NounType.Person, subtype: 'customer', data, metadata: { email } }],
{ ifAtGeneration: db.generation } // rejects if ANYTHING committed since the read
)
return committed.receipt!.ids[0]
} catch (err) {
if (err instanceof GenerationConflictError) continue // world moved — re-read + retry
throw err
} finally {
await db.release()
}
}
throw new Error('addIfMissingByEmail conflict after 5 attempts')
}
```
`ifAtGeneration` is deliberately coarse — *any* committed write invalidates it — so keep the retry bound. When you control the ID, `ifAbsent` stays the cheaper tool.
## How `_rev` relates to generations
Brainy 8.0 has exactly two write-coordination counters, at two granularities:
| Counter | Scope | What it tracks | CAS surface | Conflict error |
|---|---|---|---|---|
| **`_rev`** | One entity | Per-entity write count, bumped on every successful update | `update({ ifRev })`, `{ op: 'update', ifRev }` in `transact()` | `RevisionConflictError` |
| **Generation** | Whole store | One tick per committed `transact()` batch or single-operation write | `transact(ops, { ifAtGeneration })` | `GenerationConflictError` |
They compose: a `transact()` batch can carry per-entity `ifRev` checks *and* a whole-store `ifAtGeneration`; any failed check rejects the entire batch before anything is staged. Generations also power snapshots and time travel (`brain.now()`, `brain.asOf()`, `db.persist()`) — see the [consistency model](../concepts/consistency-model.md) and [Snapshots & Time Travel](./snapshots-and-time-travel.md).
A snapshot or historical view captures each entity *including* its `_rev` at that moment, so reading the past and writing back with `ifRev` against the live state works exactly as you'd hope: the write fails if the entity moved since the state you copied from.
## The transact envelope: batch size, budget, and bulk imports
`transact()` applies its batch atomically under one commit — which means the whole batch
shares one **apply budget**. Since 8.7.0 the budget scales with the batch:
`max(30 s, opCount × 2 s)`, or exactly what you pass as `timeoutMs`. A tripped budget rolls
the entire batch back (nothing partial survives) and throws a retryable
`TransactionTimeoutError` that names the operation it stopped at, the batch size, and the
elapsed vs budgeted time — a diagnosis, not just a failure:
```
Transaction timed out at operation 41/120 ('add') — 246012ms elapsed, budget 240000ms.
The batch rolled back atomically; retry with a higher timeoutMs or a smaller batch.
```
Practical envelope guidance for bulk work:
1. **Precompute embeddings outside the commit path.** Embedding inside `transact()` spends
the budget on model inference. Use `brain.embedBatch(texts)` and pass each vector via
the op's `vector` field — the commit then pays only storage costs, and a retried batch
never re-pays inference. (The win is *where* the inference happens, not raw embedding
throughput: on the default WASM engine, batch and sequential embedding measure
comparably, ~160 ms/text; native embedding providers may batch faster.)
2. **Chunk very large imports** into batches of a few hundred ops with one `transact()`
each. You lose whole-import atomicity but keep per-chunk atomicity, bounded memory, and
resumability — pair with `ifAbsent` upserts so a retried chunk is idempotent.
3. **Slow disks change the math, not the contract.** On network-attached storage a single
op can cost ~2 s (canonical write + fsync + index maintenance). The scaled default
absorbs that; pass an explicit `timeoutMs` only when you know better than the scale.
4. **`addMany`/`relateMany` are the convenience tier** — they chunk and batch-embed for
you, with per-item error reporting instead of batch atomicity. Choose by what you need:
atomic-all-or-nothing → `transact()`; resilient bulk load → `addMany`.

View file

@ -1,111 +0,0 @@
---
title: Quick Start
slug: getting-started/quick-start
public: true
category: getting-started
template: guide
order: 2
description: Build your first knowledge graph in 60 seconds. Add entities, create relationships, and query with Triple Intelligence — vector + graph + metadata in one call.
next:
- concepts/triple-intelligence
- api/reference
---
# Quick Start
Get Brainy running in under a minute.
## 1. Install
```bash
npm install @soulcraft/brainy
```
## 2. Initialize
```typescript
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
```
That's it. Brainy auto-configures storage, loads the embedding model, and builds the indexes.
## 3. Add Knowledge
```typescript
// Text is automatically embedded into 384-dim vectors
const reactId: string = await brain.add({
data: 'React is a JavaScript library for building user interfaces',
type: NounType.Concept,
subtype: 'library', // Sub-classification within Concept
metadata: { category: 'frontend', year: 2013 }
})
const nextId: string = await brain.add({
data: 'Next.js framework for React with server-side rendering',
type: NounType.Concept,
subtype: 'framework',
metadata: { category: 'framework', year: 2016 }
})
```
`type` is one of Brainy's 42 stable NounTypes. `subtype` is your free-form sub-classification within that type — flat string, no hierarchy, indexed on the fast path. See **[Subtypes & Facets](./subtypes-and-facets.md)** for the full guide.
## 4. Create Relationships
```typescript
// Typed graph relationships
await brain.relate({
from: nextId,
to: reactId,
type: VerbType.DependsOn
})
```
## 5. Query with Triple Intelligence
```typescript
import type { Result } from '@soulcraft/brainy'
// All three search paradigms in one call
const results: Result[] = await brain.find({
query: 'modern frontend frameworks', // Vector similarity search
where: { year: { greaterThan: 2015 } }, // Metadata filtering
connected: { to: reactId, depth: 2 } // Graph traversal
})
console.log(results[0].data) // 'Next.js framework for React...'
console.log(results[0].score) // 0.94
```
## What Just Happened
Every entity you `add()` lives in three indexes simultaneously:
| Index | What it stores | Query with |
|-------|---------------|------------|
| Vector | 384-dim embedding of `data` | `find({ query: '...' })` |
| Metadata | All `metadata` fields | `find({ where: { ... } })` |
| Graph | Typed relationships from `relate()` | `find({ connected: { ... } })` |
`find()` queries all three in parallel and fuses the results.
## Natural Language Queries
Brainy understands 220+ natural language patterns:
```typescript
// These all work without any configuration
await brain.find({ query: 'recent documents about machine learning' })
await brain.find({ query: 'articles created this week' })
await brain.find({ query: 'people who work at Anthropic' })
```
## Next Steps
- [Triple Intelligence](/docs/concepts/triple-intelligence) — understand how the query engine works
- [The Find System](/docs/guides/find-system) — advanced queries, operators, and graph traversal
- [API Reference](/docs/api/reference) — complete method documentation
- [Storage Adapters](/docs/guides/storage-adapters) — filesystem, memory

View file

@ -1,117 +0,0 @@
---
title: Reacting to Changes
slug: guides/reacting-to-changes
public: true
category: guides
template: guide
order: 11
description: Subscribe to every committed mutation with `brain.onChange` — the in-process change feed behind live UIs, cache invalidation, and realtime sync. Covers the event shape, delivery guarantees, and catch-up patterns.
next:
- guides/optimistic-concurrency
- guides/snapshots-and-time-travel
---
# Reacting to Changes
`brain.onChange(cb)` is Brainy's in-process change feed: subscribe once and
receive one event per committed mutation — **every** mutation, regardless of
how it happened. Direct calls, batch methods, `transact()`, imports, and
Virtual Filesystem writes all funnel through the same commit point the feed is
emitted from, so nothing slips past it.
```ts
const off = brain.onChange((e) => {
if (e.kind === 'entity') {
console.log(`${e.op} ${e.entity?.type} ${e.id} @ generation ${e.generation}`)
}
})
await brain.add({ data: 'Ada Lovelace', type: 'person' })
// → "add person 0198... @ generation 42"
off() // unsubscribe when done
```
## The event
```ts
interface BrainyChangeEvent {
kind: 'entity' | 'relation' | 'store'
op: 'add' | 'update' | 'remove' | 'relate' | 'unrelate' | 'updateRelation'
| 'clear' | 'restore'
id?: string
entity?: { id: string; type: string; subtype?: string;
metadata: Record<string, unknown>; service?: string }
relation?: { id: string; from: string; to: string; type: string;
metadata?: Record<string, unknown> }
generation?: number
timestamp: number
}
```
- **Entity events** (`add` / `update` / `remove`) carry the post-commit indexed
view — `type`, `subtype`, and the full custom `metadata`, so you can match
your own `where`-style filters against events without a read.
- **Deletes are fully described.** A `remove` or `unrelate` event carries the
record's *last committed state* (sourced from the commit's own history
record), not just an id.
- **Batches emit per item.** `addMany` / `updateMany` / `relateMany` /
`removeMany` emit one event per affected record; a `transact()` batch emits
one event per item, all sharing the batch's single `generation`.
- **Cascades are visible.** Removing an entity also emits `unrelate` for each
relationship the delete cascaded to.
- **Store-level events** (`kind: 'store'`) fire for the two wholesale
operations — `clear()` and `restore()` — and mean *"everything may have
changed; refetch what you care about."*
## Delivery guarantees
- **Post-commit only.** An aborted write — a losing
[`ifRev` compare-and-swap](optimistic-concurrency.md), a rejected
transaction — never emits. If you received the event, the write is durable.
- **Commit-ordered.** Events arrive in the order writes committed;
`generation` is monotonic.
- **Asynchronous, never blocking.** Delivery happens in a microtask after the
write completes. A slow listener cannot delay a write; a throwing listener
is logged and isolated from other listeners.
- **Zero overhead when unused.** With no subscribers, the write path does no
event work at all.
- **Fire-and-forget.** There is no replay or backpressure. For catch-up after
a disconnect, use the `generation` on each event together with
[`asOf()` / the transaction log](snapshots-and-time-travel.md): record the
last generation you processed, and on reconnect diff from there. For file
content specifically, `vfs.readFile(path, { asOf })` and
`vfs.history(path)` are the temporal read — see
[Snapshots & Time Travel](snapshots-and-time-travel.md).
## Patterns
**Cache invalidation** — drop cached reads for whatever changed:
```ts
brain.onChange((e) => {
if (e.kind === 'store') return cache.clear()
if (e.id) cache.delete(e.id)
})
```
**Live queries (notify-and-refetch)** — re-run a query when a relevant change
lands, rather than diffing incrementally:
```ts
brain.onChange((e) => {
if (e.kind === 'entity' && e.entity?.type === 'order') {
refreshOpenOrdersView() // debounce as needed
}
})
```
**Forwarding to other processes** — the feed is in-process by design. To push
changes to browsers or other services, forward events through your own
transport (WebSocket, SSE) from the process that owns the brain.
## Lifecycle
`onChange` returns an unsubscribe function — call it when tearing down a
subscriber (for example, when evicting a pooled instance). `brain.close()`
drops all listeners; no events are delivered for or after `close()`.

View file

@ -1,284 +0,0 @@
# Schema Migrations
Brainy includes a built-in migration system for transforming entity and verb metadata across storage versions. Migrations are pure functions that run once per storage instance, with optional snapshot backup (`backupTo`), resume support, and error tracking.
---
## Quick Start
### 1. Define a migration
Add your migration to the `MIGRATIONS` array in `src/migration/migrations.ts`:
```typescript
import type { Migration } from './types.js'
export const MIGRATIONS: Migration[] = [
{
id: '7.17.0-rename-status',
version: '7.17.0',
description: 'Rename "state" field to "status"',
applies: 'nouns',
transform: (m) => {
if ('state' in m) {
const { state, ...rest } = m
return { ...rest, status: state }
}
return null // already migrated or not applicable
}
}
]
```
### 2. Ship the new version
That's it. Brainy detects pending migrations on `init()` and either runs them automatically or warns the user to call `brain.migrate()`.
---
## How It Works
When `brain.init()` runs:
1. **Detection** — reads migration state from storage (one key lookup). Compares completed migration IDs against the `MIGRATIONS` array. If nothing pending, cost is ~0ms.
2. **Small datasets** (`autoMigrate: true`, <10K entities) migrates inline during `init()`.
3. **Large datasets or manual mode** — logs a warning. User calls `brain.migrate()` when ready.
When `brain.migrate()` runs:
1. **Backup (optional)** — with `backupTo`, a hard-link snapshot of the current generation is persisted before any transform runs. Rollback is `brain.restore(backupPath, { confirm: true })`.
2. **Transform** — iterates all nouns/verbs in paginated batches. For each entity, calls the `transform` function. If it returns a new object, saves it. If it returns `null`, skips. Vectors are never touched.
3. **Save state** — records each completed migration ID so it never re-runs.
4. **Rebuild indexes** — if any entities were modified, rebuilds the MetadataIndex.
---
## Writing Migrations
### The Migration interface
```typescript
interface Migration {
id: string // Unique ID, e.g. "7.17.0-rename-field"
version: string // Version that introduced this migration
description: string // Human-readable description
applies: 'nouns' | 'verbs' | 'both'
transform: (metadata: Record<string, unknown>) => Record<string, unknown> | null
}
```
### Transform rules
- **Return a new object** to modify the entity's metadata.
- **Return `null`** to skip (no change needed).
- **Must be idempotent** — running the same transform twice on the same data should produce the same result (or return `null` the second time). This is required because interrupted runs resume and re-encounter already-migrated entities.
- **Must be pure** — no side effects, no async, no external state.
- Transforms only see metadata. Vectors, embeddings, and the `data` field stored inside metadata are available as properties on the metadata object.
### Ordering
Migrations run in array order. Add new migrations at the end of the `MIGRATIONS` array. Each migration runs independently per entity — migration 2 sees the output of migration 1.
### Validation
`MigrationRunner.validateMigrations()` checks migration definitions and will throw on:
- Duplicate IDs
- Invalid `applies` values (must be `'nouns'`, `'verbs'`, or `'both'`)
- Non-function `transform`
- Missing or empty `id`, `version`, or `description`
---
## API Reference
### `brain.migrate(options?)`
```typescript
// Dry-run: preview what would change without writing
const preview = await brain.migrate({ dryRun: true })
// preview.pendingMigrations — array of { id, description }
// preview.affectedEntities — count of entities that would change
// preview.totalEntities — count of entities scanned
// preview.sampleChanges — up to 5 before/after samples
// preview.estimatedTime — rough time estimate string
// Apply migrations (optionally with a pre-migration snapshot)
const result = await brain.migrate({ backupTo: '/backups/pre-migration' })
// result.backupPath — snapshot path, or null when no backupTo was supplied
// result.migrationsApplied — array of migration IDs that ran
// result.entitiesProcessed — total entities scanned
// result.entitiesModified — entities actually changed
// result.errors — array of entity-level errors (non-fatal)
```
### Options
```typescript
interface MigrateOptions {
dryRun?: boolean // Preview without writing (default: false)
maxErrors?: number // Bail out after N entity errors (default: 100)
onProgress?: (progress: {
migrationId: string
processed: number
modified: number
hasMore: boolean
}) => void
}
```
---
## Error Handling
If a transform function throws on a specific entity, the error is recorded and migration continues to the next entity. The failed entity's metadata is left unchanged.
```typescript
const result = await brain.migrate()
if (result.errors.length > 0) {
for (const err of result.errors) {
console.warn(`Entity ${err.entityId} failed in ${err.migrationId}: ${err.error}`)
}
}
```
If errors exceed `maxErrors` (default: 100), the migration stops early and returns partial results. Successfully migrated entities keep their changes; failed entities are unchanged.
```typescript
// Strict mode: fail fast on any error
const result = await brain.migrate({ maxErrors: 1 })
// Lenient mode: tolerate many errors
const result = await brain.migrate({ maxErrors: 10000 })
```
---
## Backup and Rollback
Pass `backupTo` and `brain.migrate()` persists a snapshot of the current generation **before any transform runs**. On filesystem storage the snapshot is a hard-link farm — created without copying entity data, and immune to later writes (see [Snapshots & Time Travel](./snapshots-and-time-travel.md)):
```typescript
const result = await brain.migrate({ backupTo: '/backups/pre-migration-8.0' })
console.log(result.backupPath) // '/backups/pre-migration-8.0' (null when no backupTo)
```
To roll back, restore the snapshot wholesale:
```typescript
await brain.restore('/backups/pre-migration-8.0', { confirm: true })
```
Without `backupTo`, no backup is taken — transforms are idempotent (they return `null` when already applied), but a pre-migration snapshot is the cheap insurance for anything destructive.
---
## Progress Tracking
For large datasets, use the `onProgress` callback:
```typescript
await brain.migrate({
onProgress: ({ migrationId, processed, modified, hasMore }) => {
console.log(`[${migrationId}] ${processed} scanned, ${modified} modified${hasMore ? '...' : ' (done)'}`)
}
})
```
Progress is reported after each batch (batch size is determined by the storage adapter).
---
## Examples
### Rename a field
```typescript
{
id: '7.17.0-rename-state-to-status',
version: '7.17.0',
description: 'Rename metadata.state to metadata.status',
applies: 'nouns',
transform: (m) => {
if ('state' in m) {
const { state, ...rest } = m
return { ...rest, status: state }
}
return null
}
}
```
### Add a default value
```typescript
{
id: '7.18.0-add-priority-default',
version: '7.18.0',
description: 'Add priority field with default "normal"',
applies: 'both',
transform: (m) => {
if (!('priority' in m)) {
return { ...m, priority: 'normal' }
}
return null
}
}
```
### Remove a deprecated field
```typescript
{
id: '7.19.0-remove-legacy-flag',
version: '7.19.0',
description: 'Remove deprecated "legacy" field',
applies: 'nouns',
transform: (m) => {
if ('legacy' in m) {
const { legacy, ...rest } = m
return rest
}
return null
}
}
```
### Transform verb metadata
```typescript
{
id: '7.20.0-normalize-verb-weights',
version: '7.20.0',
description: 'Normalize verb weights from 0-100 to 0-1 scale',
applies: 'verbs',
transform: (m) => {
if (typeof m.weight === 'number' && m.weight > 1) {
return { ...m, weight: m.weight / 100 }
}
return null
}
}
```
---
## Storage Backend Compatibility
Migrations work identically across all storage backends (Memory, FileSystem). The system uses `BaseStorage` methods (`getNouns`, `saveNounMetadata`, `getVerbs`, `saveVerbMetadata`) which are implemented by every adapter.
Batch size and rate limiting are automatically configured per adapter — no tuning required.
---
## What Migrations Don't Do
- **Re-embedding** — migrations transform metadata only. If you change your embedding model or dimensions, that requires re-vectorizing data, which is a separate concern (not part of this system).
- **Vector modification** — the `vectors.json` files are never touched by migrations.
- **Schema enforcement** — migrations are opt-in transforms, not schema validators. Brainy's metadata is schemaless by design.

View file

@ -1,449 +0,0 @@
---
title: Snapshots & Time Travel
slug: guides/snapshots-and-time-travel
public: true
category: guides
template: guide
order: 9
description: Recipes for the Db API — instant backups with persist(), restore, time-travel debugging with asOf(), range queries over history (diff, history, since, log windows), persist-before-migrate, what-if analysis with with(), and audit trails via transaction metadata.
next:
- concepts/consistency-model
- guides/optimistic-concurrency
- guides/external-backups
---
# Snapshots & Time Travel
Brainy 8.0 treats the database as a **value**: `brain.now()` pins the
current state as an immutable `Db`, `brain.transact()` commits an atomic
batch and hands you the resulting value, `brain.asOf()` opens past state,
and `db.persist()` cuts a self-contained snapshot. This guide is the recipe
book. The precise guarantees behind every recipe live in the
[consistency model](../concepts/consistency-model.md).
## Instant backup
Pin the current state, persist it, release:
```typescript
const db = brain.now()
try {
await db.persist('/backups/2026-06-11')
} finally {
await db.release()
}
```
On filesystem storage the snapshot is built from **hard links**: every data
file in Brainy is immutable-by-rename, so the snapshot is created without
copying entity data and shares disk space with the live store. Later writes
can never alter it — a rewrite swaps the inode, the snapshot keeps the old
bytes. Cross-device targets fall back to per-file byte copies, and
persisting an in-memory brain serializes it to the same directory layout —
a real, durable store.
> Archiving a brain directory with **external tools** (`tar`, `rsync`, `cp`)?
> Some index files are sparse and can explode to their apparent size under a
> naive copy — see [External Backups & Sparse Storage](/docs/guides/external-backups).
Two things to know:
- `persist()` requires the view to still be the store's **latest**
generation. If something committed after your pin, it throws
`GenerationConflictError` instead of snapshotting the wrong state — pin
and persist before further writes, or retry with a fresh `brain.now()`.
- The target directory must be empty or absent.
For scheduled backups, this loop is the whole job:
```typescript
const db = brain.now()
try {
await db.persist(`/backups/${new Date().toISOString().slice(0, 10)}`)
} finally {
await db.release()
}
```
## Restore
`restore()` replaces the store's **entire** current state from a snapshot —
entities, relationships, indexes, history. It is deliberately loud about it:
```typescript
await brain.restore('/backups/2026-06-11', { confirm: true })
```
- `{ confirm: true }` is mandatory — current state is destroyed.
- The snapshot is copied in (never linked), so it stays independent and can
be restored again later.
- All indexes are rebuilt from the restored records.
- The generation counter is floored at its pre-restore value, so generation
numbers you observed before the restore are never reissued.
- Live `Db` pins do not survive a restore — release them first.
## Open a snapshot read-only
You do not have to restore to look inside a snapshot. `Brainy.load()` opens
it as a self-contained read-only store with the **full query surface**,
including vector search:
```typescript
const db = await Brainy.load('/backups/2026-06-11')
const hits = await db.find({ query: 'unpaid invoices from the spring campaign' })
const orders = await db.find({ type: NounType.Document, subtype: 'order' })
await db.release() // closes the underlying read-only instance
```
`brain.asOf('/backups/2026-06-11')` does the same from an existing brain.
This is also the 8.0 answer to "named branches": a branch is a name → path
mapping your application keeps, where each path is a persisted snapshot.
Need a writable copy? Restore the snapshot into a fresh data directory and
open a writer on it — instead of switching a shared store between branches
in place, every line of code always sees exactly the store it opened.
## Time-travel debugging
When production data looks wrong, query the past directly — by wall-clock
time or by generation:
```typescript
// What did this order look like yesterday?
const yesterday = await brain.asOf(new Date(Date.now() - 86_400_000))
const before = await yesterday.get(orderId)
// Full queries work at any reachable generation — search, graph, filters:
const thenActive = await yesterday.find({
type: NounType.Document,
subtype: 'order',
where: { status: 'active' }
})
await yesterday.release()
```
Pin two points in time and diff them:
```typescript
const before = await brain.asOf(1041)
const after = brain.now()
const changed = await after.since(before)
changed.nouns // entity ids touched by transactions in between
changed.verbs // relationship ids touched in between
await before.release()
await after.release()
```
Three things to remember:
- History granularity is per-write: EVERY write — `transact()` AND a
single-operation `add`/`update`/`remove`/`relate` — is its own immutable
generation, so a pin always freezes against later writes and every write is
individually addressable via `asOf()` (see the
[consistency model](../concepts/consistency-model.md)). Use `transact()` when
you want several operations to share ONE atomic generation.
- The first index-accelerated query (semantic search, traversal, cursors,
aggregation) at a historical generation builds an in-memory index
materialization — O(n at that generation), once per `Db`, freed on
`release()`. Metadata-level reads are free.
- Generations reclaimed by `compactHistory()` throw
`GenerationCompactedError` — persist anything you need to keep forever.
## Range queries over history
`asOf()` answers "what was the state AT a point". Four range verbs answer
"what happened BETWEEN two points" and "what is one entity's whole history".
They all build on the same generation records — no extra bookkeeping.
### `diff(a, b)` — what changed, classified
`since()` gives you the raw set of *touched* ids. `diff()` goes further: it
resolves each touched id at both endpoints and classifies it as **added**,
**removed**, or **modified** — split by entities (`nouns`) and relationships
(`verbs`). An id that was touched but ended up identical (changed then
reverted, or created and deleted within the interval) lands in **none** of the
buckets. Endpoints are a generation, a `Date`, or a `Db`, in either order:
```typescript
const d = await brain.diff(1041, brain.generation())
d.added.nouns // entity ids created between the two states
d.removed.nouns // entity ids deleted
d.modified.nouns // entity ids whose stored value actually changed
d.added.verbs // …relationships, the same three ways
```
Orientation is `a → b`: `added` means "exists at `b`, not at `a`". The
comparison behind `modified` is key-order-insensitive, so a no-op re-write of
the same fields never shows up as a change.
### `history(id, range?)` — one entity, every version
`asOf()` is per-*generation*; `history()` is per-*entity*. It returns every
distinct version of one id over a range, oldest first — each `value` is the
materialized state at that version (and `null` marks a removal):
```typescript
const h = await brain.history(invoiceId)
for (const v of h.versions) {
console.log(v.generation, v.value?.metadata?.status ?? '(deleted)')
}
// 1041 'draft'
// 1043 'approved'
// 1050 'paid'
```
Every version ties to the trusted `asOf()` path — `v.value` equals
`(await brain.asOf(v.generation)).get(id)`. Pass `{ from, to }` (generation or
`Date`) to bound the range; a `from` below the compaction horizon is quietly
truncated to it rather than throwing (history is best-effort over surviving
records).
### `since()` and `transactionLog()` take ranges too
`since()` accepts a `Db`, a generation number, or a `Date` — all equivalent,
all an **exclusive** lower bound (`db.since(prior)` equals
`db.since(prior.generation)`):
```typescript
await brain.now().since(1041) // ids changed after generation 1041
await brain.now().since(new Date(Date.now() - 3_600_000)) // …in the last hour
```
`transactionLog({ from, to })` windows the commit log **inclusively** on both
ends (a log window names the commits it spans — the deliberate contrast to
`since`'s exclusive lower bound); `limit` applies after the window, newest
first:
```typescript
const window = await brain.transactionLog({ from: 1041, to: 1050 }) // commits 1041…1050
const recent = await brain.transactionLog({ from: lastHour, limit: 20 })
```
### Composing them
"Which orders changed in this window?" is `diff` ids intersected with an
`asOf` query — the two agree by construction:
```typescript
const changed = await brain.diff(g1, g2)
const atG2 = await brain.asOf(g2)
const changedOrders = (await atG2.find({ type: NounType.Document, subtype: 'order' }))
.map(r => r.id)
.filter(id => changed.added.nouns.includes(id) || changed.modified.nouns.includes(id))
await atG2.release()
```
One contrast to keep straight: `diff` and `since` **throw**
`GenerationCompactedError` for a bound below the horizon, while `history`
**truncates** to the horizon — diffs must be exact, history is best-effort.
## Safe schema migration
`brain.migrate()` integrates with snapshots directly: pass `backupTo` and a
hard-link snapshot of the current generation is persisted **before any
transform runs**:
```typescript
const result = await brain.migrate({ backupTo: '/backups/pre-migration-8.0' })
console.log(result.migrationsApplied, result.backupPath)
// If the migration went wrong, roll the whole store back:
await brain.restore('/backups/pre-migration-8.0', { confirm: true })
```
The same persist-before-mutate pattern works for any risky bulk operation,
not just migrations:
```typescript
const pin = brain.now()
try {
await pin.persist('/backups/pre-bulk-edit')
} finally {
await pin.release()
}
await runRiskyBulkEdit(brain)
```
## What-if analysis
`db.with(ops)` applies a transaction **speculatively, in memory** — nothing
touches disk, the generation counter, or the indexes. Ask "what would the
store look like if…", then commit the same operations for real:
```typescript
const ops = [
{ op: 'update', id: employeeId, metadata: { team: 'platform' } },
{ op: 'relate', from: employeeId, to: milestoneId, type: VerbType.ParticipatesIn, subtype: 'assignment' }
]
const base = brain.now()
const whatIf = await base.with(ops)
await whatIf.get(employeeId) // sees the change
await whatIf.find({ where: { team: 'platform' } }) // metadata finds work
await whatIf.related(employeeId) // overlay relations included
await whatIf.release()
await base.release()
// Looks right — make it real, atomically:
await brain.transact(ops)
```
**The boundary:** speculative entities carry no embeddings (`with()` never
invokes the embedder), so semantic search, traversal, cursors, aggregation,
and `persist()` throw `SpeculativeOverlayError` on overlay views instead of
returning silently incomplete results. `get()`, metadata-filter `find()`,
and filter-based `related()` are fully supported. Overlays chain — calling
`with()` on an overlay stacks another layer.
## Audit trails
`transact()` reifies transaction metadata: whatever you pass as `meta` is
recorded durably alongside the committed generation and timestamp, readable
via `brain.transactionLog()`:
```typescript
await brain.transact(
[{ op: 'update', id: invoiceId, metadata: { status: 'approved' } }],
{ meta: { author: 'approvals-service', actor: 'jane@example.com', reason: 'PO-7741' } }
)
const log = await brain.transactionLog({ limit: 20 }) // newest first
// [{ generation: 1042, timestamp: 1765432100000, meta: { author: 'approvals-service', ... } }]
```
Combine the log with `asOf()` to reconstruct exactly what any transaction
did:
```typescript
const [entry] = await brain.transactionLog({ limit: 1 })
const after = await brain.asOf(entry.generation)
const before = await brain.asOf(entry.generation - 1)
const touched = await after.since(before)
for (const id of touched.nouns) {
console.log(id, await before.get(id), '→', await after.get(id))
}
await before.release()
await after.release()
```
For per-entity write coordination (rather than whole-store history), the
`_rev` counter and `ifRev` CAS remain the right tool — see
[optimistic concurrency](./optimistic-concurrency.md).
## Keeping history bounded
Under Model-B every write is a generation, so history can grow quickly —
Brainy auto-compacts at `close()` (time-bounded per pass) under the
**`retention`** knob (configured on the constructor). Since 8.9.0, `flush()`
never compacts: flushing is durability work and costs only what the current
window's writes cost, regardless of history backlog. A long-lived writer that
never closes keeps its history until its next explicit `compactHistory()`
schedule one in your maintenance window if you run bounded retention:
```typescript
// Zero-config: ADAPTIVE — keep as much history as free disk/RAM allows,
// reclaiming oldest-first under pressure. (This is the default.)
new Brainy({ /* retention unset */ })
// Unbounded — never reclaim history (opt in explicitly):
new Brainy({ retention: 'all' })
// Explicit CAPS — reclaim oldest-unpinned generations while ANY cap is exceeded:
new Brainy({ retention: { maxGenerations: 1000, maxAge: 7 * 86_400_000, maxBytes: 512 * 1024 ** 2 } })
```
Reclaim manually at any time (the same caps, plus an optional per-pass time
budget for maintenance windows — an early stop is a consistent prefix and the
next pass resumes):
```typescript
await brain.compactHistory({ maxGenerations: 100, maxAge: 7 * 24 * 60 * 60 * 1000 })
await brain.compactHistory({ maxBytes: 512 * 1024 ** 2, timeBudgetMs: 10_000 })
```
Compaction never breaks a pinned read — record-sets are reclaimed only when
no live `Db` could need them (live pins are ALWAYS exempt). Release views you
are done with (including the ones `transact()` returns), and `persist()` any
generation you want to keep beyond the retention window: snapshots are
self-contained and unaffected by compaction.
## Time travel for files (the VFS)
Since 8.2.0, time travel covers Virtual Filesystem **content**, not just
entity records. File bytes are retention-protected: a content blob referenced
by any generation inside the retention window is never reclaimed, so reading
the past always returns the exact bytes — never a stale field or a
dangling hash.
**`vfs.readFile(path, { asOf })`** takes a generation number or a `Date` and
returns the file's exact bytes as they stood then. It resolves the path's
current entity, then materializes its state at the target generation — so it
answers *"what did the file at this path hold at that point?"* It bypasses
the content cache; the `encoding` option still applies. Asking about a
generation before the file existed throws the usual not-found error, and
asking past the retention window's compaction horizon throws a
compacted-generation error.
**`vfs.history(path)`** returns the file's versions inside the retention
window, oldest first — one `FileVersion` per generation that wrote the file,
the newest entry being the current state:
```typescript
// A CMS page evolves…
await brain.vfs.writeFile('/pages/home.json', '{"title":"Launch"}')
await brain.vfs.writeFile('/pages/home.json', '{"title":"Launch v2"}')
await brain.vfs.writeFile('/pages/home.json', '{"title":""}') // bad deploy!
// Every version is listed and readable:
const versions = await brain.vfs.history('/pages/home.json')
// → [{ generation, timestamp, hash, size, mimeType? }, …] ascending
const good = versions[versions.length - 2]
const bytes = await brain.vfs.readFile('/pages/home.json', {
asOf: good.generation
})
// Restore = write the old bytes back. This is a NEW write (a new
// generation) — history is never rewritten, so the bad version stays
// visible in the audit trail.
await brain.vfs.writeFile('/pages/home.json', bytes)
```
Two lifecycle consequences worth stating plainly:
- **Deleting or overwriting a file no longer frees its bytes immediately.**
Old content lives until history compaction reclaims the generations that
reference it — the same `retention` budget that bounds all Model-B history
(and pinned views are exempt, exactly as above). Size your `retention` for
the file-version depth you want; `retention: 'all'` keeps every version of
every file forever.
- **After `compactHistory()` reclaims a generation, its file versions are
gone** and their bytes are physically reclaimed. (This also fixed a
pre-8.2.0 defect where overwritten content was never reclaimed at all — an
unbounded silent leak.)
## From branches to values
If you used the pre-8.0 `fork`/`checkout`/`commit`/`versions` surface, every
use case maps to a sharper tool:
| Pre-8.0 habit | 8.0 recipe |
|---|---|
| `fork()` to experiment safely | `db.with(ops)` for speculation in memory; a restored snapshot in a fresh directory for a long-lived writable copy |
| `commit()` checkpoints | `transact(ops, { meta })` — every batch is an atomic, logged, time-travelable commit |
| `checkout()` to switch branches | Open the snapshot you want — `Brainy.load(path)` read-only, or restore into its own directory. No in-place switching: every handle always sees one unambiguous store. |
| `getHistory()` | `brain.transactionLog()` + `db.since(priorDb)` |
| `versions.save()` per-entity snapshots | A pinned `Db` or persisted snapshot captures *every* entity at that moment; `asOf()` reads any entity's past state |
| `versions.restore()` | `brain.restore(snapshot, { confirm: true })` for the whole store, or read the old entity via `asOf()` and write it back with `transact()` |
| Backup branches | `db.persist(path)` — instant, hard-link-shared, self-contained |

View file

@ -1,453 +0,0 @@
# Standard Import Progress API
## ✅ Build Once, Works for ALL Formats
**Brainy provides a 100% standardized progress API** - write your UI/tool once, and it works for all 7 supported formats (CSV, PDF, Excel, JSON, Markdown, YAML, DOCX) with **zero format-specific code**.
---
## 🎯 The Standard Interface
### One Interface for Everything
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = await Brainy.create()
// THIS CODE WORKS FOR ALL 7 FORMATS - NO FORMAT-SPECIFIC LOGIC NEEDED!
await brain.import(anyBuffer, {
onProgress: (progress) => {
// Standard fields - ALWAYS available regardless of format
console.log(progress.stage) // Current stage
console.log(progress.message) // Human-readable status
// Optional fields - available when relevant
console.log(progress.processed) // Items processed so far
console.log(progress.total) // Total items (if known)
console.log(progress.entities) // Entities extracted
console.log(progress.relationships) // Relationships inferred
console.log(progress.throughput) // Items/sec (during extraction)
console.log(progress.eta) // Time remaining in ms
}
})
```
### The Complete Interface
```typescript
interface ImportProgress {
// === ALWAYS PRESENT ===
/** High-level stage (5 stages for all formats) */
stage: 'detecting' | 'extracting' | 'storing-vfs' | 'storing-graph' | 'complete'
/** Human-readable status message */
message: string
// === AVAILABLE WHEN RELEVANT ===
/** Items processed (rows, pages, nodes, etc.) */
processed?: number
/** Total items to process (if known ahead of time) */
total?: number
/** Entities extracted so far */
entities?: number
/** Relationships inferred so far */
relationships?: number
/** Processing rate (items per second) */
throughput?: number
/** Estimated time remaining (milliseconds) */
eta?: number
/** Whether data is queryable at this point */
queryable?: boolean
}
```
---
## 🎨 Generic UI Components
### React Progress Component (Works for ALL Formats)
```typescript
import { useState } from 'react'
import { Brainy } from '@soulcraft/brainy'
function UniversalImportProgress({ file }: { file: File }) {
const [progress, setProgress] = useState({
stage: 'idle',
message: 'Ready to import',
percent: 0,
entities: 0,
relationships: 0
})
const handleImport = async () => {
const buffer = await file.arrayBuffer()
const brain = await Brainy.create()
await brain.import(Buffer.from(buffer), {
// THIS WORKS FOR CSV, PDF, EXCEL, JSON, MARKDOWN, YAML, DOCX!
onProgress: (p) => {
setProgress({
stage: p.stage,
message: p.message,
// Calculate percentage from stage + processed/total
percent: calculatePercent(p),
entities: p.entities || 0,
relationships: p.relationships || 0
})
}
})
}
// Helper: Calculate percentage from progress
function calculatePercent(p: ImportProgress): number {
// Use processed/total if available
if (p.processed && p.total) {
return Math.round((p.processed / p.total) * 100)
}
// Otherwise estimate from stage
const stagePercents = {
detecting: 5,
extracting: 50,
'storing-vfs': 80,
'storing-graph': 90,
complete: 100
}
return stagePercents[p.stage] || 0
}
return (
<div className="import-progress">
{/* Stage Indicator */}
<div className="stages">
{['detecting', 'extracting', 'storing-vfs', 'storing-graph', 'complete'].map(s => (
<span
key={s}
className={progress.stage === s ? 'active' : ''}
>
{s}
</span>
))}
</div>
{/* Progress Bar */}
<div className="progress-bar">
<div style={{ width: `${progress.percent}%` }} />
</div>
{/* Status Message (format-specific but always readable) */}
<p className="message">{progress.message}</p>
{/* Counts */}
<div className="counts">
<span>Entities: {progress.entities}</span>
<span>Relationships: {progress.relationships}</span>
</div>
</div>
)
}
```
**This component works perfectly for:**
- ✅ CSV files with 10,000 rows
- ✅ PDF documents with 200 pages
- ✅ Excel workbooks with 5 sheets
- ✅ JSON files with nested structures
- ✅ Markdown documents with sections
- ✅ YAML configuration files
- ✅ DOCX documents with paragraphs
**No format detection needed. No format-specific rendering. Just works.**
---
### CLI Progress Indicator (Works for ALL Formats)
```typescript
import ora from 'ora'
import { Brainy } from '@soulcraft/brainy'
async function importWithProgress(filePath: string) {
const spinner = ora('Starting import...').start()
const brain = await Brainy.create()
try {
await brain.import(filePath, {
// THIS WORKS FOR ALL 7 FORMATS!
onProgress: (p) => {
// Update spinner text with current message
spinner.text = p.message
// Add counts if available
if (p.entities || p.relationships) {
spinner.text += ` (${p.entities || 0} entities, ${p.relationships || 0} relationships)`
}
// Add throughput/ETA if available (during extraction)
if (p.throughput && p.eta) {
const etaSec = Math.round(p.eta / 1000)
spinner.text += ` [${p.throughput.toFixed(1)}/sec, ETA: ${etaSec}s]`
}
// Change spinner when complete
if (p.stage === 'complete') {
spinner.succeed(p.message)
}
}
})
} catch (error) {
spinner.fail(`Import failed: ${error.message}`)
}
}
// Works for ANY format!
await importWithProgress('data.csv')
await importWithProgress('document.pdf')
await importWithProgress('workbook.xlsx')
await importWithProgress('config.yaml')
```
**CLI Output (same code, different formats):**
```bash
# CSV Import
⠋ Detecting format...
⠙ Parsing CSV rows (delimiter: ",")
⠹ Extracting entities from csv (45 rows/sec, ETA: 120s) (150 entities, 45 relationships)
⠸ Extracting entities from csv (45 rows/sec, ETA: 60s) (750 entities, 223 relationships)
✔ Import complete (1350 entities, 401 relationships)
# PDF Import
⠋ Detecting format...
⠙ Loading PDF document...
⠹ Processing page 5 of 23
⠸ Extracting entities from pdf (2.5 pages/sec, ETA: 30s) (45 entities, 12 relationships)
✔ Import complete (156 entities, 89 relationships)
# Excel Import
⠋ Detecting format...
⠙ Loading Excel workbook...
⠹ Reading sheet: Sales (2/5)
⠸ Extracting entities from excel (120 rows/sec, ETA: 45s) (500 entities, 234 relationships)
✔ Import complete (2340 entities, 892 relationships)
```
**Same code. Different formats. Perfect progress for all.**
---
### Dashboard with Real-Time Stats (Works for ALL Formats)
```typescript
function ImportDashboard() {
const [stats, setStats] = useState({
stage: '',
message: '',
elapsed: 0,
entities: 0,
relationships: 0,
throughput: 0,
eta: 0
})
const startTime = Date.now()
const handleImport = async (file: File) => {
await brain.import(await file.arrayBuffer(), {
// UNIVERSAL PROGRESS HANDLER - WORKS FOR ALL FORMATS!
onProgress: (p) => {
setStats({
stage: p.stage,
message: p.message,
elapsed: Date.now() - startTime,
entities: p.entities || 0,
relationships: p.relationships || 0,
throughput: p.throughput || 0,
eta: p.eta || 0
})
}
})
}
return (
<div className="dashboard">
<h2>Import Progress</h2>
<div className="metric">
<label>Stage</label>
<value>{stats.stage}</value>
</div>
<div className="metric">
<label>Status</label>
<value>{stats.message}</value>
</div>
<div className="metric">
<label>Elapsed</label>
<value>{(stats.elapsed / 1000).toFixed(1)}s</value>
</div>
<div className="metric">
<label>Entities</label>
<value>{stats.entities.toLocaleString()}</value>
</div>
<div className="metric">
<label>Relationships</label>
<value>{stats.relationships.toLocaleString()}</value>
</div>
{stats.throughput > 0 && (
<div className="metric">
<label>Throughput</label>
<value>{stats.throughput.toFixed(1)} items/sec</value>
</div>
)}
{stats.eta > 0 && (
<div className="metric">
<label>ETA</label>
<value>{(stats.eta / 1000).toFixed(0)}s</value>
</div>
)}
</div>
)
}
```
**This dashboard shows live stats for ANY format** - CSV, PDF, Excel, JSON, Markdown, YAML, DOCX.
---
## 📊 What Messages Look Like (Format-Specific Text, Standard Fields)
While the **fields are standardized**, the **message text** varies by format to be most helpful:
```typescript
// CSV Import Messages
"Detecting format..."
"Parsing CSV rows (delimiter: ",")"
"Extracted 1000 rows, inferring types..."
"Extracting entities from csv (45 rows/sec, ETA: 120s)..."
"Creating VFS structure..."
"Import complete"
// PDF Import Messages
"Detecting format..."
"Loading PDF document..."
"Processing page 5 of 23"
"Extracting entities from pdf (2.5 pages/sec, ETA: 30s)..."
"Creating VFS structure..."
"Import complete"
// Excel Import Messages
"Detecting format..."
"Loading Excel workbook..."
"Reading sheet: Sales (2/5)"
"Extracting entities from excel (120 rows/sec, ETA: 45s)..."
"Creating VFS structure..."
"Import complete"
```
**Key Point:** You can display `progress.message` directly in your UI **without parsing it**. It's always human-readable and contextually appropriate.
---
## ✅ The 5 Standard Stages (Same for ALL Formats)
Every import goes through these 5 stages in order:
| Stage | Duration | Description | Fields Available |
|-------|----------|-------------|------------------|
| **detecting** | ~1% | Format detection | `stage`, `message` |
| **extracting** | ~70% | Parse file + AI extraction | `stage`, `message`, `processed`, `total`, `entities`, `relationships`, `throughput`, `eta` |
| **storing-vfs** | ~5% | Create file structure | `stage`, `message` |
| **storing-graph** | ~20% | Create graph nodes | `stage`, `message`, `entities`, `relationships` |
| **complete** | ~1% | Finalize | `stage`, `message`, `entities`, `relationships` |
**These 5 stages are the same whether you're importing:**
- A 10MB CSV file with 50,000 rows
- A 200-page PDF document
- A 5-sheet Excel workbook
- A nested JSON structure
- A Markdown document
- A YAML configuration
- A DOCX document
---
## 🎯 Why This Matters
### Build Tools That Work for Everything
```typescript
// ONE progress handler for your entire application
function universalProgressHandler(progress: ImportProgress) {
// Update UI (works for all formats)
updateProgressBar(progress)
updateStatusText(progress.message)
updateCounts(progress.entities, progress.relationships)
// Log to analytics (works for all formats)
analytics.track('import_progress', {
stage: progress.stage,
processed: progress.processed,
total: progress.total
})
// Send to monitoring (works for all formats)
monitoring.gauge('import.entities', progress.entities)
monitoring.gauge('import.throughput', progress.throughput)
}
// Use it everywhere
await brain.import(csvFile, { onProgress: universalProgressHandler })
await brain.import(pdfFile, { onProgress: universalProgressHandler })
await brain.import(excelFile, { onProgress: universalProgressHandler })
await brain.import(jsonFile, { onProgress: universalProgressHandler })
```
### No Format Detection Needed
```typescript
// ❌ DON'T DO THIS (format-specific handling)
if (format === 'csv') {
// CSV-specific progress code
} else if (format === 'pdf') {
// PDF-specific progress code
} else if (format === 'excel') {
// Excel-specific progress code
}
// ✅ DO THIS (universal handling)
onProgress: (p) => {
// Works for ALL formats!
updateUI(p.stage, p.message, p.entities, p.relationships)
}
```
---
## 📝 Summary
**100% Standardized** - Same `ImportProgress` interface for all 7 formats
**Build Once** - Your progress UI works for CSV, PDF, Excel, JSON, Markdown, YAML, DOCX
**No Format Detection** - No need to check file type in your progress handler
**Human-Readable Messages** - Display `progress.message` directly, no parsing needed
**Standard Fields** - `stage`, `processed`, `total`, `entities`, `relationships` work everywhere
**Optional Enhancements** - `throughput`, `eta` available during extraction (all formats)
**Developers can now build monitoring tools, dashboards, CLIs, and UIs that work perfectly for all import formats with zero format-specific code!**

View file

@ -1,162 +0,0 @@
---
title: Storage Adapters
slug: guides/storage-adapters
public: true
category: guides
template: guide
order: 2
description: "Two adapters cover every deployment: in-memory for tests + ephemeral workloads, filesystem for everything that needs to persist. Both share one on-disk contract, including generational history and snapshots. Cloud backup is operator tooling, not a built-in adapter."
next:
- guides/plugins
- concepts/consistency-model
---
# Storage Adapters
Brainy 8.0 ships **two storage adapters**:
- **`FileSystemStorage`** — persistent on-disk storage. The default for any
deployment that needs to survive a restart. Runs on Node.js, Bun, and Deno.
- **`MemoryStorage`** — in-memory only. The right choice for tests, ephemeral
workloads, and short-lived demos.
Both implement the same `StorageAdapter` interface, support the full Db API
(generational history, snapshots, restore — see the
[consistency model](../concepts/consistency-model.md)), and use the same
on-disk layout (memory's "disk" is a JS Map).
## Quick start
```ts
import { Brainy } from '@soulcraft/brainy'
// Filesystem (recommended for any persistent workload):
const brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
})
// Memory (tests, ephemeral):
const brainMem = new Brainy({ storage: { type: 'memory' } })
// Auto-detect (filesystem on Node-like runtimes, memory in browsers):
const brainAuto = new Brainy({ storage: { type: 'auto' } })
```
## When to use which
| Use case | Adapter | Why |
|---|---|---|
| Production app | `filesystem` | Durable, snapshot-able, mmap-able |
| Tests, CI | `memory` | No disk teardown; fast |
| Short-lived data pipeline | `memory` | No persistence needed |
| In-browser demo | `memory` | Filesystem unavailable in browsers |
| Cloud deployment | `filesystem` on local disk + operator backup | See "Cloud backup" below |
## Cloud backup — operator tooling, not a built-in
Brainy 8.0 deliberately ships **no cloud storage adapters**. Cloud backup is
handled at the operator layer with standard tooling, the same pattern every
production database uses (Postgres, SQLite, Redis):
```bash
# After a brainy flush, sync the on-disk artefact to your cloud of choice.
gsutil rsync -r /var/lib/brainy gs://my-backup-bucket/brainy/
# or:
aws s3 sync /var/lib/brainy s3://my-backup-bucket/brainy/
# or:
rclone sync /var/lib/brainy remote:brainy-backups/
# or:
azcopy sync /var/lib/brainy "https://account.blob.core.windows.net/brainy?sv=..."
```
Brainy's filesystem layout is sync-friendly:
- Atomic writes (temp + rename) — readers never see torn files
- Per-shard files — `rsync`-style incremental sync works well
- Immutable generation records (`_generations/`) — append-only, cache-friendly
For point-in-time backups, take a filesystem snapshot (ZFS, btrfs, LVM, EBS,
etc.) or use `brain.now().persist(path)` to write a self-contained snapshot
you can sync independently of the live brain — see
[Snapshots & Time Travel](./snapshots-and-time-travel.md).
## Why no cloud adapters in 8.0?
Cloud storage adapters lived in Brainy 4.x-7.x. They were dropped in 8.0
because:
- Zero production consumers used them at scale — every known production
deployment ran on local filesystem.
- Cloud-storage HNSW / DiskANN doesn't perform — vector indexes need
low-latency random reads that S3 / GCS / R2 / Azure can't provide
consistently.
- Bundling cloud SDKs into the library cost ~3000-5000 LOC + 4-7 transitive
dependencies for a feature nobody used.
- Cloud backup via operator tooling is strictly more reliable than in-app
upload (better retry semantics, better observability, better cost control).
Brainy 8.0 is smaller, faster to install, and clearer about what it does.
## Configuration
```ts
// BrainyConfig['storage'] — either a config object or a pre-constructed adapter:
storage?:
| {
// The adapter type. Optional — a top-level `path` implies 'filesystem',
// so { path: '/data' } works without it. Defaults to 'auto'
// (filesystem on Node-like runtimes, memory otherwise).
type?: 'auto' | 'memory' | 'filesystem'
// CANONICAL directory for filesystem storage. The rest of the API already
// speaks `path` (persist(path), Brainy.load(path), asOf(path),
// restore(path)). Specifying it implies type: 'filesystem'. Passed through
// to storage factories, including plugin-provided ones.
path?: string
// REMOVED in 8.0 — passing `rootDirectory` THROWS. Use `path`.
rootDirectory?: string
// REMOVED in 8.0 — a nested `options.{path,rootDirectory}` THROWS. Use `path`.
options?: any
}
| StorageAdapter // e.g. storage: new MemoryStorage()
```
The canonical — and only — key is the top-level **`path`**. The pre-8.0 aliases
`rootDirectory`, `options.{path,rootDirectory}`, and
`fileSystemStorage.{path,rootDirectory}` were **removed in 8.0**: passing one now
throws with the exact rename (never a silent default that would misplace data on
upgrade). Because `path` implies filesystem, `{ path: '/data' }` is a complete
config; the `type` is optional.
## Direct construction
If you want to skip the factory:
```ts
import { FileSystemStorage, MemoryStorage } from '@soulcraft/brainy'
const fsStorage = new FileSystemStorage('./brainy-data')
const memStorage = new MemoryStorage()
const brain = new Brainy({ storage: fsStorage })
```
## Migration from 7.x cloud adapters
7.x consumers of `OPFSStorage`, `GcsStorage`, `R2Storage`, `S3CompatibleStorage`,
or `AzureBlobStorage` need to migrate to `FileSystemStorage` plus operator
backup tooling. The recipe:
1. On the host running Brainy, mount a local disk (NVMe recommended). Cloud
providers all expose persistent local disks: GCP Persistent Disk, AWS EBS,
Azure Managed Disks.
2. Set `storage: { type: 'filesystem', path: '/mnt/brainy-data' }`.
3. Run your existing data import once into the new local store.
4. Set up an operator backup job using `gsutil` / `aws s3` / `rclone` /
`azcopy` on a cron — hourly or whatever your RPO requires. Point it at
the brainy data dir.
5. For point-in-time backups, use filesystem snapshots or
`brain.now().persist(path)`.
Same data, same APIs, no library-side cloud code.

View file

@ -1,395 +0,0 @@
# 🌊 Streaming Imports
> **All imports stream by default - query data as it's imported**
Brainy imports always use streaming architecture with progressive index flushing, enabling you to query data while it's being imported.
---
## How It Works
Every import streams with adaptive flush intervals:
```typescript
await brain.import(file, {
onProgress: async (progress) => {
// Query data during import
if (progress.queryable) {
const products = await brain.find({ type: 'product', limit: 1000 })
console.log(`${products.length} products imported so far...`)
}
}
})
```
**Benefits**:
- ✅ **Progressive queries**: Data queryable as import proceeds
- ✅ **Crash resilient**: Partial imports survive server restarts
- ✅ **Live monitoring**: Real-time progress with actual data counts
- ✅ **Zero configuration**: Works optimally out of the box
---
## Progressive Flush Intervals
Brainy automatically adjusts flush intervals **as the import progresses**, based on current entity count:
| Current Count | Flush Interval | Reason |
|---------------|----------------|--------|
| 0-999 entities | Every 100 | Frequent early updates for better UX |
| 1K-9.9K entities | Every 1000 | Balanced performance/responsiveness |
| 10K+ entities | Every 5000 | Performance focused, minimal overhead |
**Example**: Importing 5,000 entities
- Flushes at: 100, 200, ..., 900 (9 flushes with interval=100)
- At entity #1000: Interval adjusts to 1000
- Flushes at: 1000, 2000, 3000, 4000, 5000 (5 more flushes)
- Total flushes: 14
- Overhead: ~700ms (~0.14% of import time for 5K entities)
**Why Progressive?**
- ✅ Works with known totals (file imports)
- ✅ Works with unknown totals (streaming APIs, database cursors)
- ✅ Adapts automatically as import grows
- ✅ No configuration needed
### 🎯 Engineering Insight: Why This Is Advanced
Most import systems use either:
1. **Fixed intervals** (simple but inefficient for large imports)
2. **Adaptive intervals** (efficient but requires knowing total count upfront)
Brainy uses **progressive intervals** which combine the best of both:
```typescript
// Traditional approach (requires total count)
const interval = total < 1000 ? 100 : (total < 10000 ? 1000 : 5000)
// Brainy's approach (works with unknown totals)
const interval = getProgressiveInterval(currentCount)
// Adjusts dynamically: 100 → 1000 → 5000 as import grows
```
**Real-World Impact**:
- **Known totals** (files): Optimal performance automatically
- **Unknown totals** (APIs): Still works perfectly - adjusts on the fly
- **Growing datasets**: UX-focused early (frequent updates), performance-focused later
- **Zero overhead** decisions: Algorithm adapts, developer configures nothing
This makes Brainy the **only import system** that:
- ✅ Optimizes automatically without configuration
- ✅ Works for both batch and streaming scenarios
- ✅ Balances UX and performance dynamically
- ✅ Scales from 10 to 10 million entities seamlessly
---
## Architecture
```
Import Process (Always Streaming)
├─ For each entity:
│ ├─ Extract from source
│ ├─ Classify type (SmartExtractor)
│ ├─ Write to storage ← IMMEDIATE
│ ├─ Update in-memory indexes
│ └─ entitiesSinceFlush++
├─ When entitiesSinceFlush >= interval:
│ ├─ brain.flush() ← Write indexes to disk
│ ├─ onProgress({ queryable: true })
│ └─ entitiesSinceFlush = 0
└─ Final flush at end
```
### Key Insight
Entities write to storage **immediately** on creation. Flushing only writes the search indexes:
- **Metadata Index** → Fast filtering by type, fields
- **Graph Adjacency Index** → Fast relationship traversal
- **Storage Counts** → Type statistics
**Without flush**: Entities exist but queries are slow (full table scans)
**With periodic flush**: Entities exist AND queries are fast (index lookups)
---
## Use Cases
### Use Case 1: Live Import Dashboard
Show real-time progress with queryable data:
```typescript
const stats = {
total: 0,
byType: {} as Record<string, number>
}
await brain.import(largeCSV, {
onProgress: async (progress) => {
stats.total = progress.entities || 0
// Only query after flush
if (progress.queryable) {
const products = await brain.find({ type: 'product', limit: 10000 })
const people = await brain.find({ type: 'person', limit: 10000 })
stats.byType = {
product: products.length,
person: people.length
}
// Update UI
websocket.send({ stage: progress.stage, stats })
}
}
})
```
**Output**:
```
Importing products.csv...
━━━━━━━━━━━━━━━━░░░░░░░░░░ 60%
Products: 12,453
People: 2,871
Queryable: ✅
```
---
### Use Case 2: Progress Bar with Live Counts
```typescript
import { ProgressBar } from 'cli-progress'
const progressBar = new ProgressBar.SingleBar({
format: 'Importing |{bar}| {percentage}% | {stats}'
})
await brain.import(file, {
onProgress: async (progress) => {
if (progress.stage === 'storing-graph' && progress.total) {
if (!progressBar.getProgress()) {
progressBar.start(progress.total, 0, { stats: '' })
}
let stats = `${progress.entities || 0} entities`
// Add queryable count after flush
if (progress.queryable) {
const all = await brain.find({ limit: 100000 })
stats += ` (${all.length} queryable)`
}
progressBar.update(progress.processed || 0, { stats })
}
if (progress.stage === 'complete') {
progressBar.stop()
}
}
})
```
---
### Use Case 3: Conditional Processing
Make decisions during import based on imported data:
```typescript
let shouldImportPricing = false
await brain.import(catalogCSV, {
onProgress: async (progress) => {
if (progress.queryable && progress.processed! > 1000) {
// Check if we have enough products
const products = await brain.find({ type: 'product', limit: 20000 })
if (products.length > 10000 && !shouldImportPricing) {
console.log(`Found ${products.length} products - will import pricing next`)
shouldImportPricing = true
}
}
}
})
// Conditionally import related data
if (shouldImportPricing) {
await brain.import(pricingCSV)
}
```
---
## Performance
### Benchmarks
| Import Size | Total Time | Flush Overhead | % Overhead |
|-------------|------------|----------------|------------|
| 1K entities | 1.5s | +5ms | 0.3% |
| 10K entities | 15s | +50ms | 0.3% |
| 100K entities | 150s | +500ms | 0.3% |
| 1M entities | 1500s | +5s | 0.3% |
**Conclusion**: Streaming overhead is negligible (~0.3%) for the benefits gained.
### Performance Tips
**1. Limit Query Results**
```typescript
// ❌ Bad: Fetch all entities (slow for large imports)
onProgress: async (p) => {
if (p.queryable) {
const all = await brain.find({}) // Could be 100K+ entities!
}
}
// ✅ Good: Limit results or query specific types
onProgress: async (p) => {
if (p.queryable) {
const count = await brain.find({ type: 'product', limit: 10000 }).then(r => r.length)
}
}
```
**2. Only Query When Needed**
```typescript
// ❌ Bad: Query on every progress event
onProgress: async (p) => {
const all = await brain.find({ limit: 10000 }) // Runs 100+ times!
}
// ✅ Good: Only query after flush
onProgress: async (p) => {
if (p.queryable) {
const all = await brain.find({ limit: 10000 }) // Runs ~10 times
}
}
```
**3. Disable Features You Don't Need**
```typescript
await brain.import(file, {
enableNeuralExtraction: false, // 10x faster
enableRelationshipInference: false, // 5x faster
enableConceptExtraction: false // 2x faster
})
```
---
## API Reference
### ImportProgress
```typescript
interface ImportProgress {
stage: 'detecting' | 'extracting' | 'storing-vfs' | 'storing-graph' | 'complete'
message: string
processed?: number // Current item number
total?: number // Total items
entities?: number // Entities extracted so far
relationships?: number // Relationships inferred so far
/**
* Whether data is queryable
*
* true = Indexes flushed, queries will be fast and complete
* false/undefined = Data in storage but indexes not flushed yet
*/
queryable?: boolean
}
```
### brain.flush()
Manually flush indexes to disk:
```typescript
// Add many entities
for (const entity of entities) {
await brain.add(entity)
}
// Flush indexes to make queryable
await brain.flush()
// Now queries will be fast
const results = await brain.find({ type: 'product', limit: 1000 })
```
**Performance**: ~5-50ms per flush (depends on index size)
**What Gets Flushed**:
- Metadata index (field indexes + EntityIdMapper)
- Graph adjacency index (relationship cache)
- Storage adapter counts (type statistics)
**What Doesn't Get Flushed** (already persisted):
- Entities (written immediately on `add()`)
- Relationships (written immediately on `relate()`)
---
## Troubleshooting
### Q: Queries during import are slow
**A:** Only query when `queryable === true`:
```typescript
onProgress: async (p) => {
// ✅ Good
if (p.queryable) {
const results = await brain.find({ type: 'product', limit: 1000 })
}
// ❌ Bad - queries before flush are slow
const results = await brain.find({ type: 'product', limit: 1000 })
}
```
### Q: How often does data flush?
**A:** Progressively adjusts based on current entity count:
- 0-999 entities: Every 100 entities
- 1K-9.9K: Every 1000 entities
- 10K+: Every 5000 entities
The interval increases automatically as more data is imported. Check console output to see when intervals adjust.
---
## Migration from v3.x/v4.0/v4.1
No changes required! Streaming is now always enabled with optimal defaults:
```typescript
// Before (v3.x, v4.0, v4.1): Works the same
await brain.import(file)
// After: Streaming always on, zero config
await brain.import(file)
```
The `flushInterval` option has been removed in favor of automatic progressive intervals that adjust dynamically as the import proceeds.
---
## Further Reading
- [Import Flow Guide](./import-flow.md) - Complete import pipeline explanation
- [Import Quick Reference](./import-quick-reference.md) - API cheat sheet
- [VFS Guide](./vfs-guide.md) - Virtual file system organization
---
**Questions?** Check the [FAQ](../faq.md) or [open an issue](https://github.com/soulcraft/brainy/issues)!

View file

@ -1,575 +0,0 @@
---
title: Subtypes & Facets
slug: guides/subtypes-and-facets
public: true
category: guides
template: guide
order: 7
description: Use the top-level `subtype` field to sub-classify entities within a NounType, track other metadata facets like status or role, and migrate field names without downtime.
next:
- guides/aggregation
- api/reference
---
# Subtypes & Facets
> Sub-classify entities within a NounType, track arbitrary metadata facets, and migrate field names without downtime.
## Why this exists
Brainy's `NounType` (Person, Document, Event, Concept, Task, …) is a stable, flat 42-type taxonomy. It's deliberately coarse — every product needs a way to further classify entities *within* a type. Is this `Person` an employee or a customer? Is this `Document` an invoice or a contract? Is this `Event` a meeting or a milestone?
Three layers solve this:
| Layer | Use it for | Shape |
|---|---|---|
| **`subtype`** | The primary sub-classification of an entity, one value per entity | Top-level standard field |
| **`trackField()`** | Other facets you want to count or filter on (`status`, `source`, `role`, `paradigm`) | Registered metadata field |
| **`migrateField()`** | Renaming or restructuring fields across an existing dataset | One-shot stream-and-rewrite |
## Layer 1 — `subtype`
`subtype` is a top-level standard field on every entity, alongside `type` / `confidence` / `weight`. Flat string, no hierarchy. The vocabulary is your choice — Brainy stores and counts, never validates.
### Write
```typescript
import { Brainy, NounType } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
await brain.add({
data: 'Avery Brooks — runs the AI lab',
type: NounType.Person,
subtype: 'employee', // top-level write param
metadata: { department: 'ai-lab' }
})
```
### Read
```typescript
// Top-level filter — standard-field fast path, no `where` wrapper:
const employees = await brain.find({ type: NounType.Person, subtype: 'employee' })
// Set membership:
const internal = await brain.find({
type: NounType.Person,
subtype: ['employee', 'contractor']
})
// Operator-form predicates use `where`:
const typed = await brain.find({
type: NounType.Person,
where: { subtype: { exists: true } }
})
```
### What it looks like
```json
{
"id": "01HZK3M7TW...",
"type": "person",
"subtype": "employee",
"data": "Avery Brooks — runs the AI lab",
"metadata": { "department": "ai-lab" }
}
```
`subtype` lives at the **top level** — NOT inside `metadata`, NOT inside `data`. That's what makes the fast path possible: queries on `subtype` hit the column-store index directly, never the metadata fallback.
### Counts (O(1))
```typescript
// All subtypes for a NounType
brain.counts.bySubtype(NounType.Person)
// → { employee: 12, customer: 847, vendor: 34 }
// Point count
brain.counts.bySubtype(NounType.Person, 'employee')
// → 12
// Top N
brain.counts.topSubtypes(NounType.Person, 3)
// → [['customer', 847], ['employee', 12], ['vendor', 34]]
// Distinct subtypes for a NounType
brain.subtypesOf(NounType.Person)
// → ['customer', 'employee', 'vendor']
```
These are O(1) lookups backed by `_system/subtype-statistics.json` — no scan, no storage round-trip. The rollup is incrementally maintained as entities are added, updated, and deleted.
### Aggregation
`subtype` is a first-class group-by dimension:
```typescript
const rows = await brain.find({
aggregate: {
groupBy: ['type', 'subtype'],
metrics: { count: { op: 'COUNT' } }
}
})
// [
// { groupKey: { type: 'person', subtype: 'customer' }, count: 847 },
// { groupKey: { type: 'person', subtype: 'employee' }, count: 12 },
// { groupKey: { type: 'document', subtype: 'invoice' }, count: 2103 },
// ...
// ]
```
## Layer 2 — `trackField()`
Some metadata fields aren't *the* sub-classification of an entity but are still worth counting: `status`, `source`, `role`, `paradigm`. Promoting each one to top-level would clutter the contract. `trackField()` registers a metadata field for cardinality + per-NounType breakdown stats without the contract growth.
### Register and query
```typescript
// Track a single facet
brain.trackField('status')
await brain.add({ data: 'Ship subtype', type: NounType.Task, metadata: { status: 'todo' } })
await brain.add({ data: 'Write docs', type: NounType.Task, metadata: { status: 'done' } })
await brain.counts.byField('status')
// → { todo: 1, done: 1 }
```
### Per-NounType breakdown
```typescript
brain.trackField('status', { perType: true })
await brain.counts.byField('status', { type: NounType.Task })
// → { todo: 1, done: 1 }
```
### Vocabulary whitelist (opt-in validation)
```typescript
brain.trackField('priority', { values: ['low', 'medium', 'high'] })
// Throws — 'urgent' isn't in the vocabulary:
await brain.add({
data: 'Fix bug',
type: NounType.Task,
metadata: { priority: 'urgent' }
})
```
`trackField` piggybacks on the existing aggregation engine (see the [Aggregation guide](./aggregation.md) for the underlying mechanism). Backfill-on-define means the first call to `counts.byField()` scans existing entities; subsequent calls are O(groups).
### subtype vs trackField — when to use which
- **`subtype`** when there's one primary sub-classification per entity. Limit yourself to one per NounType. Examples: Person→employee/customer/vendor; Document→invoice/contract/policy.
- **`trackField`** for anything else you want to count or filter on. No limit on how many you register. Examples: status, source, role, paradigm.
## Layer 3 — `migrateField()`
Use this when you need to rename or restructure a field across an entire dataset — for example, moving a `metadata.kind` convention up to the top-level `subtype` standard field.
### One-shot rewrite
```typescript
// Starting state: every entity has metadata.kind
const result = await brain.migrateField({
from: 'metadata.kind',
to: 'subtype'
})
console.log(result)
// {
// scanned: 1500,
// migrated: 1500,
// skipped: 0,
// errors: []
// }
```
After this returns, every entity has `subtype` populated from the old `metadata.kind` value, and `metadata.kind` is cleared.
### Deprecation window — keep both fields readable
When you can't coordinate all readers and the migration in a single deploy, use `readBoth: true` to preserve the source field alongside the new one:
```typescript
// Phase 1: dual-populate (existing readers still work against metadata.kind):
await brain.migrateField({
from: 'metadata.kind',
to: 'subtype',
readBoth: true
})
// ... readers migrate to query subtype at their own pace ...
// Phase 2: clear the source field when ready:
await brain.migrateField({ from: 'metadata.kind', to: 'subtype' })
```
### Supported paths
| Path form | Refers to |
|---|---|
| `'subtype'`, `'type'`, `'confidence'` | Top-level standard fields |
| `'metadata.X'` | A key under `entity.metadata` |
| `'data.X'` | A key under `entity.data` (when `data` is an object) |
| `'X'` (bare, non-standard) | Shorthand for `metadata.X` |
### Idempotent
`migrateField` is safe to re-run. Entities where the source is absent, or where the destination already holds the same value, are skipped. This makes it safe to use in a deploy-once-then-cleanup workflow.
### Progress reporting
```typescript
await brain.migrateField({
from: 'metadata.kind',
to: 'subtype',
batchSize: 500,
onProgress: ({ scanned, migrated }) => {
console.log(`${scanned} scanned, ${migrated} migrated`)
}
})
```
## Putting it together
A realistic adoption sequence for a brain that started without these primitives:
```typescript
import { Brainy, NounType } from '@soulcraft/brainy'
const brain = new Brainy({ storage: { type: 'filesystem', path: './brain-data' } })
await brain.init()
// 1. Migrate any existing metadata.kind convention to the new top-level subtype
await brain.migrateField({ from: 'metadata.kind', to: 'subtype', readBoth: true })
// 2. Register the other facets you want counted
brain.trackField('status', { perType: true })
brain.trackField('source')
// 3. Use subtype on every new write
await brain.add({
data: 'Quarterly review',
type: NounType.Event,
subtype: 'milestone',
metadata: { status: 'todo', source: 'planning-session' }
})
// 4. Query the breakdowns
brain.counts.bySubtype(NounType.Event)
// → { milestone: 14, meeting: 203, deadline: 7 }
await brain.counts.byField('status', { type: NounType.Event })
// → { todo: 12, done: 212 }
// 5. Once all readers are on the new field, drop the source:
await brain.migrateField({ from: 'metadata.kind', to: 'subtype' })
```
## Layer V — `subtype` on relationships (verbs)
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, indexed on the fast path.
### Write
```typescript
const ceoId = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'Avery' })
const vpId = await brain.add({ type: NounType.Person, subtype: 'employee', data: 'Jordan' })
const matrixId = await brain.add({ type: NounType.Person, subtype: 'contractor', data: 'Sam' })
await brain.relate({
from: ceoId,
to: vpId,
type: VerbType.ReportsTo,
subtype: 'direct'
})
await brain.relate({
from: ceoId,
to: matrixId,
type: VerbType.ReportsTo,
subtype: 'dotted-line'
})
```
### Read & filter
```typescript
// Direct reports — fast path filter (column-store hit, not metadata fallback)
const direct = await brain.related({
from: ceoId,
type: VerbType.ReportsTo,
subtype: 'direct'
})
// Set membership
const allReports = await brain.related({
from: ceoId,
type: VerbType.ReportsTo,
subtype: ['direct', 'dotted-line']
})
```
### Update (new — `updateRelation()`)
Verbs previously had no update method — the only way to change a relationship was delete-then-recreate. 7.30 closes that gap:
```typescript
// Promote a dotted-line report to direct without losing the edge id
await brain.updateRelation({ id: relationId, subtype: 'direct' })
// Or change weight/confidence
await brain.updateRelation({ id: relationId, weight: 0.5, confidence: 0.9 })
```
### Traversal filter
`find({ connected, subtype })` filters traversal edges by their subtype. Composes with `via` (verb-type filter):
```typescript
// All direct reports two hops deep (will be supported in Cor; for now depth-1)
const directChain = await brain.find({
connected: {
from: ceoId,
via: VerbType.ReportsTo,
subtype: 'direct',
depth: 1
}
})
```
Multi-hop subtype filtering (`depth > 1`) lights up on the Cor native path; the JS path throws today rather than return incorrect partial results.
### Counts
Same shape as the noun-side counts API:
```typescript
brain.counts.byRelationshipSubtype(VerbType.ReportsTo)
// → { direct: 12, 'dotted-line': 3 }
brain.counts.byRelationshipSubtype(VerbType.ReportsTo, 'direct') // O(1) point
// → 12
brain.counts.topRelationshipSubtypes(VerbType.ReportsTo, 3)
// → [['direct', 12], ['dotted-line', 3]]
brain.relationshipSubtypesOf(VerbType.ReportsTo)
// → ['direct', 'dotted-line']
```
These are O(1) lookups backed by the persisted `_system/verb-subtype-statistics.json` rollup — same self-heal machinery as the noun-side rollup.
### Migrate verb fields
`migrateField()` now walks verbs too:
```typescript
// Migrate verb-side metadata.kind → top-level subtype
await brain.migrateField({
from: 'metadata.kind',
to: 'subtype',
entityKind: 'verb'
})
// Or migrate both nouns and verbs in one pass
await brain.migrateField({
from: 'metadata.kind',
to: 'subtype',
entityKind: 'both'
})
```
Default is `entityKind: 'noun'` (backward-compatible with 7.29).
## Enforcement — `requireSubtype()` + brain-wide strict mode
By default (7.30) subtype is optional. Two complementary opt-in mechanisms let you enforce the pairing of type + subtype on every write:
### Per-type registration
Mark a specific `NounType` or `VerbType` as requiring a subtype, optionally with a fixed vocabulary:
```typescript
brain.requireSubtype(NounType.Person, {
values: ['employee', 'customer', 'vendor'],
required: true
})
brain.requireSubtype(VerbType.ReportsTo, {
values: ['direct', 'dotted-line'],
required: true
})
// Now this throws — Person requires a subtype:
await brain.add({ type: NounType.Person, data: 'no subtype' })
// And this throws — 'matrix' isn't in the registered vocabulary:
await brain.relate({
from: a,
to: b,
type: VerbType.ReportsTo,
subtype: 'matrix'
})
```
### Brain-wide strict mode
Enforce on every write across the whole brain:
```typescript
const brain = new Brainy({ requireSubtype: true })
// Allow specific types to omit subtype (e.g. catch-all `Thing`)
const brain2 = new Brainy({
requireSubtype: { except: [NounType.Thing, NounType.Custom] }
})
```
When strict mode is on:
- Every `add()` / `addMany()` / `update()` / `relate()` / `relateMany()` / `updateRelation()` rejects writes missing a subtype on a non-exempt type.
- `addMany()` and `relateMany()` validate every item BEFORE any storage write — atomic-fail semantics, no partial writes.
- Per-type rules registered via `requireSubtype()` compose with the brain-wide flag; specific rules win when both apply.
- Brainy's own internal writes (VFS root, VFS directories, VFS file entities) bypass enforcement via the `metadata.isVFSEntity: true` infrastructure marker.
### Migrating to strict mode on an existing brain
`brain.migrateField()` is your friend — populate `subtype` from an existing convention before flipping strict mode on:
```typescript
// Step 1: backfill subtype from your existing metadata.kind convention
await brain.migrateField({
from: 'metadata.kind',
to: 'subtype',
entityKind: 'both'
})
// Step 2: register the vocabulary
brain.requireSubtype(NounType.Person, {
values: ['employee', 'customer', 'vendor'],
required: true
})
// Step 3: future writes must have a subtype matching the vocabulary
await brain.add({ type: NounType.Person, subtype: 'employee', data: '...' })
```
## Strict mode in practice (for SDK-style vocabulary consumers)
When a platform layer like the Soulcraft SDK registers `requireSubtype()` rules on behalf of every consumer's brain, every downstream product that calls `brain.add()` / `brain.relate()` against those types must pass a matching `subtype`. Skipping the field — or passing one outside the registered vocabulary — throws at the boundary.
This pattern is powerful but surfaces a class of latent bug: any `brain.add()` call site that was written before strict-mode adoption starts rejecting writes. A representative production incident: a booking flow started returning 500 on every request because a service method called `brain.add({ type: NounType.Event, ... })` without subtype, and an SDK layer had just registered `requireSubtype()` for `NounType.Event` on every brain instance.
The fix is a four-step migration recipe — and Brainy 7.30.1+ ships diagnostic tools to make it deterministic.
### Migration recipe
1. **Inventory the gap with `brain.audit()`** — returns the deterministic list of which NounTypes and VerbTypes have entities/relationships missing subtype, grouped by type:
```typescript
const report = await brain.audit()
// {
// entitiesWithoutSubtype: { event: 24, document: 3, ... },
// relationshipsWithoutSubtype: { relatedTo: 1402 },
// total: 1429,
// scanned: 8400,
// recommendation: 'Found 1429 entries without subtype. ...'
// }
```
By default, VFS infrastructure entities are excluded (they bypass enforcement anyway via the `metadata.isVFSEntity` marker). Pass `{ includeVFS: true }` to surface them too.
2. **Bulk-migrate any existing convention** with `brain.migrateField()` if a legacy field can be lifted:
```typescript
// Common pattern: subtype mirrors a discriminator field already in metadata
await brain.migrateField({
from: 'metadata.entityType',
to: 'subtype',
readBoth: true // safety: keep the source field readable during cutover
})
```
3. **Hand-fix the remaining call sites.** The exact list is in `report.entitiesWithoutSubtype`. For each call site, add `subtype: '<value>'` to the `brain.add()` / `brain.relate()` params. Choose a stable convention (e.g. mirror `metadata.entityType` if you have one; any rule that's deterministic from the data works).
4. **Verify with `brain.audit()` again.** Re-run; total should be `0`. If you turn on brain-wide strict mode at this point, all future writes are protected.
### Brainy's own infrastructure subtype labels (reference)
Brainy's internal write paths set subtype on every entity and edge they create. Consumers don't need to do anything for these — they're documented here so you understand the data shape:
| Code path | NounType / VerbType | Subtype label |
|---|---|---|
| VFS root directory `/` | `NounType.Collection` | `'vfs-root'` |
| VFS subdirectories | `NounType.Collection` | `'vfs-directory'` |
| VFS files | (mime-driven, e.g. `Document`/`Code`/`Image`) | `'vfs-file'` |
| VFS symlinks | `NounType.File` | `'vfs-symlink'` |
| VFS Contains edges | `VerbType.Contains` | `'vfs-contains'` |
| Aggregation materialized output | `NounType.Measurement` | `'materialized-aggregate'` |
| Import-document provenance entity | `NounType.Document` | `'import-source'` |
| Importer-extracted entities (no caller default) | extractor-driven | `'imported'` |
| Importer placeholder targets | `NounType.Thing` | `'import-placeholder'` |
| Neural extraction (no caller default) | extractor-driven | `'extracted'` |
| GoogleSheets API entity writes | request-driven | `'imported-from-sheets'` |
| OData API entity writes | request-driven | `'imported-from-odata'` |
| MCP client message storage | `NounType.Message` | `'mcp-message'` |
| `brainy add` CLI (no `--subtype` flag) | user-supplied type | `'cli-add'` |
| `brainy relate` CLI (no `--subtype` flag) | user-supplied verb | `'cli-relate'` |
You can query these directly: `await brain.find({ subtype: 'vfs-file' })` returns every VFS-managed file regardless of NounType. `await brain.counts.bySubtype(NounType.Document)` shows you the import-source / imported / extracted / vfs-file breakdown.
Importer and extraction paths accept a caller-supplied `defaultSubtype` option so you can tag a whole batch with your own provenance label (e.g. `'customer-upload-2026q2'`) instead of the Brainy default `'imported'` / `'extracted'`.
### Looking ahead — Brainy 8.0
Brainy 8.0 ships:
- **`brain.fillSubtypes(rules)`** — the bulk migration helper that pairs with `audit()`. Given caller-supplied rules per NounType / VerbType, it walks the brain and fills in missing subtypes via `update()`. Pre-8.0 brains run this once before upgrading to clear migration debt.
- **`subtype: string` (non-optional)** on `AddParams<T>` and `RelateParams<T>`. TypeScript catches missing subtype at compile time, not just runtime.
- **`new Brainy({ requireSubtype: true })` becomes the default.** Consumers explicitly opt out with `{ requireSubtype: false }` during migration.
7.30.1's `audit()` is the diagnostic; 8.0's `fillSubtypes()` is the bulk fixer. Together they close the migration gap deterministically.
## Reference
### Layer 1 — `subtype` (nouns)
- `brain.add({ ..., subtype: 'value' })` — write the field
- `brain.update({ id, subtype: 'value' })` — change the field
- `brain.find({ type, subtype })` — filter (fast path)
- `brain.find({ subtype: ['a', 'b'] })` — set membership
- `brain.counts.bySubtype(type, subtype?)` — O(1) counts
- `brain.counts.topSubtypes(type, n?)` — top N by count
- `brain.subtypesOf(type)` — distinct subtype list
### Layer V — `subtype` (verbs / relationships)
- `brain.relate({ ..., subtype: 'value' })` — write the field
- `brain.updateRelation({ id, subtype, type?, weight?, ... })` — change a relationship
- `brain.related({ subtype })` — filter (fast path)
- `brain.related({ subtype: ['a', 'b'] })` — set membership
- `brain.find({ connected: { via, subtype, depth } })` — traversal filter
- `brain.counts.byRelationshipSubtype(verb, subtype?)` — O(1) counts
- `brain.counts.topRelationshipSubtypes(verb, n?)` — top N by count
- `brain.relationshipSubtypesOf(verb)` — distinct subtype list
### Layer 2 — generic facets
- `brain.trackField(name, { perType?, values? })` — register a facet
- `brain.counts.byField(name, { type? })` — facet counts
### Layer 3 — migration
- `brain.migrateField({ from, to, readBoth?, batchSize?, onProgress?, entityKind? })` — rewrite a field (nouns, verbs, or both)
### Enforcement
- `brain.requireSubtype(type, { values?, required })` — per-`NounType` / `VerbType` rule
- `new Brainy({ requireSubtype: true })` — brain-wide strict mode
- `new Brainy({ requireSubtype: { except: [type, ...] } })` — strict with exemptions

View file

@ -1,186 +0,0 @@
---
title: Upgrading from 7.x to 8.0
slug: guides/upgrading-7-to-8
public: true
category: guides
template: guide
order: 10
description: What the one-time 7→8 on-disk migration does, how 8.0 automatically recovers Virtual Filesystem content that older layouts stored in the removed copy-on-write area, and how to verify (or force) that recovery.
next:
- guides/storage-adapters
- guides/inspection
---
# Upgrading from 7.x to 8.0
Opening a 7.x on-disk store with Brainy 8.0 runs a **one-time, in-place layout
migration** the first time the store is opened. It is automatic (`autoMigrate`
defaults to `true`), it runs once, and it stamps a marker so every later open is
a no-op.
Almost everything about the upgrade is transparent: your entities, relationships,
metadata, and indexes migrate and rebuild without any action on your part. This
guide covers the **one case that needs attention** — Virtual Filesystem (VFS)
content — and how 8.0 recovers it for you.
## TL;DR
- **Just upgrade to `@soulcraft/brainy@8.0.12` (or later) and open the store.**
If a previous upgrade left VFS content stranded, 8.0.12 **heals it on open**,
with no operator action.
- Want to force or script it? Call **`await brain.vfs.adoptOrphanedBlobs()`**.
- The recovery is **non-destructive and idempotent** — it copies, never moves,
and running it twice is a no-op.
## What the migration does
The 7.x on-disk layout stored entities under a per-branch path
(`branches/<head>/entities/...`). 8.0 uses a flat layout (`entities/...`). On
first open, Brainy:
1. Collapses `branches/<head>/entities/*` into the flat `entities/*` layout.
2. Rebuilds the derived indexes (vector, graph, metadata) and count rollups from
the canonical entities.
3. Stamps `_system/migration-layout.json` so re-opening is a no-op.
4. Takes an automatic **pre-upgrade backup** of the directory before it starts,
and removes it once the upgrade is verified complete (see
[The safety net](#the-safety-net-pre-upgrade-backup) below).
The log line to expect (once per store):
```
[brainy] Migrating a 7.x branch layout (branches/main) to the 8.0 flat layout
in place — 13175 entity files. This runs once; back up the directory first if
you need a rollback (8.0 does not keep the old layout).
```
## The one thing that needs recovery: VFS content
If your application uses the Virtual Filesystem (VFS) to store file content (for
example, a CMS that keeps page documents at paths like
`/pages/homepage/page.json`), that content is held as **content blobs**.
7.x kept those blobs in the branch system's **copy-on-write area** (`_cow/`).
8.0 removed the branch/copy-on-write system, and its content blobs live in the
content-addressed store (`_cas/`). The layout migration moves entities — it does
**not** move the VFS content blobs. Left alone, a 7.x store's VFS blobs would
remain in `_cow/`, and a read of one would throw:
```
VFS: Cannot read blob for /pages/homepage/page.json:
Blob metadata not found: 6b87cfb71ad2f04602a5c157214dc42000...
```
### 8.0.12 recovers them automatically
Brainy 8.0.12 adds an **on-open recovery pass** that runs right after the layout
migration. It scans `_cow/`, and for every content blob whose `blob:` +
`blob-meta:` pair is not already in `_cas/`, it copies **both** across into the
8.0 store. It:
- **Heals a fresh 7→8 upgrade** (where `_cow/` still holds the blobs), **and**
- **Heals a store already upgraded by an earlier 8.0.x** that stranded them —
the recovery is gated on the presence of `_cow/` and its own marker, not on
the layout-migration marker, so an already-migrated store is still healed.
- Is a **cheap no-op** on a native-8.0 or fresh store (there is no `_cow/`), and
on a store already healed (a marker records completion so later opens skip the
scan).
So the operator action for a stranded store is simply: **upgrade to 8.0.12 and
open it.**
```ts
import { Brainy } from '@soulcraft/brainy'
// Opening the store is all that is required — recovery runs during init().
const brain = new Brainy({ storage: { type: 'filesystem', path: '/data/my-store' } })
await brain.init()
// The previously-failing read now succeeds.
const page = await brain.vfs.readFile('/pages/homepage/page.json')
```
On a store that needed recovery you will see:
```
[brainy] Recovered 337 VFS content blob(s) stranded by a 7→8 upgrade
(adopted _cow/ → _cas/ in place).
```
### Forcing recovery explicitly
If you would rather run the recovery deliberately (for example, in an upgrade
script that asserts a clean result before flipping traffic), call it directly:
```ts
const result = await brain.vfs.adoptOrphanedBlobs()
// → { cowBlobs, adopted, alreadyPresent, incomplete }
console.log(`adopted ${result.adopted}, already present ${result.alreadyPresent}`)
if (result.incomplete > 0) {
// One or more _cow/ blobs are missing their bytes or metadata — investigate
// _cow/ before discarding your own backup. See "The safety net" below.
}
```
`adoptOrphanedBlobs()` self-initializes the brain, so it is safe to call
immediately after construction. It returns:
| Field | Meaning |
| ---------------- | ---------------------------------------------------------------- |
| `cowBlobs` | Distinct content-blob hashes found in `_cow/`. |
| `adopted` | Newly copied into `_cas/` on this call. |
| `alreadyPresent` | Already in `_cas/` (a prior open or run adopted them). |
| `incomplete` | A `_cow/` blob missing its bytes *or* its metadata — **skipped** rather than half-adopted. |
## Verifying recovery
After opening under 8.0.12, confirm a previously-failing path reads:
```ts
const content = await brain.vfs.readFile('/pages/homepage/page.json')
console.log(content.toString().slice(0, 80))
```
If you scripted it with `adoptOrphanedBlobs()`, a clean result is
`incomplete === 0` and `adopted + alreadyPresent === cowBlobs`.
## The safety net: pre-upgrade backup
Brainy takes an automatic pre-upgrade backup and removes it once the upgrade is
**verified complete**. In 8.0.12 that verification includes VFS content: if the
blob recovery reports `incomplete > 0`, the completion marker is **not** stamped
(the next open retries) and the **pre-upgrade backup is retained** so you still
have a rollback while blobs remain unaccounted for. You will see:
```
[brainy] VFS blob recovery adopted N blob(s) but M orphaned _cow/ blob(s) are
missing their bytes or metadata and were left in place. The pre-upgrade backup
is being retained; inspect _cow/ before discarding it.
```
The recovery never deletes anything from `_cow/`, so the original blobs stay in
place for inspection or a manual rollback regardless.
## Who is affected
**Any 7.x store that used the VFS to store file content** (so it has a `_cow/`
area) is a candidate for stranded blobs on a 7→8 upgrade. Stores that never used
the VFS have no `_cow/` content blobs and are unaffected.
Because the recovery is **gated on the presence of `_cow/`**, it is
self-selecting and safe to roll out everywhere:
- On a store with stranded blobs → it adopts them.
- On a native-8.0, fresh, or non-VFS store → it is a no-op on the existence
check.
There is no configuration to set and nothing to opt into. Upgrading to 8.0.12
and opening each store is sufficient.
## Rollback
The recovery is copy-only, so no rollback of the recovery itself is ever needed.
If you need to roll back the **whole** 7→8 upgrade, restore the directory from
your pre-upgrade backup (retained automatically while recovery is incomplete, or
your own snapshot) and pin `@soulcraft/brainy@7.x`. 8.0 does not keep the old
branch layout in place, so a directory-level restore is the rollback path.

File diff suppressed because it is too large Load diff

View file

@ -1,696 +0,0 @@
# Neural Entity Extraction Guide
**Version:** 5.7.6+
**Status:** Production-Ready
**Performance:** ~15-20ms per extraction
---
## Overview
Brainy's neural extraction system uses a **4-signal ensemble architecture** to classify entities and relationships with high accuracy. The system is production-tested and handles 7 different document formats with format-specific intelligence.
### Key Components
1. **`brain.extractEntities()`** - Simplest API, use for 95% of cases
2. **`SmartExtractor`** - Direct entity type classifier (advanced)
3. **`SmartRelationshipExtractor`** - Relationship type classifier
4. **`NeuralEntityExtractor`** - Full extraction orchestrator
---
## Quick Start
### Method 1: Brain Instance (Recommended)
```typescript
import { Brainy, NounType } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Extract all entities
const entities = await brain.extractEntities('Sarah Chen founded Acme Corp')
// Returns (fast pattern + embedding ensemble; confidences are approximate):
// [
// { text: 'Sarah Chen', type: NounType.Person, confidence: 0.68 },
// { text: 'Acme Corp', type: NounType.Organization, confidence: 0.85 }
// ]
// Extract with filters
const people = await brain.extractEntities('...', {
types: [NounType.Person],
confidence: 0.8,
neuralMatching: true
})
```
> **What this is (and isn't).** `extractEntities` is a fast, dependency-free **heuristic
> ensemble** (regex/pattern + type-embedding similarity + context), not a trained NER model.
> Each candidate is typed by its own span — a type indicator in a neighbour's text (e.g. "Corp"
> in "Sarah Chen founded Acme Corp") will **not** bleed onto another candidate.
>
> **Known limitation:** a bare proper-noun place name with no structural cue (e.g. "New York",
> no comma, state code, or "in"/"at" preposition handling) can be typed as `Person` by the
> generic full-name pattern. For high-precision typing, pass `types` to constrain results, supply
> richer context, or post-validate. For state-of-the-art NER, drive extraction from an LLM at the
> application layer and store the results in Brainy.
### Method 2: Direct Import (Advanced)
```typescript
import {
SmartExtractor,
SmartRelationshipExtractor
} from '@soulcraft/brainy'
// Or use subpath imports:
import { SmartExtractor } from '@soulcraft/brainy/neural/SmartExtractor'
const brain = new Brainy()
await brain.init()
const extractor = new SmartExtractor(brain, { minConfidence: 0.7 })
const result = await extractor.extract('CEO')
// { type: NounType.Role, confidence: 0.89, source: 'ensemble', evidence: '...' }
```
---
## Architecture
### 4-Signal Ensemble
Both `SmartExtractor` and `SmartRelationshipExtractor` use parallel signal execution:
| Signal | Weight | Description | Speed |
|--------|--------|-------------|-------|
| **ExactMatch** | 40% | Dictionary lookups, aliases | ~1ms |
| **Embedding** | 35% | Semantic similarity (384-dim vectors) | ~8ms |
| **Pattern** | 20% | Regex patterns, format-aware | ~2ms |
| **Context** | 5% | Surrounding text hints | ~4ms |
**Total Execution Time:** ~15-20ms (parallel)
### Format Intelligence
The system adapts to 7 document formats:
```typescript
await extractor.extract('CEO', {
formatContext: {
format: 'excel',
columnHeader: 'Job Title', // Boosts Role type
columnIndex: 3,
adjacentHeaders: ['Name', 'Department']
}
})
```
**Supported Formats:**
- Excel - Column headers, position intelligence
- CSV - Same as Excel
- PDF - Page structure, section detection
- YAML - Key paths, nesting levels
- DOCX - Styles, headings, lists
- JSON - Key paths, schema hints
- Markdown - Headers, lists, links
---
## API Reference
### brain.extractEntities()
**Primary extraction method.** Handles candidate detection + classification automatically.
```typescript
async brain.extractEntities(
text: string,
options?: {
types?: NounType[] // Filter by types
confidence?: number // Min confidence (0-1)
includeVectors?: boolean // Add embeddings to results
neuralMatching?: boolean // Enable ensemble scoring
}
): Promise<ExtractedEntity[]>
```
**Returns:**
```typescript
interface ExtractedEntity {
text: string // Original text
type: NounType // Classified type
confidence: number // Score (0-1)
start?: number // Character offset
end?: number // Character offset
vector?: number[] // 384-dim embedding (if requested)
}
```
**Examples:**
```typescript
// Extract all entities
const all = await brain.extractEntities(markdown)
// Extract only people
const people = await brain.extractEntities(text, {
types: [NounType.Person]
})
// High-confidence only
const highConf = await brain.extractEntities(text, {
confidence: 0.9
})
// Include vectors for similarity
const withVectors = await brain.extractEntities(text, {
includeVectors: true
})
```
---
### SmartExtractor
**Direct entity type classifier.** Use when you have pre-detected candidates or need custom configuration.
```typescript
import { SmartExtractor, FormatContext } from '@soulcraft/brainy'
const extractor = new SmartExtractor(brain, {
minConfidence: 0.7, // Threshold
enableEnsemble: true, // Use all signals
enableExactMatch: true, // Dictionary lookups
enableEmbedding: true, // Semantic similarity
enablePattern: true, // Regex patterns
enableContext: true, // Context hints
weights: { // Custom signal weights
exactMatch: 0.5, // 50%
embedding: 0.3, // 30%
pattern: 0.15, // 15%
context: 0.05 // 5%
}
})
// Extract entity type
const result = await extractor.extract(
'CEO', // Candidate text
{
formatContext: { // Optional format hints
format: 'excel',
columnHeader: 'Title'
},
contextWindow: 'John is the CEO' // Optional context
}
)
```
**Returns:**
```typescript
interface ExtractionResult {
type: NounType // Classified type
confidence: number // Score (0-1)
source: string // 'exact-match' | 'embedding' | 'ensemble'
evidence: string // Human-readable explanation
signalScores?: { // Individual signal scores
exactMatch?: number
embedding?: number
pattern?: number
context?: number
}
}
```
---
### SmartRelationshipExtractor
**Relationship type classifier.** Determines verb/relationship types between entities.
```typescript
import { SmartRelationshipExtractor } from '@soulcraft/brainy'
const relExtractor = new SmartRelationshipExtractor(brain, {
minConfidence: 0.6,
enableEnsemble: true
})
// Infer relationship type
const relationship = await relExtractor.infer(
'Alice', // Subject
'UCSF', // Object
'Alice works as a researcher at UCSF', // Context
{
subjectType: NounType.Person, // Optional type hints
objectType: NounType.Organization
}
)
```
**Returns:**
```typescript
interface RelationshipExtractionResult {
type: VerbType // Classified relationship
confidence: number // Score (0-1)
source: string // Signal source
evidence: string // Explanation
signalScores?: { // Individual scores
exactMatch?: number
embedding?: number
pattern?: number
context?: number
}
}
```
**Example:**
```typescript
const rel = await relExtractor.infer(
'John',
'Acme Corp',
'John Smith is the CEO of Acme Corp'
)
// {
// type: VerbType.WorksFor,
// confidence: 0.87,
// source: 'ensemble',
// evidence: 'Ensemble: exact-match (CEO pattern) + embedding (0.89 similarity)'
// }
```
---
### NeuralEntityExtractor
**Full extraction orchestrator.** Handles candidate detection, classification, and deduplication.
```typescript
import { NeuralEntityExtractor } from '@soulcraft/brainy'
const extractor = new NeuralEntityExtractor(brain)
// Full pipeline
const entities = await extractor.extract(text, {
types: [NounType.Person, NounType.Organization],
confidence: 0.7,
neuralMatching: true
})
```
**When to use:**
- Need automatic candidate detection
- Want deduplication ("John Smith" and "Smith" → same entity)
- Building custom extraction pipelines
- Advanced configuration requirements
**Typically accessed via `brain.extractEntities()` instead.**
---
## Import Preview Mode
Extract entities **without persisting** them to the database:
```typescript
// Method 1: Using import() with preview mode
const result = await brain.import(markdownContent, {
format: 'markdown',
enableNeuralExtraction: true, // Enable extraction
enableConceptExtraction: true, // Enable concepts
createEntities: false, // DON'T persist to database
vfsPath: null, // DON'T create VFS structure
returnExtracted: true // Return extracted data
})
// Access extracted entities
const entities = result.extractedEntities
// [
// { text: 'John Smith', type: NounType.Person, confidence: 0.95 },
// ...
// ]
// Method 2: Direct extraction (simpler)
const entities = await brain.extractEntities(markdownContent, {
confidence: 0.7
})
```
**Preview Mode Options:**
| Option | Effect |
|--------|--------|
| `createEntities: false` | Don't add to database |
| `vfsPath: null` | Don't create VFS files/folders |
| `returnExtracted: true` | Include extraction results |
| `enableNeuralExtraction: true` | Run entity extraction |
| `enableConceptExtraction: true` | Extract concepts/tags |
---
## Confidence Scoring
### How Confidence is Calculated
**Ensemble Mode (default):**
```
confidence = (
exactMatch × 0.40 +
embedding × 0.35 +
pattern × 0.20 +
context × 0.05
)
```
**Signal Scores:**
- **ExactMatch:** 0.0 (no match) or 1.0 (exact match)
- **Embedding:** Cosine similarity (0.0-1.0)
- **Pattern:** Pattern match confidence (0.5-1.0)
- **Context:** Context relevance (0.0-1.0)
**Example Calculation:**
```
Text: "CEO"
ExactMatch: 1.0 (in dictionary)
Embedding: 0.89 (similar to "Role")
Pattern: 0.8 (job title pattern)
Context: 0.3 (mentioned near name)
Final: 1.0×0.40 + 0.89×0.35 + 0.8×0.20 + 0.3×0.05
= 0.40 + 0.3115 + 0.16 + 0.015
= 0.8865 (87% confidence)
```
### Recommended Thresholds
| Use Case | Threshold | Precision | Recall |
|----------|-----------|-----------|--------|
| High precision | 0.9 | 95% | 70% |
| Balanced | 0.7 | 85% | 85% |
| High recall | 0.5 | 75% | 95% |
---
## NounType Detection
### 42 Universal Types
Brainy supports 42 noun types covering 95% of all domains:
**Core 7:**
- `Person` - Human individuals
- `Organization` - Companies, institutions
- `Location` - Places, addresses
- `Thing` - Physical objects
- `Concept` - Abstract ideas
- `Event` - Occurrences, meetings
- `Agent` - Software, bots, AI
**Extended 35:**
- `Document`, `Media`, `File` - Content types
- `Message`, `Collection`, `Dataset` - Data structures
- `Product`, `Service` - Commercial
- `Task`, `Project`, `Process` - Work
- `State`, `Role`, `Language` - Properties
- `Currency`, `Measurement` - Quantitative
- `Hypothesis`, `Experiment` - Scientific
- `Contract`, `Regulation` - Legal
- ... [see types/graphTypes.ts for complete list]
### Type Detection Methods
**1. ExactMatch Signal** (40% weight)
- Dictionary: 10,000+ aliases per type
- Examples: "CEO" → Role, "USD" → Currency
- Speed: ~1ms
**2. Embedding Signal** (35% weight)
- Semantic similarity to type embeddings
- 384-dimensional vectors
- Examples: "Chief Executive" → Role (cosine: 0.92)
- Speed: ~8ms
**3. Pattern Signal** (20% weight)
- Regex patterns for each type
- Format-aware (email → Message, URL → Document)
- Examples: `\d{4}-\d{2}-\d{2}` → Event
- Speed: ~2ms
**4. Context Signal** (5% weight)
- Surrounding word patterns
- Examples: "works at [X]" → X is Organization
- Speed: ~4ms
---
## Performance Optimization
### Caching
All extractors use LRU caching:
```typescript
const extractor = new SmartExtractor(brain, {
cache: {
maxSize: 10000, // Max cached items
ttl: 3600000 // 1 hour TTL
}
})
// Cache stats
const stats = extractor.getCacheStats()
// { hits: 8432, misses: 1568, hitRate: 0.843 }
```
### Batch Processing
```typescript
// Process multiple candidates in parallel
const candidates = ['CEO', 'Alice', 'Acme Corp', 'New York']
const results = await Promise.all(
candidates.map(text => extractor.extract(text))
)
```
### Format Context Reuse
```typescript
const formatContext = {
format: 'excel' as const,
columnHeader: 'Title'
}
// Reuse context for entire column
for (const cell of column) {
await extractor.extract(cell, { formatContext })
}
```
---
## Advanced: Custom Signal Weights
Adjust weights for domain-specific extraction:
```typescript
// Medical domain: Boost pattern matching
const medicalExtractor = new SmartExtractor(brain, {
weights: {
exactMatch: 0.3,
embedding: 0.2,
pattern: 0.45, // High for medical codes
context: 0.05
}
})
// Legal domain: Boost exact matching
const legalExtractor = new SmartExtractor(brain, {
weights: {
exactMatch: 0.6, // High for legal terms
embedding: 0.25,
pattern: 0.10,
context: 0.05
}
})
```
---
## Troubleshooting
### Low Confidence Scores
**Problem:** Entities extracted with confidence <0.5
**Solutions:**
1. Add format context hints
2. Provide more surrounding context
3. Lower confidence threshold
4. Add domain-specific aliases
```typescript
// Before: Generic extraction
const result = await extractor.extract('PM')
// { confidence: 0.45 }
// After: With context
const result = await extractor.extract('PM', {
formatContext: {
format: 'excel',
columnHeader: 'Job Title'
},
contextWindow: 'The PM leads the project team'
})
// { confidence: 0.87 }
```
### Type Misclassification
**Problem:** "John Smith" classified as Organization instead of Person
**Solutions:**
1. Provide type hints
2. Add more context
3. Check for name patterns
```typescript
// Force type filtering
const people = await brain.extractEntities(text, {
types: [NounType.Person] // Only consider Person type
})
```
### Slow Extraction
**Problem:** Extraction taking >100ms
**Solutions:**
1. Enable caching
2. Reduce context window
3. Disable unused signals
4. Use batch processing
```typescript
const fastExtractor = new SmartExtractor(brain, {
enableContext: false, // Disable slowest signal
cache: { maxSize: 50000 } // Large cache
})
```
---
## Examples
### Example 1: PDF Resume Extraction
```typescript
const resume = `
John Smith
Senior Software Engineer
Acme Corp (2020-2024)
Skills: Python, TypeScript, React
Location: San Francisco, CA
`
const entities = await brain.extractEntities(resume, {
types: [NounType.Person, NounType.Organization, NounType.Location, NounType.Role],
confidence: 0.7
})
// Filter by type
const person = entities.find(e => e.type === NounType.Person)
const companies = entities.filter(e => e.type === NounType.Organization)
const locations = entities.filter(e => e.type === NounType.Location)
```
### Example 2: Excel Data Classification
```typescript
import { SmartExtractor } from '@soulcraft/brainy'
const extractor = new SmartExtractor(brain)
// Process Excel column
const results = []
for (let i = 0; i < cells.length; i++) {
const result = await extractor.extract(cells[i], {
formatContext: {
format: 'excel',
columnHeader: headers[columnIndex],
columnIndex,
rowIndex: i
}
})
results.push(result)
}
```
### Example 3: Relationship Extraction
```typescript
import { SmartRelationshipExtractor } from '@soulcraft/brainy'
const relExtractor = new SmartRelationshipExtractor(brain)
const text = 'Alice works as a researcher at UCSF'
// Extract relationship
const rel = await relExtractor.infer('Alice', 'UCSF', text, {
subjectType: NounType.Person,
objectType: NounType.Organization
})
// Create relationship in brain
if (rel.confidence > 0.7) {
await brain.relate({
from: aliceId,
to: ucsfId,
type: rel.type,
metadata: { confidence: rel.confidence }
})
}
```
---
## Best Practices
1. **Use `brain.extractEntities()` for 95% of cases**
- Handles everything automatically
- Optimal for general use
2. **Use direct extractors for:**
- Custom signal weights
- Format-specific extraction
- Batch processing optimization
3. **Always provide context when possible**
- Improves confidence by 10-20%
- Especially important for ambiguous terms
4. **Enable caching for production**
- 80-90% cache hit rate typical
- 10x speedup for repeated extractions
5. **Filter by types when you know the domain**
- Reduces false positives
- Improves performance
6. **Monitor confidence distributions**
- Adjust thresholds per use case
- Balance precision vs recall
---
## See Also
- [API Reference](./api/README.md)
- [Type System](./types/README.md)
- [Import System](./import/README.md)
- [VFS System](./vfs/README.md)
---
**Questions or Issues?**
https://github.com/soulcraftlabs/brainy/issues

View file

@ -1,711 +0,0 @@
# Capacity Planning & Operations Guide
**Brainy Enterprise Operations**
This guide provides production-ready capacity planning formulas, deployment strategies, and operational guidelines for scaling Brainy from development (2GB) to enterprise (128GB+) deployments.
---
## 📊 Quick Reference
### Memory Allocation Formula
```
totalAvailable = systemMemory × utilizationFactor
modelReservation = 150MB (Q8) or 250MB (FP32)
availableForCache = totalAvailable - modelReservation
cacheSize = availableForCache × environmentRatio
Where:
- utilizationFactor = 0.80 (leave 20% for OS and other processes)
- environmentRatio = 0.25 (dev), 0.40 (container), 0.50 (production)
```
### Adaptive Caching Strategy
```
estimatedVectorMemory = entityCount × 1536 bytes // 384 dims × 4 bytes per float
hnswCacheBudget = cacheSize × 0.80 // 80% threshold for preloading decision
if estimatedVectorMemory < hnswCacheBudget:
cachingStrategy = 'preloaded' // All vectors loaded at init
else:
cachingStrategy = 'on-demand' // Vectors loaded adaptively via UnifiedCache
```
---
## 🎯 Deployment Scenarios
### Scenario 1: Development (2GB System)
**System Profile:**
- Total RAM: 2GB
- Environment: Local development
- Expected scale: 10K-50K entities
**Memory Breakdown:**
```
System Memory: 2048 MB
OS Reserved (20%): -410 MB
Available: 1638 MB
Model Memory: -140 MB
├─ WASM + Weights: 90 MB
└─ Workspace: 50 MB
───────────────────────────
Available for Cache: 1488 MB
Dev Allocation (25%): 372 MB UnifiedCache
├─ HNSW (30%): 112 MB
├─ Metadata (40%): 149 MB
├─ Search (20%): 74 MB
└─ Shared (10%): 37 MB
```
**Capacity:**
- **Standard Mode**: Up to 70K entities (all vectors in memory)
- **Lazy Mode**: Up to 500K entities (on-demand vector loading)
- **Search Latency**: 5-15ms (standard), 8-20ms (lazy, cold)
**Recommendations:**
- ✅ Use Q8 model for smaller footprint
- ✅ System uses adaptive caching for datasets >70K entities
- ✅ Monitor cache hit rate with `getCacheStats()`
- ⚠️ Expect slower performance vs production systems
**Configuration:**
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' },
model: { precision: 'q8' },
cache: { /* auto-sized to 372MB */ }
})
```
---
### Scenario 2: Small Production (8GB System)
**System Profile:**
- Total RAM: 8GB
- Environment: Single production server
- Expected scale: 100K-500K entities
**Memory Breakdown:**
```
System Memory: 8192 MB
OS Reserved (20%): -1638 MB
Available: 6554 MB
Model Memory (Q8): -150 MB
───────────────────────────
Available for Cache: 6404 MB
Prod Allocation (50%): 3202 MB UnifiedCache
├─ HNSW (30%): 961 MB
├─ Metadata (40%): 1281 MB
├─ Search (20%): 640 MB
└─ Shared (10%): 320 MB
```
**Capacity:**
- **Standard Mode**: Up to 600K entities
- **Lazy Mode**: Up to 5M entities
- **Search Latency**: 3-8ms (standard), 5-12ms (lazy, 80% hit rate)
**Recommendations:**
- ✅ Q8 model balances performance and memory
- ✅ Adaptive on-demand caching activates automatically at ~620K entities
- ✅ Monitor memory pressure warnings
- ✅ Consider horizontal scaling beyond 3M entities
**Configuration:**
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
model: { precision: 'q8' },
// Auto-sized cache: 3202MB
})
// Monitor health
const stats = brain.hnsw.getCacheStats()
console.log(`Cache hit rate: ${stats.unifiedCache.hitRatePercent}%`)
console.log(`Caching strategy: ${stats.cachingStrategy}`)
```
---
### Scenario 3: Medium Production (32GB System)
**System Profile:**
- Total RAM: 32GB
- Environment: Production server or container
- Expected scale: 1M-10M entities
**Memory Breakdown:**
```
System Memory: 32768 MB
OS Reserved (20%): -6554 MB
Available: 26214 MB
Model Memory (Q8): -150 MB
───────────────────────────
Available for Cache: 26064 MB
Prod Allocation (50%): 13032 MB UnifiedCache
├─ HNSW (30%): 3910 MB
├─ Metadata (40%): 5213 MB
├─ Search (20%): 2606 MB
└─ Shared (10%): 1303 MB
```
**Capacity:**
- **Standard Mode**: Up to 2.5M entities
- **Lazy Mode**: Up to 20M entities
- **Search Latency**: 2-5ms (standard), 3-8ms (lazy, 85% hit rate)
**Recommendations:**
- ✅ Consider FP32 model if accuracy is critical (adds 100MB)
- ✅ Enable GCS/S3 storage for durability
- ✅ Adaptive on-demand caching handles 10M+ entities efficiently
- ✅ Monitor fairness metrics to prevent HNSW cache hogging
**Configuration:**
```typescript
const brain = new Brainy({
storage: {
type: 'gcs-native',
gcsNativeStorage: { bucketName: 'production-data' }
},
model: { precision: 'q8' } // or 'fp32' for +0.5% accuracy
})
// Verify allocation
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(`Cache allocated: ${Math.round(memoryInfo.memoryInfo.available / 1024 / 1024 / 1024)}GB`)
console.log(`Environment: ${memoryInfo.memoryInfo.environment}`)
```
---
### Scenario 4: Large Production (128GB System)
**System Profile:**
- Total RAM: 128GB
- Environment: Dedicated production server
- Expected scale: 10M-100M entities
**Memory Breakdown:**
```
System Memory: 131072 MB
OS Reserved (20%): -26214 MB
Available: 104858 MB
Model Memory (FP32): -250 MB
───────────────────────────────
Available for Cache: 104608 MB
Prod Allocation (50%): 52304 MB UnifiedCache (logarithmic scaling applies)
├─ HNSW (30%): 15691 MB
├─ Metadata (40%): 20922 MB
├─ Search (20%): 10461 MB
└─ Shared (10%): 5230 MB
```
**Logarithmic Scaling Applied:**
For systems >64GB, allocation uses logarithmic scaling to prevent over-allocation:
```
effectiveRatio = baseRatio × (1 + log10(systemGB / 64) × 0.15)
Actual cache size: ~40GB (prevents waste on 128GB systems)
```
**Capacity:**
- **Standard Mode**: Up to 10M entities
- **Lazy Mode**: Up to 100M+ entities
- **Search Latency**: 1-3ms (standard), 2-5ms (lazy, 90%+ hit rate)
**Recommendations:**
- ✅ Use FP32 model for maximum accuracy
- ✅ Monitor fairness violations (HNSW shouldn't dominate cache)
- ✅ Consider sharding beyond 50M entities
- ✅ Implement application-level caching for hot queries
**Configuration:**
```typescript
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: {
bucketName: 'enterprise-data',
region: 'us-east-1'
}
},
model: { precision: 'fp32' } // Maximum accuracy
})
// Enterprise monitoring
setInterval(() => {
const stats = brain.hnsw.getCacheStats()
if (stats.fairness.fairnessViolation) {
console.warn('FAIRNESS VIOLATION: HNSW using too much cache')
console.warn(`HNSW: ${stats.fairness.hnswAccessPercent}% access, ${stats.hnswCache.sizePercent}% size`)
}
if (stats.unifiedCache.hitRatePercent < 75) {
console.warn(`Low cache hit rate: ${stats.unifiedCache.hitRatePercent}%`)
console.warn('Recommendations:', stats.recommendations)
}
}, 60000) // Check every minute
```
---
## 🐳 Container Deployments (Docker/Kubernetes)
### Container Memory Detection
Brainy auto-detects container memory limits via cgroups v1/v2:
```typescript
// Automatic detection
const brain = new Brainy() // Detects cgroup limits automatically
// Verify detection
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(`Container: ${memoryInfo.memoryInfo.isContainer}`)
console.log(`Source: ${memoryInfo.memoryInfo.source}`) // 'cgroup-v2' or 'cgroup-v1'
console.log(`Limit: ${Math.round(memoryInfo.memoryInfo.available / 1024 / 1024)}MB`)
```
### Docker Resource Limits
**Small Container (2GB)**
```dockerfile
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --production
COPY . .
# Download models at build time
RUN npm run download-models
ENV NODE_OPTIONS="--max-old-space-size=1536"
CMD ["node", "dist/index.js"]
```
```bash
docker run \
--memory="2g" \
--memory-reservation="1.5g" \
--cpus="2" \
my-brainy-app
```
**Expected allocation:**
```
Container Limit: 2048 MB
Available: 1638 MB (80% usable)
Model Memory: -150 MB
Available for Cache: 1488 MB
Container Ratio (40%): 595 MB UnifiedCache
```
**Medium Container (8GB)**
```bash
docker run \
--memory="8g" \
--memory-reservation="6g" \
--cpus="4" \
-e NODE_OPTIONS="--max-old-space-size=6144" \
my-brainy-app
```
**Expected allocation:**
```
Container Limit: 8192 MB
Available: 6554 MB
Model Memory: -150 MB
Available for Cache: 6404 MB
Container Ratio (40%): 2562 MB UnifiedCache
```
### Kubernetes Resource Requests/Limits
**Small Pod (2GB)**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy-api
spec:
replicas: 3
template:
spec:
containers:
- name: brainy
image: my-brainy-app:latest
resources:
requests:
memory: "1.5Gi"
cpu: "500m"
limits:
memory: "2Gi"
cpu: "1000m"
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=1536"
```
**Medium Pod (8GB)**
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy-api
spec:
replicas: 2
template:
spec:
containers:
- name: brainy
image: my-brainy-app:latest
resources:
requests:
memory: "6Gi"
cpu: "2000m"
limits:
memory: "8Gi"
cpu: "4000m"
env:
- name: NODE_OPTIONS
value: "--max-old-space-size=6144"
```
**Best Practices:**
- ✅ Set `requests` to 75% of `limits` for better scheduling
- ✅ Download models at Docker build time (not runtime)
- ✅ Use `NODE_OPTIONS` to match container memory limits
- ✅ Monitor actual usage and adjust based on workload
---
## 📈 Scaling Strategies
### Adaptive Caching Behavior
The system automatically chooses the optimal caching strategy:
- ✅ **Preloaded**: Small datasets (<80% of cache) - all vectors loaded at init for zero-latency access
- ✅ **On-demand**: Large datasets (>80% of cache) - vectors loaded adaptively via UnifiedCache
- ✅ No configuration needed - system adapts automatically based on dataset size
**Auto-detection logic:**
```typescript
const vectorMemoryNeeded = entityCount × 1536 // bytes
const hnswCacheAvailable = unifiedCache.maxSize × 0.80
if (vectorMemoryNeeded < hnswCacheAvailable) {
// Preload strategy: all vectors loaded at init
console.log('Caching strategy: preloaded (all vectors in memory)')
} else {
// On-demand strategy: vectors loaded adaptively
console.log('Caching strategy: on-demand (adaptive loading via UnifiedCache)')
}
```
### When to Add More RAM
Consider increasing RAM when:
- ⚠️ Cache hit rate consistently < 70%
- ⚠️ Memory pressure warnings > 85% utilization
- ⚠️ Search latency > 20ms on hot paths
- ⚠️ On-demand caching active but working set is large
**Decision tree:**
```
If cache hit rate < 70%:
└─> Is working set < 50% of total entities?
├─> YES: Increase cache size (add RAM)
└─> NO: Working set too large, consider:
├─> Application-level caching
├─> Query optimization
└─> Sharding dataset
```
### When to Shard/Distribute
Consider sharding when:
- ⚠️ Entity count > 50M entities on single node
- ⚠️ Write throughput > 10K ops/sec
- ⚠️ Need geographic distribution
- ⚠️ Fault tolerance requirements
**Sharding strategy:**
```typescript
// Example: Geographic sharding
const usEastBrain = new Brainy({
storage: { type: 's3', s3Storage: { bucket: 'us-east-data' } }
})
const euWestBrain = new Brainy({
storage: { type: 's3', s3Storage: { bucket: 'eu-west-data' } }
})
// Route queries based on user location
async function search(query, userRegion) {
const brain = userRegion === 'US' ? usEastBrain : euWestBrain
return await brain.find(query)
}
```
---
## 🔍 Monitoring & Diagnostics
### Key Metrics to Track
**1. Cache Performance**
```typescript
const stats = brain.hnsw.getCacheStats()
// Cache hit rate (target: >80%)
console.log(`Hit rate: ${stats.unifiedCache.hitRatePercent}%`)
// HNSW cache utilization
console.log(`HNSW memory: ${stats.hnswCache.estimatedMemoryMB}MB`)
console.log(`HNSW hit rate: ${stats.hnswCache.hitRatePercent}%`)
```
**2. Memory Pressure**
```typescript
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(`Pressure: ${memoryInfo.currentPressure.pressure}`)
// Values: 'low', 'moderate', 'high', 'critical'
if (memoryInfo.currentPressure.warnings.length > 0) {
console.warn('Memory warnings:', memoryInfo.currentPressure.warnings)
}
```
**3. Fairness Metrics**
```typescript
const stats = brain.hnsw.getCacheStats()
if (stats.fairness.fairnessViolation) {
console.warn('Cache fairness violation detected')
console.warn(`HNSW: ${stats.fairness.hnswAccessPercent}% access`)
console.warn(`HNSW: ${stats.hnswCache.sizePercent}% of cache`)
}
```
**4. Query Performance**
```typescript
// Track search latency
console.time('search')
const results = await brain.find('query')
console.timeEnd('search') // Target: <10ms for hot queries
```
### Alerting Thresholds
Set up alerts for:
- ⚠️ Cache hit rate < 70% (sustained for 5+ minutes)
- 🚨 Memory utilization > 90%
- 🚨 Search latency > 50ms (p95)
- ⚠️ Fairness violations detected
**Example monitoring script:**
```typescript
async function monitorHealth() {
const stats = brain.hnsw.getCacheStats()
// Alert on low cache hit rate
if (stats.unifiedCache.hitRatePercent < 70) {
await sendAlert({
severity: 'warning',
message: `Low cache hit rate: ${stats.unifiedCache.hitRatePercent}%`,
recommendations: stats.recommendations
})
}
// Alert on memory pressure
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
if (memoryInfo.currentPressure.pressure === 'high') {
await sendAlert({
severity: 'critical',
message: 'High memory pressure detected',
warnings: memoryInfo.currentPressure.warnings
})
}
}
// Run every 60 seconds
setInterval(monitorHealth, 60000)
```
---
## 🎯 Real-World Examples
### Example 1: E-Commerce Product Catalog (500K products)
**System:** 16GB production server
**Sizing:**
```
Products: 500,000
Vector memory needed: 500K × 1536 bytes = 768 MB
HNSW cache available: (16GB × 0.8 - 150MB) × 0.5 × 0.3 = 1,915 MB
Result: Standard mode (all vectors fit in HNSW cache)
```
**Configuration:**
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
model: { precision: 'q8' }
})
await brain.init()
// Verify preloaded strategy (all vectors in memory)
const stats = brain.hnsw.getCacheStats()
console.log(`Caching strategy: ${stats.cachingStrategy}`) // 'preloaded'
console.log(`Search latency: ${stats.performance.avgSearchMs}ms`) // ~3ms
```
### Example 2: Document Search (5M documents)
**System:** 32GB production server with GCS storage
**Sizing:**
```
Documents: 5,000,000
Vector memory needed: 5M × 1536 bytes = 7,680 MB
HNSW cache available: (32GB × 0.8 - 150MB) × 0.5 × 0.3 = 3,910 MB
Result: On-demand caching (vectors loaded adaptively)
```
**Configuration:**
```typescript
const brain = new Brainy({
storage: {
type: 'gcs-native',
gcsNativeStorage: { bucketName: 'docs-production' }
},
model: { precision: 'q8' }
})
await brain.init()
// Monitor cache performance
const stats = brain.hnsw.getCacheStats()
console.log(`Caching strategy: ${stats.cachingStrategy}`) // 'on-demand'
console.log(`Cache hit rate: ${stats.unifiedCache.hitRatePercent}%`) // Target >80%
console.log(`Cold search latency: ${stats.performance.avgSearchMs}ms`) // ~12ms
// Recommendations
console.log('Recommendations:', stats.recommendations)
// Example: "Cache hit rate healthy at 84.2% - no action needed"
```
### Example 3: Knowledge Graph (20M entities)
**System:** 128GB dedicated server with S3 storage
**Sizing:**
```
Entities: 20,000,000
Vector memory needed: 20M × 1536 bytes = 30,720 MB
HNSW cache available: ~15,691 MB (after logarithmic scaling)
Result: On-demand caching with high-performance adaptive loading
```
**Configuration:**
```typescript
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: {
bucketName: 'knowledge-graph-prod',
region: 'us-east-1'
}
},
model: { precision: 'fp32' } // Maximum accuracy
})
await brain.init()
// Enterprise monitoring
const stats = brain.hnsw.getCacheStats()
console.log(`Entities: ${stats.autoDetection.entityCount.toLocaleString()}`)
console.log(`Caching strategy: ${stats.cachingStrategy}`) // 'on-demand'
console.log(`Cache hit rate: ${stats.unifiedCache.hitRatePercent}%`) // Target >85%
console.log(`HNSW cache: ${stats.hnswCache.estimatedMemoryMB}MB`)
// Fairness check
if (stats.fairness.fairnessViolation) {
console.warn('HNSW dominating cache - consider tuning eviction policies')
}
```
---
## 🛠️ Troubleshooting
### Issue: Low Cache Hit Rate (<70%)
**Diagnosis:**
```typescript
const stats = brain.hnsw.getCacheStats()
console.log(`Hit rate: ${stats.unifiedCache.hitRatePercent}%`)
console.log(`Working set: ${stats.hnswCache.estimatedMemoryMB}MB`)
```
**Solutions:**
1. **Increase cache size** (add RAM)
2. **Optimize query patterns** (reduce random access)
3. **Implement application-level caching**
4. **Consider sharding if working set > available cache**
### Issue: High Memory Pressure (>85%)
**Diagnosis:**
```typescript
const memoryInfo = brain.hnsw.unifiedCache.getMemoryInfo()
console.log(`Pressure: ${memoryInfo.currentPressure.pressure}`)
console.log(`Warnings:`, memoryInfo.currentPressure.warnings)
```
**Solutions:**
1. **Reduce cache size manually** (override auto-detection)
2. **Reduce entity count** (archive old data - system automatically uses on-demand caching for large datasets)
3. **Increase system RAM**
### Issue: Fairness Violations
**Diagnosis:**
```typescript
const stats = brain.hnsw.getCacheStats()
if (stats.fairness.fairnessViolation) {
console.log(`HNSW access: ${stats.fairness.hnswAccessPercent}%`)
console.log(`HNSW cache: ${stats.hnswCache.sizePercent}%`)
}
```
**Solutions:**
1. **Contact support** (fairness policies may need tuning)
2. **Monitor over time** (may self-correct as access patterns stabilize)
3. **File GitHub issue** with diagnostics
---
## 📚 Additional Resources
- **[Migration Guide](../guides/migration-3.36.0.md)** - Upgrading to **[Architecture Overview](../architecture/data-storage-architecture.md)** - Deep dive into storage and caching
- **[GitHub Issues](https://github.com/soulcraftlabs/brainy/issues)** - Report problems or ask questions
---
**Production-ready. Enterprise-scale. Zero-config.** 🚀

View file

@ -1,83 +0,0 @@
---
title: Performance Envelopes
slug: guides/performance-envelopes
public: true
category: guides
template: guide
order: 40
description: Measured per-operation latency envelopes at stated scales — what to expect, on what hardware, and exactly how each number was produced.
next:
- guides/find-limits
---
# Performance Envelopes
Every number on this page is **measured, never projected** — produced by the script
cited at the bottom, against the built package (the artifact you install), on the stated
hardware. Each entry says what was measured, at what scale, on which storage backend.
When a release touches a measured path, that operation is re-measured and this page
updates in the same release.
Two scopes to keep straight:
- **These envelopes are the pure-JS engine** (no native accelerator registered) on
filesystem storage. This is the floor every deployment gets from `npm install` alone.
- **Accelerated deployments** (the optional native provider) publish their own numbers —
this page never claims them.
## Read operations
Reads are where the architecture pays off: after the write path has done its indexing
work, queries answer from purpose-built indexes without scanning.
| Operation | 1,000 entities | 10,000 entities | Notes |
|---|---|---|---|
| `get(id)` (warm) | p50 < 0.1ms | p50 < 0.1ms | served from cache/metadata index |
| `find` (metadata: indexed equality + range, limit 100) | p50 1.0ms · p95 1.8ms | p50 7.0ms · p95 8.9ms | column-store bitmap paths |
| `related(id)` (per-node adjacency) | p50 < 0.1ms · p95 0.2ms | p50 < 0.1ms | LSM adjacency index O(degree), scale-independent |
| `find` (semantic: embed + HNSW, 1k docs) | p50 178ms · p95 393ms | — | dominated by WASM query embedding (measured on a machine under concurrent load — treat the p95 as an upper bound); the vector search itself is single-digit ms |
## Write operations
Under Model-B **every write is its own durable generation** — a single-op `add` pays
serialization, before-image staging, and fsync before it acks. That durability is priced
into the write path visibly, by design:
| Operation | 1,000 entities | 10,000 entities | Notes |
|---|---|---|---|
| `add` (single-op) | p50 167ms · p95 171ms | p50 165ms · p95 172ms | full durable generation per write — flat across scale |
| `addMany` (bulk) | ~163ms/entity | ~187ms/entity | **currently per-item commits** — see the honest note below |
| `relateMany` | ~0.8ms/edge | ~0.9ms/edge | edges batch efficiently today |
| `flush` (steady-state, 1 pending write) | p50 8ms · p95 10ms | p50 45ms · p95 52ms | durability-only since 8.9.0 — cost no longer depends on history backlog or retention mode |
**The honest note on bulk writes:** `addMany` today commits each item as its own
generation (the same durability as single-op `add`, serialized by the single-writer
lock), so bulk-load cost is N × single-op cost. Batched chunk commits (one generation
and one fsync window per chunk, as `removeMany` already does) are designed into the
unified-commit work on the current roadmap. Until that ships, size bulk imports
accordingly — 10k entities is minutes, not seconds, on filesystem storage.
## Open / close
| Operation | 1,000 entities | 10,000 entities | Notes |
|---|---|---|---|
| `open` (empty store) | ~560ms | ~190ms | includes embedder initialization |
| `open` (warm, populated, clean shutdown) | 763ms | 4.9s | pure-JS vector index load dominates and grows with entity count; the native accelerator exists precisely to remove this |
| `close` | bounded | bounded | auto-compaction pass is time-bounded (~5s max) since 8.9.0 |
A store that was NOT cleanly closed pays index rebuilds on top of the warm-open
number (tens of seconds at 10k) — clean shutdown is worth engineering for.
## How these were produced
- **Hardware**: Intel Core i9-14900HX (32 threads), 62GB RAM, NVMe, Linux, Node v22.
- **Backend**: `storage: { type: 'filesystem' }`, pure JS (no native providers).
- **Embeddings**: deterministic stub for non-semantic ops (isolates engine cost);
the real WASM embedder for the semantic row (that's what you'll run).
- **Method**: p50/p95 over 50200 samples per op against the built `dist/`;
the measuring script ships in the repo history and re-runs per release.
Numbers on different hardware will differ; the *shape* (sub-2ms indexed reads,
~160ms embedding-bound semantic queries, durability-priced writes) is the envelope
you should hold your deployment against. If your measurements diverge from these
shapes by an order of magnitude, something is wrong — file it.

View file

@ -1,567 +0,0 @@
---
title: Transactions & Atomicity
slug: guides/transactions
public: true
category: guides
template: guide
order: 10
description: How Brainy keeps every write atomic — automatic per-operation transactions with rollback, and brain.transact() for atomic multi-write batches with compare-and-swap.
next:
- concepts/consistency-model
- guides/optimistic-concurrency
---
# Transaction System
**Status:** ✅ Production Ready
## Overview
Brainy's transaction system provides **atomic operations** with automatic rollback on failure. All operations within a transaction either succeed completely or fail completely - there are no partial failures.
### Key Benefits
- **Atomicity**: All operations succeed or all rollback
- **Consistency**: Indexes and storage remain consistent
- **Automatic**: Transparently used by all `brain.add()`, `brain.update()`, `brain.remove()`, and `brain.relate()` operations
- **Composable**: `brain.transact()` runs a multi-write batch through the same machinery as exactly one atomic commit
## Architecture
```
User Code (brain.add(), brain.update(), brain.transact(), etc.)
Transaction Manager (orchestration)
Operations (SaveNounMetadataOperation, SaveNounOperation, etc.)
Storage Adapter (sharding, ID-first routing)
```
### How It Works
Every write operation in Brainy automatically uses transactions:
```typescript
// Internally, this uses a transaction
const id = await brain.add({
data: { name: 'Alice', role: 'Engineer' },
type: NounType.Person
})
```
**Transaction Flow:**
1. **Begin Transaction**: TransactionManager creates new transaction
2. **Add Operations**: Operations added to transaction (SaveNounMetadataOperation, SaveNounOperation)
3. **Execute**: Each operation executes in sequence
4. **Commit**: All operations succeeded → changes persist
5. **Rollback**: Any operation failed → all changes reverted
### Rollback Mechanism
Each operation implements both **execute** and **undo**:
```typescript
class SaveNounMetadataOperation {
async execute(): Promise<void> {
// Save new metadata
await this.storage.saveNounMetadata(this.id, this.metadata)
}
async undo(): Promise<void> {
// Restore previous metadata (or delete if new entity)
if (this.previousMetadata) {
await this.storage.saveNounMetadata(this.id, this.previousMetadata)
} else {
await this.storage.deleteNounMetadata(this.id)
}
}
}
```
**On failure:**
- Operations rolled back in **reverse order**
- Previous state fully restored
- Indexes updated to reflect rollback
## Compatibility with Advanced Features
### Multi-Write Batches: `brain.transact()`
✅ **The 8.0 path for atomic multi-entity writes**
Single-operation methods each commit their own transaction. When several
writes must succeed or fail **together**, use `brain.transact()` — a
declarative batch that commits as exactly one generation, with optional
whole-store compare-and-swap and durable transaction metadata:
```typescript
const db = await brain.transact([
{ op: 'add', id: orderId, type: NounType.Document, subtype: 'order', data: 'Order #1042' },
{ op: 'update', id: customerId, metadata: { lastOrderAt: Date.now() }, ifRev: customer._rev },
{ op: 'relate', from: customerId, to: orderId, type: VerbType.Creates, subtype: 'purchase' }
], { meta: { author: 'order-service' } })
db.receipt.ids // resolved id per operation, in input order
```
**How It Works:**
- The batch executes through the same TransactionManager as single
operations, wrapped in the generational commit protocol: before-images are
staged and fsynced first, and the atomic manifest rename is the commit
point — a crash anywhere before it rolls back to the exact
pre-transaction bytes.
- Per-entity `ifRev` and whole-store `ifAtGeneration` provide
compare-and-swap at two granularities; any conflict rejects the entire
batch before anything is staged.
- The returned `Db` is a pinned, snapshot-isolated view of the committed
state.
See the **[consistency model](concepts/consistency-model.md)** for the
full guarantees (snapshot isolation, time travel, snapshots) and
**[Snapshots & Time Travel](guides/snapshots-and-time-travel.md)** for
recipes.
### Sharding
✅ **Fully Compatible**
Transactions work across multiple shards:
```typescript
// Entities with different UUID prefixes go to different shards
const id1 = 'aaa00000-1111-4111-8111-111111111111' // Shard: aaa
const id2 = 'bbb00000-2222-4222-8222-222222222222' // Shard: bbb
await brain.add({ id: id1, data: { name: 'Entity A' }, type: NounType.Thing })
await brain.relate({ from: id1, to: id2, type: VerbType.RelatesTo })
// Transaction handles cross-shard atomicity automatically
```
**How It Works:**
- Sharding is transparent to transactions
- `analyzeKey()` method routes to correct shard based on UUID
- Transaction operations don't need to know about shards
- Rollback works across all shards involved
### ID-First Storage
✅ **Fully Compatible**
Transactions work with direct ID-first paths - no type routing needed!
```typescript
// Entities stored with direct ID-first paths
const personId = await brain.add({
data: { name: 'John Doe' },
type: NounType.Person // → entities/nouns/{shard}/{id}/metadata.json
})
const orgId = await brain.add({
data: { name: 'Acme Corp' },
type: NounType.Organization // → entities/nouns/{shard}/{id}/metadata.json
})
// Type changes handled atomically (type is just metadata)
await brain.update({
id: personId,
type: NounType.Organization, // Type change
data: { name: 'Doe Corp' }
})
```
**How It Works:**
- Type information stored in metadata.noun field
- Storage layer uses O(1) ID-first path construction
- No type cache needed (removed in a previous version)
- Type counters adjusted on commit/rollback
- 40x faster path lookups (eliminates 42-type search)
### Storage Adapter Interface
✅ **Fully Compatible**
Transactions go through the `StorageAdapter` interface, so both shipped adapters (filesystem, memory) and any custom plugin adapter inherit the same atomicity guarantees:
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
})
await brain.add({ data: { name: 'Entity' }, type: NounType.Thing })
```
**How It Works:**
- Transactions operate through `StorageAdapter` interface
- Custom adapters registered via the plugin system implement the same interface
- Atomicity guaranteed at the write-coordinator level
- Read-after-write consistency maintained inside a single Brainy process
## Examples
### Basic Add Operation
```typescript
import { Brainy } from '@soulcraft/brainy'
import { NounType } from '@soulcraft/brainy/types'
const brain = new Brainy()
await brain.init()
// Automatically uses transaction
const id = await brain.add({
data: { name: 'Alice', role: 'Engineer' },
type: NounType.Person
})
// If add fails, all changes rolled back automatically
```
### Update with Type Change
```typescript
// Original entity
const id = await brain.add({
data: { name: 'John Smith', category: 'individual' },
type: NounType.Person
})
// Update with type change (atomic)
await brain.update({
id,
type: NounType.Organization, // Type change
data: { name: 'Smith Corp', category: 'business' }
})
// If update fails, original type and data restored
```
### Creating Relationships
```typescript
const personId = await brain.add({
data: { name: 'Alice' },
type: NounType.Person
})
const projectId = await brain.add({
data: { name: 'Project X' },
type: NounType.Thing
})
// Create relationship (atomic)
await brain.relate({
from: personId,
to: projectId,
type: VerbType.WorksOn
})
// If relate fails, no partial relationship created
```
### Batch Operations
```typescript
// Multiple operations, all atomic
for (let i = 0; i < 100; i++) {
await brain.add({
data: { name: `Entity ${i}`, index: i },
type: NounType.Thing
})
}
// Each add() is a separate transaction
// If any add fails, only that specific add is rolled back
```
### Delete with Cascade
```typescript
const personId = await brain.add({
data: { name: 'Bob' },
type: NounType.Person
})
const projectId = await brain.add({
data: { name: 'Project Y' },
type: NounType.Thing
})
await brain.relate({
from: personId,
to: projectId,
type: VerbType.WorksOn
})
// Delete person (atomic - deletes entity + relationships)
await brain.remove(personId)
// If delete fails, both entity and relationships remain
```
## Error Handling
Transactions automatically handle errors and rollback:
```typescript
try {
await brain.add({
data: { name: 'Test Entity' },
type: NounType.Thing,
vector: [1, 2, 3] // Wrong dimension → error
})
} catch (error) {
// Transaction automatically rolled back
// No partial data in storage or indexes
console.error('Add failed:', error.message)
}
```
**Common Error Scenarios:**
- **Invalid vector dimension**: Automatic rollback
- **Type validation failure**: Automatic rollback
- **Storage write failure**: Automatic rollback
- **Index update failure**: Automatic rollback
## Performance Considerations
### Transaction Overhead
**What a transaction costs:**
- A typical single-operation write wraps 2-8 operations (metadata + data + indexes) in one transaction
- The overhead is bookkeeping (operation objects + undo state), not extra I/O on the success path
- Rollback cost is proportional to the operations already applied (each is undone in reverse order)
**Optimization:**
- Operations executed sequentially (not parallel) for consistency
- Rollback only happens on failure (success path is fast)
- Index updates batched within transaction
### Auditing Committed Batches
Every committed `brain.transact()` batch is recorded in the transaction
log, newest first:
```typescript
await brain.transact(ops, { meta: { author: 'import-job' } })
const entries = await brain.transactionLog({ limit: 10 })
// [{ generation: 1042, timestamp: 1765432100000, meta: { author: 'import-job' } }]
```
Single-operation writes advance the generation counter but do not append
log entries — see the [consistency model](concepts/consistency-model.md)
for the history-granularity contract.
## Best Practices
### 1. Let Brainy Handle Transactions
```typescript
// ✅ Recommended: Use Brainy's API (transactions automatic)
await brain.add({ data, type })
await brain.update({ id, data })
await brain.remove(id)
// ❌ Avoid: Direct storage access bypasses transactions
await brain.storage.saveNoun(noun) // No transaction protection
```
### 2. Handle Errors Gracefully
```typescript
// ✅ Recommended: Catch errors, transaction rolls back automatically
try {
const id = await brain.add({ data, type })
return id
} catch (error) {
console.error('Add failed, rolled back:', error)
// Decide how to handle (retry, log, alert user)
}
```
### 3. Validate Before Operations
```typescript
// ✅ Recommended: Validate early to avoid unnecessary rollbacks
if (!isValidVector(vector, brain.dimension)) {
throw new Error(`Vector must have ${brain.dimension} dimensions`)
}
await brain.add({ data, type, vector })
```
### 4. Batch Related Writes with `transact()`
```typescript
// ✅ Recommended: writes that must land together go in one batch
await brain.transact([
{ op: 'add', id: orderId, type: NounType.Document, subtype: 'order', data: 'Order #1042' },
{ op: 'relate', from: customerId, to: orderId, type: VerbType.Creates, subtype: 'purchase' }
])
// ❌ Avoid: sequential single operations when partial application is unacceptable
const id = await brain.add({ ... }) // commits alone
await brain.relate({ ... }) // a crash here leaves the entity unlinked
```
### 5. Understand Atomicity Guarantees
**What Transactions GUARANTEE:**
- ✅ Atomicity within a single Brainy process
- ✅ Consistent state across all indexes
- ✅ Automatic rollback on failure
- ✅ Works with all storage adapters (filesystem, memory, custom plugin adapters)
**What Transactions DON'T Provide:**
- ❌ Two-phase commit across multiple Brainy instances
- ❌ Distributed locking across processes
- ❌ Cross-datacenter ACID guarantees
**Design:** Transactions ensure atomicity at the **write-coordinator level** inside one process. Cross-instance coordination, if you need it, lives in your service layer.
## Testing Transactions
### Unit Tests
```typescript
import { describe, it, expect } from 'vitest'
import { Brainy } from '@soulcraft/brainy'
describe('Transaction Tests', () => {
it('should rollback on failure', async () => {
const brain = new Brainy()
await brain.init()
const id1 = await brain.add({ data: { name: 'Entity 1' }, type: NounType.Thing })
try {
await brain.add({
data: null as any, // Invalid - will fail
type: NounType.Thing
})
} catch (e) {
// Expected failure
}
// First entity should still exist (rollback didn't affect it)
const entity1 = await brain.get(id1)
expect(entity1).toBeTruthy()
})
})
```
### Integration Tests
See `tests/transaction/integration/` for comprehensive integration tests covering:
- Sharding integration (`sharding-transactions.test.ts`)
- Type-aware integration (`typeaware-transactions.test.ts`)
The atomicity guarantees of `brain.transact()` — including crash recovery
through the real recovery path — are proven in
`tests/integration/db-mvcc.test.ts`.
## Troubleshooting
### High Rollback Rate
**Symptom:** a high share of writes throw and roll back
**Possible Causes:**
1. Invalid vector dimensions
2. Type validation errors
3. Storage write failures (disk full, network issues)
4. Index corruption
**Solutions:**
- Validate data before operations
- Check storage adapter health
- Monitor disk space and network connectivity
- Review error logs for patterns
### Slow Transaction Performance
**Symptom:** Operations take > 100ms per transaction
**Possible Causes:**
1. Large metadata objects
2. Remote storage latency
3. Many indexes enabled
4. Disk I/O bottleneck
**Solutions:**
- Optimize metadata size
- Use local caching for remote storage
- Disable unused indexes
- Use SSD storage
## Architecture Details
### Transaction Lifecycle
```
1. BEGIN
2. ADD OPERATIONS
- SaveNounMetadataOperation
- SaveNounOperation
- UpdateGraphIndexOperation
3. EXECUTE (sequential)
- Execute operation 1 → Success
- Execute operation 2 → Success
- Execute operation 3 → FAILURE
4. ROLLBACK (reverse order)
- Undo operation 2
- Undo operation 1
5. THROW ERROR
```
### Operation Types
| Operation | Description | Undo Behavior |
|-----------|-------------|---------------|
| `SaveNounMetadataOperation` | Save entity metadata | Restore previous metadata or delete if new |
| `SaveNounOperation` | Save entity data | Restore previous data or delete if new |
| `UpdateGraphIndexOperation` | Update graph index | Restore previous index state |
| `SaveVerbMetadataOperation` | Save relationship metadata | Restore previous metadata or delete if new |
| `SaveVerbOperation` | Save relationship data | Restore previous data or delete if new |
### Storage Adapter Integration
Transactions use the `StorageAdapter` interface:
```typescript
interface StorageAdapter {
saveNounMetadata(id: string, metadata: NounMetadata): Promise<void>
saveNoun(noun: Noun): Promise<void>
deleteNounMetadata(id: string): Promise<void>
deleteNoun(id: string): Promise<void>
// ... other methods
}
```
**Key Insight:** Both shipped storage adapters (filesystem, memory) — and any custom plugin adapter — implement this interface. Transactions work with **any** storage adapter automatically.
## Additional Resources
- **Unit Tests:** `tests/transaction/Transaction.test.ts`, `tests/transaction/TransactionManager.test.ts`
- **Integration Tests:** `tests/transaction/integration/`
- **MVCC Proofs:** `tests/integration/db-mvcc.test.ts` (atomicity, CAS, crash recovery for `brain.transact()`)
- **Consistency Model:** [docs/concepts/consistency-model.md](concepts/consistency-model.md)
## Summary
Brainy's transaction system provides **production-ready atomic operations** with automatic rollback. Every single-operation write is transactional out of the box, and `brain.transact()` extends the same guarantee to multi-write batches — one atomic commit, with compare-and-swap and durable transaction metadata.
**Key Takeaways:**
- ✅ **Automatic**: No manual transaction management needed for single operations
- ✅ **Atomic**: All operations succeed or all rollback — per operation and per `transact()` batch
- ✅ **Compatible**: Works with all storage adapters and features
- ✅ **Coordinated**: Per-entity `ifRev` and whole-store `ifAtGeneration` CAS reject conflicting batches before anything is staged
Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.remove()`, and `brain.relate()` — and reach for `brain.transact()` whenever several writes must land together.

View file

@ -4,48 +4,47 @@ Common issues and solutions for Brainy.
## 🤖 Model Loading Issues
### "Failed to initialize Candle Embedding Engine"
### "Failed to load embedding model"
**Symptoms**: Error during `brain.init()` with WASM loading failure.
**Symptoms**: Error during `brain.init()` with model loading failure.
**Causes & Solutions**:
1. **WASM file missing**
1. **No local models + remote downloads blocked**
```bash
# Verify WASM exists (~90MB with embedded model)
ls -lh dist/embeddings/wasm/pkg/candle_embeddings_bg.wasm
# Rebuild if missing
npm run build
# Solution: Download models manually
npm run download-models
```
2. **Memory too low**
2. **Network connectivity issues**
```bash
# Ensure at least 256MB available
# For Docker:
docker run -m 512m my-app
# Solution: Allow remote models
export BRAINY_ALLOW_REMOTE_MODELS=true
# Or pre-download in connected environment
npm run download-models
```
3. **Corrupted WASM**
3. **Incorrect model path**
```bash
# Rebuild the Candle WASM
npm run build:candle
npm run build
# Check if models exist
ls ./models/Xenova/all-MiniLM-L6-v2/onnx/model.onnx
# Set correct path
export BRAINY_MODELS_PATH=/correct/path/to/models
```
### Slow Initialization (>500ms)
### Models Download Very Slowly
**Symptoms**: Long wait times during first `brain.init()`.
**Cause**: WASM parsing takes ~200ms, which is normal for the 90MB file.
**Symptoms**: Long wait times during first initialization.
**Solutions**:
```typescript
// Initialize once at startup, not per-request
await brain.init() // Do this once
```bash
# Pre-download during build/CI
npm run download-models
// Reuse for all requests
const results = await brain.find(query)
# For Docker - download during image build
RUN npm run download-models
```
### Container Out of Memory During Model Load
@ -76,12 +75,17 @@ ENV BRAINY_MODEL_DTYPE=q8
chmod 755 ./brainy-data
# Use custom writable path
const brain = new Brainy({
const brain = new BrainyData({
storage: {
type: 'filesystem',
adapter: 'filesystem',
path: '/tmp/brainy-data'
}
})
# Or use memory storage
const brain = new BrainyData({
storage: { forceMemoryStorage: true }
})
```
### "ENOENT: no such file or directory"
@ -94,9 +98,9 @@ const brain = new Brainy({
mkdir -p ./brainy-data
# Check storage configuration
const brain = new Brainy({
const brain = new BrainyData({
storage: {
type: 'filesystem',
adapter: 'filesystem',
path: '/full/path/to/storage' // Use absolute path
}
})
@ -122,7 +126,7 @@ const brain = new Brainy({
2. **Network issues**
```typescript
// Set initialization timeout
const brain = new Brainy()
const brain = new BrainyData()
// Use Promise.race for timeout
const initPromise = Promise.race([
@ -149,20 +153,20 @@ const brain = new Brainy({
1. **Check if data exists**
```typescript
const stats = await brain.getStats()
const stats = await brain.getStatistics()
console.log(`Total items: ${stats.nounCount}`)
```
2. **Verify embedding generation**
```typescript
const id = await brain.add("test content", { nounType: 'content' })
const id = await brain.add("test content")
const item = await brain.get(id)
console.log('Item:', item) // Should have metadata and vector
```
3. **Test with exact match**
```typescript
const results = await brain.find("test content") // Exact text
const results = await brain.search("test content") // Exact text
console.log('Exact match results:', results)
```
@ -175,13 +179,12 @@ const brain = new Brainy({
1. **Add more context to queries**
```typescript
// Instead of: "cat"
const results = await brain.find("domestic cat animal pet")
const results = await brain.search("domestic cat animal pet")
```
2. **Use metadata filtering**
```typescript
const results = await brain.find({
query: "animals",
const results = await brain.search("animals", {
where: { category: "pets" },
limit: 10
})
@ -191,7 +194,6 @@ const brain = new Brainy({
```typescript
// Ensure consistent, descriptive content
await brain.add("Domestic cat - small carnivorous mammal", {
nounType: 'content',
category: "animals",
subcategory: "pets"
})
@ -207,7 +209,7 @@ const brain = new Brainy({
1. **Enable search cache**
```typescript
const brain = new Brainy({
const brain = new BrainyData({
cache: {
search: {
maxSize: 1000,
@ -220,14 +222,13 @@ const brain = new Brainy({
2. **Use appropriate limits**
```typescript
// Don't fetch more than needed
const results = await brain.find({ query: "query", limit: 10 })
const results = await brain.search("query", { limit: 10 })
```
3. **Consider metadata filtering first**
```typescript
// Filter by metadata first, then semantic search
const results = await brain.find({
query: "query",
const results = await brain.search("query", {
where: { category: "specific" }, // Reduces search space
limit: 10
})
@ -249,7 +250,7 @@ const brain = new Brainy({
// Process in batches instead of loading all at once
for (let i = 0; i < data.length; i += 100) {
const batch = data.slice(i, i + 100)
await Promise.all(batch.map(item => brain.add(item, { nounType: 'content' })))
await Promise.all(batch.map(item => brain.add(item)))
}
```
@ -278,11 +279,11 @@ const brain = new Brainy({
run: npm test
```
2. **Use temporary filesystem storage in tests**
2. **Use memory storage in tests**
```typescript
// In test setup
const brain = new Brainy({
storage: { type: 'filesystem', path: '/tmp/brainy-test' }
const brain = new BrainyData({
storage: { forceMemoryStorage: true }
})
```
@ -350,7 +351,7 @@ ENV BRAINY_MODELS_PATH=./models
Enable verbose logging to see what's happening:
```typescript
const brain = new Brainy({
const brain = new BrainyData({
logging: { verbose: true }
})
```
@ -362,11 +363,11 @@ Verify your Brainy setup:
```typescript
// Basic health check
try {
const brain = new Brainy()
const brain = new BrainyData()
await brain.init()
const id = await brain.add("health check", { nounType: 'content' })
const results = await brain.find("health")
const id = await brain.add("health check")
const results = await brain.search("health")
console.log('✅ Brainy is working correctly')
console.log(`Added item: ${id}`)
@ -391,8 +392,8 @@ node -e "console.log(process.memoryUsage())"
# Platform info
node -e "console.log(process.platform, process.arch)"
# Verify WASM file exists (model embedded inside)
ls -la dist/embeddings/wasm/pkg/candle_embeddings_bg.wasm
# Brainy models
ls -la ./models/Xenova/all-MiniLM-L6-v2/
```
### Report Issues

View file

@ -1,518 +0,0 @@
# Universal Display Augmentation
The Universal Display Augmentation is a powerful AI-powered system that automatically enhances any data stored in Brainy with intelligent display fields and descriptions. It provides a rich, visual experience while maintaining complete backward compatibility and zero performance impact until accessed.
## 🎯 Overview
### What It Does
- **AI-Powered Enhancement**: Uses existing IntelligentTypeMatcher for semantic type detection
- **Smart Titles**: Generates contextual, human-readable titles
- **Rich Descriptions**: Creates enhanced descriptions with context
- **Relationship Formatting**: Formats verb relationships in human-readable form
- **Zero Conflicts**: Uses method-based API to avoid namespace conflicts with user data
### Key Benefits
- **Zero Configuration**: Enabled by default with intelligent fallbacks
- **High Performance**: Lazy computation with intelligent LRU caching
- **Complete Isolation**: Can be disabled, replaced, or configured independently
- **Developer Friendly**: Clean API with TypeScript support and autocomplete
- **Backward Compatible**: Graceful degradation if unavailable
## 🚀 Quick Start
### Basic Usage
```typescript
import { Brainy } from '@soulcraft/brainy'
const brainy = new Brainy()
await brainy.init()
// Add some data
const personId = await brainy.add('John Doe', {
type: 'Person',
role: 'CEO',
company: 'Acme Corp'
})
// Get enhanced result
const person = await brainy.getNoun(personId)
// Access user data (unchanged)
console.log(person.metadata.role) // "CEO"
// Access display fields (new capability)
const display = await person.getDisplay()
console.log(display.title) // "John Doe"
console.log(display.description) // "CEO at Acme Corp"
console.log(display.type) // "Person"
```
### CLI Usage
```bash
# Enhanced search results with AI-powered descriptions
brainy search "CEO"
# Output:
# ✅ Found 2 results:
#
# 1. John Doe (Person)
# 🎯 Relevance: 95.3%
# CEO at Acme Corp
# executive, leadership
# Enhanced item display
brainy get person-123
# Output:
# ID: person-123
# Title: John Doe
# Type: Person
# Description: CEO at Acme Corp
# Debug display augmentation
brainy get person-123 --display-debug
```
## 📊 API Reference
### Enhanced Result Methods
Every result from `getNoun()`, `search()`, `find()`, etc. gains these methods:
#### `getDisplay(field?: string)`
Get computed display fields.
```typescript
// Get all display fields
const allFields = await result.getDisplay()
// Get specific field
const title = await result.getDisplay('title')
const type = await result.getDisplay('type')
```
**Returns**: `ComputedDisplayFields` or specific field value
#### `getAvailableFields(namespace: string)`
List available computed fields for a namespace.
```typescript
const fields = result.getAvailableFields('display')
// ['title', 'description', 'type', 'tags', 'relationship', 'confidence']
```
#### `getAvailableAugmentations()`
List available augmentation namespaces.
```typescript
const augmentations = result.getAvailableAugmentations()
// ['display']
```
#### `explore()`
Debug method to explore entity structure.
```typescript
await result.explore()
// Prints detailed information about the entity and its computed fields
```
### Display Fields
All computed display fields available through `getDisplay()`:
```typescript
interface ComputedDisplayFields {
title: string // Primary display name (AI-computed)
description: string // Enhanced description with context
type: string // Human-readable type (from AI detection)
tags: string[] // Generated display tags
relationship?: string // Human-readable relationship (verbs only)
confidence: number // AI confidence score (0-1)
// Debug fields (optional)
reasoning?: string // AI reasoning for type detection
alternatives?: Array<{type: string, confidence: number}>
computedAt: number // Timestamp of computation
version: string // Augmentation version
}
```
## 🎨 Clean, Minimal Design
The display augmentation focuses on content over visual clutter:
- **Smart Titles**: AI-generated contextual names
- **Enhanced Descriptions**: Rich, informative descriptions
- **Type Detection**: Intelligent classification without visual noise
- **Professional Aesthetic**: Clean, minimal output that matches modern design standards
## ⚙️ Configuration
### Default Configuration
```typescript
const DEFAULT_CONFIG: DisplayConfig = {
enabled: true, // Enable display augmentation
cacheSize: 1000, // LRU cache size
lazyComputation: true, // Compute on first access
batchSize: 50, // Batch size for operations
confidenceThreshold: 0.7, // Minimum confidence for AI decisions
// No icon configuration needed - clean, minimal approach
customFieldMappings: {}, // Custom field patterns
priorityFields: {}, // Priority field configurations
debugMode: false // Enable debug logging
}
```
### Runtime Configuration
```typescript
// Get display augmentation
const displayAug = (brainy as any).augmentations.get('display')
// Update configuration
displayAug.configure({
cacheSize: 2000,
confidenceThreshold: 0.8,
debugMode: true
})
// Clear cache
displayAug.clearCache()
// Get performance stats
const stats = displayAug.getStats()
console.log(`Cache hit ratio: ${stats.cacheHitRatio}%`)
```
### Brainy Configuration
Configure at initialization:
```typescript
const brainy = new Brainy({
augmentations: {
display: {
enabled: true,
cacheSize: 2000,
debugMode: true
}
}
})
```
## 🧠 AI Integration
### IntelligentTypeMatcher Integration
The display augmentation leverages existing AI infrastructure:
```typescript
// Uses existing type detection
const typeMatcher = IntelligentTypeMatcher.getInstance()
const detectedType = await typeMatcher.detectType(data)
// Maps to enhanced descriptions and smart titles
const description = await generateEnhancedDescription(data, detectedType)
const title = await generateSmartTitle(data, detectedType)
```
### Neural Import Patterns
Reuses patterns from the import system:
```typescript
// Leverages existing field detection patterns
const titleFields = ['name', 'title', 'displayName', 'label']
const descriptionFields = ['description', 'summary', 'bio', 'about']
// Smart field mapping based on data analysis
const bestTitle = findBestMatch(data, titleFields)
const bestDescription = findBestMatch(data, descriptionFields)
```
## ⚡ Performance
### Lazy Computation
Display fields are computed only when accessed:
```typescript
const result = await brainy.getNoun(id) // No computation yet
// First access triggers computation
const display = await result.getDisplay() // Computes and caches
// Subsequent accesses use cache
const sameDisplay = await result.getDisplay() // Instant from cache
```
### Intelligent Caching
- **LRU Cache**: Least recently used eviction
- **Request Deduplication**: Prevents duplicate concurrent computations
- **Batch Optimization**: Efficient bulk operations
- **Statistics Tracking**: Performance monitoring
### Cache Statistics
```typescript
const stats = displayAugmentation.getStats()
console.log({
totalComputations: stats.totalComputations,
cacheHitRatio: stats.cacheHitRatio, // 0.85 = 85%
averageComputationTime: stats.averageComputationTime, // in ms
commonTypes: stats.commonTypes // Most frequent types
})
```
## 🔌 Augmentation Architecture
### BaseAugmentation Integration
```typescript
export class UniversalDisplayAugmentation extends BaseAugmentation {
readonly name = 'display'
readonly version = '1.0.0'
readonly timing = 'after' as const
readonly priority = 50
readonly metadata: MetadataAccess = {
reads: '*', // Read all user data for analysis
writes: ['_display'] // Cache in isolated namespace
}
operations = ['get', 'search', 'findSimilar', 'getVerb'] as const
}
```
### Registry Integration
```typescript
// Default augmentations (enabled automatically)
import { createDefaultAugmentations } from './defaultAugmentations.js'
const augmentations = createDefaultAugmentations({
display: {
enabled: true,
cacheSize: 1000
}
})
// Manual registration
brainy.registerAugmentation(new UniversalDisplayAugmentation())
```
## 🧪 Testing
### Unit Tests
```typescript
import { describe, it, expect } from 'vitest'
describe('Universal Display Augmentation', () => {
it('should enhance results with display fields', async () => {
const result = await brainy.getNoun(id)
expect(result.getDisplay).toBeDefined()
const display = await result.getDisplay()
expect(display.title).toBeDefined()
expect(display.icon).toBeDefined()
expect(display.confidence).toBeGreaterThan(0)
})
})
```
### Integration Tests
```bash
# Run display augmentation tests
npm test tests/display-augmentation.test.ts
# Test CLI integration
npm test tests/cli.test.ts
# Performance tests
npm test tests/performance/display.test.ts
```
### Manual Testing
```bash
# Test CLI enhancements
brainy add "John Doe" -m '{"type":"Person","role":"CEO"}'
brainy search "CEO"
brainy get <id> --display-debug
# Test various data types
brainy add "Apple Inc" -m '{"type":"Organization"}'
brainy add "MacBook Pro" -m '{"type":"Product"}'
brainy search "*" --limit 10
```
## 🚀 Advanced Usage
### Custom Configuration
```typescript
const displayAug = (brainy as any).augmentations.get('display')
displayAug.configure({
confidenceThreshold: 0.8,
debugMode: true
})
```
### Custom Field Mappings
```typescript
displayAug.configure({
customFieldMappings: {
title: ['customName', 'displayTitle', 'label'],
description: ['summary', 'details', 'info']
}
})
```
### Batch Precomputation
```typescript
// Precompute display fields for better performance
const entities = await brainy.find({ limit: 100 })
await displayAug.precomputeBatch(
entities.map(e => ({ id: e.id, data: e.metadata }))
)
```
## 🔧 Debugging
### Debug Mode
```typescript
displayAug.configure({ debugMode: true })
// Or via CLI
brainy get <id> --display-debug
```
### Explore Entity Structure
```typescript
const result = await brainy.getNoun(id)
await result.explore()
// Output:
// 📋 Entity Exploration: person-123
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
//
// 👤 User Data:
// • name: "John Doe"
// • role: "CEO"
// • company: "Acme Corp"
//
// 🎨 Display Fields:
// • title: "John Doe"
// • description: "CEO at Acme Corp"
// • type: "Person"
// • icon: "👤"
// • confidence: 0.92
```
### Performance Analysis
```typescript
const stats = displayAug.getStats()
console.log('Performance Analysis:', {
efficiency: `${(stats.cacheHitRatio * 100).toFixed(1)}% cache hits`,
speed: `${stats.averageComputationTime.toFixed(1)}ms average`,
usage: `${stats.totalComputations} total computations`,
popular: stats.commonTypes.map(t => `${t.type} (${t.percentage}%)`)
})
```
## 🎯 Best Practices
### When to Use
✅ **Use display augmentation for:**
- Search result presentation
- User interface display
- Report generation
- Data exploration
- Visual dashboards
❌ **Don't use for:**
- Data processing logic
- Business rule validation
- Storage or indexing
- Performance-critical operations
### Performance Tips
1. **Leverage Caching**: Display fields are cached automatically
2. **Batch Operations**: Use bulk operations when possible
3. **Selective Access**: Only access display fields when needed
4. **Monitor Performance**: Check cache hit ratios regularly
### Error Handling
```typescript
try {
const display = await result.getDisplay()
// Use enhanced display
} catch (error) {
// Fallback to basic display
const basicTitle = result.metadata?.name || result.content || result.id
}
```
## 🔮 Future Enhancements
### Planned Features
- **Custom Augmentations**: Plugin system for custom display logic
- **Theme Support**: Different styling themes and formatting options
- **Internationalization**: Multi-language display fields
- **Rich Media**: Support for images and rich content
- **Analytics**: Usage tracking and optimization suggestions
### Extensibility
The display augmentation is designed for extensibility:
```typescript
// Custom display augmentation
class CustomDisplayAugmentation extends BaseAugmentation {
name = 'custom-display'
async computeFields(result: any, namespace: string) {
return {
customTitle: this.generateCustomTitle(result),
customIcon: this.getCustomIcon(result)
}
}
}
```
## 📚 Related Documentation
- [Augmentation System Architecture](./augmentation-architecture.md)
- [IntelligentTypeMatcher Guide](./intelligent-type-matcher.md)
- [CLI Reference](./cli-reference.md)
- [Performance Optimization](./performance-guide.md)
- [API Reference](./api-reference.md)
## 🤝 Contributing
Contributions welcome! Areas for improvement:
1. **Additional Icon Mappings**: More comprehensive icon coverage
2. **AI Model Integration**: Enhanced type detection accuracy
3. **Performance Optimization**: Cache optimization and batch processing
4. **Documentation**: More examples and use cases
5. **Testing**: Edge cases and integration scenarios
See [CONTRIBUTING.md](../CONTRIBUTING.md) for development guidelines.

Some files were not shown because too many files have changed in this diff Show more