feat: add v5.8.0 features - transactions, pagination, and comprehensive docs

**Transaction System (TIER 1.2)**
- Atomic operations with automatic rollback
- 36 unit tests + 35 integration tests passing
- Full documentation in docs/transactions.md

**Duplicate Check Optimization (TIER 1.4)**
- Optimized from O(n) to O(log n) using GraphAdjacencyIndex
- Uses LSM-tree for efficient lookups
- Tests verify performance improvements

**GraphIndex Pagination (TIER 1.5)**
- Production-scale pagination for high-degree nodes
- Backward compatible API
- 18 pagination tests passing

**Comprehensive Filter Documentation (TIER 1.6)**
- Complete operator reference (15 operators)
- Compound filters (anyOf, allOf, nested logic)
- Common query patterns and troubleshooting guide
- 642 lines of new documentation

**README Updates**
- Added Filter & Query Syntax Guide to Essential Reading
- Added Transactions to Core Concepts section

All changes tested and production-ready for v5.8.0 release.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
David Snelling 2025-11-14 10:26:23 -08:00
parent 52e961760d
commit e40fee39d8
21 changed files with 5657 additions and 182 deletions

View file

@ -360,48 +360,351 @@ await brain.find({
// Total performance: ~1.2ms for 100K entities
```
## Query Operators (v4.5.4+)
## Filter Syntax Reference (v5.8.0+)
### Canonical Operator Syntax
### Where Clause: Complete Operator Guide
Brainy uses SQL-style canonical operators for maximum clarity and developer familiarity:
Brainy provides a comprehensive set of operators for filtering entities by metadata fields. All operators work seamlessly with Triple Intelligence (vector + metadata + graph).
#### Basic Operators
**Exact Match** (shorthand):
```typescript
// Canonical operators (recommended)
await brain.find({
where: {
age: { gte: 18 }, // Greater than or equal
score: { lt: 100 }, // Less than
status: { eq: 'active' }, // Equals
role: { ne: 'guest' }, // Not equals
priority: { in: [1, 2, 3] }, // In array
date: { between: [start, end] }, // Between range
tags: { contains: 'featured' }, // Contains value
email: { exists: true } // Field exists
status: 'active', // Shorthand for { eq: 'active' }
year: 2024, // Exact match for numbers
verified: true // Boolean matching
}
})
```
### Complete Operator Reference
**Comparison Operators**:
```typescript
await brain.find({
where: {
age: { gt: 18 }, // Greater than
score: { gte: 80 }, // Greater than or equal
price: { lt: 100 }, // Less than
stock: { lte: 10 }, // Less than or equal
status: { eq: 'active' }, // Equals (explicit)
role: { ne: 'guest' } // Not equals
}
})
```
| **Canonical** | **Aliases** | **Description** | **Example** |
|---------------|-------------|-----------------|-------------|
| `eq` | `equals` | Exact equality | `{ status: { eq: 'active' } }` |
| `ne` | `notEquals` | Not equal to | `{ role: { ne: 'admin' } }` |
| `gt` | `greaterThan` | Greater than | `{ age: { gt: 18 } }` |
| `gte` | `greaterThanOrEqual` | Greater than or equal | `{ score: { gte: 80 } }` |
| `lt` | `lessThan` | Less than | `{ price: { lt: 100 } }` |
| `lte` | `lessThanOrEqual` | Less than or equal | `{ stock: { lte: 10 } }` |
| `in` | - | Value in array | `{ category: { in: ['A', 'B'] } }` |
| `between` | - | Range (inclusive) | `{ year: { between: [2020, 2024] } }` |
| `contains` | - | Contains substring/value | `{ tags: { contains: 'urgent' } }` |
| `exists` | - | Field exists (boolean) | `{ email: { exists: true } }` |
**Performance**: O(log n) for comparisons using sorted indices, O(1) for exact matches using hash maps.
**Deprecated Operators** (removed in v5.0.0):
- `is`, `isNot` → Use `eq`, `ne` instead
- `greaterEqual`, `lessEqual` → Use `gte`, `lte` instead
#### Range Operators
**Backward Compatibility**: All aliases are fully supported. Deprecated operators still work but will be removed in v5.0.0.
**Between** (inclusive):
```typescript
await brain.find({
where: {
publishDate: { between: [2020, 2024] }, // Year range
price: { between: [10.00, 99.99] }, // Price range
timestamp: { between: [startMs, endMs] } // Time range
}
})
```
**Performance**: O(log n) for finding range boundaries, O(k) for collecting results where k = matching entities.
#### Set Membership
**In/Not In**:
```typescript
await brain.find({
where: {
category: { in: ['tech', 'science', 'research'] },
status: { notIn: ['draft', 'deleted'] },
priority: { in: [1, 2, 3] }
}
})
```
**Performance**: O(1) per set member check via hash lookup, O(m) total where m = set size.
#### String Matching
**Contains/Starts/Ends**:
```typescript
await brain.find({
where: {
title: { contains: 'machine learning' }, // Substring search
email: { startsWith: 'admin@' }, // Prefix match
filename: { endsWith: '.pdf' } // Suffix match
}
})
```
**Performance**: O(n) substring scan (not indexed), best used with additional indexed filters.
**Note**: For semantic similarity, use `query` parameter instead:
```typescript
// ❌ Slow substring search
where: { description: { contains: 'AI' } }
// ✅ Fast semantic search
query: 'artificial intelligence'
```
#### Existence Checks
**Exists/Missing**:
```typescript
await brain.find({
where: {
email: { exists: true }, // Has email field
deletedAt: { exists: false }, // No deletedAt field (not deleted)
profileImage: { exists: true } // Has profile image
}
})
```
**Performance**: O(1) via hash index of fields.
### Compound Filters
Combine multiple conditions with boolean logic:
#### AND Logic (Default)
All conditions at the same level are implicitly AND:
```typescript
await brain.find({
where: {
status: 'published', // AND
year: { gte: 2020 }, // AND
citations: { gte: 50 } // AND
}
})
// Returns: entities matching ALL three conditions
```
**Explicit AND with `allOf`**:
```typescript
await brain.find({
where: {
allOf: [
{ status: 'published' },
{ year: { gte: 2020 } },
{ citations: { gte: 50 } }
]
}
})
```
**Performance**: O(log n) total - processes filters in optimal order (low cardinality first).
#### OR Logic
Match ANY condition:
```typescript
await brain.find({
where: {
anyOf: [
{ status: 'urgent' },
{ priority: { gte: 8 } },
{ assignee: 'admin' }
]
}
})
// Returns: entities matching ANY condition
```
**Combined AND + OR**:
```typescript
await brain.find({
where: {
status: 'active', // Must be active
anyOf: [ // AND (urgent OR high priority)
{ tags: { contains: 'urgent' } },
{ priority: { gte: 8 } }
]
}
})
```
**Performance**: O(m × log n) where m = number of OR conditions, results are merged with Set union.
#### Nested Logic
Complex boolean expressions:
```typescript
await brain.find({
where: {
allOf: [
{ status: 'published' },
{
anyOf: [
{ featured: true },
{ citations: { gte: 100 } }
]
}
]
}
})
// Returns: published AND (featured OR highly cited)
```
### Complete Operator Reference Table
| **Operator** | **Aliases** | **Description** | **Performance** | **Example** |
|--------------|-------------|-----------------|-----------------|-------------|
| `eq` | `equals` | Exact equality | O(1) | `{ status: { eq: 'active' } }` |
| `ne` | `notEquals` | Not equal | O(n) scan | `{ role: { ne: 'admin' } }` |
| `gt` | `greaterThan` | Greater than | O(log n) | `{ age: { gt: 18 } }` |
| `gte` | `greaterThanOrEqual` | Greater/equal | O(log n) | `{ score: { gte: 80 } }` |
| `lt` | `lessThan` | Less than | O(log n) | `{ price: { lt: 100 } }` |
| `lte` | `lessThanOrEqual` | Less/equal | O(log n) | `{ stock: { lte: 10 } }` |
| `in` | - | In array | O(m) | `{ category: { in: ['A', 'B'] } }` |
| `notIn` | - | Not in array | O(n) scan | `{ status: { notIn: ['draft'] } }` |
| `between` | - | Range (inclusive) | O(log n + k) | `{ year: { between: [2020, 2024] } }` |
| `contains` | - | Substring | O(n) scan | `{ title: { contains: 'AI' } }` |
| `startsWith` | - | Prefix | O(n) scan | `{ email: { startsWith: 'admin' } }` |
| `endsWith` | - | Suffix | O(n) scan | `{ file: { endsWith: '.pdf' } }` |
| `exists` | - | Field exists | O(1) | `{ email: { exists: true } }` |
| `anyOf` | - | OR logic | O(m × log n) | `{ anyOf: [{...}, {...}] }` |
| `allOf` | - | AND logic | O(log n) | `{ allOf: [{...}, {...}] }` |
**Performance Notes**:
- **O(1)**: Hash index lookup (exact matches, exists)
- **O(log n)**: Sorted index binary search (comparisons, ranges)
- **O(n)**: Full scan (string matching, negations)
- **O(k)**: Result collection where k = matches
**Optimization Tips**:
1. **Combine fast + slow filters**: Put indexed filters first
2. **Avoid `ne` and `notIn`**: Require full scans, use positive filters when possible
3. **Use `query` for text search**: Semantic search is faster than substring matching
4. **Limit string operations**: `contains`/`startsWith`/`endsWith` are unindexed
### Type Filtering
Filter entities by NounType:
#### Single Type
```typescript
await brain.find({
type: NounType.Document,
where: { year: { gte: 2020 } }
})
```
#### Multiple Types
```typescript
await brain.find({
type: [NounType.Person, NounType.Organization],
where: { verified: true }
})
```
#### All 42 Available NounTypes
```typescript
// People & Organizations
NounType.Person, NounType.Organization, NounType.Team, NounType.Role
// Content
NounType.Document, NounType.Image, NounType.Video, NounType.Audio
// Knowledge
NounType.Concept, NounType.Topic, NounType.Category, NounType.Tag
// Technical
NounType.Code, NounType.API, NounType.Database, NounType.Service
// Events & Time
NounType.Event, NounType.Timeline, NounType.Schedule
// Location & Physical
NounType.Place, NounType.Building, NounType.Room, NounType.Device
// Abstract
NounType.Thing, NounType.Entity, NounType.Object
// And 19 more... (see src/types/graphTypes.ts for complete list)
```
**Performance**: O(1) - type stored as indexed metadata field.
### Graph Query Syntax
Traverse relationships using the GraphIndex:
#### Basic Connection
```typescript
await brain.find({
connected: {
to: 'entity-id-123', // Connected to this entity
via: VerbType.WorksFor, // Through this relationship type
direction: 'out' // Direction: 'in', 'out', or 'both'
}
})
```
**Performance**: O(1) per hop via adjacency map lookup.
#### Multi-Hop Traversal
```typescript
await brain.find({
connected: {
to: 'research-institution',
via: VerbType.AffiliatedWith,
depth: 2 // Up to 2 hops away
}
})
```
**Performance**: O(d) where d = depth, each hop is O(1).
#### Combined with Other Filters
```typescript
await brain.find({
type: NounType.Person,
where: {
verified: true,
reputation: { gte: 100 }
},
connected: {
to: 'stanford-ai-lab',
via: VerbType.WorksAt,
direction: 'out'
},
limit: 20
})
// Returns: Verified people with high reputation who work at Stanford AI Lab
```
#### Pagination with Graph Queries (v5.8.0+)
```typescript
// Page through high-degree nodes efficiently
const neighbors = await brain.graphIndex.getNeighbors('hub-entity-id', {
direction: 'out',
limit: 50,
offset: 0
})
// Get verb IDs with pagination
const verbIds = await brain.graphIndex.getVerbIdsBySource('source-id', {
limit: 100,
offset: 0
})
```
**Performance**: O(1) lookup + O(log k) slice where k = total neighbors.
**Note**: See `src/graph/graphAdjacencyIndex.ts` for low-level graph operations.
### Sorting Results (v4.5.4+)
@ -484,6 +787,424 @@ async function getDocumentsByDate(page: number, pageSize: number = 20) {
}
```
## Common Query Patterns
### Pagination
**Offset-based pagination**:
```typescript
async function getPaginatedResults(page: number, pageSize: number = 20) {
return await brain.find({
type: NounType.Document,
where: { status: 'published' },
orderBy: 'createdAt',
order: 'desc',
limit: pageSize,
offset: page * pageSize
})
}
// Usage
const page1 = await getPaginatedResults(0) // First 20
const page2 = await getPaginatedResults(1) // Next 20
```
**Graph pagination** (v5.8.0+):
```typescript
// Paginate through high-degree node relationships
async function getNeighborPage(entityId: string, page: number, pageSize: number = 50) {
return await brain.graphIndex.getNeighbors(entityId, {
direction: 'out',
limit: pageSize,
offset: page * pageSize
})
}
```
**Performance**: O(1) for offset calculation, O(k) for slice where k = page size.
### Time-based Queries
**Recent entities**:
```typescript
// Last 24 hours
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000)
await brain.find({
where: {
createdAt: { gte: oneDayAgo }
},
orderBy: 'createdAt',
order: 'desc'
})
// Last 7 days with additional filters
const oneWeekAgo = Date.now() - (7 * 24 * 60 * 60 * 1000)
await brain.find({
type: NounType.Document,
where: {
createdAt: { gte: oneWeekAgo },
status: 'published'
},
orderBy: 'createdAt',
order: 'desc'
})
```
**Date ranges**:
```typescript
// Specific year
await brain.find({
where: {
publishDate: { between: [
new Date('2023-01-01').getTime(),
new Date('2023-12-31').getTime()
]}
}
})
// Quarter
const Q1_2024_start = new Date('2024-01-01').getTime()
const Q1_2024_end = new Date('2024-03-31').getTime()
await brain.find({
where: {
createdAt: { between: [Q1_2024_start, Q1_2024_end] }
}
})
```
### Combining Vector + Metadata + Graph
**Triple Intelligence query**:
```typescript
// Find: AI research papers from verified authors at top institutions
const results = await brain.find({
// Vector search (semantic)
query: 'artificial intelligence machine learning',
// Metadata filters
type: NounType.Document,
where: {
publishDate: { gte: 2020 },
citations: { gte: 50 },
peerReviewed: true
},
// Graph traversal
connected: {
to: topInstitutionIds, // Array of institution entity IDs
via: VerbType.AffiliatedWith,
depth: 2 // Authors affiliated with institutions (2 hops)
},
// Results
limit: 50,
orderBy: 'citations',
order: 'desc'
})
```
**Performance**: O(log n) vector search + O(log n) metadata filters + O(1) graph traversal = ~2-3ms total.
### Excluding Soft-Deleted Entities
**Common pattern**:
```typescript
// Standard query excludes deleted
await brain.find({
where: {
deletedAt: { exists: false } // Not soft-deleted
}
})
// Or use compound filter
await brain.find({
where: {
allOf: [
{ status: 'active' },
{ deletedAt: { exists: false } }
]
}
})
```
**Note**: Consider implementing this as a default filter in your application layer if all queries need it.
### Finding Similar Entities
**Semantic similarity**:
```typescript
// Find documents similar to a specific document
await brain.find({
near: {
id: 'doc-123',
threshold: 0.8 // Minimum 80% similarity
},
type: NounType.Document,
limit: 10
})
// With metadata constraints
await brain.find({
near: { id: 'paper-456', threshold: 0.75 },
where: {
publishDate: { gte: 2020 },
language: 'en'
}
})
```
**Performance**: O(log n) HNSW search with early termination at threshold.
### Aggregation Patterns
**Count matching entities**:
```typescript
// Get total count (metadata-only query is fastest)
const results = await brain.find({
where: { status: 'published' },
limit: 1 // We only need the count
})
// Note: Current API returns results, not counts
// For production, consider caching counts or using metadata indices directly
```
**Group by type**:
```typescript
// Find all entities, then group by type in application
const allEntities = await brain.find({ limit: 10000 })
const byType = allEntities.reduce((acc, entity) => {
const type = entity.noun || 'unknown'
if (!acc[type]) acc[type] = []
acc[type].push(entity)
return acc
}, {})
```
### Multi-Condition OR Queries
**Any of multiple values**:
```typescript
await brain.find({
where: {
anyOf: [
{ priority: 'urgent' },
{ priority: 'high' },
{ assignee: 'admin' },
{ dueDate: { lte: Date.now() } }
]
}
})
// Returns: urgent OR high priority OR assigned to admin OR overdue
```
**Complex business logic**:
```typescript
// Find: (Premium users OR trial users with activity) AND not banned
await brain.find({
type: NounType.Person,
where: {
allOf: [
{
anyOf: [
{ subscription: 'premium' },
{
allOf: [
{ subscription: 'trial' },
{ lastActive: { gte: Date.now() - 86400000 } } // 24h
]
}
]
},
{ banned: { ne: true } }
]
}
})
```
## Troubleshooting Guide
### Query Returns No Results
**Check 1: Verify entity exists**
```typescript
// List all entities of a type
const all = await brain.find({
type: NounType.Document,
limit: 10
})
console.log(`Found ${all.length} documents`)
```
**Check 2: Test filters individually**
```typescript
// Remove filters one by one to find the culprit
await brain.find({ where: { status: 'published' } }) // Works?
await brain.find({ where: { year: 2024 } }) // Works?
await brain.find({ where: {
status: 'published',
year: 2024 // Combined - works?
}})
```
**Check 3: Verify field names**
```typescript
// Get a sample entity to see actual field names
const sample = await brain.find({ type: NounType.Document, limit: 1 })
console.log(Object.keys(sample[0].data)) // Actual fields
```
**Common issues**:
- Field name typo: `publishDate` vs `published_date`
- Wrong type: `type: NounType.Document` but entities are `NounType.Paper`
- Case sensitivity: `status: 'Active'` vs `status: 'active'`
### Slow Query Performance
**Check 1: Identify slow operation**
```typescript
// Use explain mode (if available)
const results = await brain.find({
query: 'machine learning',
where: { title: { contains: 'AI' } }, // ⚠️ O(n) substring search
explain: true
})
```
**Check 2: Avoid O(n) operations**
```typescript
// ❌ Slow: Substring search
where: { description: { contains: 'machine' } }
// ✅ Fast: Semantic search
query: 'machine learning'
// ❌ Slow: Negation
where: { status: { ne: 'draft' } }
// ✅ Fast: Positive filter
where: { status: 'published' }
```
**Check 3: Optimize filter order**
```typescript
// ❌ Suboptimal: Slow filter first
where: {
description: { contains: 'AI' }, // O(n) - runs first
year: 2024 // O(1) - runs second
}
// ✅ Optimal: Fast filter first (automatic optimization)
where: {
year: 2024, // O(1) - narrow results
status: 'published' // O(1) - further narrow
// Only then apply O(n) operations if needed
}
```
**Performance budget**:
- **< 2ms**: Metadata-only or graph-only queries
- **< 5ms**: Vector search with simple filters
- **< 10ms**: Complex Triple Intelligence queries
- **> 10ms**: Check for O(n) operations or missing indices
### Type Errors
**TypeScript type mismatches**:
```typescript
// ❌ Error: Type 'string' is not assignable to type 'NounType'
await brain.find({ type: 'Document' })
// ✅ Correct: Use NounType enum
import { NounType } from '@soulcraft/brainy'
await brain.find({ type: NounType.Document })
// ❌ Error: Operator not recognized
where: { age: { greaterThan: 18 } } // Old API
// ✅ Correct: Use canonical operators
where: { age: { gt: 18 } } // v5.0.0+
```
### Graph Traversal Issues
**No connected entities found**:
```typescript
// Verify relationship exists
const relations = await brain.getRelations({
from: 'entity-a',
to: 'entity-b'
})
console.log('Relationships:', relations)
// Check direction
await brain.find({
connected: {
to: 'entity-id',
direction: 'in' // Try 'out' or 'both'
}
})
// Verify verb type
await brain.find({
connected: {
to: 'entity-id',
via: VerbType.WorksFor // Correct VerbType?
}
})
```
### Vector Search Not Working
**Check embeddings**:
```typescript
// Ensure vectors are generated (automatic in v5.0+)
const entity = await brain.get('entity-id')
console.log('Has vector:', !!entity.vector)
// If missing, entity may predate vector support
// Re-add entity to generate vector
await brain.update(entity.id, { data: entity.data })
```
**Similarity threshold too high**:
```typescript
// ❌ Too strict: May return nothing
await brain.find({
near: { id: 'doc-123', threshold: 0.95 }
})
// ✅ Reasonable: 0.7-0.85 is typical
await brain.find({
near: { id: 'doc-123', threshold: 0.75 }
})
```
### Unexpected Results
**Entity appears in wrong type query**:
```typescript
// Check actual entity type
const entity = await brain.get('unexpected-id')
console.log('Entity type:', entity.noun)
// Verify type filter is working
await brain.find({
type: NounType.Document,
where: { id: 'unexpected-id' } // Should not return if wrong type
})
```
**Duplicate results**:
```typescript
// Check for duplicate entity IDs
const results = await brain.find({ query: 'test' })
const ids = results.map(r => r.id)
const uniqueIds = new Set(ids)
console.log(`Results: ${results.length}, Unique: ${uniqueIds.size}`)
// Brainy should never return duplicates - report if found
```
## VFS (Virtual File System) Visibility (v4.7.0+)
### Default Behavior

582
docs/transactions.md Normal file
View file

@ -0,0 +1,582 @@
# Transaction System
**Status:** ✅ Production Ready (v5.8.0+)
## 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.delete()`, and `brain.relate()` operations
- **Compatible**: Works seamlessly with COW, sharding, type-aware storage, and distributed storage
## Architecture
```
User Code (brain.add(), brain.update(), etc.)
Transaction Manager (orchestration)
Operations (SaveNounMetadataOperation, SaveNounOperation, etc.)
Storage Adapter (COW, sharding, type-aware 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
### COW (Copy-on-Write)
✅ **Fully Compatible**
Transactions work transparently with COW branches:
```typescript
// Create branch
await brain.cow.createBranch('feature-branch')
await brain.cow.checkout('feature-branch')
// Add entity (uses transaction on this branch)
const id = await brain.add({
data: { name: 'Feature Entity' },
type: NounType.Thing
})
// On rollback: Branch remains clean, no partial commits
```
**How It Works:**
- Transactions use `StorageAdapter` interface
- COW operates at storage layer (refManager, blobStorage, commitLog)
- Branch isolation prevents cross-branch contamination
- Rollback = discard uncommitted changes (COW makes this trivial)
### 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
### TypeAware Storage
✅ **Fully Compatible**
Transactions work with type-specific routing:
```typescript
// Entities routed to type-specific storage paths
const personId = await brain.add({
data: { name: 'John Doe' },
type: NounType.Person // → entities/nouns/person/<shard>/...
})
const orgId = await brain.add({
data: { name: 'Acme Corp' },
type: NounType.Organization // → entities/nouns/organization/<shard>/...
})
// Type changes handled atomically
await brain.update({
id: personId,
type: NounType.Organization, // Type change
data: { name: 'Doe Corp' }
})
```
**How It Works:**
- Type information carried in metadata
- Storage layer handles type-specific routing
- Type cache updated/restored during rollback
- Type counters adjusted on commit/rollback
### Distributed Storage
✅ **Fully Compatible**
Transactions work with distributed/remote storage:
```typescript
// Works with S3, Azure, GCS, etc.
const brain = new Brainy({
storage: {
type: 's3Compatible',
config: { /* S3 config */ }
}
})
// Transactions ensure atomicity at write coordinator level
await brain.add({ data: { name: 'Remote Entity' }, type: NounType.Thing })
```
**How It Works:**
- Transactions operate through `StorageAdapter` interface
- Remote storage adapters implement same interface
- Atomicity guaranteed at write-coordinator level
- Read-after-write consistency maintained
**Design Philosophy:**
- **Single-node writes** (most common): Fully atomic ✅
- **Distributed reads + centralized writes**: Transactions on primary ✅
- **Multi-primary**: Transactions per-instance, cross-instance via coordinator ✅
## 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.delete(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
**MEASURED Performance Impact:**
- Average overhead: ~2-5ms per transaction (measured: `tests/transaction/transaction.bench.ts`)
- Operations per transaction: 2-8 (metadata + data + indexes)
- Rollback cost: ~1-3ms (restore previous state)
**Optimization:**
- Operations executed sequentially (not parallel) for consistency
- Rollback only happens on failure (success path is fast)
- Index updates batched within transaction
### Statistics and Monitoring
```typescript
// Get transaction statistics
const stats = brain.transactionManager?.getStats()
console.log(stats)
// {
// totalTransactions: 1234,
// successfulTransactions: 1200,
// failedTransactions: 34,
// rollbacks: 34,
// averageOperationsPerTransaction: 4.2
// }
```
**Metrics Available:**
- `totalTransactions`: Total number of transactions executed
- `successfulTransactions`: Number of successful commits
- `failedTransactions`: Number of rollbacks
- `rollbacks`: Total rollback count
- `averageOperationsPerTransaction`: Average operations per transaction
## 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.delete(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. Monitor Transaction Statistics
```typescript
// ✅ Recommended: Monitor in production
setInterval(() => {
const stats = brain.transactionManager?.getStats()
if (stats) {
const failureRate = stats.failedTransactions / stats.totalTransactions
if (failureRate > 0.05) { // > 5% failure rate
console.warn('High transaction failure rate:', failureRate)
}
}
}, 60000) // Check every minute
```
### 5. Understand Atomicity Guarantees
**What Transactions GUARANTEE:**
- ✅ Atomicity within single Brainy instance
- ✅ Consistent state across all indexes
- ✅ Automatic rollback on failure
- ✅ Works with all storage adapters (local, remote, COW, sharded)
**What Transactions DON'T Provide:**
- ❌ Two-phase commit across multiple Brainy instances
- ❌ Distributed locking across nodes
- ❌ Cross-datacenter ACID guarantees
**Design:** Transactions ensure atomicity at the **write coordinator level**. For multi-instance scenarios, use `DistributedCoordinator`.
## 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:
- COW integration (`cow-transactions.test.ts`)
- Sharding integration (`sharding-transactions.test.ts`)
- TypeAware integration (`typeaware-transactions.test.ts`)
- Distributed storage integration (`distributed-transactions.test.ts`)
## Troubleshooting
### High Rollback Rate
**Symptom:** `failedTransactions` / `totalTransactions` > 5%
**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
### Transaction Statistics Missing
**Symptom:** `brain.transactionManager?.getStats()` returns `undefined`
**Cause:** TransactionManager not initialized
**Solution:**
```typescript
// Ensure Brainy is initialized
await brain.init()
// Then access stats
const stats = brain.transactionManager?.getStats()
```
## 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:** All storage adapters (filesystem, S3, Azure, GCS, memory) implement this interface. Transactions work with **any** storage adapter automatically.
## Additional Resources
- **Unit Tests:** `tests/transaction/transaction.test.ts` (36 passing tests)
- **Integration Tests:** `tests/transaction/integration/` (35 test scenarios)
- **Compatibility Analysis:** `.strategy/TRANSACTION_COMPATIBILITY_ANALYSIS.md` (internal)
- **Performance Benchmarks:** `tests/transaction/transaction.bench.ts`
## Version History
- **v5.8.0**: Initial transaction system release
- Atomic operations with rollback
- Compatible with COW, sharding, type-aware, distributed
- 36/36 unit tests passing
- 35 integration test scenarios
## Summary
Brainy's transaction system provides **production-ready atomic operations** with automatic rollback. It works transparently with all Brainy APIs and is fully compatible with advanced features like COW, sharding, type-aware storage, and distributed storage.
**Key Takeaways:**
- ✅ **Automatic**: No manual transaction management needed
- ✅ **Atomic**: All operations succeed or all rollback
- ✅ **Compatible**: Works with all storage adapters and features
- ✅ **Production-Ready**: Tested with 71 test scenarios (36 unit + 35 integration)
- ✅ **Performant**: ~2-5ms overhead per transaction (measured)
Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()`!