Compare commits

..

No commits in common. "main" and "v7.33.0" have entirely different histories.

669 changed files with 150750 additions and 58342 deletions

View file

@ -11,12 +11,15 @@
- **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
- `opfsStorage.ts` -- browser Origin Private File System (formerly browserStorage.ts)
- `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
- `s3CompatibleStorage.ts` -- generic S3-compatible (AWS, MinIO, etc.)
- `r2Storage.ts` -- Cloudflare R2
- `gcsStorage.ts` -- Google Cloud Storage
- `azureBlobStorage.ts` -- Azure Blob Storage
- Supporting: `batchS3Operations.ts`, `optimizedS3Search.ts`
- **COW** (`src/storage/cow/`): Copy-on-Write infrastructure for versioning and branching
- CommitLog, CommitObject, CommitBuilder, BlobStorage, RefManager, TreeObject
### Vector Search (`src/hnsw/`)
- `hnswIndex.ts` -- HNSW-based approximate nearest neighbor search

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

9
.gitignore vendored
View file

@ -93,14 +93,9 @@ docs/internal/
# Rust/Cargo build artifacts
src/embeddings/candle-wasm/target/
src/embeddings/candle-wasm/Cargo.lock
src/embeddings/wasm/pkg/
# 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/*
# But keep the pre-built WASM (committed for users without Rust)
!src/embeddings/wasm/pkg/*.wasm
!src/embeddings/wasm/pkg/*.js
!src/embeddings/wasm/pkg/*.d.ts

24
.versionrc.json Normal file
View file

@ -0,0 +1,24 @@
{
"types": [
{"type": "feat", "section": "✨ Features"},
{"type": "fix", "section": "🐛 Bug Fixes"},
{"type": "docs", "section": "📚 Documentation"},
{"type": "refactor", "section": "♻️ Code Refactoring"},
{"type": "perf", "section": "⚡ Performance Improvements"},
{"type": "test", "section": "✅ Tests"},
{"type": "build", "section": "🔧 Build System"},
{"type": "ci", "section": "🔄 CI/CD"},
{"type": "style", "hidden": true},
{"type": "chore", "hidden": true}
],
"compareUrlFormat": "https://github.com/soulcraftlabs/brainy/compare/{{previousTag}}...{{currentTag}}",
"commitUrlFormat": "https://github.com/soulcraftlabs/brainy/commit/{{hash}}",
"issueUrlFormat": "https://github.com/soulcraftlabs/brainy/issues/{{id}}",
"userUrlFormat": "https://github.com/{{user}}",
"releaseCommitMessageFormat": "chore(release): {{currentTag}}",
"issuePrefixes": ["#"],
"header": "# Changelog\n\nAll notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.\n",
"scripts": {
"postbump": "echo '✅ Version bumped to' $(node -p \"require('./package.json').version\")"
}
}

View file

@ -2,439 +2,56 @@
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
### [8.9.0](https://github.com/soulcraftlabs/brainy/compare/v8.8.2...v8.9.0) (2026-07-19)
### [7.33.0](https://github.com/soulcraftlabs/brainy/compare/v7.32.2...v7.33.0) (2026-06-19)
- docs: measured performance envelopes v1 (per-op p50/p95 at 1k and 10k, pure-JS floor) (5cabd78)
- fix: release drains in-flight writer-lock heartbeat — no phantom lock after unlink (70e4bc8)
- feat: flush() never compacts — history maintenance moves to close() with bounded passes (300d9f2)
### [8.8.2](https://github.com/soulcraftlabs/brainy/compare/v8.8.1...v8.8.2) (2026-07-19)
- fix: one field-resolution law across aggregation hooks, source.where, removeMany, and find() spellings (945d92d)
- chore: push public docs to the soulcraft.com ingest door on release (42037d0)
### [8.8.1](https://github.com/soulcraftlabs/brainy/compare/v8.8.0...v8.8.1) (2026-07-18)
- fix: O(1) adaptive retention accounting + historyStats fleet audit (6207e48)
- fix: import dedup off-switch honesty + brain-owned lifecycle for the background pass (4fcef7b)
### [8.8.0](https://github.com/soulcraftlabs/brainy/compare/v8.7.1...v8.8.0) (2026-07-17)
- feat: OS-limit detection for pool-scale deployments (16a73b8)
### [8.7.1](https://github.com/soulcraftlabs/brainy/compare/v8.7.0...v8.7.1) (2026-07-17)
- fix: race-proof writer-lock acquisition + machine-readable conflict through init (01a3b46)
### [8.7.0](https://github.com/soulcraftlabs/brainy/compare/v8.6.0...v8.7.0) (2026-07-17)
- feat: scaled transact budgets + labeled timeout diagnostics + envelope docs (6ef9fcb)
### [8.6.0](https://github.com/soulcraftlabs/brainy/compare/v8.5.2...v8.6.0) (2026-07-17)
- feat: brain.auditGraph() — read-only graph-truth audit (2a03fae)
### [8.5.2](https://github.com/soulcraftlabs/brainy/compare/v8.5.1...v8.5.2) (2026-07-17)
- fix: exception-safe aggregation backfill + generation-verified adoption + loud open-path guards (a77b064)
### [8.5.1](https://github.com/soulcraftlabs/brainy/compare/v8.5.0...v8.5.1) (2026-07-17)
- fix: aggregation state adoption on reopen + single-flight backfill + query-cap ratchet removal (da55be7)
- docs: external-backups/sparse-storage guide + generation fact log concept (593bb8b)
### [8.5.0](https://github.com/soulcraftlabs/brainy/compare/v8.4.0...v8.5.0) (2026-07-15)
- test: tolerant timing assertion in the execution-time measure test (4dc0a92)
- feat: committedGeneration capability + pinned durability/stability contracts (d1ecee1)
- docs: RELEASES.md entry for 8.5.0 (provider fact-log access + shared verifier) (e4f37cd)
- feat: provider access to the fact log + shared stamp verifier via internals (352e356)
### [8.4.0](https://github.com/soulcraftlabs/brainy/compare/v8.3.3...v8.4.0) (2026-07-15)
- docs: RELEASES.md entry for 8.4.0 (generation fact log + family stamp) (4a60b43)
- feat: entity-tree family stamp — sourceGeneration + rollup coherence at open (2888ae6)
- feat: generation fact log — after-image commit records, dual-written at every commit point (38b0041)
### [8.3.3](https://github.com/soulcraftlabs/brainy/compare/v8.3.2...v8.3.3) (2026-07-15)
- docs: RELEASES.md entry for 8.3.3 (rename containment fix + repair) (c3feafd)
- test: lens-consistency regression — combined vs subtype-only vs canonical ground truth (4fb41f9)
- fix: VFS rename moves the containment edge — no ghost in the old directory (af8c179)
### [8.3.2](https://github.com/soulcraftlabs/brainy/compare/v8.3.1...v8.3.2) (2026-07-14)
- docs: RELEASES.md entry for 8.3.2 (honest counters) (0932ecd)
- fix: honest counters — removal never re-reads the removed record + repairIndex recounts and persists all rollups (2e2ba9c)
### [8.3.1](https://github.com/soulcraftlabs/brainy/compare/v8.3.0...v8.3.1) (2026-07-14)
- docs: RELEASES.md entry for 8.3.1 (full-removal deletes + family-scoped gate) (c0c68ac)
- fix: full-removal canonical deletes + family-scoped migration gate (366f9a9)
- docs: cite the cross-layer integrity contract generically in comments and notes (1d26988)
### [8.3.0](https://github.com/soulcraftlabs/brainy/compare/v8.2.8...v8.3.0) (2026-07-13)
- docs: RELEASES.md entry for 8.3.0 (heal-cost + cross-layer integrity contract) (7692c6f)
- perf: parallel + id-only canonical enumeration (heal-cost dominant term) (ec5b933)
- feat: registered-blob family contract — declared index blobs are undeletable (bfa1762)
- feat: validateIndexConsistency delegates to provider invariants (6bcb54f)
### [8.2.8](https://github.com/soulcraftlabs/brainy/compare/v8.2.7...v8.2.8) (2026-07-13)
- fix: honest index readiness — no silently-empty queries on a cold index (d0f69c7)
### [8.2.7](https://github.com/soulcraftlabs/brainy/compare/v8.2.6...v8.2.7) (2026-07-13)
- fix: restore loadBinaryBlob fault-propagation (native column-store lockstep) (b6c7039)
### [8.2.6](https://github.com/soulcraftlabs/brainy/compare/v8.2.5...v8.2.6) (2026-07-13)
- docs: RELEASES.md entry for 8.2.6 (write/index-spine hardening) (a873852)
- chore: hold loadBinaryBlob fault-propagation for the cortex column-store lockstep (36c10c1)
- fix: aggregation surfaces materialize/state-load failures loudly (02eff64)
- fix: surface a degraded derived index on reads instead of serving it silently (ba958d9)
- fix: saveBinaryBlob never acks a durable write that stored nothing (7feba49)
- fix: refuse writes when single-op history cannot be made durable (54c1836)
- fix: clear() wipes the full native/derived footprint, not a subset (d8301f8)
- fix: surface segment/entity read faults loudly instead of masking as absent (af5d2f3)
- fix: spine hardening pass 1 (part) — count symmetry, honest partial-load, flush durability, read-fault propagation (119087a)
- test: pin the read-your-writes contract under the single writer (eb9c4eb)
### [8.2.5](https://github.com/soulcraftlabs/brainy/compare/v8.2.4...v8.2.5) (2026-07-12)
- docs: RELEASES.md entry for 8.2.5 (honest rollback-failure response) (a7c7aa5)
- fix: honest response when a transaction rollback cannot complete (711d2f0)
### [8.2.4](https://github.com/soulcraftlabs/brainy/compare/v8.2.3...v8.2.4) (2026-07-12)
- docs: RELEASES.md entry for 8.2.4 (non-destructive restore) (4574695)
- fix: non-destructive, crash-resumable restore (a2f4f6a)
### [8.2.3](https://github.com/soulcraftlabs/brainy/compare/v8.2.2...v8.2.3) (2026-07-12)
- docs: RELEASES.md entry for 8.2.3 (transact durability barrier) (be5ce0b)
- fix: transact durability barrier — committed transactions are durable on return (3b8fa51)
### [8.2.2](https://github.com/soulcraftlabs/brainy/compare/v8.2.1...v8.2.2) (2026-07-11)
- docs: RELEASES.md entry for 8.2.2 (transaction timeout rollback) (ed97006)
- fix: transaction timeout rolls back applied operations (no torn state) (508a8e3)
### [8.2.1](https://github.com/soulcraftlabs/brainy/compare/v8.2.0...v8.2.1) (2026-07-10)
- test: update graph-index operation constructors to the VerbEndpointInts signature (62a449d)
- docs: RELEASES.md entry for 8.2.1 (transact forward-ref parity fix) (7089782)
- fix: transact forward references resolve graph endpoint ints at execute time (a175406)
### [8.2.0](https://github.com/soulcraftlabs/brainy/compare/v8.1.0...v8.2.0) (2026-07-10)
- docs: RELEASES.md entry for 8.2.0 (temporal VFS) (98ceadc)
- feat: temporal VFS — file content joins the Model-B immutability model (a3467e1)
- docs: pin the write-path invariant in the plugin contract (the onChange change-feed guarantee) (4af8fb3)
### [8.1.0](https://github.com/soulcraftlabs/brainy/compare/v8.0.17...v8.1.0) (2026-07-10)
- docs: RELEASES.md entry for 8.1.0 (brain.onChange change feed) (4e9be08)
- feat: brain.onChange — the in-process change feed for every committed mutation (fd5edb5)
### [8.0.17](https://github.com/soulcraftlabs/brainy/compare/v8.0.16...v8.0.17) (2026-07-08)
- docs: RELEASES.md entry for 8.0.17 (canonical count recovery + dead-machinery sweep) (6b8b9cb)
- fix: count recovery scans the canonical layout; remove the dead 7.x hnsw sharding machinery (352e2da)
### [8.0.16](https://github.com/soulcraftlabs/brainy/compare/v8.0.15...v8.0.16) (2026-07-08)
- docs: RELEASES.md entry for 8.0.16 (atomic ifAbsent/upsert + exact blob refCounts) (54e7c0e)
- fix: atomic ifAbsent/upsert inserts + exact blob reference counts under concurrency (867939e)
### [8.0.15](https://github.com/soulcraftlabs/brainy/compare/v8.0.14...v8.0.15) (2026-07-08)
- docs: RELEASES.md entry for 8.0.15 (atomic ifRev CAS) (b1fe25a)
- fix: ifRev CAS is atomic — the revision check now runs under the commit mutex (9a3d1bd)
### [8.0.14](https://github.com/soulcraftlabs/brainy/compare/v8.0.13...v8.0.14) (2026-07-07)
- docs: RELEASES.md entry for 8.0.14 (migration preserves branch-scoped non-entity state) (64188a3)
- fix: 7→8 migration preserves branch-scoped non-entity state instead of deleting it (a93bb4e)
### [8.0.13](https://github.com/soulcraftlabs/brainy/compare/v8.0.12...v8.0.13) (2026-07-07)
- docs: RELEASES.md entry for 8.0.13 (accurate boot log for established stores) (38e8de5)
- fix: an established store no longer boot-logs "New installation" (3086916)
### [8.0.12](https://github.com/soulcraftlabs/brainy/compare/v8.0.11...v8.0.12) (2026-07-07)
- docs: RELEASES.md entry for 8.0.12 (7→8 VFS recovery, zero-rebuild cold open, strict query operators) (d9017e7)
- fix: recover VFS content blobs stranded by a 7→8 upgrade, in place on open (c0f6ccd)
- fix: validate where-operators and align the in-memory matcher to the documented set (6821e19)
- docs: correct rc-era time-travel staleness + record the embedding-model ordering constraint (68da660)
- fix: cold-open no longer re-derives durable indexes — complete the readiness contract for all three providers (61c247c)
- docs: RELEASES.md entry for 8.0.11 (exit-hang class closed for every op shape) (4fde94b)
### [8.0.11](https://github.com/soulcraftlabs/brainy/compare/v8.0.10...v8.0.11) (2026-07-02)
- fix: no script shape can hang on brainy's internals — unref every maintenance timer + one-shot beforeExit (30eacbd)
- docs: RELEASES.md entry for 8.0.10 (clean process exit after close) (2da2736)
### [8.0.10](https://github.com/soulcraftlabs/brainy/compare/v8.0.9...v8.0.10) (2026-07-02)
- fix: a bare script now exits cleanly after close() — release every process keep-alive (c540d63)
### [8.0.9](https://github.com/soulcraftlabs/brainy/compare/v8.0.8...v8.0.9) (2026-07-02)
- feat: guarded plugin auto-detection — installing @soulcraft/cor is the opt-in (588267b)
### [8.0.8](https://github.com/soulcraftlabs/brainy/compare/v8.0.7...v8.0.8) (2026-07-02)
- docs: plugins are explicit opt-in — correct the README scale section and plugins config comment (e420369)
### [8.0.7](https://github.com/soulcraftlabs/brainy/compare/v8.0.1...v8.0.7) (2026-07-02)
- docs: GA version is 8.0.7 — npm retired 8.0.0-8.0.6 (January dev-cycle unpublishes) (5db2c41)
- chore(release): 8.0.1 (48bea9e)
- docs: flagship README for the 8.0 GA; GA version is 8.0.1 (e44620e)
- docs: rename the native provider to @soulcraft/cor across public docs and JSDoc (bf4a333)
- chore(release): 8.0.0 (a3c2717)
- docs: RELEASES.md 8.0.0 GA entry (RC notes become history) (4584d0b)
- feat: promote the 8.0 u64-id line to main for the 8.0.0 GA (55d57f8)
- fix(8.0): byte-copy _id_mapper/* in the pre-upgrade backup (cor's write-new nuance) (a30ed72)
- chore(release): 7.33.5 (29b9d5f)
- fix: metadata cold-read guard — no more silent [] on cold find({where}) (7.33.5) (9dc4c5e)
- fix(8.0): metadata cold-read guard — no more silent [] on cold find({where}) (79e8709)
- docs(8.0): add module JSDoc to typeValidation.ts (the one file missing a module block) (ab53fa0)
- feat(8.0): auto pre-upgrade backup — hard-link snapshot before the 7.x→8.0 migration (1aad1f6)
- fix(ci): commit the prebuilt wasm pkg + build before test:bun (green CI on fresh clone) (ed178e2)
- chore(release): 7.33.4 (2be3d0f)
- fix: never serve a silent [] from find({connected}) on a cold-loaded graph (fd699d0)
- chore(release): 7.33.3 (d1665bb)
- fix: re-validate find() results against the predicate (index-integrity guard) (7b5db0d)
- chore(release): 7.33.2 (9593a27)
- fix: graph adjacency cold-load consistency guard — no more silent [] on connected (1694f68)
- chore(release): 7.33.1 (811c7da)
- fix: getNouns cursor pagination re-scanned the first page forever (permanent CPU loop) (6721c52)
- chore(release): 7.33.0 (526aaad)
- feat: visibility tier (public/internal/system) on nouns + verbs (3a62445)
- chore(release): 7.32.2 (c53dd61)
### [7.32.2](https://github.com/soulcraftlabs/brainy/compare/v7.32.1...v7.32.2) (2026-06-19)
- refactor: rename BackupData → PortableGraph (the type is interchange, not a backup) (89036de)
- chore(release): 7.32.1 (5e7379d)
### [7.32.1](https://github.com/soulcraftlabs/brainy/compare/v7.32.0...v7.32.1) (2026-06-17)
- fix: getNouns().totalCount reports true total, not page size; quiet benign mmap-vector log (edff637)
- chore(release): 7.32.0 (adec0ba)
### [7.32.0](https://github.com/soulcraftlabs/brainy/compare/v7.31.8...v7.32.0) (2026-06-16)
- feat: portable graph export()/import() (BackupData v1) on brain.data() (a408d37)
- chore(release): 7.31.8 (89c6d04)
### [7.31.8](https://github.com/soulcraftlabs/brainy/compare/v7.31.7...v7.31.8) (2026-06-16)
- fix: query-cap memory misread (MemAvailable + floor) + rootDirectory getter for native mmap fast-path (3f8e097)
- chore(release): 7.31.7 (4f8159c)
### [7.31.7](https://github.com/soulcraftlabs/brainy/compare/v7.31.6...v7.31.7) (2026-06-11)
- fix: vfs.rename() issues a metadata-only update + rollback of fresh adds removes them (ac29b0e)
- chore(release): 7.31.6 (9b52629)
### [7.31.6](https://github.com/soulcraftlabs/brainy/compare/v7.31.5...v7.31.6) (2026-06-11)
- fix: remap reserved fields from update() metadata patches to their canonical location (67e5fc8)
- chore(release): 7.31.5 (e5ec658)
### [7.31.5](https://github.com/soulcraftlabs/brainy/compare/v7.31.4...v7.31.5) (2026-06-11)
- fix: feature-detect setVectorBackend before wiring the mmap-vector backend (a537b36)
- chore(release): 7.31.4 (a8cbab6)
### [7.31.4](https://github.com/soulcraftlabs/brainy/compare/v7.31.3...v7.31.4) (2026-06-10)
- fix: feature-detect setConnectionsCodec before wiring the connections codec (747ab97)
- chore(release): 7.31.3 (cfb051c)
### [7.31.3](https://github.com/soulcraftlabs/brainy/compare/v7.31.2...v7.31.3) (2026-06-10)
- fix: mmap-vector backend capacity NaN at the provider FFI boundary (eade6ff)
### [8.0.0-rc.9](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.8...v8.0.0-rc.9) (2026-07-01)
- docs(8.0): RELEASES.md — rc.9 (migration LOCK + 6x cosine + ES2023/Node22 floor) (3a33987)
- chore(8.0): ES2023 target + drop DOM lib + downlevelIteration (config truth-up) (cf74c25)
- perf(8.0): allocation-free distance loops (6x cosine) — evidence-revised Fork X (b5bc73f)
- feat(8.0): #18 coordinated migration LOCK — block-and-queue the 7.x→8.0 auto-upgrade (67bbf69)
- chore(8.0): modernize toolchain + position Bun as a runtime (ca9129a)
### [8.0.0-rc.8](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.7...v8.0.0-rc.8) (2026-06-30)
- docs(8.0): RELEASES.md — rc.8 (no-freeze online whole-brain auto-upgrade) (5af48a9)
- feat(8.0): no-freeze auto-upgrade hooks — isMigrating() deference + stampBrainFormat() + brain-format export (b6b9198)
### [8.0.0-rc.7](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.6...v8.0.0-rc.7) (2026-06-30)
- docs(8.0): RELEASES.md — rc.7 (cold-graph self-heal + billion-scale RAM + version handshake) (1ddc786)
- perf(8.0): bound per-id generation history chains (O(W+L) resident, was O(N)) (a859d6e)
- feat(8.0): eager graphIndex.init() before the isReady() rebuild gate (8f4787b)
- feat(8.0): version-handshake marker (formatInfo + indexEpoch) for whole-brain auto-upgrade (fc7f110)
- fix(8.0): never serve a silent [] from find({connected}) on a cold-loaded graph (229b067)
- perf(8.0): represent the committed-generation ledger as an interval set (93f61db)
- perf(8.0): drop O(N)-resident id-keyed storage caches; source counts from the record (b6beb7f)
### [8.0.0-rc.6](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.5...v8.0.0-rc.6) (2026-06-29)
- docs(8.0): RELEASES.md — rc.6 (perf + native-provider contract + test hygiene) (6daa70e)
- feat(8.0): wire the two cor-confirmed metadata-provider contract additions (8b19122)
- test(8.0): re-home orphaned test files into the gate + guard against recurrence (3f9f140)
- perf(8.0): negation/absence where-operators via roaring-bitmap difference (5f974ab)
- perf(8.0): HNSW removeItem is O(in-degree) via a reverse-adjacency index (72df557)
### [8.0.0-rc.5](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.4...v8.0.0-rc.5) (2026-06-29)
- docs(8.0): RELEASES.md — rc.5 hardening + the breaking operator removal (6c9a438)
- refactor(8.0): remove the 4 deprecated query-operator aliases (clean break) (ddcc0c7)
- refactor(8.0): remove dead/deprecated code (legacy sweep) (b9369f2)
- refactor(8.0): API-surface + quality polish from the readiness audit (a52dba2)
- fix(8.0): close GA-blocking correctness gaps from the readiness audit (47e8031)
- docs(8.0): correct public docs to the real 8.0 API + honest perf claims (40d2cd5)
- fix(8.0): re-validate find() results against the predicate (index-integrity guard) (3d11619)
### [8.0.0-rc.4](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.3...v8.0.0-rc.4) (2026-06-24)
- docs(8.0): drop the DeletedItemsIndex section + pseudo-code from index-architecture (e7b50cf)
- fix(8.0): gate native graph analytics on the provider readiness flag (d321cf5)
- refactor(8.0): remove dead, unreachable, and unwired modules (bf0afe8)
- build(8.0): clean dist before every build so stale artifacts never ship (03d6540)
- feat(8.0): #35 part-3 — supply at-gen candidate vectors for the native exact-rerank (c9e2169)
### [8.0.0-rc.3](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.2...v8.0.0-rc.3) (2026-06-23)
- test(8.0): de-flake the VFS path-cache timing assertion (0e8972c)
- feat(8.0): asOf at-gen vector defer — provider-served historical semantic search (#35) (1c363e8)
- perf(8.0): bound find({ where, orderBy }) sort to the page (CTX-BR-FIND-ORDERBY) (450084b)
- feat(8.0): brain.graph.subgraph(query) query→expand fusion (#61) (82dde92)
- feat(8.0): vector allowedIds predicate-pushdown into find() (#46) (dd325f2)
- feat(8.0): graph analytics — brain.graph.rank / communities / path (632d90a)
- fix(8.0): restore() reloads a native entity-id mapper before graphIndex.rebuild() (4d0b64f)
- test(8.0): cover pending-tier range queries + setRetentionBudget adaptive reclaim (3783e61)
- feat(8.0): Model-B per-write generation-stamping + adaptive retention knob (5c3bb2c)
- test(8.0): Model-B write-perf + scalability spike harnesses (afac7f9)
- perf(8.0): per-id history chains for O(log) historical reads + bounded delta cache (ceed70d)
- refactor(8.0): graph analytics contract — intent names, not algorithm names (f3e6911)
- docs(8.0): RELEASES — native provider is @soulcraft/cor 3.0 (fix cortex 3.0 self-contradiction) (96d9c0b)
- test(8.0): cover the native graph seam + make provider resolution factory-tolerant (29410bc)
### [8.0.0-rc.2](https://github.com/soulcraftlabs/brainy/compare/v8.0.0-rc.1...v8.0.0-rc.2) (2026-06-21)
- docs(8.0): RELEASES rc.2 additions — graph engine + additive wins + correctness fixes (18f27cb)
- feat(8.0): brain.graph.export() + noun-walk cursor + noun visibility hydration (c2a84c9)
- feat(8.0): brain.graph.subgraph() + native-provider routing (8c2b57a)
- feat(8.0): related({ node }) — one-call both-direction incident edges (d4de48d)
- feat(8.0): GraphAccelerationProvider contract — the native graph-engine seam (a3d6fdb)
- perf(8.0): cursor pagination for the verb walk — full edge pagination O(N²) → O(N) (682e786)
- perf(8.0): visibility-aware fast adjacency — related() stays O(degree) under default visibility (a914313)
- feat(8.0): upsert + FindParams.includeVectors + removeMany adaptive chunking (4cc2088)
- docs(8.0): note reserved-field default-throw in RELEASES rc additions (1bc709d)
- feat(8.0): reserved-field enforcement — reservedFieldPolicy defaults to throw (54c7c39)
- docs: mark 8.0.0-rc.1 published (npm tag rc) + note rc.1 additions (ae3fe82)
### [8.0.0-rc.1](https://github.com/soulcraftlabs/brainy/compare/v7.31.2...v8.0.0-rc.1) (2026-06-20)
- feat(8.0): id-normalization (#18) + aggregation min/max delete-safety + RC-safe release (d02e522)
- feat(8.0): API simplification — remove neural()/Db.search, one storage `path` key, integration→0 (606445c)
- feat(8.0): 7.x→8.0 layout migration — fix silent total data loss on first open (0c4a51c)
- feat(8.0): temporal range verbs — diff, history, since(gen|Date), asOf{exclusive}, transactionLog window (2c84f86)
- refactor(8.0): rename BackupData → PortableGraph (the type is interchange, not a backup) (373a481)
- fix(8.0): VFS path-cache instance-scoping + verb totalCount page-cap (3a3aa43)
- fix(8.0): multi-valued array fields index every element (contains no longer misses) (eccf420)
- fix(8.0): column-store range queries honor exclusive bounds (lessThan/greaterThan) (009e506)
- fix(8.0): per-type counts rehydrate after cold reopen (column store, not dead sparse index) (d918f49)
- feat(8.0): version-coupling guard — a mismatched/failed native plugin fails loud, never silent JS fallback (1264fec)
- test(8.0): boundary guard forbids @soulcraft/cor too (cortex→cor rename) (b198281)
- fix(8.0): stats() per-type counts no longer inflate with HNSW re-saves (21d02d3)
- fix(8.0): getNouns().totalCount reports true total, not page size (port of 7.32.1) (b2005ff)
- fix(8.0): real bugs surfaced by integration hardening — where-intersect, related() offset, relate() updatedAt (5eaf579)
- test(8.0): integration rot pass — 77→17 failures (parallel per-file hardening) (e5997a1)
- test(8.0): begin integration rot pass — clear-persistence (drop COW internals) + metadata-only addRelationship→relate (c600468)
- docs(8.0): RELEASES — portable export/import (BackupData v1) + distinctCount any-type section (4741e23)
- fix(8.0): distinctCount aggregates distinct values of any type + edge-case regression tests (574a8b1)
- feat(8.0): validateBackup() dry-run + includeContent blob round-trip test + clone test (7aad803)
- docs(8.0): export/import guide + api/README portable backup section (c2b73d4)
- feat(8.0): portable graph export()/import() (BackupData v1) — Db.export + polymorphic import (010ccf8)
- feat(8.0): visibility field (public/internal/system) on nouns + verbs (f4dea80)
- test(8.0): close brains in afterEach (count-sync, get-relations teardown) (0ca0e5c)
- fix(8.0): neural.clusters()/similarity must request vectors from get() (cc1a431)
- test(8.0): drop dead s3/distributed/cloud scripts + 32GB→8GB integration heap (73a7d82)
- test(8.0): remove dead s3/distributed/cloud scripts + stale s3 suite (af1ee46)
- test(8.0): Tier-1 integration via deterministic embedder (suite runnable again) (542b52e)
- test(8.0): use valid camelCase VerbType values in test-factory (e31ba89)
- test(8.0): get() resolves null for absent custom ids instead of throwing (dc94af3)
- fix(8.0): accept application-supplied entity ids, not just UUIDs (36b7216)
- fix(8.0): honor top-level storage.path as a rootDirectory alias (5096f90)
- feat(8.0): thread commit generation through the graph-write provider contract (0951fa1)
- fix(8.0): drive query-cap off MemAvailable + floor auto-detected caps (b26d3d4)
- test(8.0): A/B benchmark harness (open leg) — generic corpus+metrics lib, brainy-alone scaling bench, boundary guard, real-embedding recall guard (c605b34)
- docs(8.0): remove unbacked Cortex '5.2x' perf claim + dangling /docs/cortex/comparison link (33caa52)
- docs(8.0): measured find() performance at 5k/100k in SCALING.md (f986832)
- test(8.0): asOf() error-path spot-checks + find() triple-composition correctness + scale-bench harness (af96064)
- docs(8.0): RELEASES.md — record removed BrainyZeroConfig + isFullyInitialized/awaitBackgroundInit in the breaking-change inventory (f12ca68)
- refactor(8.0)!: remove orphaned zero-config subsystem + dead cloud/progressive-init storage vestige (35b9d7e)
- refactor(8.0)!: remove distributed clustering subsystem — inert/orphaned, scale is single-process + native provider (00d3203)
- feat(8.0): zero-config finalize + cut JS quantization (config.vector = recall + persistMode) (f8e0079)
- fix(8.0): vfs.rename() issues a metadata-only update (port of the 7.31.7 fix) (f4c5d97)
- chore(8.0): final pre-RC1 sweep — API consistency, named errors, orphans, zero-cast codebase (1f7e365)
- feat(8.0): reserved-field contract — one canonical location, typed prevention, unified read/write (970e08c)
- feat(8.0): brain.fillSubtypes migration helper + pre-RC1 gap closure (c446783)
- docs(8.0): RELEASES.md 8.0.0 release-candidate entry — full breaking-change inventory + upgrade guide (9b0f4ac)
- refactor(8.0): delete DataAPI — superseded by Db persist/restore + import API + stats (478fa17)
- docs(8.0): consistency-model concept + snapshots guide — Db API replaces branching docs (cc8037d)
- feat(8.0): full query surface at historical generations via ephemeral index materialization (e5feae4)
- feat(8.0)!: delete fork/branch/commit/history/versions — superseded by the Db API (8f93add)
- feat(8.0): generational MVCC storage + Datomic-style Db API (now/transact/asOf/with/persist) (431cd64)
- fix(8.0): createIndex resolves the canonical 'vector' provider key — drop diskann/hnsw key lookups + legacy migration APIs (49e4948)
- feat(8.0): u64 BigInt graph provider contract — punch list a-d,g,h (2427bb7)
- chore(8.0): delete vectorStore:mmap wiring — dead in the 8.0 provider world (62f6472)
- chore(8.0): collapse dead defensive guards + redundant polyfills (42159f2)
- chore(8.0)!: drop browser support, cloud SDKs, legacy pipeline, dead threading (266715a)
- docs(8.0): Phase F — deep clean across 21 docs (adda157)
- chore(8.0): Phase C + D + E — config simplification, TODO sweep, test race fix (2626ab8)
- chore(8.0): Phase A + B — purge all @deprecated APIs + cacheManager dead branches (cb16a39)
- chore(8.0): step-7 follow-through — collapse remaining cloud branches + docs sweep (scaffold step 13) (9f9a415)
- feat(8.0)!: flip requireSubtype default to true (BRAINY-8.0-SUBTYPE-CONTRACT § C-1) (780fb64)
- fix(8.0): implement multi-hop subtype BFS in pure JS (open-core works standalone) (221fc45)
- refactor(8.0): SubtypeRegistry hook + drop multi-hop subtype throw (scaffold steps 11-12) (ed75f25)
- docs(8.0): document subtype required-by-default deferral (scaffold step 10) (1eb0ffc)
- refactor(8.0): drop strictConfig — surface too small to justify the option (scaffold step 9) (694a31f)
- refactor(8.0): simplify config.vector to 3 knobs + fold persistMode (scaffold step 8) (8e76740)
- refactor(8.0): drop cloud + OPFS storage adapters; filesystem + memory only (scaffold step 7) (0e6263a)
- refactor(8.0): final cleanup — drop HnswProvider alias + config.hnsw + 'hnsw' surface (scaffold step 6) (b20666e)
- refactor(8.0): strictConfig + brain.stats() vector field + wireConnectionsCodec feature-detect (scaffold step 5) (3e1ef95)
- refactor(8.0): add saveVectorIndexData / getVectorIndexData storage contract (scaffold step 4) (356f044)
- refactor(8.0): rename HNSWIndex class → JsHnswVectorIndex (scaffold step 3) (f39d420)
- refactor(8.0): add config.vector path + 'vectors' cache category (scaffold step 2) (8f87b35)
- refactor(8.0): rename HnswProvider → VectorIndexProvider (8.0 scaffold) (076c26f)
### [7.31.2](https://github.com/soulcraftlabs/brainy/compare/v7.31.1...v7.31.2) (2026-06-09)
- docs: correct misleading SQ4 quantization comment in type definitions (89e4d81)
@ -1971,7 +1588,7 @@ Expected behavior after upgrade:
### 🐛 Bug Fixes
* **storage**: Fix `clear()` not deleting COW version control data (consumer-reported)
* **storage**: Fix `clear()` not deleting COW version control data ([#workshop-bug-report](https://github.com/soulcraftlabs/brainy/issues))
- Fixed all storage adapters to properly delete `_cow/` directory on clear()
- Fixed in-memory entity counters not being reset after clear()
- Prevents COW reinitialization after clear() by setting `cowEnabled = false`

View file

@ -12,7 +12,7 @@ Handoff file: `/home/dpsifr/.strategy/PLATFORM-HANDOFF.md`
**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`)
**Current version:** `@soulcraft/brainy@7.19.10`
---

480
README.md
View file

@ -1,217 +1,437 @@
<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="https://raw.githubusercontent.com/soulcraftlabs/brainy/main/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/%40soulcraft%2Fbrainy.svg)](https://www.npmjs.com/package/@soulcraft/brainy)
[![npm downloads](https://img.shields.io/npm/dm/@soulcraft/brainy.svg)](https://www.npmjs.com/package/@soulcraft/brainy)
[![Documentation](https://img.shields.io/badge/docs-soulcraft.com-blue.svg)](https://soulcraft.com/docs)
[![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>
**Three database paradigms. One API. Zero configuration.**
---
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.
Built because we were tired of stitching together Pinecone + Neo4j + MongoDB and spending weeks on configuration before writing a single line of business logic. Brainy unifies vector search, graph traversal, and metadata filtering so you don't have to choose.
**New here?** → **[What is Brainy? — plain-language overview, no jargon](docs/eli5.md)**
## Quick start
---
## Install
```bash
bun add @soulcraft/brainy # Bun ≥ 1.1 — recommended
npm install @soulcraft/brainy # Node.js ≥ 22
npm install @soulcraft/brainy
```
## Quick Start
```javascript
import { Brainy, NounType, VerbType } from '@soulcraft/brainy'
const brain = new Brainy() // in-memory; one line swaps to disk
const brain = new Brainy()
await brain.init()
// Text auto-embeds locally; metadata auto-indexes
const react = await brain.add({
// Add knowledge — text auto-embeds, metadata auto-indexes
const reactId = 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',
const nextId = await brain.add({
data: 'Next.js framework for React with server-side rendering',
type: NounType.Concept,
subtype: 'framework',
metadata: { category: 'frontend', year: 2016 }
metadata: { category: 'framework', year: 2016 }
})
await brain.relate({ from: next, to: react, type: VerbType.DependsOn, subtype: 'runtime' })
// Create a relationship
await brain.relate({ from: nextId, to: reactId, type: VerbType.BuiltOn })
// Query all three paradigms at once
const results = await brain.find({
query: 'modern frontend frameworks', // Vector similarity
where: { year: { greaterThan: 2015 } }, // Metadata filtering
connected: { to: reactId, depth: 2 } // Graph traversal
})
```
## One query, three engines
**[Full API Reference](docs/api/README.md)** | **[soulcraft.com/docs](https://soulcraft.com/docs)**
---
## Three Indexes, One Query
Every piece of knowledge lives in three indexes simultaneously:
- **`data`** → **Vector index** — Content for semantic search. Strings auto-embed into 384-dim vectors. Queried with `find({ query: '...' })`.
- **`metadata`** → **Metadata index** — Structured fields for filtering. O(1) lookups. Queried with `find({ where: { ... } })`.
- **`relate()`** → **Graph index** — Typed, directed relationships between entities. Traversed with `find({ connected: { ... } })`.
```javascript
// Data → vector index (semantic search)
const articleId = await brain.add({
data: 'A deep dive into transformer architectures',
type: NounType.Document,
metadata: { author: 'Dr. Chen', year: 2024, tags: ['AI'] } // → metadata index
})
// Relationships → graph index
await brain.relate({ from: authorId, to: articleId, type: VerbType.Authored })
// Query all three at once
brain.find({
query: 'attention mechanisms', // Vector similarity
where: { year: { greaterThan: 2023 } }, // Metadata filter
connected: { from: authorId, depth: 1 } // Graph traversal
})
```
**[Data Model Reference](docs/DATA_MODEL.md)** | **[Query Operators](docs/QUERY_OPERATORS.md)**
---
## Features
### Triple Intelligence
Vector search + graph traversal + metadata filtering in every query. No stitching services together — one `find()` call combines all three.
```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
query: 'machine learning',
where: { department: 'engineering', level: 'senior' },
connected: { from: teamLeadId, via: VerbType.WorksWith, depth: 2 }
})
```
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.
### Hybrid Search
## Feature tour
### The database is a value
Pin it, rewind it, fork it. Snapshot isolation without a server.
Automatically combines keyword (text) and semantic (vector) search. No configuration needed.
```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
await brain.find({ query: 'David Smith' }) // Auto: text + semantic
await brain.find({ query: 'AI concepts', searchMode: 'semantic' }) // Semantic only
await brain.find({ query: 'exact id', searchMode: 'text' }) // Text only
```
**[Consistency model](docs/concepts/consistency-model.md)** · **[Snapshots & time travel](docs/guides/snapshots-and-time-travel.md)**
### Query Operators
### 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:
Filter metadata with equality, comparison, array, existence, pattern, and logical operators:
```javascript
await brain.find({ query: 'David Smith' }) // auto: text + semantic
await brain.find({ query: 'AI concepts', searchMode: 'semantic' }) // semantic only
await brain.find({
where: {
status: 'active', // Exact match
score: { greaterThan: 90 }, // Comparison
tags: { contains: 'ai' }, // Array
anyOf: [{ role: 'admin' }, { role: 'owner' }] // Logical OR
}
})
```
### A typed graph, not a bag of edges
**[Query Operators Reference](docs/QUERY_OPERATORS.md)** — all operators with indexed/in-memory matrix
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:
### Graph Relationships
Typed, directed edges between entities. Traverse connections at any depth.
```javascript
await brain.add({ data: 'Avery Brooks', type: NounType.Person, subtype: 'employee' })
await brain.relate({ from: personId, to: projectId, type: VerbType.WorksOn })
brain.counts.bySubtype(NounType.Person) // O(1) — { employee: 12, customer: 847 }
brain.requireSubtype(NounType.Person, { values: ['employee', 'customer'], required: true })
const results = await brain.find({
connected: { from: personId, via: VerbType.WorksOn, depth: 3 }
})
```
**[Type system](docs/architecture/noun-verb-taxonomy.md)** · **[Subtypes & facets](docs/guides/subtypes-and-facets.md)**
### Git-Style Branching
### Graph analytics built in
Fork your entire database in <100ms. Snowflake-style copy-on-write.
```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
const experiment = await brain.fork('test-migration')
await experiment.add({ data: 'test data', type: NounType.Concept })
await experiment.commit({ message: 'Add test data', author: 'dev@co.com' })
await brain.checkout('test-migration')
// Time-travel: query at any past commit
const snapshot = await brain.asOf(commitId)
const pastResults = await snapshot.find({ query: 'historical data' })
await snapshot.close()
```
### Write-time aggregations
**[Branching Documentation](docs/features/instant-fork.md)**
`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)**
### Entity Versioning
### Import anything
Save, restore, and compare entity snapshots.
```javascript
const userId = await brain.add({ data: 'Alice', type: NounType.Person })
await brain.versions.save(userId, { tag: 'v1.0' })
await brain.update(userId, { data: 'Alice Smith' })
await brain.versions.save(userId, { tag: 'v2.0' })
const diff = await brain.versions.compare(userId, 1, 2)
await brain.versions.restore(userId, 1)
```
### Virtual Filesystem
File operations with semantic search built in.
```javascript
const vfs = brain.vfs
await vfs.writeFile('/docs/readme.md', 'Project documentation')
const content = await vfs.readFile('/docs/readme.md')
const tree = await vfs.getTreeStructure('/docs', { maxDepth: 3 })
// Semantic file search
const matches = await vfs.search('React components with hooks')
```
**[VFS Quick Start](docs/vfs/QUICK_START.md)** | **[Common Patterns](docs/vfs/COMMON_PATTERNS.md)**
### Import Anything
CSV, Excel, PDF, URLs — auto-detected format, auto-classified entities.
```javascript
await brain.import('customers.csv')
await brain.import('sales.xlsx') // every sheet
await brain.import('research-paper.pdf') // tables extracted
await brain.import('sales-data.xlsx', { excelSheets: ['Q1', 'Q2'] })
await brain.import('research-paper.pdf', { pdfExtractTables: true })
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)**
**[Import Guide](docs/guides/import-anything.md)**
### A filesystem that understands content
### Entity Extraction
AI-powered named entity recognition with 4-signal ensemble scoring.
```javascript
await brain.vfs.writeFile('/docs/readme.md', 'Project documentation')
await brain.vfs.search('React components with hooks') // semantic file search
const entities = await brain.extractEntities('John Smith founded Acme Corp in New York')
// [
// { text: 'John Smith', type: NounType.Person, confidence: 0.95 },
// { text: 'Acme Corp', type: NounType.Organization, confidence: 0.92 },
// { text: 'New York', type: NounType.Location, confidence: 0.88 }
// ]
```
**[VFS quick start](docs/vfs/QUICK_START.md)**
**[Neural Extraction Guide](docs/neural-extraction.md)**
### Operations-grade by default
### Plugin System
- **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
```
Optional native acceleration via `@soulcraft/cortex` — SIMD distance calculations, CRoaring bitmaps, Candle ML embeddings.
```javascript
const brain = new Brainy({ storage: { type: 'filesystem', path: './data' } })
await brain.init() // @soulcraft/cor detected — same code, native engines underneath
const brain = new Brainy({ plugins: ['@soulcraft/cortex'] })
await brain.init()
```
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.
Plugins are opt-in. Brainy never auto-imports packages unless listed in `plugins`.
Open core, commercial accelerator: Brainy is MIT and complete on its own; Cor is licensed and funds both.
**[Plugin Documentation](docs/PLUGINS.md)**
## 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)**
## Type System
## Use cases
42 noun types and 127 verb types form a universal knowledge protocol:
**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.
```
42 Nouns × 127 Verbs = 5,334 base relationship combinations
```
Model any domain — healthcare (`Patient → diagnoses → Condition`), finance (`Account → transfers → Transaction`), education (`Student → completes → Course`), or your own.
### Subtypes — sub-classification within a NounType *or* VerbType
Both noun types and verb types are intentionally coarse. Use the top-level `subtype` field to sub-classify entities AND relationships within a type — flat string, no hierarchy, your choice of vocabulary:
```javascript
// Nouns: sub-classify entities
await brain.add({
data: 'Avery Brooks — runs the AI lab',
type: NounType.Person,
subtype: 'employee' // 'customer', 'vendor', 'contractor', …
})
// Verbs: sub-classify relationships
await brain.relate({
from: ceoId,
to: vpId,
type: VerbType.ReportsTo,
subtype: 'direct' // 'dotted-line', 'matrix', …
})
// Filter on the fast path — column-store hit, not metadata fallback:
const employees = await brain.find({ type: NounType.Person, subtype: 'employee' })
const directReports = await brain.getRelations({ from: ceoId, subtype: 'direct' })
// O(1) counts via the persisted rollups:
brain.counts.bySubtype(NounType.Person)
// → { employee: 12, customer: 847, vendor: 34 }
brain.counts.byRelationshipSubtype(VerbType.ReportsTo)
// → { direct: 12, 'dotted-line': 3 }
```
**Enforce the pairing.** Register a vocabulary per type or turn on brain-wide strict mode to ensure every entity AND relationship has both `type` AND `subtype`:
```javascript
// Per-type rule with vocabulary
brain.requireSubtype(NounType.Person, { values: ['employee', 'customer'], required: true })
// Or brain-wide strict mode
const brain = new Brainy({ requireSubtype: true })
```
For other facets you want counted (`status`, `source`, `role`), register them with `brain.trackField(name)`. Renaming an existing convention to `subtype`? Use `brain.migrateField({from, to, entityKind: 'both'})` to walk nouns AND verbs in one pass. Full guide: **[Subtypes & Facets](docs/guides/subtypes-and-facets.md)**.
**[Noun-Verb Taxonomy](docs/architecture/noun-verb-taxonomy.md)** | **[Stage 3 Canonical Reference](docs/STAGE3-CANONICAL-TAXONOMY.md)**
---
## Storage: Memory to Cloud
The same API at every scale. Change one config line to go from prototype to production.
### Development — Zero Config
```javascript
const brain = new Brainy()
```
### Production — Filesystem with Compression
```javascript
const brain = new Brainy({
storage: { type: 'filesystem', path: './data', compression: true }
})
```
### Cloud — S3, GCS, Azure, Cloudflare R2
```javascript
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: { bucketName: 'my-knowledge-base', region: 'us-east-1' }
}
})
```
Performance benchmarks and capacity planning in **[docs/PERFORMANCE.md](docs/PERFORMANCE.md)**.
**[Cloud Deployment Guide](docs/deployment/CLOUD_DEPLOYMENT_GUIDE.md)** | **[Capacity Planning](docs/operations/capacity-planning.md)**
---
## Use Cases
- **AI agents** — Persistent memory with semantic recall and relationship tracking
- **Knowledge bases** — Auto-linking, semantic search, relationship-aware navigation
- **Semantic search** — Find by meaning across codebases, documents, or media
- **Enterprise knowledge** — CRM, product catalogs, institutional memory
- **Interactive experiences** — Game worlds, NPCs, and characters that remember
- **Content platforms** — Similarity-based discovery, intelligent tagging
---
## 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) |
### Start Here
- **[Brainy explained simply](docs/eli5.md)** — Plain-language overview, no jargon, no code
### Core
- **[API Reference](docs/api/README.md)** — Every method with parameters, returns, and examples
- **[Data Model](docs/DATA_MODEL.md)** — Entity structure, data vs metadata
- **[Query Operators](docs/QUERY_OPERATORS.md)** — All BFO operators with examples
- **[Find System](docs/FIND_SYSTEM.md)** — Natural language find() and hybrid search
### Architecture
- **[Architecture Overview](docs/architecture/overview.md)** — System design and components
- **[Triple Intelligence](docs/architecture/triple-intelligence.md)** — Vector + graph + metadata unified query
- **[Noun-Verb Taxonomy](docs/architecture/noun-verb-taxonomy.md)** — Universal type system
- **[Data Storage Architecture](docs/architecture/data-storage-architecture.md)** — Type-aware indexing and HNSW
### Virtual Filesystem
- **[VFS Quick Start](docs/vfs/QUICK_START.md)** — Build file explorers that never crash
- **[VFS Core](docs/vfs/VFS_CORE.md)** — Full VFS API reference
- **[Semantic VFS](docs/vfs/SEMANTIC_VFS.md)** — AI-powered file navigation
### Guides
- **[Import Anything](docs/guides/import-anything.md)** — CSV, Excel, PDF, URLs
- **[Framework Integration](docs/guides/framework-integration.md)** — React, Vue, Angular, Svelte
- **[Natural Language Queries](docs/guides/natural-language.md)** — Master the find() method
### Operations
- **[Cloud Deployment](docs/deployment/CLOUD_DEPLOYMENT_GUIDE.md)** — AWS, GCS, Azure
- **[Capacity Planning](docs/operations/capacity-planning.md)** — Memory, storage, and scaling
- **[Performance](docs/PERFORMANCE.md)** — Benchmarks and architecture details
- Cost Optimization: **[AWS S3](docs/operations/cost-optimization-aws-s3.md)** | **[GCS](docs/operations/cost-optimization-gcs.md)** | **[Azure](docs/operations/cost-optimization-azure.md)** | **[R2](docs/operations/cost-optimization-cloudflare-r2.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.
**Bun 1.0+** (recommended) or **Node.js 22 LTS**
## Contributing & license
```bash
bun install @soulcraft/brainy # Bun — best performance
npm install @soulcraft/brainy # Node.js — fully supported
```
Contributions welcome — see **[CONTRIBUTING.md](CONTRIBUTING.md)**. MIT © Brainy Contributors.
> **Deprecation Notice:** Browser support (OPFS, Web Workers, WASM embeddings) is deprecated in v7.10.0 and will be removed in v8.0.0. Brainy v8+ will be server-only.
## Single-Writer Model
Brainy is **single-writer, many-reader** on filesystem storage. One writer
holds an exclusive lock on the data directory; any number of readers can
inspect it concurrently. Opening a second writer throws with the PID of the
existing one.
```typescript
// Live application — writer mode is the default
const brain = new Brainy({ storage: { type: 'filesystem', rootDirectory: '/data/brain' } })
await brain.init()
// Out-of-band diagnostics from a separate process — safe to run while the
// writer is live
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', rootDirectory: '/data/brain' }
})
await reader.requestFlush({ timeoutMs: 5000 })
const stats = await reader.stats()
```
For incident debugging, use the `brainy inspect` CLI:
```bash
brainy inspect stats /data/brain
brainy inspect find /data/brain --where '{"entityType":"booking"}'
brainy inspect explain /data/brain --where '{"entityType":"booking"}'
brainy inspect health /data/brain
```
See [the multi-process model](docs/concepts/multi-process.md) and the
[inspection guide](docs/guides/inspection.md) for the full story, including
stale-lock detection, the cross-process flush RPC, and what's not yet
enforced on cloud storage backends.
## Contributing
We welcome contributions! See **[CONTRIBUTING.md](CONTRIBUTING.md)** for guidelines.
## License
MIT © Brainy Contributors

File diff suppressed because it is too large Load diff

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

@ -5,24 +5,31 @@ 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.
description: Eliminate N+1 query patterns with batchGet() and storage-level batch APIs. Achieve 90%+ faster cloud storage access — from 12.7 seconds down to under 1 second.
next:
- api/reference
- guides/find-system
---
# Batch Operations API
> **Production-Ready** | Zero N+1 Query Patterns
> **Enterprise Production-Ready** | Zero N+1 Query Patterns | 90%+ Performance Improvement
## Overview
Brainy provides batch operations at the storage layer to eliminate N+1 query patterns for VFS operations, relationship queries, and entity retrieval.
Brainy introduces comprehensive batch operations at the storage layer, eliminating N+1 query patterns and dramatically improving performance for VFS operations, relationship queries, and entity retrieval on cloud storage.
### 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.
**Before optimization:**
- VFS `getTreeStructure()` on cloud storage: **12.7 seconds** for directory with 12 files
- N+1 query pattern: 1 directory query + N individual file queries (22 sequential calls × 580ms latency)
**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.
**After optimization:**
- VFS `getTreeStructure()`: **<1 second** for 12 files (90%+ improvement)
- 2-3 batched calls instead of 22 sequential calls
- Native cloud storage batch APIs for maximum throughput
**IMPORTANT:** The batch optimizations apply **ONLY to `getTreeStructure()`**, not to `readFile()` or individual `get()` operations. See changes for comprehensive storage path optimizations.
---
@ -47,7 +54,8 @@ results.size // → 3 (number of found entities)
**Performance:**
- Memory storage: Instant (parallel reads)
- Filesystem storage: Parallel reads, scales with available IOPS
- Cloud storage (GCS/S3/Azure): <500ms for 100 entities
- Throughput: 50-200+ entities/second depending on adapter
**Use Cases:**
- Loading multiple entities for display
@ -77,13 +85,13 @@ for (const [id, metadata] of metadataMap) {
**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
- ✅ COW-aware (respects branch paths)
- ✅ 40x faster than v5.x type-first architecture
**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
- ~1ms per 100 entities (consistent, no cache misses!)
- Cloud storage: Parallel downloads (100-150 concurrent)
- No type search delays - every ID maps directly to storage path
---
@ -121,8 +129,130 @@ for (const [sourceId, verbs] of results) {
- Bulk export of relationship data
**Performance:**
- Memory storage: single in-memory pass over the metadata index
- Filesystem storage: parallel reads through the metadata index
- Memory storage: <10ms for 1000 relationships
- Cloud storage: Batched reads with parallel metadata fetches
---
### 4. `storage.readBatchWithInheritance(paths, targetBranch?)`
COW-aware batch path resolution with branch inheritance.
```typescript
const storage = brain.storage as BaseStorage
const paths = [
'entities/nouns/{shard}/id1/metadata.json',
'entities/nouns/{shard}/id2/metadata.json'
]
// Resolves to: branches/{branch}/entities/nouns/{shard}/{id}/metadata.json
const results: Map<string, any> = await storage.readBatchWithInheritance(paths, 'my-branch')
// Automatically inherits from parent branches for missing entities
```
**Features:**
- ✅ Branch path resolution (`branches/{branch}/...`)
- ✅ Write cache integration (read-after-write consistency)
- ✅ COW inheritance (fallback to parent commits for missing entities)
- ✅ Adapter-agnostic (works with all storage adapters)
---
## Cloud Adapter Native Batch APIs
### GCS Storage
```typescript
const gcsStorage = new GCSStorage({ bucketName: 'my-bucket' })
// Native batch API with 100 concurrent downloads
const results = await gcsStorage.readBatch(paths)
// Configuration
gcsStorage.getBatchConfig() // → {
// maxBatchSize: 1000,
// maxConcurrent: 100,
// operationsPerSecond: 1000
// }
```
**Performance:**
- 100 concurrent downloads
- ~300-500ms for 100 objects
- HTTP/2 multiplexing for optimal throughput
---
### S3 Compatible Storage
Works with Amazon S3, Cloudflare R2, and other S3-compatible services.
```typescript
const s3Storage = new S3CompatibleStorage({ bucketName: 'my-bucket' })
// Native batch API with 150 concurrent downloads
const results = await s3Storage.readBatch(paths)
// Configuration
s3Storage.getBatchConfig() // → {
// maxBatchSize: 1000,
// maxConcurrent: 150,
// operationsPerSecond: 5000
// }
```
**Performance:**
- 150 concurrent downloads
- ~200-500ms for 150 objects
- S3 handles 5000+ ops/second with burst capacity
---
### R2 Storage (Cloudflare)
```typescript
const r2Storage = new R2Storage({ bucketName: 'my-bucket' })
// Fastest cloud storage with zero egress fees
const results = await r2Storage.readBatch(paths)
// Configuration
r2Storage.getBatchConfig() // → {
// maxBatchSize: 1000,
// maxConcurrent: 150,
// operationsPerSecond: 6000
// }
```
**Performance:**
- 150 concurrent downloads
- ~200-400ms for 150 objects (fastest!)
- Zero egress fees enable aggressive caching
---
### Azure Blob Storage
```typescript
const azureStorage = new AzureBlobStorage({ containerName: 'my-container' })
// Native batch API with 100 concurrent downloads
const results = await azureStorage.readBatch(paths)
// Configuration
azureStorage.getBatchConfig() // → {
// maxBatchSize: 1000,
// maxConcurrent: 100,
// operationsPerSecond: 3000
// }
```
**Performance:**
- 100 concurrent downloads
- ~400-600ms for 100 blobs
- Good throughput with Azure's global network
---
@ -133,10 +263,12 @@ VFS operations automatically use batch APIs for maximum performance.
### Directory Traversal
```typescript
// Tree traversal uses batched reads under the hood
// OLD: Sequential N+1 pattern (12.7 seconds for 12 files)
const tree = await brain.vfs.getTreeStructure('/my-dir')
// NEW Parallel breadth-first with batching (<1 second)
// ✅ PathResolver.getChildren() uses brain.batchGet() internally
// ✅ Parallel traversal of directories at the same tree level
// ✅ Parallel traversal of directories at same tree level
// ✅ 2-3 batched calls instead of 22 sequential calls
```
@ -150,11 +282,17 @@ VFS.getTreeStructure()
→ brain.batchGet(childIds) [1 call instead of N]
↓ BATCHED
→ storage.getNounMetadataBatch(ids) [1 call instead of N]
↓ ADAPTER
→ Filesystem: Promise.all() parallel reads
↓ ADAPTER-SPECIFIC
→ GCS: readBatch() with 100 concurrent downloads
→ S3: readBatch() with 150 concurrent downloads
→ Memory: Promise.all() parallel reads
```
**Performance Gains:**
- **Before**: 22 sequential calls × 580ms = 12.7 seconds
- **After**: 2-3 batched calls = <1 second
- **Improvement**: **90%+ faster** on cloud storage
---
## Advanced Features Compatibility
@ -171,7 +309,7 @@ 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
// Every ID maps directly to exactly ONE path - 40x faster!
const id = 'abc-123'
const shard = getShardIdFromUuid(id) // → 'ab' (first 2 hex chars)
const path = `entities/nouns/${shard}/${id}/metadata.json`
@ -183,9 +321,9 @@ const path = `entities/nouns/${shard}/${id}/metadata.json`
```
**Benefits:**
- **O(1)** path lookups (eliminates the 42-type sequential search the old type-first layout required)
- **40x faster** on GCS/S3 (eliminates 42-type sequential search)
- **Simpler code** - removed 500+ lines of type cache complexity
- **Scalable** - works at large scale without type tracking overhead
- **Scalable** - works at billion-scale without type tracking overhead
---
@ -203,39 +341,95 @@ const path = `entities/nouns/${shard}/${id}/metadata.json`
---
### ✅ Generational MVCC (8.0)
### ✅ COW (Copy-on-Write)
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).
Batch operations respect branch isolation and time-travel:
```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()
// Main branch
const brain = await Brainy.create({ enableCOW: true })
await brain.add({ type: 'document', data: 'Main' })
// Create fork
const fork = await brain.fork('experiment')
// Batch operations are isolated
await brain.batchGet([id1, id2]) // → Reads from: branches/main/...
await fork.batchGet([id1, id2]) // → Reads from: branches/experiment/...
```
**Inheritance:**
- Entities missing from child branch automatically inherit from parent commits
- `readBatchWithInheritance()` walks commit history for missing items
- Preserves fork semantics while maintaining performance
---
### ✅ fork() and checkout()
```typescript
const fork = await brain.fork('my-branch')
await fork.add({ type: 'document', data: 'Fork entity' })
// Batch operations use correct branch
const results = await fork.batchGet([id1, id2])
// → Reads from: branches/my-branch/...
// Checkout changes active branch
await fork.checkout('main')
const mainResults = await fork.batchGet([id1, id2])
// → Reads from: branches/main/...
```
---
## Why Batching Is Faster
### ✅ asOf() Time-Travel
Batching's advantage is structural, not a fixed multiplier (the actual speedup
depends on storage backend, IOPS, and batch size):
```typescript
// Create historical snapshot
await brain.commit('v1.0')
const snapshot = await brain.asOf('v1.0')
- **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.
// Batch operations on historical data
const results = await snapshot.batchGet([id1, id2])
// → Reads from historical tree state
```
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.
Historical queries use `HistoricalStorageAdapter` which wraps batch operations to point at specific commits.
---
## Performance Benchmarks
### VFS Operations (12 Files)
| Storage | Before optimization | After optimization | Improvement |
|---------|---------------|---------------|-------------|
| **GCS** | 12.7s | <1s | **92% faster** |
| **S3** | 13.2s | <1s | **92% faster** |
| **R2** | 11.8s | <0.8s | **93% faster** |
| **Azure** | 14.5s | <1s | **93% faster** |
| **Memory** | 150ms | 50ms | **67% faster** |
### Entity Batch Retrieval (100 Entities)
| Storage | Individual Gets | Batch Get | Improvement |
|---------|----------------|-----------|-------------|
| **GCS** | 5.8s | 0.4s | **93% faster** |
| **S3** | 5.2s | 0.3s | **94% faster** |
| **R2** | 4.9s | 0.25s | **95% faster** |
| **Azure** | 6.5s | 0.5s | **92% faster** |
| **Memory** | 180ms | 15ms | **92% faster** |
### Throughput (Entities/Second)
| Storage | Individual | Batch | Improvement |
|---------|-----------|-------|-------------|
| **GCS** | 17 ent/s | 250 ent/s | **14.7x** |
| **S3** | 19 ent/s | 333 ent/s | **17.5x** |
| **R2** | 20 ent/s | 400 ent/s | **20x** |
| **Azure** | 15 ent/s | 200 ent/s | **13.3x** |
| **Memory** | 556 ent/s | 6667 ent/s | **12x** |
---
@ -300,7 +494,7 @@ 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.
**Performance Gain:** 10-20x faster on cloud storage.
---
@ -310,7 +504,7 @@ const entities = Array.from(results.values())
```typescript
const allVerbs = []
for (const sourceId of sourceIds) {
const verbs = await brain.related({ from: sourceId })
const verbs = await brain.getRelations({ from: sourceId })
allVerbs.push(...verbs)
}
```
@ -326,7 +520,7 @@ for (const verbs of results.values()) {
}
```
**Performance Gain:** One batched metadata fetch instead of one query per source entity.
**Performance Gain:** 5-10x faster due to batched metadata fetches.
---
@ -347,9 +541,12 @@ for (const id of ids) {
### 2. **Batch Size Recommendations**
| Storage | Optimal Batch Size | Max Batch Size |
|---------|--------------------|----------------|
|---------|-------------------|----------------|
| **Memory** | Unlimited | Unlimited |
| **Filesystem** | 100-500 | 1000 |
| **FileSystem** | 100-500 | 1000 |
| **GCS** | 100-500 | 1000 |
| **S3/R2** | 100-1000 | 1000 |
| **Azure** | 100-500 | 1000 |
**Guideline:** For batches >1000, split into chunks of 500-1000.
@ -417,42 +614,68 @@ High-Level API (src/brainy.ts)
Storage Layer (src/storage/baseStorage.ts)
COW Layer (readBatchWithInheritance)
Adapter Layer (readBatchFromAdapter)
Storage Adapter (FileSystemStorage / MemoryStorage)
Cloud Adapter (GCS/S3/Azure native batch APIs)
```
### Parallel Reads
### Automatic Fallback
Both shipped adapters fall back to `Promise.all` over individual reads:
If an adapter doesn't implement `readBatch()`, the system automatically falls back to parallel individual reads:
```typescript
// BaseStorage.readBatchFromAdapter()
return await Promise.all(resolvedPaths.map(path => this.read(path)))
if (typeof selfWithBatch.readBatch === 'function') {
// Use native batch API
return await selfWithBatch.readBatch(resolvedPaths)
} else {
// Automatic parallel fallback
return await Promise.all(resolvedPaths.map(path => this.read(path)))
}
```
**Shipped Adapters:**
**Adapters with Native Batch:**
- ✅ GCSStorage
- ✅ S3CompatibleStorage
- ✅ R2Storage
- ✅ AzureBlobStorage
**Adapters with Parallel Fallback:**
- MemoryStorage
- FileSystemStorage
- OPFSStorage
- HistoricalStorageAdapter (delegates to underlying)
---
## API Summary
## Release Notes
**Version:** 5.12.0
**Release Date:** 2025-11-19
**Status:** Production-Ready
**Breaking Changes:** None (backward compatible)
**New APIs:**
- `brain.batchGet(ids, options?)` - High-level batch entity retrieval
- `storage.getNounMetadataBatch(ids)` - Storage-level metadata batch
- `storage.getVerbsBySourceBatch(sourceIds, verbType?)` - Batch relationship queries
- `storage.readBatchWithInheritance(paths, targetBranch?)` - COW-aware batch reads
**Performance Improvements:**
- VFS operations: single batched pass instead of N sequential reads
- Entity retrieval: N+1 reads collapsed into one batched pass
- VFS operations: 90%+ faster on cloud storage
- Entity retrieval: 10-20x throughput improvement
- 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)
- ✅ COW (branch isolation, inheritance)
- ✅ fork() and checkout()
- ✅ asOf() time-travel
- ✅ All 6 indexes respected (HNSW, TypeAwareHNSW, MetadataIndex, GraphAdjacency, Version, DeletedItems)
---

View file

@ -0,0 +1,435 @@
# Creating Augmentations for Brainy
> **Updated** - Includes metadata structure changes and type system improvements
## 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
}
```
## Breaking Changes for Augmentation Developers
### 1. Metadata Structure Separation
Brainy introduces strict metadata/vector separation for billion-scale performance:
```typescript
// ✅ Metadata has required type field
interface NounMetadata {
noun: NounType // Required! Must be a valid noun type
[key: string]: any // Your custom metadata
}
interface VerbMetadata {
verb: VerbType // Required! Must be a valid verb type
sourceId: string
targetId: string
[key: string]: any
}
```
### 2. Storage Adapter Return Types
Storage adapters now return different types at different boundaries:
```typescript
// Internal methods: Pure structures (no metadata)
abstract _getNoun(id: string): Promise<HNSWNoun | null>
// Public API: WithMetadata structures
abstract getNoun(id: string): Promise<HNSWNounWithMetadata | null>
```
### 3. Verb Property Renamed
The verb relationship field changed from `type` to `verb`:
```typescript
// ❌ v3.x
verb.type === 'relatedTo'
// ✅ Current
verb.verb === 'relatedTo'
```
## Creating a Storage Augmentation
Storage augmentations are special - they provide the storage backend for Brainy.
### Important: Storage Requirements
Your storage adapter MUST:
1. **Wrap metadata** with required `noun`/`verb` fields
2. **Return pure structures** from internal `_methods`
3. **Return WithMetadata types** from public methods
```typescript
import { StorageAugmentation } from 'brainy/augmentations'
import { BaseStorageAdapter, HNSWNoun, HNSWNounWithMetadata, NounMetadata } from 'brainy'
export class MyCustomStorage extends BaseStorageAdapter {
// Internal method: Returns pure structure
async _getNoun(id: string): Promise<HNSWNoun | null> {
const data = await this.fetchFromDatabase(id)
return data ? {
id: data.id,
vector: data.vector,
nounType: data.type
} : null
}
// Public method: Returns WithMetadata structure
async getNoun(id: string): Promise<HNSWNounWithMetadata | null> {
const noun = await this._getNoun(id)
if (!noun) return null
// Fetch metadata separately
const metadata = await this.getNounMetadata(id)
return {
...noun,
metadata: metadata || { noun: noun.nounType || 'thing' }
}
}
// CRITICAL: Always save with proper metadata structure
async saveNoun(noun: HNSWNoun, metadata?: NounMetadata): Promise<void> {
// Validate metadata has required 'noun' field
if (!metadata?.noun) {
throw new Error('NounMetadata requires "noun" field')
}
await this.database.save({
id: noun.id,
vector: noun.vector,
nounType: noun.nounType,
metadata: metadata // Stored separately
})
}
}
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 Brainy()
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'` - Adding data
- `'search'`, `'similar'` - Searching
- `'update'`, `'delete'` - Modifications
- `'saveNoun'`, `'saveVerb'` - Storage operations
- `'all'` - Intercept everything
## Context Available to Augmentations
```typescript
interface AugmentationContext {
brain: Brainy // The brain instance
storage: StorageAdapter // Storage backend
config: BrainyConfig // 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
### General 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
### Specific Best Practices
8. **Always include `noun` field** when creating/modifying NounMetadata:
```typescript
const metadata: NounMetadata = {
noun: 'thing', // REQUIRED!
yourField: 'value'
}
```
9. **Use `verb` property** not `type` when working with relationships:
```typescript
// ✅ Correct
if (verb.verb === 'relatedTo') { ... }
// ❌ Wrong (v3.x pattern)
if (verb.type === 'relatedTo') { ... }
```
10. **Access metadata correctly** from storage:
```typescript
// ✅ Correct - metadata is already structured
const nounType = noun.metadata.noun
// ⚠️ Fallback pattern for robustness
const nounType = noun.metadata?.noun || 'thing'
```
11. **Respect the two-file storage pattern** - Don't mix vector and metadata operations:
```typescript
// ✅ Good - Separate concerns
await storage.saveNoun(noun)
await storage.saveMetadata(noun.id, metadata)
// ❌ Bad - Mixing concerns
await storage.saveNounWithEverything(combinedData)
```
## Testing Your Augmentation
```typescript
import { Brainy } from 'brainy'
import { MyAugmentation } from './my-augmentation'
describe('MyAugmentation', () => {
let brain: Brainy
beforeEach(async () => {
brain = new Brainy()
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

@ -238,7 +238,7 @@ await brain.relate({
Fast-path filter on the verb side:
```typescript
const direct = await brain.related({
const direct = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: 'direct'

View file

@ -193,21 +193,21 @@ console.log('✅ Created relationships')
console.log('\n🔍 Querying relationships...')
// Who works for TechCorp?
const techcorpEmployees = await brain.related({
const techcorpEmployees = await brain.getRelations({
to: techcorpId,
type: VerbType.WorksWith
})
console.log(`TechCorp has ${techcorpEmployees.length} employees`)
// Who works on the AI project?
const projectContributors = await brain.related({
const projectContributors = await brain.getRelations({
to: projectId,
type: [VerbType.WorksOn, VerbType.Manages]
})
console.log(`AI Project has ${projectContributors.length} contributors`)
// Who does John collaborate with?
const johnsCollaborators = await brain.related({
const johnsCollaborators = await brain.getRelations({
from: johnId,
type: VerbType.CollaboratesWith
})
@ -294,7 +294,7 @@ await brain.update({
1. Create an organizational hierarchy (CEO → Managers → Engineers)
2. Build a project dependency graph
3. Model a social network with CollaboratesWith relationships
4. Query "Who reports to Alice?" using related()
4. Query "Who reports to Alice?" using getRelations()
5. Batch update all projects to add a "year: 2024" field
### Next Steps
@ -429,7 +429,23 @@ fusionResults.forEach(r => {
}
})
// 5. SIMILARITY: Find similar documents
// 5. NEURAL API: Automatic clustering
console.log('\n\n🤖 NEURAL API: Automatic Clustering')
const neural = brain.neural()
const clusters = await neural.clusters({
maxClusters: 3,
minClusterSize: 1
})
console.log(`Found ${clusters.length} semantic clusters:`)
clusters.forEach((cluster, i) => {
console.log(`\n Cluster ${i + 1}: ${cluster.label || cluster.id}`)
console.log(` Members: ${cluster.members.length}`)
console.log(` Centroid topics: ${cluster.metadata?.topics?.join(', ') || 'N/A'}`)
})
// 6. SIMILARITY: Find similar documents
console.log('\n\n🔍 SIMILARITY: Find Similar Documents')
const similarTo = await brain.similar({
to: paper1, // Entity ID of first AI paper
@ -443,6 +459,19 @@ similarTo.forEach(r => {
console.log(` [${r.score.toFixed(3)}] ${r.entity.data?.substring(0, 50)}...`)
})
// 7. OUTLIER DETECTION
console.log('\n\n🚨 OUTLIER DETECTION')
const outliers = await neural.outliers({
method: 'statistical',
threshold: 2.0 // 2 standard deviations
})
console.log(`Found ${outliers.length} outlier documents:`)
outliers.forEach(o => {
const entity = await brain.get(o.id)
console.log(` [Anomaly score: ${o.score.toFixed(3)}] ${entity?.data?.substring(0, 50)}...`)
})
await brain.close()
```
@ -647,7 +676,7 @@ if (readmeEntity.length > 0) {
}
// Query relationships
const conceptDocs = await brain.related({
const conceptDocs = await brain.getRelations({
from: conceptId,
type: VerbType.DocumentedBy
})
@ -769,7 +798,7 @@ await brain.relate({
})
// Query across boundaries
const conceptDocs = await brain.related({
const conceptDocs = await brain.getRelations({
from: conceptId,
type: VerbType.DocumentedBy
})
@ -823,7 +852,7 @@ Ready for production deployment? Level 5 covers **planet-scale architecture**.
## Level 5: Production Scale (90 minutes)
### What You'll Learn
- Production filesystem storage and off-site backup
- Cloud storage (GCS, S3, R2)
- Performance optimization
- Batch imports (CSV, Excel, PDF)
- Metadata query optimization
@ -834,13 +863,18 @@ Ready for production deployment? Level 5 covers **planet-scale architecture**.
```typescript
import { Brainy, NounType } from '@soulcraft/brainy'
// 1. PRODUCTION STORAGE - Filesystem with off-site snapshots
console.log('Initializing production storage...\n')
// 1. PRODUCTION STORAGE - Google Cloud Storage (Native SDK)
console.log('☁️ Initializing production storage...\n')
const brain = new Brainy({
storage: {
type: 'filesystem',
path: '/var/lib/brainy'
type: 'gcs-native', // Native GCS SDK (recommended)
gcsNativeStorage: {
bucketName: 'my-brainy-production',
// ADC (Application Default Credentials) - zero config in Cloud Run/GCE!
// Or provide credentials:
// keyFilename: '/path/to/service-account.json'
}
},
// Performance tuning
@ -854,8 +888,7 @@ const brain = new Brainy({
})
await brain.init()
console.log('Brainy initialized with filesystem storage')
console.log('Snapshot /var/lib/brainy off-site via cron with `gsutil rsync` / `aws s3 sync` / `rclone`.\n')
console.log('✅ Brainy initialized with GCS Native storage\n')
// 2. BATCH IMPORT - CSV File
console.log('📊 Importing CSV data...\n')
@ -934,6 +967,26 @@ console.log(` Failed: ${batchResult.failed.length}`)
console.log(` Duration: ${duration}ms`)
console.log(` Throughput: ${(batchResult.successful.length / (duration / 1000)).toFixed(0)} entities/sec`)
// 5. ADVANCED CLUSTERING (Large Dataset)
console.log('\n\n🤖 Clustering 1000 entities...\n')
const neural = brain.neural()
// Use fast clustering for large datasets
const clusters = await neural.clusterFast({
maxClusters: 5
})
console.log(`Found ${clusters.length} clusters:`)
clusters.forEach((cluster, i) => {
console.log(`\n Cluster ${i + 1}: ${cluster.label || cluster.id}`)
console.log(` Size: ${cluster.members.length} members`)
console.log(` Density: ${(cluster.density || 0).toFixed(3)}`)
if (cluster.metadata?.keywords) {
console.log(` Keywords: ${cluster.metadata.keywords.slice(0, 5).join(', ')}`)
}
})
// 6. PRODUCTION STATISTICS
console.log('\n\n📊 Production Statistics:\n')
@ -991,7 +1044,7 @@ console.log('✅ Brain closed cleanly')
console.log('\n\n🎓 Production Deployment Complete!')
console.log('\n📚 Key Production Learnings:')
console.log(' 1. Use filesystem storage and snapshot the data directory off-site from your scheduler')
console.log(' 1. Use native cloud storage (GCS, S3, R2) for persistence')
console.log(' 2. Batch operations = 100x faster than individual ops')
console.log(' 3. Metadata query optimization for complex filters')
console.log(' 4. Monitor query performance with explain: true')
@ -1004,22 +1057,41 @@ console.log(' 7. Stream large imports with progress callbacks')
#### 1. **Storage Options Comparison**
| Storage | Use Case | Performance | Setup |
|---------|----------|-------------|-------|
| Memory | Dev/testing | Fastest | Zero config |
| Filesystem | Production | Fast | Local path + scheduled off-site backup |
| Storage | Use Case | Performance | Cost | Setup |
|---------|----------|-------------|------|-------|
| Memory | Dev/testing | Fastest | Free | Zero config |
| Filesystem | Local prod | Fast | Free | Local path |
| GCS Native | GCP prod | Fast | $$$ | Service account |
| S3 | AWS prod | Fast | $$$ | Access keys |
| R2 | Cloudflare | Fast | $ | Access keys |
#### 2. **Off-Site Backup**
#### 2. **GCS Native vs S3-Compatible**
```bash
# Cron / systemd timer / k8s CronJob — pick whatever you already operate
*/15 * * * * rclone sync /var/lib/brainy remote:brainy-backup
*/15 * * * * aws s3 sync /var/lib/brainy s3://my-bucket/brainy-backup
*/15 * * * * gsutil rsync -r /var/lib/brainy gs://my-bucket/brainy-backup
```typescript
// ✅ RECOMMENDED: GCS Native SDK
{
storage: {
type: 'gcs-native',
gcsNativeStorage: {
bucketName: 'my-bucket'
// ADC handles auth automatically in Cloud Run/GCE!
}
}
}
// ⚠️ LEGACY: S3-compatible mode
{
storage: {
type: 'gcs',
gcsStorage: {
bucketName: 'my-bucket',
accessKeyId: process.env.GCS_ACCESS_KEY,
secretAccessKey: process.env.GCS_SECRET_KEY
}
}
}
```
Brainy itself never reaches out to an object store. Snapshot `path` from your scheduler.
#### 3. **Performance Optimization**
```typescript
@ -1092,8 +1164,8 @@ const brain = new Brainy({ verbose: true })
#### Before Deployment
- [ ] Provision a writable `path` on the host
- [ ] Set up an off-site snapshot job (`rclone` / `aws s3 sync` / `gsutil rsync`)
- [ ] Choose cloud storage (GCS/S3/R2)
- [ ] Set up authentication (service account/access keys)
- [ ] Configure caching
- [ ] Test batch operations
- [ ] Benchmark query performance
@ -1117,12 +1189,12 @@ const brain = new Brainy({ verbose: true })
### Practice Exercises
1. Deploy Brainy with filesystem storage + scheduled off-site backup
1. Deploy Brainy with GCS Native storage
2. Import a 10,000 row CSV file
3. Measure query performance for different filters
4. Optimize a slow query using getOptimalQueryPlan()
5. Set up monitoring dashboard
6. Test restore from off-site snapshot
6. Create backup/restore scripts
---
@ -1140,7 +1212,7 @@ const brain = new Brainy({ verbose: true })
#### Advanced Topics
- **Multi-instance Deployments**: Run multiple Brainy processes behind your own routing layer
- **Distributed Systems**: Sharding, replication, coordination
- **Custom Augmentations**: Extend Brainy with plugins
- **Streaming Pipelines**: Real-time data ingestion
- **Security**: Encryption, access control, audit logs
@ -1151,6 +1223,7 @@ const brain = new Brainy({ verbose: true })
- 📚 [API Reference](../api/README.md) - Complete API documentation
- 📁 [VFS Guide](../vfs/VFS_API_GUIDE.md) - Virtual Filesystem deep dive
- 🤖 [Neural API](../guides/neural-api.md) - Advanced neural operations
- 🌐 [Distributed Guide](../guides/distributed-system.md) - Planet-scale architecture
- 💬 [Discord Community](https://discord.gg/brainy) - Get help, share projects
#### Share Your Success

396
docs/EXTENDING_STORAGE.md Normal file
View file

@ -0,0 +1,396 @@
# 🔌 Extending Brainy Storage with Augmentations
## Overview
Brainy's zero-config system is **fully extensible**. Augmentations can register new storage providers, presets, and auto-detection logic that integrates seamlessly with the existing system.
## How Storage Extensions Work
### 1. Storage Provider Registration
When an augmentation is installed, it can register a new storage provider:
```typescript
import { StorageProvider, registerStorageAugmentation } from '@soulcraft/brainy/config'
const redisProvider: StorageProvider = {
type: 'redis',
name: 'Redis Storage',
description: 'High-performance in-memory data store',
priority: 10, // Higher priority = checked first in auto-detection
// Auto-detection logic
async detect(): Promise<boolean> {
// Check if Redis is available
if (process.env.REDIS_URL) {
try {
const redis = await import('ioredis')
const client = new redis.default(process.env.REDIS_URL)
await client.ping()
await client.quit()
return true
} catch {
return false
}
}
return false
},
// Configuration builder
async getConfig(): Promise<any> {
return {
type: 'redis',
redisStorage: {
url: process.env.REDIS_URL,
prefix: 'brainy:',
ttl: 3600
}
}
}
}
// Register the provider
registerStorageAugmentation(redisProvider)
```
### 2. Using Extended Storage
Once registered, the new storage type works with zero-config:
```typescript
// Auto-detection will now check Redis
const brain = new Brainy() // Will use Redis if available!
// Or explicitly specify
const brain = new Brainy({ storage: 'redis' })
// Or with custom config
const brain = new Brainy({
storage: {
type: 'redis',
redisStorage: {
url: 'redis://localhost:6379',
prefix: 'myapp:'
}
}
})
```
## Real-World Examples
### Redis Augmentation
```typescript
// @soulcraft/brainy-redis package
export class RedisStorageAugmentation {
async init() {
// Register the storage provider
registerStorageAugmentation({
type: 'redis',
name: 'Redis Storage',
priority: 10,
async detect() {
return !!(process.env.REDIS_URL || process.env.REDIS_HOST)
},
async getConfig() {
return {
type: 'redis',
redisStorage: {
url: process.env.REDIS_URL ||
`redis://${process.env.REDIS_HOST}:${process.env.REDIS_PORT || 6379}`
}
}
}
})
// Register Redis-specific presets
registerPresetAugmentation('redis-cache', {
storage: 'redis',
model: ModelPrecision.Q8,
features: ['core', 'cache'],
distributed: true,
description: 'Redis-backed cache layer',
category: PresetCategory.SERVICE
})
}
}
```
### MongoDB Augmentation
```typescript
// @soulcraft/brainy-mongodb package
export class MongoStorageAugmentation {
async init() {
registerStorageAugmentation({
type: 'mongodb',
name: 'MongoDB Storage',
priority: 8,
async detect() {
return !!(process.env.MONGODB_URI || process.env.MONGO_URL)
},
async getConfig() {
return {
type: 'mongodb',
mongoStorage: {
uri: process.env.MONGODB_URI,
database: 'brainy',
collection: 'vectors'
}
}
}
})
}
}
```
### PostgreSQL + pgvector Augmentation
```typescript
// @soulcraft/brainy-postgres package
export class PostgresStorageAugmentation {
async init() {
registerStorageAugmentation({
type: 'postgres',
name: 'PostgreSQL + pgvector',
priority: 9,
async detect() {
const url = process.env.DATABASE_URL
if (url?.includes('postgres')) {
// Check for pgvector extension
const client = new Client({ connectionString: url })
await client.connect()
const result = await client.query(
"SELECT * FROM pg_extension WHERE extname = 'vector'"
)
await client.end()
return result.rows.length > 0
}
return false
},
async getConfig() {
return {
type: 'postgres',
postgresStorage: {
connectionString: process.env.DATABASE_URL,
table: 'brainy_vectors'
}
}
}
})
}
}
```
## Auto-Detection Priority
Storage providers are checked in priority order:
1. **Custom providers** (highest priority first)
2. **Cloud storage** (S3, GCS, R2)
3. **Database storage** (Redis, MongoDB, PostgreSQL)
4. **Local storage** (filesystem, OPFS)
5. **Memory** (fallback)
```typescript
// Example priority chain
Redis (priority: 10) → PostgreSQL (9) → MongoDB (8) → S3 (5) → Filesystem (1) → Memory (0)
```
## Creating Custom Presets
Augmentations can also register new presets:
```typescript
registerPresetAugmentation('redis-cluster', {
storage: 'redis',
model: ModelPrecision.Q8,
features: ['core', 'cache', 'cluster'],
distributed: true,
role: DistributedRole.HYBRID,
cache: {
hotCacheMaxSize: 100000, // Large distributed cache
autoTune: true
},
description: 'Redis Cluster configuration',
category: PresetCategory.SERVICE
})
// Users can then use:
const brain = new Brainy('redis-cluster')
```
## Type Safety with Extensions
To maintain type safety with dynamic storage types:
```typescript
// Augmentation declares its types
declare module '@soulcraft/brainy' {
interface StorageTypes {
redis: {
url: string
prefix?: string
ttl?: number
}
}
interface PresetNames {
'redis-cache': 'redis-cache'
'redis-cluster': 'redis-cluster'
}
}
```
## Best Practices for Storage Augmentations
1. **Always provide auto-detection** - Check environment variables and connectivity
2. **Set appropriate priority** - Higher for specialized storage, lower for general
3. **Handle failures gracefully** - Return false from detect() if not available
4. **Document requirements** - List required packages and environment variables
5. **Provide presets** - Include common configuration patterns
6. **Maintain compatibility** - Ensure model precision matches across instances
## Example: Complete Redis Augmentation
```typescript
import {
StorageProvider,
registerStorageAugmentation,
registerPresetAugmentation,
PresetCategory,
ModelPrecision,
DistributedRole
} from '@soulcraft/brainy/config'
import Redis from 'ioredis'
export class BrainyRedisAugmentation {
private client: Redis
async init() {
// Register storage provider
registerStorageAugmentation({
type: 'redis',
name: 'Redis Vector Storage',
description: 'Redis with RediSearch for vector similarity',
priority: 10,
requirements: {
env: ['REDIS_URL'],
packages: ['ioredis', 'redis']
},
async detect() {
if (!process.env.REDIS_URL) return false
try {
const client = new Redis(process.env.REDIS_URL)
// Check for RediSearch module
const modules = await client.call('MODULE', 'LIST')
const hasRediSearch = modules.some(m => m[1] === 'search')
await client.quit()
return hasRediSearch
} catch {
return false
}
},
async getConfig() {
return {
type: 'redis',
redisStorage: {
url: process.env.REDIS_URL,
prefix: process.env.REDIS_PREFIX || 'brainy:',
index: process.env.REDIS_INDEX || 'brainy-vectors',
ttl: process.env.REDIS_TTL ? parseInt(process.env.REDIS_TTL) : undefined
}
}
}
})
// Register presets
this.registerPresets()
}
private registerPresets() {
// Fast cache preset
registerPresetAugmentation('redis-fast-cache', {
storage: 'redis' as any,
model: ModelPrecision.Q8,
features: ['core', 'cache', 'search'],
distributed: false,
cache: {
hotCacheMaxSize: 10000,
autoTune: true
},
description: 'Redis-backed fast cache',
category: PresetCategory.SERVICE
})
// Distributed cache preset
registerPresetAugmentation('redis-distributed', {
storage: 'redis' as any,
model: ModelPrecision.AUTO,
features: ['core', 'cache', 'search', 'cluster'],
distributed: true,
role: DistributedRole.HYBRID,
cache: {
hotCacheMaxSize: 50000,
autoTune: true
},
description: 'Redis distributed cache cluster',
category: PresetCategory.SERVICE
})
// Session store preset
registerPresetAugmentation('redis-sessions', {
storage: 'redis' as any,
model: ModelPrecision.Q8,
features: ['core', 'cache'],
distributed: false,
cache: {
hotCacheMaxSize: 5000,
autoTune: false
},
description: 'Redis session storage',
category: PresetCategory.SERVICE
})
}
}
// Usage after installing the augmentation:
import { Brainy } from '@soulcraft/brainy'
import '@soulcraft/brainy-redis' // Registers the augmentation
// Now Redis is automatically detected!
const brain = new Brainy() // Uses Redis if REDIS_URL is set
// Or use a Redis preset
const brain = new Brainy('redis-fast-cache')
// Or explicitly configure
const brain = new Brainy({
storage: 'redis',
model: ModelPrecision.FP32
})
```
## Summary
The extensible configuration system allows:
1. **New storage types** via `registerStorageAugmentation()`
2. **Custom presets** via `registerPresetAugmentation()`
3. **Auto-detection logic** that integrates with zero-config
4. **Type-safe extensions** with TypeScript declarations
5. **Priority-based selection** for intelligent defaults
This ensures Brainy can grow with new storage technologies while maintaining its zero-configuration philosophy!

View file

@ -22,7 +22,7 @@ Brainy's `find()` method is the most advanced query system in any vector databas
### 1. Vector Intelligence (HNSW Index)
- **Purpose**: Semantic similarity search using embeddings
- **Algorithm**: Hierarchical Navigable Small World (HNSW)
- **Performance**: O(log n) search
- **Performance**: O(log n) search, ~1.8ms typical
- **Data Structure**: Multi-layer graph with 16 connections per node
- **Use Cases**: "Find similar documents", "Content like this"
@ -36,14 +36,14 @@ Brainy's `find()` method is the most advanced query system in any vector databas
### 3. Metadata Intelligence (Incremental Indices)
- **Purpose**: Fast filtering on structured data
- **Algorithm**: HashMap for exact matches, Sorted arrays for ranges
- **Performance**: O(1) exact, O(log n) ranges
- **Performance**: O(1) exact, O(log n) ranges, <1ms typical
- **Data Structure**: `Map<field:value, Set<id>>` + sorted value arrays
- **Use Cases**: "Documents from 2023", "Status equals active"
### 4. Graph Intelligence (Adjacency Maps)
- **Purpose**: Relationship traversal and connection analysis
- **Algorithm**: Pure O(1) neighbor lookups via Map operations
- **Performance**: O(1) per hop (measured <1 ms per neighbor lookup, validated up to 1M relationships `tests/performance/graph-scale-performance.test.ts:238`)
- **Performance**: O(1) per hop, ~0.1ms typical
- **Data Structure**: `Map<sourceId, Set<targetId>>`
- **Use Cases**: "Papers connected to MIT", "Authors who collaborated"
@ -373,40 +373,36 @@ return results.slice(offset, offset + limit)
### Query Performance by Type
| Query Type | Index Used | Complexity | Example |
|------------|------------|------------|---------|
| **Semantic Search** | HNSW Vector | O(log n) | `"AI research papers"` |
| **Exact Metadata** | HashMap | O(1) | `{status: "published"}` |
| **Range Metadata** | Sorted Array | O(log n) | `{year: {greaterThan: 2020}}` |
| **Graph Traversal** | Adjacency Map | O(1) | `{connected: {to: "mit"}}` |
| **Type Detection** | Pre-embedded Types | O(t) | `"documents"``NounType.Document` |
| **Field Matching** | Field Embeddings | O(f) | `"by"``"author"` |
| **Combined Query** | All Indices | O(log n) | NLP + filters + graph |
| Query Type | Index Used | Performance | Example |
|------------|------------|-------------|---------|
| **Semantic Search** | HNSW Vector | O(log n), ~1.8ms | `"AI research papers"` |
| **Exact Metadata** | HashMap | O(1), ~0.8ms | `{status: "published"}` |
| **Range Metadata** | Sorted Array | O(log n), ~0.6ms | `{year: {greaterThan: 2020}}` |
| **Graph Traversal** | Adjacency Map | O(1), ~0.1ms | `{connected: {to: "mit"}}` |
| **Type Detection** | Pre-embedded Types | O(t), ~0.3ms | `"documents"``NounType.Document` |
| **Field Matching** | Field Embeddings | O(f), ~0.1ms | `"by"``"author"` |
| **Combined Query** | All Indices | O(log n), ~1.8ms | NLP + filters + graph |
Where:
- n = number of entities in database
- t = number of types (169 total: 42 noun + 127 verb)
- f = number of fields for detected entity type (typically 5-15)
Absolute latencies depend on hardware, embedding model, and storage backend; the columns above describe how each stage scales. The graph stage is the one path with a committed scale benchmark (see [Scalability](#scalability)).
### Scalability
The cost of each query 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) |
| Database Size | Vector Search | Metadata Filter | Graph Query | Combined |
|---------------|---------------|-----------------|-------------|----------|
| **1K entities** | 0.8ms | 0.3ms | 0.05ms | 1.1ms |
| **10K entities** | 1.2ms | 0.5ms | 0.08ms | 1.5ms |
| **100K entities** | 1.8ms | 0.8ms | 0.1ms | 2.1ms |
| **1M entities** | 2.5ms | 1.2ms | 0.1ms | 2.8ms |
| **10M entities** | 3.8ms | 1.8ms | 0.1ms | 4.2ms |
**Key Performance Notes:**
- Graph queries stay O(1) regardless of scale
- Metadata ranges scale as O(log n), not O(n)
- Vector search degrades gracefully due to HNSW
- Type-aware NLP adds minimal overhead (single embedding pass, no full scan)
- Type-aware NLP adds minimal overhead (~0.4ms)
## Example Query Flows
@ -459,7 +455,7 @@ await brain.find({
},
limit: 10
})
// Executes as O(1) type filter + O(1)/O(log n) metadata + O(1) graph lookup — no full scan
// Total performance: ~1.2ms for 100K entities
```
## Filter Syntax Reference
@ -1005,7 +1001,7 @@ const results = await brain.find({
})
```
**Performance**: O(log n) vector search + O(log n) metadata filters + O(1) graph traversal — bounded by the vector stage.
**Performance**: O(log n) vector search + O(log n) metadata filters + O(1) graph traversal = ~2-3ms total.
### Excluding Soft-Deleted Entities
@ -1232,7 +1228,7 @@ where: { age: { gt: 18 } }
**No connected entities found**:
```typescript
// Verify relationship exists
const relations = await brain.related({
const relations = await brain.getRelations({
from: 'entity-a',
to: 'entity-b'
})

View file

@ -48,7 +48,7 @@ await storage.setLifecyclePolicy({
```typescript
// v3: Delete one at a time (slow, expensive)
for (const id of idsToDelete) {
await brain.remove(id) // 1000 API calls for 1000 entities
await brain.delete(id) // 1000 API calls for 1000 entities
}
// v4.0.0: Batch delete (fast, cheap)
@ -426,6 +426,16 @@ npm install @soulcraft/brainy@^3.50.0
7. Verify data integrity thoroughly
8. Enable lifecycle policies gradually
### Scenario 4: Multi-Node Distributed System
**Recommended approach:**
1. Perform blue-green deployment:
- Keep v3 nodes running (blue)
- Deploy v4 nodes (green)
- Migrate data once
- Switch traffic to v4 nodes
- Decommission v3 nodes
## Cost Savings After Migration
### Enable All v4.0.0 Features

View file

@ -2,23 +2,21 @@
## 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`).
Brainy achieves industry-leading performance through carefully optimized data structures and algorithms. All performance claims are verified through actual benchmarks on production code.
### Core Performance Summary
| Component | Operation | Time Complexity | Example latency (100-item run)\* | Data Structure |
| Component | Operation | Time Complexity | Measured Performance | 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 |
| **Vector Search** | k-NN search | **O(log n)** | 1.8ms | HNSW 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
@ -28,32 +26,25 @@ Where:
### 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 }`.
**Massive Performance Improvement**: `brain.get()` is now **76-81% faster** by default!
| 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 |
| Operation | Before | After | Speedup | Use Case |
|-----------|------------------|-----------------|---------|----------|
| **brain.get() (metadata-only)** | 43ms, 6KB | **10ms, 300 bytes** | **76-81%** | VFS, existence checks, metadata |
| **brain.get({ includeVectors: true })** | 43ms, 6KB | 43ms, 6KB | 0% | Similarity calculations |
| **VFS readFile()** | 53ms | **~13ms** | **75%** | File operations |
| **VFS readdir(100 files)** | 5.3s | **~1.3s** | **75%** | 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.
**Key Innovation**: Lazy vector loading - only load 384-dimensional embeddings when explicitly needed.
**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
- **94% of brain.get() calls** don't need vectors (VFS, admin tools, import utilities, data APIs)
- **Vector data is 95% of entity size** (6KB vectors vs 300 bytes metadata)
- **Zero code changes** for most applications - automatic speedup!
**When to use what**:
```typescript
// DEFAULT: Metadata-only (skips the vector load) - use for:
// DEFAULT: Metadata-only (76-81% faster) - use for:
const entity = await brain.get(id)
// - VFS operations (readFile, stat, readdir)
// - Existence checks: if (await brain.get(id)) ...
@ -64,7 +55,7 @@ const entity = await brain.get(id)
const entity = await brain.get(id, { includeVectors: true })
// - Computing similarity on THIS entity
// - Manual vector operations
// - Vector index graph traversal
// - HNSW graph traversal
```
## Architecture Deep Dive
@ -152,14 +143,14 @@ class GraphAdjacencyIndex {
**Key Innovation:** Pure Map/Set operations - no database queries, no loops, just direct memory access.
### 4. Vector Index - O(log n)
### 4. HNSW Vector Search - O(log n)
The default vector index (`JsHnswVectorIndex`) provides logarithmic approximate nearest neighbor search through a hierarchical graph:
Hierarchical Navigable Small World graphs provide logarithmic approximate nearest neighbor search:
```typescript
class JsHnswVectorIndex {
class HNSWIndex {
private nouns: Map<string, HNSWNoun> = new Map()
interface HNSWNoun {
id: string
vector: number[]
@ -247,7 +238,7 @@ const results = await Promise.all(searchPromises)
|-----------|--------------|---------|
| 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` |
| HNSW | ~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 |
@ -261,40 +252,34 @@ const results = await Promise.all(searchPromises)
## 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.
### Real-world Performance Test (100 items)
```
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)
📊 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:
| Items | Metadata O(1) | Range O(log n) | Graph O(1) | Vector O(log n) |
|-------|---------------|----------------|------------|-----------------|
| 100 | 0.8ms | 0.6ms | 0.09ms | 1.8ms |
| 1,000 | 0.8ms | 0.9ms | 0.09ms | 2.5ms |
| 10,000 | 0.8ms | 1.2ms | 0.09ms | 3.2ms |
| 100,000 | 0.8ms | 1.5ms | 0.09ms | 4.1ms |
| 1,000,000 | 0.8ms | 1.8ms | 0.09ms | 5.0ms |
| 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) |
*Note: O(1) operations maintain constant time regardless of scale*
## 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 |
| **Brainy** | O(1) HashMap | O(1) Adjacency | O(log n) HNSW | 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 |
@ -320,7 +305,7 @@ Only the graph adjacency index carries a committed scale assertion:
- ✅ **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
- ✅ **Horizontally Scalable**: Stateless operations support clustering
- ✅ **Zero Stubs**: Every line of code is production-ready
## Lazy Loading Performance
@ -390,47 +375,110 @@ const brain = new Brainy({ disableAutoRebuild: true })
await brain.init() // Instant (0-10ms)
```
### Automatic Self-Tuning
### Automatic Self-Tuning (Current & Planned)
**✅ Currently Implemented:**
- **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
- **Default Tuning**: Research-based defaults (M=16, ef=200)
- **Lazy Loading**: Indices built only when needed
- **Cache Management**: LRU caches with TTL
**🚧 Planned Enhancements:**
- **Dynamic Storage Selection**: Auto-switch between memory/disk based on size
- **Adaptive Index Parameters**: Adjust M and ef based on query patterns
- **Smart Cache Sizing**: Scale caches based on available memory
- **Predictive Optimization**: Learn from usage patterns
### 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
All defaults are research-based and production-tested:
- **HNSW M=16**: Optimal balance of recall/speed for most datasets
- **efConstruction=200**: High quality graph construction
- **Cache TTL=5min**: Balances freshness with performance
- **Flush Interval=30s**: Non-blocking background persistence
### Vector Index Tuning Knobs
### Progressive Enhancement
Brainy 8.0 exposes two knobs on `config.vector`:
Brainy learns and improves over time:
1. **Query Pattern Learning**: Frequently used patterns get cached
2. **Index Optimization**: Auto-rebuilds indices when fragmented
3. **Memory Management**: Coordinates caches across all components
4. **Predictive Loading**: Pre-warms caches for common queries
### Massive Scale Deployment
For enterprise and massive scale deployments, Brainy's architecture scales to billions of items with implemented S3 storage and distributed sharding.
**Currently Implemented:**
- Memory storage (production-ready)
- Disk storage (production-ready)
- S3-compatible storage (AWS S3, Cloudflare R2, Google Cloud Storage, MinIO, Backblaze B2)
- Distributed sharding with ConsistentHashRing
- Single-node deployment (scales to ~1M items)
- Multi-node deployment with sharding (scales to billions)
**Available Today:**
```javascript
const brain = new Brainy({
vector: {
recall: 'fast', // 'fast' | 'balanced' | 'accurate'
persistMode: 'deferred' // 'immediate' | 'deferred'
// S3-compatible storage for unlimited scale - WORKS NOW
const brain = new Brainy({
storage: {
type: 's3',
bucketName: 'my-brainy-data',
region: 'us-east-1',
credentials: {
accessKeyId: 'YOUR_ACCESS_KEY',
secretAccessKey: 'YOUR_SECRET_KEY'
}
// Works with: AWS S3, MinIO, Cloudflare R2, Backblaze B2, Google Cloud Storage
}
})
// Cloudflare R2 storage - WORKS NOW
const brain = new Brainy({
storage: {
type: 'r2',
bucketName: 'my-brainy-data',
accountId: 'YOUR_ACCOUNT_ID',
accessKeyId: 'YOUR_R2_ACCESS_KEY',
secretAccessKey: 'YOUR_R2_SECRET_KEY'
}
})
// Google Cloud Storage - WORKS NOW
const brain = new Brainy({
storage: {
type: 'gcs',
bucketName: 'my-brainy-data',
region: 'us-central1',
credentials: {
accessKeyId: 'YOUR_ACCESS_KEY',
secretAccessKey: 'YOUR_SECRET_KEY'
}
}
})
```
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 |
| Scale | Items | Storage Strategy | Performance | Status |
|-------|-------|-----------------|-------------|--------|
| **Small** | <10K | Memory (automatic) | Sub-millisecond | Implemented |
| **Medium** | 10K-1M | Disk with memory cache | 1-5ms | ✅ Implemented |
| **Large** | 1M-100M | S3 with memory cache | 2-10ms | ✅ Implemented |
| **Massive** | 100M-10B | S3 + distributed sharding | 5-20ms | ✅ Implemented |
| **Planetary** | 10B+ | Multi-region S3 + Edge cache | 10-50ms | 🚧 Roadmap |
For >10M entities, run multiple Brainy processes behind your own routing layer — Brainy 8.0 doesn't ship cluster coordination.
### S3-Compatible Storage Benefits
### Architecture
- **Unlimited Scale**: No practical limit on dataset size
- **Cost Effective**: $0.023/GB/month for standard storage
- **Durability**: 99.999999999% (11 9's) durability
- **Global**: Multi-region replication available
- **Compatible**: Works with any S3-compatible API (MinIO, R2, B2)
### Distributed Architecture (Implemented)
```
┌─────────────────────────────────────────┐
@ -442,51 +490,103 @@ For >10M entities, run multiple Brainy processes behind your own routing layer
│ Brainy Core │
│ (Triple Intelligence Engine) │
├─────────────────────────────────────────┤
│ Memory │ Vector │ Metadata │
│ Cache │ Index │ Index │
│ Memory │ Shard │ Metadata │
│ Cache │ Manager │ Index │
└─────────────┬───────────────────────────┘
┌─────────────▼───────────────────────────┐
│ Storage Layer │
├──────────┬──────────┬──────────────────┤
Vectors │ Graph │ Files
(sharded)│ Edges │ (filesystem) │
HNSW │ Graph │ Objects
Vectors │ Edges │ (S3/R2/GCS) │
└──────────┴──────────┴──────────────────┘
```
For off-site replication, snapshot `path` from your scheduler (`gsutil rsync`, `aws s3 sync`, `rclone`, or `tar`).
**Distributed Sharding (Implemented):**
- ConsistentHashRing with 150 virtual nodes
- 64 shards by default
- Replication factor of 3
- Automatic rebalancing on node addition/removal
### Auto-Sharding for Horizontal Scale (Implemented)
Brainy includes a complete sharding implementation with ConsistentHashRing:
```javascript
import { ShardManager } from '@soulcraft/brainy/distributed'
// Create shard manager with custom configuration
const shardManager = new ShardManager({
shardCount: 64, // Default: 64 shards
replicationFactor: 3, // Default: 3 replicas
virtualNodes: 150, // Default: 150 virtual nodes
autoRebalance: true // Default: true
})
// Add nodes to the cluster
shardManager.addNode('node-1')
shardManager.addNode('node-2')
shardManager.addNode('node-3')
// Sharding automatically:
// - Uses consistent hashing for even distribution
// - Maintains replicas for fault tolerance
// - Rebalances on node changes
// - Provides O(1) shard lookups
```
### 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)
Even at massive scale, Brainy maintains excellent performance:
- **Metadata queries**: Still O(1) with distributed hash tables
- **Graph traversal**: O(1) with edge locality optimization
- **Vector search**: O(log n) with hierarchical sharding
- **Write throughput**: 100K+ writes/second with S3 batching
- **Read throughput**: 1M+ reads/second with caching
### Zero-Config with Autoscaling
### Zero-Config with Autoscaling (Implemented)
Brainy includes extensive autoscaling capabilities:
**✅ Implemented 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
- **Auto-optimize**: Enabled by default in Graph and HNSW indices
- **Auto-rebalance**: Shards automatically rebalance on node changes
- **Zero-config presets**: Production, development, minimal modes
- **Adaptive memory**: Scales caches based on available memory
- **Environment detection**: Browser vs Node.js vs Serverless
**🚧 Roadmap Autoscaling:**
- Dynamic HNSW parameter adjustment (M, ef)
- Predictive query pattern caching
- Multi-region auto-replication
- Automatic cross-node data migration
## Implementation Status
### Fully Implemented and Production-Ready
### 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
- **O(log n) vector search** via HNSW
- **220 NLP patterns** with pre-computed embeddings
- **Filesystem and memory storage** adapters
- **S3-compatible storage** (AWS S3, R2, GCS, MinIO, B2)
- **Distributed sharding** with ConsistentHashRing
- **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)
- **Sub-2ms response times** for complex queries
### 🚧 Roadmap Features
- Dynamic HNSW parameter tuning
- Predictive query pattern caching
- Multi-region S3 replication
- Automatic cross-node data migration
- Edge caching layer
## 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.
Brainy delivers on its promise of **production-ready Triple Intelligence** with measured, verified performance characteristics. All listed features are fully implemented, tested, and benchmarked. No stubs, no mocks, no theoretical claims - just real, working code with measured performance.

View file

@ -5,14 +5,15 @@ 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.
description: Replace any Brainy subsystem — distance functions, embeddings, HNSW index, metadata index, aggregation — with a custom implementation or native Rust via Cortex.
next:
- cortex/comparison
- 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.
Brainy has a plugin system that allows third-party packages to replace internal subsystems with custom implementations. This is how `@soulcraft/cortex` provides native Rust acceleration, and it's the same system available to any developer.
## Architecture Overview
@ -23,19 +24,20 @@ Brainy's plugin system uses **named providers** — string keys mapped to implem
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.
Plugins are **opt-in** — brainy never auto-imports packages. You must explicitly list plugins in the config:
```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
const brain = new Brainy({
plugins: ['@soulcraft/cortex'] // explicitly load cortex
})
```
| `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 |
| `undefined` (default) | No plugins loaded |
| `false` | No plugins loaded |
| `[]` | No plugins loaded |
| `['@soulcraft/cortex']` | Load only the listed packages |
Plugins registered programmatically via `brain.use(plugin)` are always activated regardless of the `plugins` config.
@ -69,7 +71,7 @@ export default myPlugin
### 2. Package exports
Your package must export the plugin as the default export so brainy's plugin loader can resolve it:
Your package must export the plugin as the default export so brainy's auto-detection works:
```typescript
// index.ts
@ -107,7 +109,7 @@ Each key has a specific expected signature. Brainy checks for these during `init
#### `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.
Replaces the default cosine distance function used in HNSW 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 => {
@ -149,21 +151,10 @@ context.registerProvider('embedBatch', async (texts: string[]) => {
### 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.
#### `hnsw`
**Type:** `(config: object, distanceFunction: Function, options: object) => HNSWIndex-compatible`
#### `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:
Factory function that creates an HNSW index instance. The returned object must implement the `HNSWIndex` public API:
- `addItem(item: { id: string, vector: number[] }): Promise<string>`
- `search(queryVector: number[], k: number, filter?, options?): Promise<Array<[string, number]>>`
@ -183,36 +174,15 @@ Factory function that creates a vector index instance. The returned object must
- `setUseParallelization(boolean): void`
For type-aware indexes (separate graph per noun type), also implement:
- `getIndexForType(type: string): VectorIndexProvider` (duck-typed detection)
- `getIndexForType(type: string): HNSWIndex` (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)
context.registerProvider('hnsw', (config, distanceFn, options) => {
return new MyNativeHNSWIndex(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`
@ -246,7 +216,7 @@ context.registerProvider('aggregation', (storage) => {
})
```
When provided by an optional native acceleration plugin (such as `@soulcraft/cor`), this enables:
When provided by a native plugin like `@soulcraft/cortex`, 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
@ -257,7 +227,7 @@ When provided by an optional native acceleration plugin (such as `@soulcraft/cor
#### `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`).
Replaces the global `UnifiedCache` singleton used for VFS path resolution, semantic caching, and HNSW vector caching. Must implement the `UnifiedCache` interface (available from `@soulcraft/brainy/internals`).
```typescript
import type { UnifiedCache } from '@soulcraft/brainy/internals'
@ -282,7 +252,7 @@ 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.
These provider keys have **no JavaScript fallback** — they represent capabilities that require native code (SIMD, mmap, sub-microsecond latency). They are available when a native plugin like `@soulcraft/cortex` is installed.
Use `brain.getProvider('analytics:hyperloglog')` to check availability. Returns `undefined` if no plugin provides it.
@ -368,11 +338,11 @@ console.log(diag)
// embeddings: { source: 'plugin' },
// embedBatch: { source: 'plugin' },
// distance: { source: 'plugin' },
// vector: { source: 'default' },
// hnsw: { source: 'default' },
// ...
// },
// indexes: {
// vector: { size: 0, type: 'JsHnswVectorIndex' },
// hnsw: { size: 0, type: 'TypeAwareHNSWIndex' },
// metadata: { type: 'MetadataIndexManager', initialized: true },
// graph: { type: 'GraphAdjacencyIndex', initialized: true, wiredToStorage: true }
// }
@ -390,8 +360,8 @@ brainy diagnostics
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
[brainy] Plugin activated: @soulcraft/cortex
[brainy] Providers: 8/10 native (@soulcraft/cortex) | default: hnsw, 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`.
@ -412,7 +382,7 @@ If a required provider is missing, the error message tells you exactly what's wr
```
[brainy] Required providers using JS fallback: graphIndex.
Active plugins: @soulcraft/cor.
Active plugins: @soulcraft/cortex.
These providers must be supplied by a plugin for this deployment.
Check plugin installation, license, and native module availability.
```

View file

@ -215,9 +215,9 @@ console.log('Server running on http://localhost:3000')
```
**Benefits:**
- ✅ Native Bun runtime performance
- ✅ Native Bun performance (~2x faster than Node.js)
- ✅ No framework dependencies
- ✅ Pure WASM — no native binaries, bundler-friendly
- ✅ Works with `bun --compile` for single-binary deployment
- ✅ Built-in TypeScript support
### Pattern 4: Express/Node.js Middleware (Legacy)

View file

@ -10,8 +10,8 @@ All operators work with `find({ where: { ... } })` and filter on **metadata fiel
| Operator | Alias | Description | Example |
|----------|-------|-------------|---------|
| `eq` | `equals` | Exact match | `{ status: { eq: 'active' } }` |
| `ne` | `notEquals` | Not equal | `{ status: { ne: 'deleted' } }` |
| `equals` | `eq`, `is` | Exact match | `{ status: { equals: 'active' } }` |
| `notEquals` | `ne`, `isNot` | Not equal | `{ status: { notEquals: 'deleted' } }` |
**Shorthand:** A bare value is treated as `equals`:
@ -27,10 +27,10 @@ brain.find({ where: { status: { equals: 'active' } } })
| 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 } }` |
| `greaterThan` | `gt` | Greater than | `{ age: { greaterThan: 18 } }` |
| `greaterEqual` | `gte` | Greater or equal | `{ score: { greaterEqual: 90 } }` |
| `lessThan` | `lt` | Less than | `{ price: { lessThan: 100 } }` |
| `lessEqual` | `lte` | Less or equal | `{ rating: { lessEqual: 3 } }` |
| `between` | — | Inclusive range `[min, max]` | `{ year: { between: [2020, 2025] } }` |
```typescript
@ -160,9 +160,9 @@ Brainy's MetadataIndex supports a subset of operators natively for O(1) field lo
| `equals` / `eq` | Yes | Yes |
| `notEquals` / `ne` | — | Yes |
| `greaterThan` / `gt` | Yes | Yes |
| `greaterThanOrEqual` / `gte` | Yes | Yes |
| `greaterEqual` / `gte` | Yes | Yes |
| `lessThan` / `lt` | Yes | Yes |
| `lessThanOrEqual` / `lte` | Yes | Yes |
| `lessEqual` / `lte` | Yes | Yes |
| `between` | Yes | Yes |
| `oneOf` / `in` | Yes | Yes |
| `noneOf` | — | Yes |
@ -216,25 +216,25 @@ See the **[Subtypes & Facets guide](./guides/subtypes-and-facets.md)** for the f
### Filter relationships by subtype (7.30+)
Verbs are first-class peers — `related()` and graph traversal both honor subtype filters on the fast path:
Verbs are first-class peers — `getRelations()` and graph traversal both honor subtype filters on the fast path:
```typescript
// Filter relationships by VerbType subtype
const direct = await brain.related({
const direct = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: 'direct'
})
// Set membership on verb subtype
const all = await brain.related({
const all = await brain.getRelations({
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)
// multi-hop subtype filtering lands on Cortex native)
const reports = await brain.find({
connected: {
from: ceoId,

View file

@ -35,7 +35,6 @@ const results = await brain.find({
| **[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 |
---
@ -48,7 +47,7 @@ const results = await brain.find({
| [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 |
| [Index Architecture](./architecture/index-architecture.md) | HNSW, Graph, and Metadata indexing |
| [Zero Configuration](./architecture/zero-config.md) | Auto-adapts to any environment |
---
@ -71,7 +70,6 @@ See [vfs/](./vfs/) for the complete VFS documentation set.
| 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 |
@ -83,16 +81,24 @@ See [vfs/](./vfs/) for the complete VFS documentation set.
| Document | Description |
|----------|-------------|
| [Storage Architecture](./architecture/storage-architecture.md) | Filesystem and memory adapters, on-disk artifact layout, operator-layer backup |
| [Cloud Deployment](./deployment/CLOUD_DEPLOYMENT_GUIDE.md) | Deploy on AWS, GCP, Azure, Cloudflare |
| [Extending Storage](./EXTENDING_STORAGE.md) | Create custom storage adapters |
| [AWS S3 Cost Optimization](./operations/cost-optimization-aws-s3.md) | 96% cost savings |
| [GCS Cost Optimization](./operations/cost-optimization-gcs.md) | 94% savings with Autoclass |
| [Azure Cost Optimization](./operations/cost-optimization-azure.md) | 95% savings |
| [R2 Cost Optimization](./operations/cost-optimization-cloudflare-r2.md) | Zero egress fees |
| [Capacity Planning](./operations/capacity-planning.md) | Scale to millions of entities |
---
## Plugins
## Plugins & Augmentations
| Document | Description |
|----------|-------------|
| [Plugins](./PLUGINS.md) | Plugin system overview — providers, `plugins` config, `brain.use()` |
| [Plugins](./PLUGINS.md) | Plugin system overview |
| [Creating Augmentations](./CREATING-AUGMENTATIONS.md) | Build custom plugins |
| [Augmentations Reference](./augmentations/COMPLETE-REFERENCE.md) | Full augmentation API |
| [Augmentations Developer Guide](./augmentations/DEVELOPER-GUIDE.md) | Plugin development guide |
---

View file

@ -119,13 +119,4 @@ gh release create vY.Y.Y --generate-notes
- **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.)
- **NEVER use "BREAKING CHANGE" for internal changes**

View file

@ -1,239 +1,502 @@
# Brainy Scaling Guide
# 🚀 Brainy Scaling Guide - Enterprise for Everyone
> **One Line Summary**: Single-node by design — Brainy scales by getting the most out of one machine plus operator-layer backup.
> **One Line Summary**: Start with one node, scale to hundreds. Zero configuration required.
## Table of Contents
## 📖 Table of Contents
- [Quick Start](#quick-start)
- [How Brainy Scales](#how-brainy-scales)
- [How It Works](#how-it-works)
- [Storage Configurations](#storage-configurations)
- [Scaling Patterns](#scaling-patterns)
- [Real World Examples](#real-world-examples)
## Quick Start
### In-Memory
### Single Node (Default)
```typescript
import Brainy from '@soulcraft/brainy'
const brain = new Brainy({ storage: { type: 'memory' } })
const brain = new Brainy() // That's it!
```
### On-Disk (Default for Node)
### Multi-Node (Auto-Discovery)
```typescript
// Node 1
const brain = new Brainy() // Starts as primary
// Node 2 (different server)
const brain = new Brainy() // Auto-discovers Node 1, becomes replica!
```
**That's literally all you need!** Brainy handles everything else automatically.
## How It Works
### 🎯 The Magic: Zero Configuration
Brainy uses **intelligent defaults** and **auto-discovery** to eliminate configuration:
1. **First node starts** → Becomes primary automatically
2. **Second node starts** → Discovers first node via UDP broadcast
3. **Nodes negotiate** → Elect leader, distribute shards
4. **Data flows** → Automatic replication and routing
5. **Node fails** → Automatic failover in <1 second
### 🔄 Automatic Node Discovery
```typescript
// Three ways Brainy finds other nodes (auto-selected):
// 1. LOCAL NETWORK (Default)
// Uses UDP broadcast on port 7946
// Perfect for: On-premise, same VPC
// 2. CLOUD NATIVE (Auto-detected)
// Kubernetes: Uses k8s DNS service discovery
// AWS: Uses EC2 tags or Route53
// Azure: Uses Azure DNS
// 3. EXPLICIT (When needed)
const brain = new Brainy({
storage: { type: 'filesystem', path: './brainy-data' }
peers: ['node1.example.com', 'node2.example.com']
})
```
## How Brainy Scales
### 📊 Data Distribution
Brainy 8.0 is a **single-node library**. There is no cluster, no peer discovery, no S3 coordination. Scaling means:
When you add data, Brainy automatically:
- **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
```typescript
brain.add({ name: "John" }, 'person')
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._
// Behind the scenes:
// 1. Hash ID to determine shard (consistent hashing)
// 2. Find nodes responsible for this shard
// 3. Write to primary shard owner
// 4. Replicate to N backup nodes (default: 2)
// 5. Confirm write when majority acknowledge
```
## Storage Configurations
### Filesystem (Recommended for Production)
### 🗂️ Storage Adapter Patterns
Brainy intelligently adapts to your storage setup:
#### Pattern 1: Separate Storage Per Node (Recommended)
```typescript
// Node 1 - Own filesystem
const brain1 = new Brainy({
storage: '/data/node1' // or auto: './brainy-data'
})
// Node 2 - Own filesystem
const brain2 = new Brainy({
storage: '/data/node2' // or auto: './brainy-data'
})
// ✅ BENEFITS:
// - No conflicts between nodes
// - Fast local reads
// - True horizontal scaling
// - Survives network partitions
```
#### Pattern 2: Separate S3 Buckets Per Node
```typescript
// Node 1 - Own S3 bucket
const brain1 = new Brainy({
storage: 's3://brainy-node-1' // Auto-uses AWS credentials
})
// Node 2 - Own S3 bucket
const brain2 = new Brainy({
storage: 's3://brainy-node-2'
})
// ✅ BENEFITS:
// - Infinite storage capacity
// - Geographic distribution
// - No local disk needed
// - Built-in durability
```
#### Pattern 3: Shared S3 Bucket (Coordinated)
```typescript
// All nodes - Shared bucket with coordination
const brain = new Brainy({
storage: 's3://shared-brainy-data',
// Brainy automatically adds node-specific prefixes!
})
// What happens automatically:
// - Node 1 writes to: s3://shared-brainy-data/node-1/
// - Node 2 writes to: s3://shared-brainy-data/node-2/
// - Metadata in: s3://shared-brainy-data/_cluster/
// - Coordination via S3 conditional writes
// ✅ BENEFITS:
// - Single bucket to manage
// - Easy backup/restore
// - Cost effective
// - Automatic namespace isolation
```
#### Pattern 4: Mixed Storage (Hybrid)
```typescript
// Hot data on local SSD, cold data in S3
const brain = new Brainy({
storage: {
type: 'filesystem',
path: '/var/lib/brainy'
hot: '/fast-ssd/brainy', // Recent/frequent data
cold: 's3://brainy-archive' // Older data
}
// Brainy automatically promotes/demotes data!
})
```
- 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
### 🌍 Cloud Provider Auto-Detection
### Auto
```typescript
const brain = new Brainy({
storage: { type: 'auto', path: './data' }
storage: 'cloud://brainy-data' // Auto-detects provider!
})
// Automatically uses:
// - AWS: S3 + DynamoDB for metadata
// - Google Cloud: GCS + Firestore
// - Azure: Blob Storage + Cosmos DB
// - Cloudflare: R2 + D1
// - Vercel: Blob + KV
```
- Picks `filesystem` when running on Node with a writable `path`
- Falls back to `memory` otherwise
### 📝 Storage Coordination Rules
When multiple nodes share storage, Brainy automatically:
1. **Namespace Isolation**: Each node gets unique prefix
2. **Lock-Free Writes**: Uses atomic operations
3. **Consistent Metadata**: Coordinated via consensus
4. **Conflict Resolution**: Version vectors for conflicts
5. **Garbage Collection**: Automatic cleanup of old data
## Scaling Patterns
### Stage 1: Prototype (Memory)
### 📈 Progressive Scaling Journey
#### Stage 1: Prototype (1 node, memory)
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
// Development, tests, <100K items
const brain = new Brainy() // Memory storage, single node
// Perfect for: Development, testing, <1000 items
```
### Stage 2: Production (Filesystem)
#### Stage 2: Production (1 node, disk)
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' }
storage: './data' // Persistent storage
})
// Most production workloads up to ~10M entities on a single host
// Perfect for: Small apps, <100K items
```
### Stage 3: Higher Throughput (Tune the Vector Index)
#### Stage 3: High Availability (2-3 nodes)
```typescript
// Just start same code on multiple servers!
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
vector: {
recall: 'fast', // Trade recall for latency
persistMode: 'deferred' // Batch persistence
}
storage: './data' // Each node's own storage
})
// Automatic: Leader election, replication, failover
// Perfect for: Critical apps, <1M items
```
### 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.
#### Stage 4: Scale Out (N nodes)
```typescript
// Same code, more servers!
const brain = new Brainy({
storage: 's3://brainy-{{nodeId}}' // Template auto-filled
})
// Automatic: Sharding, load balancing, geo-distribution
// Perfect for: Large apps, unlimited items
```
### 🎯 Common Scaling Scenarios
#### Scenario: Read-Heavy Application
```typescript
// Brainy auto-detects read-heavy pattern and:
// 1. Increases cache size
// 2. Creates more read replicas
// 3. Routes reads to nearest node
// 4. Caches popular items on all nodes
const brain = new Brainy() // No config needed!
```
#### Scenario: Multi-Tenant SaaS
```typescript
// Brainy auto-detects tenant patterns and:
// 1. Shards by tenant ID
// 2. Isolates tenant data
// 3. Routes by tenant
// 4. Separate rate limits per tenant
const brain = new Brainy() // Detects from your queries!
```
#### Scenario: Geographic Distribution
```typescript
// Deploy nodes in different regions
// Brainy automatically:
// 1. Detects node locations (via latency)
// 2. Replicates data geographically
// 3. Routes to nearest node
// 4. Handles region failures
// US-East
const brain = new Brainy({ region: 'us-east' }) // Optional hint
// EU-West (auto-discovers US-East)
const brain = new Brainy({ region: 'eu-west' })
```
## Real World Examples
### Example 1: Single-Node App With Backup
### Example 1: Blog Platform
```typescript
// Day 1: Single server
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' }
storage: './blog-data'
})
```
Schedule (cron / systemd timer):
```bash
*/15 * * * * rclone sync /var/lib/brainy remote:brainy-backup
// Month 6: Add redundancy (on second server)
const brain = new Brainy({
storage: './blog-data' // Different machine!
})
// Automatically syncs with first server
// Year 2: Global scale
// US Server
const brain = new Brainy({
storage: 's3://blog-us/data'
})
// EU Server
const brain = new Brainy({
storage: 's3://blog-eu/data'
})
// Asia Server
const brain = new Brainy({
storage: 's3://blog-asia/data'
})
// All automatically coordinate!
```
### Example 2: Tests
### Example 2: E-Commerce Site
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
// Fast, no cleanup needed between runs
// Development
const brain = new Brainy() // Memory storage
// Staging (Kubernetes)
const brain = new Brainy({
storage: process.env.STORAGE_PATH // Uses PVC
})
// Auto-discovers other pods via K8s DNS
// Production (AWS)
const brain = new Brainy({
storage: 's3://shop-data',
cache: 'elasticache://shop-cache' // Optional
})
// Auto-scales with ECS/EKS
```
### Example 3: Multi-Tenant Service
Spin up one Brainy instance per tenant, each in its own directory:
### Example 3: Analytics Platform
```typescript
function brainForTenant(tenantId: string) {
return new Brainy({
storage: {
type: 'filesystem',
path: `/var/lib/brainy/${tenantId}`
}
})
// Ingestion nodes (write-optimized)
const brain = new Brainy({
role: 'writer', // Hint for optimization
storage: '/fast-nvme/ingest'
})
// Query nodes (read-optimized)
const brain = new Brainy({
role: 'reader', // More cache, indexes
storage: 's3://analytics-archive'
})
// Automatically coordinates between writers and readers!
```
## 🔧 Storage Adapter Specifics
### Local Filesystem
```typescript
{
storage: './data' // or absolute: '/var/lib/brainy'
// Each node MUST have separate directory
// Can be network mounted (NFS, EFS)
}
```
Your service layer handles routing and isolation; Brainy stays simple.
### Example 4: Higher Recall at Scale
### AWS S3
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
vector: {
recall: 'accurate'
{
storage: 's3://bucket-name/prefix'
// Uses AWS SDK credentials (env, IAM role, etc)
// Supports S3-compatible (MinIO, Ceph)
}
```
### Cloudflare R2
```typescript
{
storage: 'r2://bucket-name'
// Uses Wrangler or API tokens
// Zero egress fees!
}
```
### Google Cloud Storage
```typescript
{
storage: 'gs://bucket-name'
// Uses Application Default Credentials
}
```
### Azure Blob Storage
```typescript
{
storage: 'azure://container-name'
// Uses DefaultAzureCredential
}
```
### Mixed/Tiered
```typescript
{
storage: {
hot: './local-cache', // Fast SSD
warm: 's3://regular-data', // Standard storage
cold: 's3://glacier-archive' // Cheap archive
}
// Automatic tiering based on access patterns
}
```
## 🎭 Advanced Patterns
### Pattern: Blue-Green Deployment
```typescript
// Blue cluster (current)
const brain = new Brainy({
cluster: 'blue',
storage: 's3://prod-blue'
})
// Green cluster (new version)
const brain = new Brainy({
cluster: 'green',
storage: 's3://prod-green',
syncFrom: 'blue' // Real-time sync during migration
})
```
## Tuning Knobs Summary
### Pattern: Federation
```typescript
// Region 1 Cluster
const brain1 = new Brainy({
federation: 'global',
region: 'us-east',
storage: 's3://us-east-data'
})
| 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 |
// Region 2 Cluster
const brain2 = new Brainy({
federation: 'global',
region: 'eu-west',
storage: 's3://eu-west-data'
})
// Clusters coordinate for global queries!
```
## Monitoring & Observability
### Pattern: Edge Computing
```typescript
// Edge nodes (in CDN POPs)
const brain = new Brainy({
mode: 'edge',
storage: 'memory', // RAM only
upstream: 'https://main-cluster.example.com'
})
// Caches frequently accessed data at edge
```
## 📊 Monitoring & Observability
Brainy automatically exposes metrics:
```typescript
const stats = await brain.stats()
const metrics = brain.getMetrics()
// {
// nounCount: 50000,
// verbCount: 80000,
// vectorIndex: { ... },
// storage: { used: '45GB' }
// nodes: { total: 5, healthy: 5 },
// shards: { total: 20, local: 4 },
// replication: { factor: 2, lag: 45 },
// operations: { reads: 10000, writes: 1000 },
// storage: { used: '45GB', available: '955GB' }
// }
```
## Troubleshooting
## 🚨 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: Nodes don't discover each other
```typescript
// Solution 1: Check network allows UDP 7946
// Solution 2: Use explicit peers
const brain = new Brainy({
peers: ['10.0.0.1:7946', '10.0.0.2:7946']
})
```
### 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: Storage conflicts
```typescript
// Ensure each node has unique storage path
// ❌ WRONG: All nodes use './data'
// ✅ RIGHT: Node1: './data1', Node2: './data2'
// ✅ RIGHT: Use {{nodeId}} template
```
### 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
### Issue: Slow performance
```typescript
// Brainy auto-tunes, but you can hint:
const brain = new Brainy({
profile: 'read-heavy' // or 'write-heavy', 'balanced'
})
```
## Best Practices
## 🎯 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**
1. **Let Brainy Auto-Configure**: Don't over-configure
2. **Separate Storage Per Node**: Avoids conflicts
3. **Use S3 for Large Scale**: Infinite capacity
4. **Start Simple**: Single node → Scale when needed
5. **Monitor Metrics**: Watch for bottlenecks
6. **Trust Auto-Scaling**: It learns your patterns
## Summary
## 🚀 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`
- **Zero Config**: Just `new Brainy()` at any scale
- **Auto-Discovery**: Nodes find each other
- **Smart Storage**: Adapts to any backend
- **Progressive Scaling**: 1 → 100 nodes seamlessly
- **Self-Tuning**: Learns and optimizes
- **No DevOps**: It just works!
**This is Enterprise for Everyone - enterprise-grade scaling with toy-like simplicity!**
---
*Questions? Issues? Visit [github.com/soullabs/brainy](https://github.com/soullabs/brainy)*

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,306 @@
# 🧠 **Brainy Clustering Algorithms - Complete Analysis**
## 🎯 **Current State & Capabilities**
### **✅ Existing Infrastructure (Excellent Foundation)**
#### **1. HNSW Hierarchical Clustering**
```typescript
// ALREADY IMPLEMENTED & OPTIMIZED
brain.neural.clusters({ algorithm: 'hierarchical', level: 2 })
```
**How it works:**
- **Leverages HNSW natural hierarchy**: Uses existing index levels as natural cluster boundaries
- **O(n) performance**: Much faster than O(n²) traditional clustering
- **Multi-level granularity**: Higher levels = fewer, broader clusters; Lower levels = more, specific clusters
- **Representative sampling**: Uses HNSW level nodes as natural cluster centers
**Performance characteristics:**
- **Excellent for large datasets** (millions of items)
- **Preserves semantic relationships** from vector space
- **Automatic granularity control** via level parameter
#### **2. Distance-Based Algorithms**
```typescript
// COMPREHENSIVE DISTANCE FUNCTIONS AVAILABLE
euclideanDistance, cosineDistance, manhattanDistance, dotProductDistance
```
**Optimized implementations:**
- **Batch processing**: `calculateDistancesBatch()` with parallelization
- **Multiple metrics**: Choose optimal distance function per use case
- **Performance optimized**: Faster than GPU for small vectors due to no transfer overhead
#### **3. Rich Semantic Taxonomy**
```typescript
// 25+ NOUN TYPES & 35+ VERB TYPES
NounType: Person, Organization, Document, Concept, Event, Media, etc.
VerbType: RelatedTo, Contains, PartOf, Causes, CreatedBy, etc.
```
**Semantic clustering capabilities:**
- **Type-based clustering**: Group by semantic categories
- **Cross-type relationships**: Use verb types to find semantic bridges
- **Hierarchical taxonomies**: Natural clustering within and across types
#### **4. Graph Structure**
```typescript
// VERB RELATIONSHIPS CREATE RICH GRAPH
await brain.relate(sourceId, targetId, VerbType.Causes, { strength: 0.8 })
```
**Graph-based clustering potential:**
- **Connected components**: Find strongly connected groups
- **Community detection**: Use relationship strength for clustering
- **Multi-modal clustering**: Combine graph + vector + taxonomy
## 🚀 **Advanced Clustering Algorithms We Can Implement**
### **1. ✅ Already Implemented: HNSW Hierarchical**
```typescript
// PRODUCTION READY - Uses existing HNSW levels
const clusters = await brain.neural.clusters({
algorithm: 'hierarchical',
level: 2, // Control granularity
maxClusters: 15
})
```
**Performance:** **A+** - O(n) leveraging existing index structure
### **2. 🔥 Semantic Taxonomy Clustering**
```typescript
// IMPLEMENT: Fast type-based clustering with cross-type bridges
const clusters = await brain.neural.clusterByDomain('nounType', {
preserveTypeBoundaries: false, // Allow cross-type clusters
bridgeStrength: 0.8, // Minimum relationship strength for bridges
hybridWeighting: {
taxonomy: 0.4, // 40% weight to type similarity
vector: 0.4, // 40% weight to vector similarity
graph: 0.2 // 20% weight to relationship strength
}
})
```
**Algorithm approach:**
1. **Primary clustering by taxonomy**: Group by NounType/VerbType first
2. **Vector refinement**: Sub-cluster within types using vector similarity
3. **Cross-type bridging**: Find relationships that bridge type boundaries
4. **Weighted fusion**: Combine taxonomy + vector + graph signals
**Performance:** **A+** - O(n log n) - taxonomy grouping is O(n), refinement is HNSW-accelerated
### **3. 🔥 Graph Community Detection**
```typescript
// IMPLEMENT: Relationship-based clustering
const clusters = await brain.neural.clusterByConnections({
algorithm: 'modularity', // or 'louvain', 'leiden'
minCommunitySize: 3,
relationshipWeights: {
[VerbType.Creates]: 1.0,
[VerbType.PartOf]: 0.8,
[VerbType.RelatedTo]: 0.5
}
})
```
**Algorithm approach:**
1. **Build weighted graph**: Use verbs as edges, weights from relationship types + metadata
2. **Community detection**: Apply Louvain or Leiden algorithm for modularity optimization
3. **Semantic enhancement**: Use vector similarity to refine community boundaries
**Performance:** **A** - O(n log n) for sparse graphs, handles millions of relationships efficiently
### **4. 🔥 Multi-Modal Fusion Clustering**
```typescript
// IMPLEMENT: Best of all worlds
const clusters = await brain.neural.clusters({
algorithm: 'multimodal',
signals: {
vector: { weight: 0.5, metric: 'cosine' },
graph: { weight: 0.3, algorithm: 'modularity' },
taxonomy: { weight: 0.2, crossTypeThreshold: 0.8 }
},
fusion: 'weighted_ensemble' // or 'consensus', 'hierarchical'
})
```
**Algorithm approach:**
1. **Independent clustering**: Run HNSW, graph, and taxonomy clustering separately
2. **Consensus building**: Find agreement between different clustering results
3. **Conflict resolution**: Use weighted voting or hierarchical merging for disagreements
4. **Quality optimization**: Iteratively refine based on silhouette scores
**Performance:** **A** - O(n log n) - parallel execution of component algorithms
### **5. 💎 Temporal Pattern Clustering**
```typescript
// IMPLEMENT: Time-aware clustering using existing infrastructure
const clusters = await brain.neural.clusterByTime('createdAt', [
{ start: new Date('2024-01-01'), end: new Date('2024-06-30'), label: 'H1 2024' },
{ start: new Date('2024-07-01'), end: new Date('2024-12-31'), label: 'H2 2024' }
], {
evolution: 'track', // Track how clusters evolve over time
stability: 0.7, // Minimum stability threshold
trendAnalysis: true // Include trend detection
})
```
**Algorithm approach:**
1. **Time window clustering**: Apply HNSW clustering within each time window
2. **Cluster evolution tracking**: Match clusters across time windows using vector similarity
3. **Trend analysis**: Detect growing, shrinking, merging, splitting patterns
4. **Stability scoring**: Measure cluster consistency over time
**Performance:** **A+** - O(k*n log n) where k = number of time windows
### **6. 💎 DBSCAN with Adaptive Parameters**
```typescript
// IMPLEMENT: Density-based clustering with smart parameter selection
const clusters = await brain.neural.clusters({
algorithm: 'dbscan',
autoParams: true, // Automatically select eps and minPts
distanceMetric: 'cosine',
outlierHandling: 'soft' // Soft assignment instead of hard outliers
})
```
**Algorithm approach:**
1. **Adaptive parameter selection**: Use HNSW k-NN distances to estimate optimal eps
2. **Multi-scale analysis**: Run DBSCAN at multiple scales and merge results
3. **Soft outlier assignment**: Assign outliers to nearest clusters with confidence scores
**Performance:** **A** - O(n log n) using HNSW for neighbor queries
## 📊 **Performance Comparison Matrix**
| Algorithm | Time Complexity | Space | Large Scale | Semantic Quality | Graph Aware |
|-----------|----------------|-------|-------------|------------------|-------------|
| **HNSW Hierarchical** | O(n) | O(n) | ✅ Excellent | ✅ Very Good | ❌ No |
| **Taxonomy Fusion** | O(n log n) | O(n) | ✅ Excellent | 🔥 Exceptional | ⚡ Partial |
| **Graph Communities** | O(n log n) | O(e) | ✅ Very Good | ✅ Very Good | 🔥 Exceptional |
| **Multi-Modal** | O(n log n) | O(n) | ✅ Very Good | 🔥 Exceptional | 🔥 Exceptional |
| **Temporal Patterns** | O(k*n log n) | O(n) | ⚡ Good | ✅ Very Good | ⚡ Partial |
| **Adaptive DBSCAN** | O(n log n) | O(n) | ✅ Very Good | ✅ Very Good | ❌ No |
## 🎯 **Specific Improvements Using Existing Capabilities**
### **1. Enhanced HNSW Clustering (Easy Win)**
```typescript
// IMPROVE EXISTING: Add semantic post-processing
private async enhanceHNSWClusters(clusters: SemanticCluster[]): Promise<SemanticCluster[]> {
return Promise.all(clusters.map(async cluster => {
// Get actual metadata for cluster members
const members = await this.brain.getNouns(cluster.members.map(id => ({ id })))
// Analyze semantic characteristics
const semanticProfile = this.analyzeSemanticProfile(members)
// Generate meaningful cluster labels
const label = await this.generateClusterLabel(members, semanticProfile)
// Calculate cluster coherence using multiple signals
const coherence = this.calculateMultiModalCoherence(members)
return {
...cluster,
label,
semanticProfile,
coherence,
quality: coherence.overall
}
}))
}
```
### **2. Intelligent Algorithm Selection**
```typescript
// IMPLEMENT: Smart routing based on data characteristics
private selectOptimalAlgorithm(dataCharacteristics: {
size: number,
dimensionality: number,
graphDensity: number,
typeDistribution: Record<string, number>
}): string {
if (dataCharacteristics.size > 100000) {
return 'hierarchical' // HNSW scales best
}
if (dataCharacteristics.graphDensity > 0.1) {
return 'multimodal' // Rich graph structure
}
if (Object.keys(dataCharacteristics.typeDistribution).length > 10) {
return 'taxonomy' // Diverse semantic types
}
return 'hierarchical' // Safe default
}
```
### **3. Streaming Cluster Updates**
```typescript
// IMPLEMENT: Incremental clustering using existing infrastructure
public async updateClusters(newItems: string[]): Promise<SemanticCluster[]> {
// Use HNSW nearest neighbor for fast cluster assignment
const assignments = await Promise.all(
newItems.map(async itemId => {
const neighbors = await this.brain.neural.neighbors(itemId, { limit: 5 })
return this.assignToNearestCluster(itemId, neighbors, this.existingClusters)
})
)
// Incrementally update cluster centroids and boundaries
return this.updateClusterBoundaries(assignments)
}
```
## 🏆 **Recommended Implementation Priority**
### **🔥 Phase 1: High Impact, Easy Implementation**
1. **Enhanced HNSW Clustering**: Add semantic post-processing to existing algorithm
2. **Taxonomy-Aware Clustering**: Leverage existing NounType/VerbType enums
3. **Intelligent Algorithm Selection**: Route based on data characteristics
### **⚡ Phase 2: Advanced Features**
4. **Graph Community Detection**: Use existing verb relationships
5. **Multi-Modal Fusion**: Combine all signals intelligently
6. **Streaming Updates**: Incremental cluster maintenance
### **💎 Phase 3: Cutting Edge**
7. **Temporal Pattern Analysis**: Track cluster evolution over time
8. **Adaptive DBSCAN**: Dynamic parameter selection
9. **Explainable Clustering**: Generate cluster explanations and reasoning
## 🎯 **Key Advantages of Our Approach**
### **✅ Leverages Existing Infrastructure**
- **HNSW index**: Already optimized for large-scale vector operations
- **Distance functions**: Battle-tested and performance-optimized
- **Semantic taxonomy**: Rich type system with 60+ semantic categories
- **Graph structure**: Relationship network from verb connections
### **✅ Multiple Clustering Paradigms**
- **Vector similarity**: Traditional embedding-based clustering
- **Graph structure**: Relationship-based community detection
- **Semantic taxonomy**: Type-aware intelligent grouping
- **Temporal patterns**: Time-aware cluster evolution
- **Multi-modal fusion**: Best of all worlds
### **✅ Scalability & Performance**
- **O(n) hierarchical clustering**: Leveraging HNSW levels
- **Parallel processing**: Batch distance calculations optimized
- **Streaming support**: Real-time cluster updates
- **Memory efficient**: Existing index structures reused
**Our clustering algorithms are not just competitive - they're architecturally superior by leveraging Brainy's unique multi-modal semantic infrastructure.**

View file

@ -33,7 +33,7 @@
│ ▼ │
│ ┌──────────────────────┐ │
│ │ AggregationProvider │ (optional, registered by │
│ │ ├─ incrementalUpdate│ plugin like @soulcraft/cor)
│ │ ├─ incrementalUpdate│ plugin like @soulcraft/cortex)│
│ │ ├─ rebuildAggregate │ │
│ │ ├─ queryAggregate │ │
│ │ └─ serialize/restore│ │
@ -97,7 +97,7 @@ On `brain.update(entity)`, the engine reverses the old entity's contribution and
### Delete Handling
On `brain.remove(id)`, the engine reverses the entity's contribution:
On `brain.delete(id)`, the engine reverses the entity's contribution:
- `count` and `sum` are decremented
- `min`/`max` may become stale (marked in `staleMinMax` for lazy recompute)
@ -135,7 +135,7 @@ M2 is clamped to zero on remove to prevent floating-point drift from producing n
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.
The Cortex 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

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 Brainy({
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

@ -73,10 +73,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 +100,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()
@ -239,10 +239,15 @@ 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'
mode: 'reader', // or 'writer' or 'hybrid'
distributed: {
role: 'reader',
cacheStrategy: 'aggressive',
prefetch: true
}
})
```
@ -280,7 +285,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 +297,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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,550 @@
# 🏗️ Distributed Storage Architecture
> **Technical deep-dive**: How Brainy coordinates storage across multiple nodes and adapters
## Storage Adapter Layer
### Base Storage Interface
Every storage adapter implements this interface:
```typescript
interface StorageAdapter {
// Basic operations
get(key: string): Promise<any>
set(key: string, value: any): Promise<void>
delete(key: string): Promise<void>
// Batch operations
getBatch(keys: string[]): Promise<Map<string, any>>
setBatch(items: Map<string, any>): Promise<void>
// Atomic operations (for coordination)
compareAndSwap(key: string, oldVal: any, newVal: any): Promise<boolean>
increment(key: string, delta: number): Promise<number>
// Namespace support
withNamespace(namespace: string): StorageAdapter
}
```
### Storage Coordination Strategies
#### Strategy 1: Isolated Storage (Default)
Each node has completely separate storage:
```
Node-1 → Local FS: /data/node1/
└── shards/
├── shard-001/
├── shard-045/
└── shard-127/
Node-2 → Local FS: /data/node2/
└── shards/
├── shard-023/
├── shard-067/
└── shard-089/
```
**Coordination**: Via network messages only
- Shard ownership tracked in distributed consensus
- Data transfer via direct node-to-node communication
- No storage-level conflicts possible
#### Strategy 2: Shared Storage with Namespacing
Multiple nodes share storage but use namespaces:
```
S3 Bucket: brainy-cluster/
├── node-abc123/
│ ├── shards/
│ └── wal/
├── node-def456/
│ ├── shards/
│ └── wal/
└── _cluster/
├── topology.json
├── shard-map.json
└── elections/
```
**Coordination**: Via storage-level atomic operations
- Each node owns its namespace
- Cluster metadata in shared `_cluster/` namespace
- Atomic operations for leader election
- Conditional writes prevent conflicts
#### Strategy 3: Shared Storage with Fine-Grained Locking
Advanced mode for full shared storage:
```
S3 Bucket: brainy-shared/
├── shards/
│ ├── 001/
│ │ ├── data.bin
│ │ └── .lock (atomic)
│ ├── 002/
│ │ ├── data.bin
│ │ └── .lock
└── metadata/
├── index/
└── locks/
```
**Coordination**: Via distributed locking
- Shard-level locks using atomic operations
- Lock acquisition via compare-and-swap
- Automatic lock expiry (lease-based)
- Deadlock detection and recovery
## Storage Adapter Implementations
### 1. Filesystem Adapter
```typescript
class FilesystemAdapter implements StorageAdapter {
constructor(private basePath: string) {}
async get(key: string) {
const path = this.keyToPath(key)
return fs.readFile(path, 'json')
}
async compareAndSwap(key: string, oldVal: any, newVal: any) {
// Use file locking for atomicity
const lockfile = `${this.keyToPath(key)}.lock`
await flock(lockfile, 'ex') // Exclusive lock
try {
const current = await this.get(key)
if (deepEqual(current, oldVal)) {
await this.set(key, newVal)
return true
}
return false
} finally {
await funlock(lockfile)
}
}
withNamespace(ns: string) {
return new FilesystemAdapter(path.join(this.basePath, ns))
}
}
```
### 2. S3 Adapter
```typescript
class S3Adapter implements StorageAdapter {
constructor(
private bucket: string,
private prefix: string = ''
) {}
async get(key: string) {
const result = await s3.getObject({
Bucket: this.bucket,
Key: `${this.prefix}${key}`
})
return JSON.parse(result.Body)
}
async compareAndSwap(key: string, oldVal: any, newVal: any) {
// Use S3's conditional writes
const fullKey = `${this.prefix}${key}`
// Get current version
const head = await s3.headObject({
Bucket: this.bucket,
Key: fullKey
})
// Conditional put with ETag
try {
await s3.putObject({
Bucket: this.bucket,
Key: fullKey,
Body: JSON.stringify(newVal),
IfMatch: head.ETag // Only succeeds if unchanged
})
return true
} catch (err) {
if (err.code === 'PreconditionFailed') {
return false
}
throw err
}
}
withNamespace(ns: string) {
const newPrefix = `${this.prefix}${ns}/`
return new S3Adapter(this.bucket, newPrefix)
}
}
```
### 3. Cloudflare R2 Adapter
```typescript
class R2Adapter implements StorageAdapter {
// Similar to S3 but with R2-specific optimizations
async compareAndSwap(key: string, oldVal: any, newVal: any) {
// R2 supports conditional headers
const response = await fetch(`${this.endpoint}/${key}`, {
method: 'PUT',
body: JSON.stringify(newVal),
headers: {
'If-Match': await this.getETag(key)
}
})
return response.ok
}
// R2-specific: Use Workers for edge computing
async getWithCache(key: string) {
// Check Cloudflare edge cache first
const cached = await caches.default.match(key)
if (cached) return cached.json()
// Fallback to R2
const value = await this.get(key)
// Cache at edge
await caches.default.put(key, new Response(JSON.stringify(value)))
return value
}
}
```
## Distributed Coordination Patterns
### Pattern 1: Leader-Based Coordination
```typescript
class LeaderCoordinator {
async acquireShardOwnership(shardId: string) {
if (!this.isLeader()) {
// Only leader assigns shards
return this.requestFromLeader('acquireShard', shardId)
}
// Leader logic
const shardMap = await this.storage.get('_cluster/shard-map')
if (!shardMap[shardId].owner) {
shardMap[shardId].owner = this.nodeId
// Atomic update
const success = await this.storage.compareAndSwap(
'_cluster/shard-map',
shardMap,
{ ...shardMap, [shardId]: { owner: this.nodeId } }
)
if (success) {
this.broadcast('shardAssigned', { shardId, owner: this.nodeId })
}
}
}
}
```
### Pattern 2: Consensus-Based Coordination
```typescript
class ConsensusCoordinator {
async acquireShardOwnership(shardId: string) {
// Propose to all nodes
const proposal = {
type: 'ACQUIRE_SHARD',
shardId,
nodeId: this.nodeId,
term: this.currentTerm
}
// Raft consensus
const votes = await this.gatherVotes(proposal)
if (votes.length > this.nodes.length / 2) {
// Majority agreed
await this.commitProposal(proposal)
return true
}
return false
}
}
```
### Pattern 3: Storage-Native Coordination
```typescript
class StorageNativeCoordinator {
async acquireShardOwnership(shardId: string) {
// Use storage adapter's native coordination
const lockKey = `_locks/shard-${shardId}`
const lease = {
owner: this.nodeId,
expires: Date.now() + 30000 // 30 second lease
}
// Try to acquire lock atomically
const acquired = await this.storage.compareAndSwap(
lockKey,
null, // Must not exist
lease
)
if (acquired) {
// Start lease renewal
this.startLeaseRenewal(lockKey, lease)
return true
}
return false
}
private startLeaseRenewal(key: string, lease: any) {
setInterval(async () => {
const renewed = await this.storage.compareAndSwap(
key,
lease,
{ ...lease, expires: Date.now() + 30000 }
)
if (!renewed) {
// Lost lease
this.handleLeaseLoss(key)
}
}, 10000) // Renew every 10s
}
}
```
## Multi-Storage Patterns
### Hybrid Storage (Hot/Cold)
```typescript
class HybridStorageAdapter implements StorageAdapter {
constructor(
private hot: StorageAdapter, // Fast SSD
private cold: StorageAdapter // Cheap S3
) {}
async get(key: string) {
// Try hot storage first
const hotValue = await this.hot.get(key).catch(() => null)
if (hotValue) {
this.updateAccessTime(key)
return hotValue
}
// Fallback to cold storage
const coldValue = await this.cold.get(key)
// Promote to hot storage if frequently accessed
if (this.shouldPromote(key)) {
await this.hot.set(key, coldValue)
}
return coldValue
}
async set(key: string, value: any) {
// Write to hot storage
await this.hot.set(key, value)
// Async write to cold storage
setImmediate(() => {
this.cold.set(key, value).catch(console.error)
})
}
// Background process to demote cold data
async runTiering() {
const hotKeys = await this.hot.listKeys()
for (const key of hotKeys) {
const lastAccess = await this.getAccessTime(key)
if (Date.now() - lastAccess > 7 * 24 * 60 * 60 * 1000) {
// Not accessed in 7 days, demote to cold
await this.cold.set(key, await this.hot.get(key))
await this.hot.delete(key)
}
}
}
}
```
### Geo-Distributed Storage
```typescript
class GeoDistributedAdapter implements StorageAdapter {
constructor(
private regions: Map<string, StorageAdapter>
) {}
async get(key: string) {
// Determine closest region
const region = await this.getClosestRegion()
// Try local region first
const localValue = await this.regions.get(region)
.get(key)
.catch(() => null)
if (localValue) return localValue
// Fallback to other regions
for (const [name, adapter] of this.regions) {
if (name !== region) {
const value = await adapter.get(key).catch(() => null)
if (value) {
// Replicate to local region for next time
this.regions.get(region).set(key, value)
return value
}
}
}
throw new Error('Key not found in any region')
}
async set(key: string, value: any) {
// Write to local region immediately
const region = await this.getClosestRegion()
await this.regions.get(region).set(key, value)
// Async replication to other regions
for (const [name, adapter] of this.regions) {
if (name !== region) {
adapter.set(key, value).catch(console.error)
}
}
}
}
```
## Storage Optimization Strategies
### 1. Write Batching
```typescript
class BatchingAdapter implements StorageAdapter {
private writeBatch = new Map()
private batchTimer?: NodeJS.Timeout
async set(key: string, value: any) {
this.writeBatch.set(key, value)
if (!this.batchTimer) {
this.batchTimer = setTimeout(() => this.flush(), 100)
}
if (this.writeBatch.size >= 1000) {
await this.flush()
}
}
private async flush() {
if (this.writeBatch.size === 0) return
const batch = new Map(this.writeBatch)
this.writeBatch.clear()
await this.underlying.setBatch(batch)
if (this.batchTimer) {
clearTimeout(this.batchTimer)
this.batchTimer = undefined
}
}
}
```
### 2. Read Caching
```typescript
class CachingAdapter implements StorageAdapter {
private cache = new LRU({ max: 10000 })
async get(key: string) {
// Check cache
if (this.cache.has(key)) {
return this.cache.get(key)
}
// Read from storage
const value = await this.underlying.get(key)
// Cache for next time
this.cache.set(key, value)
return value
}
async set(key: string, value: any) {
// Invalidate cache
this.cache.delete(key)
// Write through
await this.underlying.set(key, value)
}
}
```
### 3. Compression
```typescript
class CompressingAdapter implements StorageAdapter {
async set(key: string, value: any) {
const json = JSON.stringify(value)
// Compress if beneficial
if (json.length > 1024) {
const compressed = await gzip(json)
await this.underlying.set(key, {
_compressed: true,
data: compressed.toString('base64')
})
} else {
await this.underlying.set(key, value)
}
}
async get(key: string) {
const stored = await this.underlying.get(key)
if (stored._compressed) {
const compressed = Buffer.from(stored.data, 'base64')
const json = await gunzip(compressed)
return JSON.parse(json)
}
return stored
}
}
```
## Summary
Brainy's storage layer is designed for:
1. **Flexibility**: Works with any storage backend
2. **Coordination**: Multiple strategies for different needs
3. **Performance**: Batching, caching, compression
4. **Scalability**: From single file to geo-distributed
5. **Simplicity**: Complexity hidden behind simple interface
The key insight: **Storage is just a plugin**. The intelligence is in the coordination layer above it!
---
*For user-facing documentation, see [SCALING.md](../SCALING.md)*

View file

@ -10,14 +10,14 @@ Brainy has **3 main indexes** at the top level, each with multiple sub-indexes m
| 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 |
| **TypeAwareHNSWIndex** | Type-aware vector similarity search | 42 type-specific HNSW 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 |
| **GraphAdjacencyIndex** | Relationship traversal | 4 LSM-trees + bidirectional adjacency maps | O(1) 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)
**TypeAwareHNSWIndex contains:**
- **42 type-specific HNSW indexes** - One per NounType (automatically rebuilt via parent)
**MetadataIndexManager contains:**
- **ChunkManager** - Adaptive chunked sparse indexing
@ -183,7 +183,7 @@ async getIdsForMultipleFields(pairs: [{field, value}, ...]): Promise<string[]> {
- 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):
**Benchmark Results** (1,000 queries on various dataset sizes):
| Dataset Size | Operation | Set Time | Roaring Time | Speedup | Memory Savings |
|--------------|-----------|----------|--------------|---------|----------------|
| 10,000 entities | 3-field intersection | 3.74ms | 1.14ms | **3.3x faster** | 90% |
@ -398,16 +398,14 @@ const DEFAULT_EXCLUDE_FIELDS = [
**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
## 2. HNSWIndex - 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 {
class HNSWIndex {
// Per-noun indexes for efficiency
private nouns: Map<string, HNSWNoun> = new Map()
@ -439,7 +437,7 @@ class HNSWNode {
### Hierarchical Graph Structure
The default vector index builds a multi-layered graph:
HNSW builds a multi-layered graph:
```
Layer 2: [entry] ←→ [node1] (sparse, long-range connections)
@ -578,6 +576,16 @@ const reachable = await this.graphIndex.traverse({
// Complexity: O(V + E) breadth-first search, but each neighbor lookup is O(1)
```
## Notes on Other Indexes
### DeletedItemsIndex (Not Currently Used)
**Status**: Utility class exists in `src/utils/deletedItemsIndex.ts` but is **not instantiated** in the Brainy class.
**Purpose** (if used): O(1) tracking of soft-deleted items without removing data.
**Current Behavior**: Brainy uses hard deletes via `storage.deleteNoun()` and `storage.deleteVerb()`. Soft-delete functionality is not currently integrated.
## Shared Memory Management: UnifiedCache
All three main indexes share a single **UnifiedCache** instance for coordinated memory management.
@ -595,7 +603,7 @@ class UnifiedCache {
// 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.hnswIndex = new HNSWIndex(storage, { unifiedCache })
this.graphIndex = new GraphAdjacencyIndex(storage, { unifiedCache })
```
@ -615,7 +623,7 @@ Each index uses different key prefixes:
// Metadata index
cache.set(`meta:${field}:${value}`, indexEntry)
// Vector index
// HNSW index
cache.set(`vector:${id}`, vectorData)
// Graph index
@ -637,7 +645,7 @@ async add(params: AddParams): Promise<string> {
// Add to metadata index (field filtering)
await this.metadataIndex.addToIndex(id, params.metadata)
// Add to vector index (vector search)
// Add to HNSW index (vector search)
await this.index.addEntity(id, vector, params.noun)
// Relationships added via separate relate() calls
@ -674,6 +682,9 @@ async find(query: FindQuery): Promise<Result[]> {
results = results.filter(r => connectedIds.includes(r.id))
}
// Step 4: Filter deleted items
results = results.filter(r => !this.deletedItemsIndex.isDeleted(r.id))
return results
}
```
@ -689,7 +700,7 @@ async update(params: UpdateParams): Promise<void> {
await this.metadataIndex.removeFromIndex(params.id, existing.metadata)
await this.metadataIndex.addToIndex(params.id, params.metadata)
// Update vector index (re-embed if content changed)
// Update HNSW index (re-embed if content changed)
if (params.content) {
const newVector = await this.embedder(params.content)
await this.index.updateEntity(params.id, newVector)
@ -715,7 +726,10 @@ async stats(): Promise<Statistics> {
relationships: this.graphIndex.getTotalRelationshipCount(),
relationshipTypes: this.graphIndex.getRelationshipCountsByType(),
// From vector index
// From deleted items index
deletedItems: this.deletedItemsIndex.getDeletedCount(),
// From HNSW index
vectorIndexSize: this.index.getSize()
}
}
@ -732,16 +746,16 @@ async stats(): Promise<Statistics> {
async init(): Promise<void> {
// When disableAutoRebuild: false (default)
const metadataStats = await this.metadataIndex.getStats()
const vectorIndexSize = this.index.size()
const hnswIndexSize = this.index.size()
const graphIndexSize = await this.graphIndex.size()
if (metadataStats.totalEntries === 0 ||
vectorIndexSize === 0 ||
hnswIndexSize === 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(),
hnswIndexSize === 0 ? this.index.rebuild() : Promise.resolve(),
graphIndexSize === 0 ? this.graphIndex.rebuild() : Promise.resolve()
])
}
@ -781,7 +795,7 @@ The **TripleIntelligenceSystem** (`src/triple/TripleIntelligenceSystem.ts`) comb
class TripleIntelligenceSystem {
constructor(
private metadataIndex: MetadataIndexManager,
private vectorIndex: VectorIndexProvider,
private hnswIndex: HNSWIndex,
private graphIndex: GraphAdjacencyIndex,
private embedder: EmbedderFunction,
private storage: BaseStorage
@ -794,7 +808,7 @@ class TripleIntelligenceSystem {
// 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.hnswIndex.search(parsed.vector, parsed.limit),
this.graphIndex.traverse(parsed.graphConstraints)
])
@ -808,7 +822,7 @@ class TripleIntelligenceSystem {
### Operation Complexity by Index
| Operation | MetadataIndexManager | TypeAwareVectorIndex | GraphAdjacencyIndex |
| Operation | MetadataIndexManager | TypeAwareHNSWIndex | GraphAdjacencyIndex |
|-----------|---------------------|-------------------|---------------------|
| **Add** | O(1) per field | O(log n) | O(1) |
| **Remove** | O(1) per field | O(log n) | O(1) |
@ -823,15 +837,15 @@ 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).
**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 HNSW).
### 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 |
| **TypeAwareHNSWIndex** | ~1.5 KB | Vector (384 dims × 4 bytes) + graph connections across 42 type-specific indexes |
| **GraphAdjacencyIndex** | ~50 bytes per relationship | Bidirectional references + metadata in 4 LSM-trees |
**Total overhead**: ~1.6 KB per entity + ~50 bytes per relationship
@ -842,20 +856,19 @@ Where:
### 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:
All indexes scale gracefully:
| 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) |
| Database Size | Metadata Filter | Vector Search | Graph Hop | Combined Query |
|---------------|----------------|---------------|-----------|----------------|
| **1K entities** | 0.3ms | 0.8ms | 0.05ms | 1.1ms |
| **10K entities** | 0.5ms | 1.2ms | 0.08ms | 1.5ms |
| **100K entities** | 0.8ms | 1.8ms | 0.1ms | 2.1ms |
| **1M entities** | 1.2ms | 2.5ms | 0.1ms | 2.8ms |
**Key observations**:
- Graph queries stay O(1) regardless of scale
- Metadata filtering scales sub-linearly
- Vector search degrades gracefully due to the hierarchical index
- Vector search degrades gracefully due to HNSW
- Combined queries remain fast even at scale
## Best Practices
@ -868,7 +881,7 @@ All indexes scale gracefully. The cost of each stage is governed by its algorith
- Field discovery (what filters are available)
- Type-based querying (find all characters, all items)
**Vector Index**:
**HNSWIndex**:
- Semantic similarity search ("find similar documents")
- Content-based retrieval ("find posts about AI")
- Fuzzy matching (when exact matches aren't required)
@ -893,9 +906,9 @@ All indexes scale gracefully. The cost of each stage is governed by its algorith
### Memory Management
1. **Configure UnifiedCache appropriately** - Balance between speed and memory
2. **Use lazy loading** - Vector index loads vectors on-demand
2. **Use lazy loading** - HNSW 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
4. **Consider storage adapter** - Memory storage = fastest, S3 = most scalable
## Related Documentation
@ -909,15 +922,15 @@ All indexes scale gracefully. The cost of each stage is governed by its algorith
### Level 1: Main Indexes (3)
All have rebuild() methods and are covered by lazy loading:
1. **TypeAwareVectorIndex** - `src/hnsw/typeAwareHNSWIndex.ts:403`
1. **TypeAwareHNSWIndex** - `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)
- **42 type-specific HNSW 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)
- **4 LSM-trees** (lsmTreeSource, lsmTreeTarget, lsmTreeVerbsBySource, lsmTreeVerbsByTarget)
- **In-memory graph structures** (sourceIndex, targetIndex, verbIndex)
### Lazy Loading

View file

@ -1,6 +1,6 @@
# Initialization and Rebuild Processes
This document explains how Brainy's four indexes (MetadataIndex, vector index, GraphAdjacencyIndex, DeletedItemsIndex) initialize and rebuild from persisted storage.
This document explains how Brainy's four indexes (MetadataIndex, HNSWIndex, GraphAdjacencyIndex, DeletedItemsIndex) initialize and rebuild from persisted storage.
## Core Principle: All Indexes Are Disk-Based
@ -11,7 +11,7 @@ This document explains how Brainy's four indexes (MetadataIndex, vector index, G
| 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 |
| **HNSWIndex** | Vector embeddings + HNSW 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 |
@ -32,7 +32,7 @@ The MetadataIndex now persists two components:
- 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.
All storage operations use the **StorageAdapter** interface, which works with FileSystem, OPFS, S3, GCS, R2, and Memory backends.
## Initialization Process
@ -84,12 +84,12 @@ async init(): Promise<void> {
// STEP 2: Check index sizes (lazy initialization triggers here)
const metadataStats = await this.metadataIndex.getStats()
const vectorIndexSize = this.index.size()
const hnswIndexSize = this.index.size()
const graphIndexSize = await this.graphIndex.size()
// STEP 3: Rebuild empty indexes from storage in parallel
if (metadataStats.totalEntries === 0 ||
vectorIndexSize === 0 ||
hnswIndexSize === 0 ||
graphIndexSize === 0) {
const rebuildStartTime = Date.now()
@ -97,7 +97,7 @@ async init(): Promise<void> {
metadataStats.totalEntries === 0
? this.metadataIndex.rebuild()
: Promise.resolve(),
vectorIndexSize === 0
hnswIndexSize === 0
? this.index.rebuild()
: Promise.resolve(),
graphIndexSize === 0
@ -207,13 +207,13 @@ private async ensureIndexesLoaded(): Promise<void> {
### 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)
1. **Load persisted data** from storage (HNSW 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)
### 1. HNSWIndex Rebuild (Correct Pattern)
```typescript
// src/hnsw/hnswIndex.ts (lines 809-947)
@ -236,7 +236,7 @@ public async rebuild(options: {
const availableCache = this.unifiedCache.getRemainingCapacity()
const shouldPreload = vectorMemory < availableCache * 0.3
// STEP 4: Load entities with persisted vector index connections
// STEP 4: Load entities with persisted HNSW connections
let hasMore = true
let cursor: string | undefined = undefined
@ -246,7 +246,7 @@ public async rebuild(options: {
})
for (const nounData of result.items) {
// Load vector graph data from storage (NOT recomputed!)
// Load HNSW graph data from storage (NOT recomputed!)
const hnswData = await this.storage.getHNSWData(nounData.id)
// Create noun with restored connections
@ -274,14 +274,14 @@ public async rebuild(options: {
```
**Key Points**:
- ✅ Loads vector index connections from storage via `getHNSWData()`
- ✅ Loads HNSW 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)
### 2. TypeAwareHNSWIndex Rebuild (Fixed in v3.45.0)
**Critical Architectural Fix**: The type-aware vector index previously had TWO major bugs:
**Critical Architectural Fix**: TypeAwareHNSWIndex 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
@ -300,7 +300,7 @@ public async rebuild(options?: {
index.clear()
}
// STEP 2: Determine preloading strategy (same as vector index)
// STEP 2: Determine preloading strategy (same as HNSWIndex)
const totalNouns = await this.storage.getNounCount()
const vectorMemory = totalNouns * 384 * 4
const availableCache = this.unifiedCache.getRemainingCapacity()
@ -319,7 +319,7 @@ public async rebuild(options?: {
})
for (const nounData of result.items) {
// CORRECT: Load persisted vector index data (not recomputed!)
// CORRECT: Load persisted HNSW data (not recomputed!)
const hnswData = await this.storage.getHNSWData(nounData.id)
const noun = {
@ -533,7 +533,7 @@ const unifiedCache = getGlobalCache() // Singleton, 100MB default
// MetadataIndex
this.unifiedCache = unifiedCache
// Vector index
// HNSWIndex
this.unifiedCache = unifiedCache
// GraphAdjacencyIndex
@ -549,7 +549,7 @@ this.unifiedCache = unifiedCache
### Rebuild Times (Typical Hardware)
| Dataset Size | Metadata | Vector | Graph | Total (Parallel) |
| Dataset Size | Metadata | HNSW | Graph | Total (Parallel) |
|--------------|----------|------|-------|------------------|
| 1K entities | 50ms | 100ms | 30ms | **150ms** |
| 10K entities | 200ms | 500ms | 150ms | **600ms** |
@ -563,7 +563,7 @@ this.unifiedCache = unifiedCache
| 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) |
| **HNSWIndex** | ~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 |
@ -573,9 +573,9 @@ this.unifiedCache = unifiedCache
### O(N) vs O(N log N) Comparison
**Before fix** (TypeAwareVectorIndex bug):
**Before fix** (TypeAwareHNSWIndex bug):
```typescript
// BAD: Recomputes vector index connections during rebuild
// BAD: Recomputes HNSW connections during rebuild
for (const noun of nouns) {
await index.addItem(noun) // O(log N) per item → O(N log N) total
}
@ -652,7 +652,7 @@ console.timeEnd('rebuild')
// For 10K entities:
// - Expected: 500-800ms (loading from storage)
// - Bug: 5-10 minutes (recomputing vector index connections)
// - Bug: 5-10 minutes (recomputing HNSW connections)
```
**Solution**: Ensure index is loading from storage, not calling `addItem()` during rebuild.
@ -706,8 +706,8 @@ console.log('Nouns in storage:', nouns.items.length)
## 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.45.0** (October 2025): Fixed TypeAwareHNSWIndex.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.35.0** (August 2025): HNSW connections first persisted to storage
- **v3.0.0** (September 2025): Initial 3-tier index architecture

View file

@ -22,7 +22,7 @@ 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
`FileSystemStorage` (e.g. Cortex's `MmapFileSystemStorage`) inherits the
real ones for free.
This works correctly today. The question is whether the methods *belong*
@ -32,7 +32,7 @@ on `BaseStorage`.
`BaseStorage` already mixes several concerns:
- entity / verb CRUD primitives
- generational record hooks (8.0 MVCC)
- COW (copy-on-write) lifecycle
- type-statistics tracking
- count persistence
- multi-process safety (new)
@ -83,11 +83,12 @@ Benefits:
## 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
`FileSystemStorage` is the only one in-tree, but Cortex'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.
- The current state works. Real failure modes
(BR-CX-INTERFACE-GAP) 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

File diff suppressed because it is too large Load diff

View file

@ -6,14 +6,14 @@ Brainy is a multi-dimensional AI database that combines vector similarity, graph
### Brainy (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
- **4-Index Architecture**: MetadataIndex, HNSWIndex, GraphAdjacencyIndex, DeletedItemsIndex (see [Index Architecture](./index-architecture.md))
- **Storage System**: Universal storage adapters (FileSystem, S3, OPFS, Memory)
- **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
@ -42,13 +42,12 @@ brainy-data/
└── 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 +59,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
@ -84,6 +83,7 @@ Brainy's extensible plugin architecture allows for powerful enhancements:
### Core Augmentations
- **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
@ -111,7 +111,7 @@ 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

View file

@ -1,46 +1,46 @@
# Storage Architecture
> **Updated**: Metadata/vector separation, UUID-based sharding, on-disk artifact for operator-layer backup
> **Updated**: Metadata/vector separation, UUID-based sharding, lifecycle management
## Storage Structure
### Architecture: Metadata/Vector Separation
Entities and relationships are split into **2 separate files** for optimal performance at billion-entity scale:
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
├── _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/
│ ├── nouns/
│ ├── vectors/ # HNSW graph data (sharded by UUID)
│ │ ├── 00/ # Shard 00 (first 2 hex digits)
│ │ │ ├── 00123456-....json # Vector + HNSW 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/
```
### Why Split Metadata and Vectors?
**Performance at scale:**
- **Vector search operations**: Only load vectors (4KB) during search, not metadata (2-10KB)
- **HNSW 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
@ -52,69 +52,115 @@ brainy-data/
const uuid = "3fa85f64-5717-4562-b3fc-2c963f66afa6"
const shard = uuid.substring(0, 2) // "3f"
// Vector path: entities/nouns/vectors/3f/3fa85f64-....json
// 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
- **Cloud storage optimization**: 200x faster than unsharded (30s → 150ms)
- **Parallel operations**: Load 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 and production features:
### FileSystem Storage (Node.js, default)
### FileSystem Storage (Node.js)
```typescript
const brain = new Brainy({
storage: {
type: 'filesystem',
path: './data'
}
storage: {
type: 'filesystem',
path: './data',
compression: true // Gzip compression (60-80% space savings)
}
})
```
- **Use case**: Server applications, CLI tools, single-node deployments
- **Performance**: Direct file I/O
- **Use case**: Server applications, CLI tools
- **Performance**: Direct file I/O with optional compression
- **Persistence**: Permanent on disk
- **Features**:
- **Batch Delete**: Efficient bulk deletion with retries
- **UUID Sharding**: Automatic 256-shard distribution
- **Gzip Compression**: 60-80% storage savings with minimal CPU overhead
- **Batch Delete**: Efficient bulk deletion with retries
- **UUID Sharding**: Automatic 256-shard distribution
### Memory Storage
### S3 Compatible Storage (AWS, MinIO, R2)
```typescript
const brain = new Brainy({
storage: {
type: 'memory'
}
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**: 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
- **Use case**: Distributed applications, cloud deployments
- **Performance**: Network dependent, with intelligent caching
- **Persistence**: Cloud storage durability (99.999999999%)
- **Features**:
- **Lifecycle Policies**: Automatic tier transitions (Standard → IA → Glacier → Deep Archive)
- **Intelligent-Tiering**: Automatic optimization based on access patterns (up to 95% savings)
- **Batch Delete**: Efficient bulk deletion (1000 objects per request)
- **Cost Impact**: $138k/year → $5.9k/year at 500TB (96% savings!)
### Auto
### Google Cloud Storage (GCS)
```typescript
const brain = new Brainy({
storage: {
type: 'auto',
path: './data'
}
storage: {
type: 'gcs',
bucketName: 'my-brainy-data',
keyFilename: './service-account.json' // Or use ADC
}
})
```
`'auto'` picks `'filesystem'` when running on Node.js with a writable `path`, and falls back to `'memory'` otherwise.
- **Use case**: Google Cloud deployments
- **Performance**: Global CDN with edge caching
- **Persistence**: 99.999999999% durability
- **Features**:
- **Lifecycle Policies**: Automatic tier transitions (Standard → Nearline → Coldline → Archive)
- **Autoclass**: Intelligent automatic tier optimization
- **Batch Delete**: Efficient bulk operations
- **Cost Impact**: $138k/year → $8.3k/year at 500TB (94% savings!)
## Backup and Off-Site Replication
### Azure Blob Storage
```typescript
const brain = new Brainy({
storage: {
type: 'azure',
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: 'brainy-data'
}
})
```
- **Use case**: Azure cloud deployments
- **Performance**: Global replication with CDN
- **Persistence**: LRS, ZRS, GRS, RA-GRS options
- **Features**:
- **Blob Tier Management**: Hot/Cool/Archive tiers (99% cost savings)
- **Lifecycle Policies**: Automatic tier transitions and deletions
- **Batch Delete**: BlobBatchClient for efficient bulk operations
- **Batch Tier Changes**: Move thousands of blobs efficiently
- **Archive Rehydration**: Smart rehydration with priority options
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.
### Origin Private File System (Browser)
```typescript
const brain = new Brainy({
storage: {
type: 'opfs'
}
})
```
- **Use case**: Browser applications, PWAs
- **Performance**: Near-native file system speed
- **Persistence**: Permanent in browser (with quota limits)
- **Features**:
- **Quota Monitoring**: Real-time quota tracking and warnings
- **Batch Delete**: Efficient bulk deletion
- **Storage Status**: Detailed usage/available reporting
## Metadata Indexing System
@ -124,12 +170,12 @@ Tracks all unique values for each field:
```json
// __metadata_field_index__field_category.json
{
"values": {
"technology": 45,
"science": 32,
"business": 28
},
"lastUpdated": 1699564234567
"values": {
"technology": 45,
"science": 32,
"business": 28
},
"lastUpdated": 1699564234567
}
```
@ -139,11 +185,11 @@ Maps field+value combinations to entity IDs:
```json
// __metadata_index__category_technology_chunk0.json
{
"field": "category",
"value": "technology",
"ids": ["uuid1", "uuid2", "uuid3", ...],
"chunk": 0,
"total": 45
"field": "category",
"value": "technology",
"ids": ["uuid1", "uuid2", "uuid3", ...],
"chunk": 0,
"total": 45
}
```
@ -161,14 +207,14 @@ High-performance deduplication system for streaming data:
```json
// __entity_registry__.json
{
"mappings": {
"did:plc:alice123": "550e8400-e29b-41d4-a716-446655440000",
"handle:alice.bsky.social": "550e8400-e29b-41d4-a716-446655440000"
},
"stats": {
"totalMappings": 10000,
"lastSync": 1699564234567
}
"mappings": {
"did:plc:alice123": "550e8400-e29b-41d4-a716-446655440000",
"handle:alice.bsky.social": "550e8400-e29b-41d4-a716-446655440000"
},
"stats": {
"totalMappings": 10000,
"lastSync": 1699564234567
}
}
```
@ -178,46 +224,216 @@ High-performance deduplication system for streaming data:
- **Cache**: LRU with configurable TTL
- **Sync**: Periodic or on-demand
## Durability
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:
```json
{
"timestamp": 1699564234567,
"operation": "add",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"content": "...",
"metadata": {}
},
"checksum": "sha256:..."
}
```
### Recovery Process
2. Replay operations from last checkpoint
3. Verify checksums for integrity
## Storage Optimization
### 1. Batch Operations
### 1. Lifecycle Policies (Cloud Storage)
**Automatic cost optimization through tier transitions:**
```typescript
// S3: Set lifecycle policy for automatic archival
await storage.setLifecyclePolicy({
rules: [{
id: 'archive-old-data',
prefix: 'entities/',
status: 'Enabled',
transitions: [
{ days: 30, storageClass: 'STANDARD_IA' }, // Move to IA after 30 days
{ days: 90, storageClass: 'GLACIER' }, // Archive after 90 days
{ days: 365, storageClass: 'DEEP_ARCHIVE' } // Deep archive after 1 year
]
}]
})
// GCS: Set lifecycle policy
await storage.setLifecyclePolicy({
rules: [{
condition: { age: 30 },
action: { type: 'SetStorageClass', storageClass: 'NEARLINE' }
}, {
condition: { age: 90 },
action: { type: 'SetStorageClass', storageClass: 'COLDLINE' }
}, {
condition: { age: 365 },
action: { type: 'SetStorageClass', storageClass: 'ARCHIVE' }
}]
})
// Azure: Set lifecycle policy
await storage.setLifecyclePolicy({
rules: [{
name: 'archiveOldData',
enabled: true,
type: 'Lifecycle',
definition: {
filters: { blobTypes: ['blockBlob'] },
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 90 }
}
}
}
}]
})
```
**Cost Impact (500TB dataset):**
| Storage | Before | After | Savings |
|---------|--------|-------|---------|
| **AWS S3** | $138,000/yr | $5,940/yr | **96%** |
| **GCS** | $138,000/yr | $8,300/yr | **94%** |
| **Azure** | $107,520/yr | $5,016/yr | **95%** |
### 2. Intelligent-Tiering (S3)
**Automatic optimization without retrieval fees:**
```typescript
// Enable S3 Intelligent-Tiering
await storage.enableIntelligentTiering('entities/', 'auto-optimize')
// Benefits:
// - Automatic tier transitions based on access patterns
// - No retrieval fees (unlike Glacier)
// - Up to 95% cost savings
// - No performance impact on frequently accessed data
```
### 3. Autoclass (GCS)
**Google Cloud's intelligent automatic optimization:**
```typescript
// Enable GCS Autoclass
await storage.enableAutoclass({
terminalStorageClass: 'ARCHIVE' // Optional: Set lowest tier
})
// Benefits:
// - Automatic optimization based on access patterns
// - No data retrieval delays
// - Transparent tier transitions
// - Up to 94% cost savings
```
### 4. Compression (FileSystem)
```typescript
// Enable gzip compression for local storage
const brain = new Brainy({
storage: {
type: 'filesystem',
path: './data',
compression: true // 60-80% space savings
}
})
// Performance impact:
// - Write: +10-20ms per file (gzip compression)
// - Read: +5-10ms per file (gzip decompression)
// - Space savings: 60-80% for typical JSON data
// - CPU overhead: Minimal (~5% CPU)
```
### 5. Batch Operations
```typescript
// Efficient batch delete
await storage.batchDelete([
'entities/nouns/vectors/00/00123456-....json',
'entities/nouns/metadata/00/00123456-....json'
// ...
'entities/nouns/vectors/00/00123456-....json',
'entities/nouns/metadata/00/00123456-....json',
// ... up to 1000 objects
])
// Benefits:
// - S3: 1000 objects per request (vs 1 per request)
// - GCS: 100 objects per request
// - Azure: 256 objects per batch
// - Automatic retry logic with exponential backoff
// - Throttling protection
// Batch writes for performance
await brain.addBatch([
{ content: "item1", metadata: {} },
{ content: "item2", metadata: {} },
{ content: "item3", metadata: {} }
{ content: "item1", metadata: {} },
{ content: "item2", metadata: {} },
{ content: "item3", metadata: {} }
])
// Single transaction, optimized I/O
```
### 2. Caching Strategy
### 6. Quota Monitoring (OPFS)
```typescript
// Configure caching
// Get quota status for browser storage
const status = await storage.getStorageStatus()
console.log(status)
// {
// type: 'opfs',
// available: true,
// details: {
// usage: 45829120, // 43.7 MB used
// quota: 536870912, // 512 MB available
// usagePercent: 8.5,
// quotaExceeded: false
// }
// }
// Proactive quota management:
// - Monitor usage before writes
// - Warn users when approaching quota
// - Automatically clean up old data
```
### 7. Tier Management (Azure)
```typescript
// Change blob tier for cost optimization
await storage.changeBlobTier(blobPath, 'Cool') // Hot → Cool (50% savings)
await storage.changeBlobTier(blobPath, 'Archive') // Cool → Archive (99% savings)
// Batch tier changes (efficient)
await storage.batchChangeTier([blob1, blob2, blob3], 'Cool')
// Rehydrate from Archive when needed
await storage.rehydrateBlob(blobPath, 'Standard') // Standard or High priority
```
### 8. Caching Strategy
```typescript
// Configure caching per storage type
const brain = new Brainy({
storage: {
type: 'filesystem',
path: './data',
cache: {
enabled: true,
maxSize: 1000, // Maximum cached items
ttl: 300000, // 5 minutes
strategy: 'lru' // Least recently used
}
}
storage: {
type: 'filesystem',
cache: {
enabled: true,
maxSize: 1000, // Maximum cached items
ttl: 300000, // 5 minutes
strategy: 'lru' // Least recently used
}
}
})
```
@ -227,8 +443,8 @@ const brain = new Brainy({
```typescript
// Automatic locking for write operations
await brain.storage.withLock('resource-id', async () => {
// Exclusive access to resource
await brain.storage.saveNoun(id, data)
// Exclusive access to resource
await brain.storage.saveNoun(id, data)
})
```
@ -239,39 +455,54 @@ 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 the whole brain to a portable PortableGraph document
const graph = await brain.data().then(d => d.export(undefined, { includeVectors: true }))
```
### 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 })
// Restore a PortableGraph document (dedup-by-id merge by default)
await brain.data().then(d => d.import(graph, { onConflict: 'merge' }))
```
### Move to a new directory
See the [Export & Import guide](../guides/export-and-import.md) for partial exports
(by id, collection, connected neighbourhood, VFS subtree, or predicate).
### 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 Brainy({ storage: { type: 'filesystem' } })
const newBrain = new Brainy({ storage: { type: 's3' } })
await oldBrain.init()
await newBrain.init()
// Transfer all data via a portable graph
const data = await oldBrain.data().then(d => d.export(undefined, { includeVectors: true }))
await newBrain.data().then(d => d.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
@ -279,34 +510,58 @@ await brain.restore('/backups/2026-06-11', { confirm: true })
const stats = await brain.storage.getStatistics()
console.log(stats)
// {
// totalSize: 1048576,
// entityCount: 1000,
// indexSize: 204800,
// walSize: 10240,
// cacheHitRate: 0.85
// totalSize: 1048576,
// entityCount: 1000,
// indexSize: 204800,
// walSize: 10240,
// cacheHitRate: 0.85
// }
```
## 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**: FileSystem with compression (local persistence, small storage footprint)
2. **Production Server**: FileSystem with compression or cloud storage with lifecycle policies
3. **Browser Apps**: OPFS with quota monitoring
4. **Distributed**: S3/GCS/Azure with Intelligent-Tiering/Autoclass
### 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`
1. **Read-heavy**: Enable aggressive caching + cloud CDN
2. **Write-heavy**: Batch operations + async writes
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
4. **Archival**: Cloud storage with lifecycle policies (96% cost savings!)
5. **Large-scale**: Metadata/vector separation + UUID sharding + lifecycle policies
### Cost Optimization
1. **Enable lifecycle policies** for cloud storage (automated cost reduction)
2. **Use Intelligent-Tiering (S3)** or Autoclass (GCS) for automatic optimization
3. **Enable compression** for FileSystem storage (60-80% space savings)
4. **Monitor quota** for OPFS (prevent quota exceeded errors)
5. **Use batch operations** for bulk deletions (efficient API usage)
6. **Consider tier management** for Azure (Hot/Cool/Archive tiers)
**Example Cost Savings (500TB dataset):**
- Without lifecycle policies: **$138,000/year**
- With lifecycle policies: **$5,940/year**
- **Savings: $132,060/year (96%)**
### Monitor and Maintain
1. Regular statistics collection
2. Watch disk usage and shard balance
2. Monitor lifecycle policy effectiveness
3. Index optimization
4. Cache tuning based on hit rates
5. Verify backup runs (test restore quarterly)
5. Track storage costs and tier distribution
6. Review quota usage (OPFS) and storage growth patterns
### Production Deployment Checklist
- ✅ Enable lifecycle policies on cloud storage
- ✅ Configure batch delete for cleanup operations
- ✅ Enable compression for FileSystem storage
- ✅ Set up quota monitoring for OPFS
- ✅ Configure appropriate tier transitions
- ✅ Enable Intelligent-Tiering (S3) or Autoclass (GCS)
- ✅ Monitor storage costs and optimize regularly
## API Reference
@ -314,5 +569,6 @@ 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
**Version**: 4.0.0
**Last Updated**: 2025-10-17
**Key Features**: Metadata/vector separation, UUID sharding, lifecycle management, tier optimization

View file

@ -23,37 +23,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
// 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
interface TripleQuery {
// Vector/Semantic search
like?: string | Vector | any
similar?: string | Vector | any
// 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 }
// 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
// Field/Attribute search
where?: Record<string, any>
// Advanced options
limit?: number
boost?: 'recent' | 'popular' | 'verified' | string
explain?: boolean
threshold?: number
}
```
@ -76,16 +68,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 +109,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 +126,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 +161,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",
// where: {
// like: "AI papers",
// where: {
// institution: "Stanford",
// published: { gte: "2024-01-01" }
// published: { $gte: "2024-01-01" }
// }
// }
```
@ -196,11 +188,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 +213,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,8 +283,8 @@ const results = await brain.find({
```typescript
// Find similar content with constraints
const results = await brain.find({
query: searchText,
where: {
like: query,
where: {
status: 'published',
language: 'en'
}
@ -298,10 +295,10 @@ const results = await brain.find({
```typescript
// Find items related to a specific item
const results = await brain.find({
connected: {
connected: {
to: itemId,
depth: 2,
via: VerbType.RelatedTo
type: 'similar'
},
limit: 20
})
@ -312,11 +309,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,11 +1,11 @@
---
title: Zero Configuration
slug: concepts/zero-config
public: false
public: true
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).
description: Brainy auto-detects storage, initializes embeddings, and builds indexes — no configuration required. Works in Node.js, Bun, OPFS, and cloud environments.
next:
- getting-started/installation
- guides/storage-adapters
@ -13,145 +13,770 @@ next:
# 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 { Brainy } from 'brainy'
// That's it. No config needed.
const brain = new Brainy()
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 Brainy() // 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.add(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 Brainy()
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
// Explicit configuration when needed
const brain = new Brainy({
storage: { type: 'filesystem', path: '/var/lib/brainy' },
vector: {
recall: 'accurate',
persistMode: 'immediate'
// Override auto-detection
storage: {
type: 'filesystem',
path: '/custom/path'
},
cache: { maxSize: 50000, ttl: 600_000 }
// Override auto-optimization
optimization: {
autoIndex: false,
autoCache: false,
autoBatch: false
},
// Override auto-scaling
scaling: {
maxMemory: 2 * GB,
maxConnections: 100,
maxBatchSize: 1000
}
})
await brain.init()
```
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,451 @@
# 🔌 Brainy Augmentations Complete Reference
> **All augmentations that power Brainy's extensibility - with locations, usage, and examples**
>
> **⚠️ Update**: Updated for metadata structure changes and billion-scale optimizations
## Quick Start
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({
// Augmentations auto-configure based on environment
storage: 'auto', // Storage augmentation
cache: true, // Cache augmentation
index: true // Index augmentation
})
await brain.init() // Augmentations initialize automatically
```
## Augmentation Architecture
### Key Improvements for Billion-Scale Performance
1. **Metadata/Vector Separation**: Augmentations now work with separated metadata and vectors
- Metadata stored separately from vector data
- 99.2% memory reduction for type tracking
- Two-file storage pattern for optimal I/O
2. **Type System Enforcement**: All metadata requires type fields
- `NounMetadata` requires `noun: NounType`
- `VerbMetadata` requires `verb: VerbType`
- Type inference system available as public API
3. **Storage Adapter Pattern**: Internal vs public method distinction
- `_methods`: Return pure structures (HNSWNoun, HNSWVerb)
- Public methods: Return WithMetadata types
- MetadataEnforcer Proxy ensures proper access
### What This Means for Augmentation Users
**✅ If you use built-in augmentations**: No changes needed! They're all updated.
**⚠️ If you created custom storage augmentations**: Update your storage adapter to:
- Wrap metadata with required `noun`/`verb` fields
- Follow the internal/public method pattern
- Use two-file storage approach
**⚠️ If you access relationship data**: Change `verb.type` to `verb.verb`
## 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
- **Billion-scale ready**: Optimized for datasets with billions of nouns and verbs
### 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 Brainy({ 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 Brainy({
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 Brainy({ storage: 'opfs' })
```
### S3StorageAugmentation
**Location**: `src/augmentations/storageAugmentations.ts`
**Manual**: Requires AWS credentials
**Purpose**: AWS S3-compatible cloud storage
```typescript
const brain = new Brainy({
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 Brainy({
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 Brainy({
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.getStats() // 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)
**Auto-enabled**: When `wal: true`
**Purpose**: Write-ahead logging for crash recovery
```typescript
const brain = new Brainy({ 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.add(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 Brainy({
// These auto-register augmentations:
storage: 'auto', // Storage augmentation
cache: true, // Cache augmentation
index: true, // Index augmentation
metrics: true // Metrics augmentation
})
```
### Manual Registration
```typescript
const brain = new Brainy()
// 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
**Brainy 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.getStats()
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,620 @@
# Augmentation Configuration System
**Version**: 2.0.0
**Status**: Production Ready
## Overview
The Brainy Augmentation Configuration System provides a VSCode-style extension architecture with multiple configuration sources, schema validation, and tool discovery. This system maintains Brainy's zero-config philosophy while enabling sophisticated enterprise configuration management.
## Table of Contents
- [Quick Start](#quick-start)
- [Configuration Sources](#configuration-sources)
- [Creating Configurable Augmentations](#creating-configurable-augmentations)
- [Configuration Discovery](#configuration-discovery)
- [Runtime Configuration](#runtime-configuration)
- [Environment Variables](#environment-variables)
- [Configuration Files](#configuration-files)
- [CLI Commands](#cli-commands)
- [Tool Integration](#tool-integration)
- [Migration Guide](#migration-guide)
## Quick Start
### Using an Augmentation with Configuration
```typescript
import { Brainy } from '@soulcraft/brainy'
// Zero-config (uses defaults)
const brain = new Brainy()
// With custom configuration
immediateWrites: true,
checkpointInterval: 300000 // 5 minutes
}))
```
### Configuring via Environment Variables
```bash
export BRAINY_AUG_CACHE_TTL=600000
```
### Configuring via Files
Create a `.brainyrc` file in your project root:
```json
{
"augmentations": {
"wal": {
"enabled": true,
"immediateWrites": true,
"maxSize": 20971520
},
"cache": {
"ttl": 600000,
"maxSize": 2000
}
}
}
```
## Configuration Sources
Configuration is resolved in the following priority order (highest to lowest):
1. **Runtime Updates** - Dynamic configuration changes via API
2. **Constructor Parameters** - Code-time configuration
3. **Environment Variables** - `BRAINY_AUG_<NAME>_<KEY>`
4. **Configuration Files** - `.brainyrc`, `brainy.config.json`
5. **Schema Defaults** - Default values from manifest
### Resolution Example
```typescript
// Schema default
{ maxSize: 10485760 }
// File configuration (.brainyrc)
{ maxSize: 20971520 }
// Environment variable
// Constructor parameter
// Final resolved value: 41943040 (constructor wins)
```
## Creating Configurable Augmentations
### Step 1: Extend ConfigurableAugmentation
```typescript
import { ConfigurableAugmentation, AugmentationManifest } from '@soulcraft/brainy'
export class MyAugmentation extends ConfigurableAugmentation {
name = 'my-augmentation'
timing = 'around' as const
metadata = 'none' as const
operations = ['search', 'add']
priority = 50
constructor(config?: MyConfig) {
super(config) // Handles configuration resolution
}
// Required: Provide manifest for discovery
getManifest(): AugmentationManifest {
return {
id: 'my-augmentation',
name: 'My Augmentation',
version: '1.0.0',
description: 'Does something amazing',
category: 'performance',
configSchema: {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: 'Enable this augmentation'
},
threshold: {
type: 'number',
default: 100,
minimum: 1,
maximum: 1000,
description: 'Processing threshold'
}
}
}
}
}
// Optional: Handle runtime configuration changes
protected async onConfigChange(newConfig: MyConfig, oldConfig: MyConfig): Promise<void> {
if (newConfig.threshold !== oldConfig.threshold) {
// React to threshold change
this.updateThreshold(newConfig.threshold)
}
}
async execute<T>(operation: string, params: any, next: () => Promise<T>): Promise<T> {
if (!this.config.enabled) {
return next()
}
// Your augmentation logic here
return next()
}
}
```
### Step 2: Define Configuration Interface
```typescript
interface MyConfig {
enabled?: boolean
threshold?: number
mode?: 'fast' | 'balanced' | 'thorough'
}
```
### Step 3: Add JSON Schema in Manifest
```typescript
configSchema: {
type: 'object',
properties: {
enabled: {
type: 'boolean',
default: true,
description: 'Enable augmentation'
},
threshold: {
type: 'number',
default: 100,
minimum: 1,
maximum: 1000,
description: 'Processing threshold'
},
mode: {
type: 'string',
default: 'balanced',
enum: ['fast', 'balanced', 'thorough'],
description: 'Processing mode'
}
},
required: [],
additionalProperties: false
}
```
## Configuration Discovery
The Discovery API allows tools to discover and configure augmentations dynamically:
```typescript
import { AugmentationDiscovery } from '@soulcraft/brainy'
const discovery = new AugmentationDiscovery(brain.augmentations)
// Discover all augmentations with manifests
const listings = await discovery.discover({
includeConfig: true,
includeSchema: true
})
// Get configuration schema
const schema = await discovery.getConfigSchema('wal')
// Validate configuration
const validation = await discovery.validateConfig('wal', {
enabled: true,
maxSize: 'invalid' // Will fail validation
})
// Update configuration at runtime
await discovery.updateConfig('wal', {
checkpointInterval: 120000
})
```
## Runtime Configuration
### Update Configuration Dynamically
```typescript
// Get augmentation
const wal = brain.augmentations.get('wal')
// Update configuration
await wal.updateConfig({
checkpointInterval: 300000
})
// Get current configuration
const config = wal.getConfig()
```
### React to Configuration Changes
```typescript
class MyAugmentation extends ConfigurableAugmentation {
protected async onConfigChange(newConfig: any, oldConfig: any): Promise<void> {
// Stop old processes
if (oldConfig.enabled && !newConfig.enabled) {
await this.stop()
}
// Start new processes
if (!oldConfig.enabled && newConfig.enabled) {
await this.start()
}
// Update settings
if (newConfig.interval !== oldConfig.interval) {
this.rescheduleTimer(newConfig.interval)
}
}
}
```
## Environment Variables
### Naming Convention
```bash
BRAINY_AUG_<AUGMENTATION_ID>_<CONFIG_KEY>=value
```
### Examples
```bash
# Cache augmentation
BRAINY_AUG_CACHE_ENABLED=true
BRAINY_AUG_CACHE_MAX_SIZE=2000
BRAINY_AUG_CACHE_TTL=600000
# Complex values (JSON)
BRAINY_AUG_MYAUG_FILTERS='["*.js","*.ts"]'
BRAINY_AUG_MYAUG_OPTIONS='{"deep":true,"follow":false}'
```
### Docker Example
```dockerfile
ENV BRAINY_AUG_CACHE_TTL=600000
```
## Configuration Files
### File Locations (Priority Order)
1. `.brainyrc` (current directory)
2. `.brainyrc.json` (current directory)
3. `brainy.config.json` (current directory)
4. `~/.brainy/config.json` (user home)
5. `~/.brainyrc` (user home)
### File Format
```json
{
"augmentations": {
"wal": {
"enabled": true,
"immediateWrites": true,
"maxSize": 20971520,
"checkpointInterval": 300000
},
"cache": {
"enabled": true,
"maxSize": 2000,
"ttl": 600000
},
"metrics": {
"enabled": false
}
}
}
```
### Per-Environment Configuration
```json
{
"augmentations": {
"wal": {
"development": {
"enabled": true,
"immediateWrites": true,
"maxSize": 5242880
},
"production": {
"enabled": true,
"immediateWrites": false,
"maxSize": 104857600,
"checkpointInterval": 60000
}
}
}
}
```
## CLI Commands
### List Augmentations with Configuration
```bash
# Show all augmentations with config status
brainy augment list --detailed
# Show configuration for specific augmentation
brainy augment config wal
# Set configuration value
brainy augment config wal --set immediateWrites=true
# Show environment variable names
brainy augment config wal --env
# Export configuration schema
brainy augment schema wal > wal-schema.json
# Validate configuration file
brainy augment validate --file config.json
```
### Interactive Configuration
```bash
# Interactive configuration wizard
brainy augment configure wal
? Operation mode?
Performance (immediate writes)
Durability (synchronous writes)
Custom
? Maximum log size? (10MB) 20MB
? Checkpoint interval? (1 minute) 5 minutes
Configuration saved to .brainyrc
```
## Tool Integration
### Brain-Cloud Explorer UI
```typescript
// Auto-generate configuration form from schema
const ConfigurationUI = ({ augmentationId }) => {
const [manifest, setManifest] = useState(null)
const [config, setConfig] = useState({})
useEffect(() => {
// Fetch manifest with schema
fetch(`/api/augmentations/${augmentationId}/manifest`)
.then(res => res.json())
.then(setManifest)
// Get current configuration
discovery.getConfig(augmentationId)
.then(setConfig)
}, [augmentationId])
const handleSave = async (newConfig) => {
// Validate configuration
const validation = await fetch(`/api/augmentations/${augmentationId}/validate`, {
method: 'POST',
body: JSON.stringify(newConfig)
}).then(res => res.json())
if (validation.valid) {
// Apply configuration
await discovery.updateConfig(augmentationId, newConfig)
}
}
// Render form based on schema
return <SchemaForm
schema={manifest?.configSchema}
values={config}
onSubmit={handleSave}
/>
}
```
### VS Code Extension
```json
// package.json contribution points
{
"contributes": {
"configuration": {
"title": "Brainy Augmentations",
"properties": {
"brainy.augmentations.wal.enabled": {
"type": "boolean",
"default": true,
},
"brainy.augmentations.wal.maxSize": {
"type": "number",
"default": 10485760,
}
}
}
}
}
```
## Migration Guide
### Migrating from BaseAugmentation
**Before:**
```typescript
export class MyAugmentation extends BaseAugmentation {
constructor(config: MyConfig = {}) {
super()
this.config = {
enabled: config.enabled ?? true,
threshold: config.threshold ?? 100
}
}
// No manifest
// No config discovery
// No runtime updates
}
```
**After:**
```typescript
export class MyAugmentation extends ConfigurableAugmentation {
constructor(config?: MyConfig) {
super(config) // Config resolution handled automatically
}
getManifest(): AugmentationManifest {
return {
id: 'my-augmentation',
name: 'My Augmentation',
version: '1.0.0',
description: 'Does something amazing',
category: 'performance',
configSchema: {
type: 'object',
properties: {
enabled: { type: 'boolean', default: true },
threshold: { type: 'number', default: 100 }
}
}
}
}
// Optional: Handle config changes
protected async onConfigChange(newConfig: MyConfig, oldConfig: MyConfig): Promise<void> {
// React to changes
}
}
```
### Backwards Compatibility
The system maintains full backwards compatibility:
1. **BaseAugmentation still works** - Existing augmentations continue to function
2. **Constructor config still works** - Existing configuration patterns preserved
3. **Zero-config still works** - Defaults are applied automatically
4. **Progressive enhancement** - Add features as needed
## Best Practices
### 1. Always Provide Defaults
```typescript
configSchema: {
properties: {
enabled: {
type: 'boolean',
default: true, // Always provide defaults
description: 'Enable this feature'
}
}
}
```
### 2. Use Descriptive Configuration Keys
```typescript
// Good
checkpointInterval: 60000
// Bad
ci: 60000
```
### 3. Validate Configuration
```typescript
protected async onConfigChange(newConfig: any, oldConfig: any): Promise<void> {
// Validate before applying
if (newConfig.maxSize < 1048576) {
throw new Error('maxSize must be at least 1MB')
}
// Apply changes
this.maxSize = newConfig.maxSize
}
```
### 4. Document Environment Variables
```typescript
/**
* Environment Variables:
* - BRAINY_AUG_MYAUG_ENABLED: Enable augmentation (boolean)
* - BRAINY_AUG_MYAUG_THRESHOLD: Processing threshold (number)
* - BRAINY_AUG_MYAUG_MODE: Processing mode (fast|balanced|thorough)
*/
```
### 5. Provide Configuration Examples
```typescript
configExamples: [
{
name: 'Production',
description: 'Optimized for production use',
config: {
enabled: true,
mode: 'thorough',
threshold: 500
}
},
{
name: 'Development',
description: 'Lightweight for development',
config: {
enabled: true,
mode: 'fast',
threshold: 10
}
}
]
```
## Troubleshooting
### Configuration Not Loading
1. Check file locations and names
2. Verify JSON syntax in config files
3. Check environment variable names (case-sensitive)
4. Use `brainy augment config <name> --debug` to see resolution
### Validation Errors
1. Check schema requirements
2. Verify data types match schema
3. Check minimum/maximum constraints
4. Use discovery API to validate before applying
### Runtime Updates Not Working
1. Ensure augmentation extends ConfigurableAugmentation
2. Implement onConfigChange if needed
3. Check for validation errors
4. Verify augmentation is initialized
## API Reference
See the [Discovery API Documentation](./discovery-api.md) for complete API details.
## Examples
See the [examples directory](../../examples/augmentation-config/) for complete working examples.

View file

@ -0,0 +1,527 @@
# 🛠️ Brainy Augmentation Developer Guide
> **How to create, test, and use augmentations in Brainy**
>
> **⚠️ Update**: This guide has been updated with breaking changes for metadata structure and type system improvements.
## Migration Guide
### What Changed?
1. **Metadata Structure**: All metadata now requires type fields (`noun` or `verb`)
2. **Property Rename**: `verb.type``verb.verb` for relationships
3. **Two-File Storage**: Vectors and metadata stored separately for performance
4. **Return Types**: Storage methods distinguish between internal (pure) and public (WithMetadata) returns
### Migration Checklist
- [ ] Update metadata creation to include required `noun` field
- [ ] Change `verb.type` to `verb.verb` in all relationship code
- [ ] Update storage adapter methods to follow internal/public pattern
- [ ] Ensure metadata access uses correct structure
### Quick Migration Example
```typescript
// ❌ v3.x
const verb = {
type: 'relatedTo',
sourceId: 'a',
targetId: 'b'
}
if (verb.type === 'relatedTo') { ... }
// ✅ Current
const verb = {
verb: 'relatedTo',
sourceId: 'a',
targetId: 'b'
}
const metadata: VerbMetadata = {
verb: 'relatedTo',
sourceId: 'a',
targetId: 'b'
}
if (verb.verb === 'relatedTo') { ... }
```
## 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 = ['add'] 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 === 'add') {
console.log('Noun added:', params.noun)
// Access metadata correctly
if (params.noun?.metadata) {
console.log('Noun type:', params.noun.metadata.noun) // Required field
}
// You can access the brain instance
const stats = await context?.brain.getStats()
console.log('Total nouns:', stats.totalNouns)
}
}
protected async onShutdown(): Promise<void> {
// Cleanup
console.log('MyFirstAugmentation shutting down')
}
}
```
## Using Your Augmentation
```typescript
import { Brainy } from '@soulcraft/brainy'
import { MyFirstAugmentation } from './my-first-augmentation'
const brain = new Brainy()
// Register before init()
brain.augmentations.register(new MyFirstAugmentation())
await brain.init()
// Now your augmentation runs automatically!
await brain.add('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.add('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 === 'add') {
// 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 = [
'add', // Adding data
'update', // Updating data
'delete', // Deleting data
'get', // Retrieving data
'search', // Searching
'find', // Triple Intelligence queries
'relate', // Adding relationships
'unrelate', // Removing relationships
'clear', // Clearing data
'all' // Hook ALL operations
] as const
```
### Example: Multi-Operation Hook
```typescript
class AuditAugmentation extends BaseAugmentation {
readonly operations = ['add', 'update', 'delete'] 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.getStats()
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 = ['add', 'update', 'delete'] 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
// Create instant COW snapshot
const snapshotName = `backup-${Date.now()}`
await brain.fork(snapshotName)
console.log(`Automatic snapshot created: ${snapshotName}`)
}
}
```
### 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 = ['add', 'get'] as const
readonly priority = 90 // Run early
async execute<T>(operation: string, params: any): Promise<any> {
if (operation === 'add') {
// Encrypt before storing
if (params.metadata?.sensitive) {
params.content = await this.encrypt(params.content)
params.encrypted = true
}
return params
}
if (operation === 'get' && 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 { Brainy } from '@soulcraft/brainy'
import { MyAugmentation } from './my-augmentation'
describe('MyAugmentation', () => {
it('should hook into addNoun', async () => {
const brain = new Brainy({ 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.add('test data')
// Verify it was called
expect(executeSpy).toHaveBeenCalledWith(
'add',
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": ["add"],
"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,667 @@
# Augmentation Examples - Import, Store, Export
This guide shows two complete workflows:
1. **Simple Handler** - Just import a new file type
2. **Full Augmentation** - Import + Store + Export (premium-ready)
---
## Workflow 1: Simple Handler (Import Only)
**Use case:** You want to import a new file type (e.g., CAD files) into Brainy's knowledge graph.
### Step 1: Create the Handler
```typescript
// src/handlers/CADHandler.ts
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import type { FormatHandlerOptions, ProcessedData } from '@soulcraft/brainy/augmentations/intelligentImport'
import { parseCAD } from 'cad-parser' // Your parsing library
export class CADHandler extends BaseFormatHandler {
readonly format = 'cad'
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, ['image/vnd.dwg', 'image/vnd.dxf'])
}
return false
}
async process(data: Buffer | string, options: FormatHandlerOptions): Promise<ProcessedData> {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Parse CAD file
const cad = await parseCAD(buffer)
// Extract entities for knowledge graph
const entities = []
// Document entity
entities.push({
type: 'CADDocument',
filename: options.filename,
units: cad.units,
bounds: cad.bounds,
version: cad.version
})
// Layer entities
for (const layer of cad.layers) {
entities.push({
type: 'CADLayer',
name: layer.name,
color: layer.color,
visible: layer.visible
})
}
// Object entities
for (const obj of cad.objects) {
entities.push({
type: 'CADObject',
objectType: obj.type,
layer: obj.layer,
geometry: obj.geometry
})
}
return {
format: 'cad',
data: entities,
metadata: {
rowCount: entities.length,
fields: ['type', 'name', 'geometry'],
processingTime: Date.now() - startTime,
layerCount: cad.layers.length,
objectCount: cad.objects.length
},
filename: options.filename
}
}
}
```
### Step 2: Register the Handler
```typescript
// src/index.ts
import { globalHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
import { CADHandler } from './handlers/CADHandler.js'
// Register handler globally
globalHandlerRegistry.registerHandler({
name: 'cad',
mimeTypes: ['image/vnd.dwg', 'image/vnd.dxf'],
extensions: ['.dwg', '.dxf', '.dwf'],
loader: async () => new CADHandler()
})
```
### Step 3: Use It
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Import CAD file - automatically routed to CADHandler
const result = await brain.import({
type: 'file',
data: cadFileBuffer,
filename: 'floor-plan.dwg'
})
console.log(`Imported ${result.entities.length} CAD entities`)
// Query the imported data
const layers = await brain.find({ type: 'CADLayer' })
const objects = await brain.find({ type: 'CADObject', layer: 'WALLS' })
```
**That's it!** Simple handlers just import data. Brainy handles storage automatically.
---
## Workflow 2: Full Augmentation (Import + Store + Export)
**Use case:** You want a complete solution that imports project files, stores them with special logic, and exports results (e.g., React project analyzer).
### Step 1: Create the Augmentation
```typescript
// @yourcompany/brainy-react-analyzer
import { BaseAugmentation, type AugmentationContext } from '@soulcraft/brainy'
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import type { ProcessedData } from '@soulcraft/brainy/augmentations/intelligentImport'
import { parse } from '@babel/parser'
import traverse from '@babel/traverse'
import * as t from '@babel/types'
/**
* React Project Analyzer Augmentation
*
* Features:
* - Import: Parse React components, extract props, hooks, imports
* - Store: Create relationships between components
* - Export: Generate component diagram, dependency graph
*/
export class ReactAnalyzerAugmentation extends BaseAugmentation {
readonly name = 'react-analyzer'
readonly timing = 'before' as const
readonly operations = ['import', 'export'] as any[]
readonly priority = 75
private handler: ReactComponentHandler
constructor(config = {}) {
super(config)
this.handler = new ReactComponentHandler()
}
async execute<T = any>(
operation: string,
params: any,
next: () => Promise<T>
): Promise<T> {
// IMPORT: Parse React files
if (operation === 'import' && this.isReactFile(params)) {
return this.handleImport(params, next)
}
// EXPORT: Generate diagrams/reports
if (operation === 'export' && params.format === 'react-diagram') {
return this.handleExport(params, next)
}
return next()
}
private isReactFile(params: any): boolean {
const filename = params.filename || ''
return (
(filename.endsWith('.tsx') || filename.endsWith('.jsx')) &&
params.data?.includes('React')
)
}
private async handleImport<T>(params: any, next: () => Promise<T>): Promise<T> {
// Parse React component
const processed = await this.handler.process(params.data, params.options)
// Enrich with relationships
params.data = processed.data
params.metadata = {
...params.metadata,
reactAnalysis: processed.metadata
}
// Continue to next augmentation/storage
const result = await next()
// Post-process: Create component relationships
await this.createComponentRelationships(processed, result)
return result
}
private async createComponentRelationships(
processed: ProcessedData,
result: any
): Promise<void> {
const brain = this.getBrain()
if (!brain) return
// Find the component entity that was created
const component = processed.data.find(d => d.type === 'ReactComponent')
if (!component) return
// Create relationships for imports
for (const imp of component.imports || []) {
// Find or create imported component
const imported = await brain.findOne({
type: 'ReactComponent',
name: imp.name
})
if (imported) {
// Create "Imports" relationship
await brain.createRelation({
source: result.entities[0].id,
verb: 'Imports',
target: imported.id,
metadata: {
importPath: imp.path,
importType: imp.type
}
})
}
}
// Create relationships for prop types
for (const prop of component.props || []) {
if (prop.typeRef) {
const typeEntity = await brain.findOne({
type: 'TypeDefinition',
name: prop.typeRef
})
if (typeEntity) {
await brain.createRelation({
source: result.entities[0].id,
verb: 'UsesPropType',
target: typeEntity.id
})
}
}
}
}
private async handleExport<T>(params: any, next: () => Promise<T>): Promise<T> {
const brain = this.getBrain()
if (!brain) return next()
// Query all React components
const components = await brain.find({ type: 'ReactComponent' })
// Build dependency graph
const graph = await this.buildDependencyGraph(components)
// Generate diagram
const diagram = this.generateMermaidDiagram(graph)
return {
format: 'react-diagram',
diagram,
components: components.length,
dependencies: graph.edges.length
} as T
}
private async buildDependencyGraph(components: any[]): Promise<any> {
const brain = this.getBrain()
const nodes = components.map(c => ({
id: c.id,
name: c.name,
props: c.props
}))
const edges = []
for (const component of components) {
const imports = await brain.getRelated(component.id, 'Imports')
for (const imp of imports) {
edges.push({
from: component.id,
to: imp.id,
type: 'imports'
})
}
}
return { nodes, edges }
}
private generateMermaidDiagram(graph: any): string {
let mermaid = 'graph TD\n'
for (const node of graph.nodes) {
mermaid += ` ${node.id}[${node.name}]\n`
}
for (const edge of graph.edges) {
mermaid += ` ${edge.from} --> ${edge.to}\n`
}
return mermaid
}
}
/**
* React Component Handler
*/
class ReactComponentHandler extends BaseFormatHandler {
readonly format = 'react'
canHandle(data: any): boolean {
if (typeof data === 'object' && 'filename' in data) {
return data.filename?.match(/\.(tsx|jsx)$/) !== null
}
return false
}
async process(data: Buffer | string, options: any): Promise<ProcessedData> {
const code = Buffer.isBuffer(data) ? data.toString('utf-8') : data
// Parse with Babel
const ast = parse(code, {
sourceType: 'module',
plugins: ['jsx', 'typescript']
})
// Extract component info
const components: any[] = []
const imports: any[] = []
const exports: any[] = []
traverse(ast, {
// Detect function components
FunctionDeclaration(path) {
if (this.isReactComponent(path.node)) {
components.push({
type: 'ReactComponent',
name: path.node.id?.name,
componentType: 'function',
props: this.extractProps(path),
hooks: this.extractHooks(path),
state: this.extractState(path)
})
}
},
// Detect class components
ClassDeclaration(path) {
if (this.isReactClassComponent(path.node)) {
components.push({
type: 'ReactComponent',
name: path.node.id.name,
componentType: 'class',
props: this.extractClassProps(path),
state: this.extractClassState(path),
lifecycle: this.extractLifecycleMethods(path)
})
}
},
// Extract imports
ImportDeclaration(path) {
imports.push({
type: 'Import',
from: path.node.source.value,
imports: path.node.specifiers.map(s => ({
name: s.local.name,
imported: t.isImportSpecifier(s) ? s.imported.name : null
}))
})
},
// Extract exports
ExportNamedDeclaration(path) {
exports.push({
type: 'Export',
name: path.node.declaration?.id?.name
})
}
})
// Enrich components with import info
for (const component of components) {
component.imports = imports
component.exports = exports.find(e => e.name === component.name)
}
return {
format: 'react',
data: components,
metadata: {
rowCount: components.length,
fields: ['type', 'name', 'props', 'hooks'],
processingTime: Date.now() - startTime,
componentCount: components.length,
importCount: imports.length,
exportCount: exports.length
},
filename: options.filename
}
}
private isReactComponent(node: any): boolean {
// Check if function returns JSX
return node.body?.body?.some(stmt =>
t.isReturnStatement(stmt) && this.isJSX(stmt.argument)
)
}
private isJSX(node: any): boolean {
return t.isJSXElement(node) || t.isJSXFragment(node)
}
private extractProps(path: any): any[] {
const params = path.node.params
if (params.length === 0) return []
const propsParam = params[0]
if (t.isObjectPattern(propsParam)) {
return propsParam.properties.map(p => ({
name: p.key.name,
type: p.typeAnnotation?.typeAnnotation?.type
}))
}
return []
}
private extractHooks(path: any): string[] {
const hooks: string[] = []
path.traverse({
CallExpression(hookPath) {
const callee = hookPath.node.callee
if (t.isIdentifier(callee) && callee.name.startsWith('use')) {
hooks.push(callee.name)
}
}
})
return hooks
}
private extractState(path: any): any[] {
const stateVars: any[] = []
path.traverse({
CallExpression(hookPath) {
if (
t.isIdentifier(hookPath.node.callee) &&
hookPath.node.callee.name === 'useState'
) {
const parent = hookPath.parent
if (t.isVariableDeclarator(parent) && t.isArrayPattern(parent.id)) {
const [stateVar] = parent.id.elements
if (t.isIdentifier(stateVar)) {
stateVars.push({
name: stateVar.name,
initialValue: hookPath.node.arguments[0]
})
}
}
}
}
})
return stateVars
}
}
```
### Step 2: Package as NPM Module
```json
// package.json
{
"name": "@yourcompany/brainy-react-analyzer",
"version": "1.0.0",
"description": "React project analyzer for Brainy",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
".": {
"import": "./dist/index.js",
"require": "./dist/index.cjs"
}
},
"keywords": ["brainy", "react", "analyzer", "augmentation"],
"peerDependencies": {
"@soulcraft/brainy": "^5.2.0"
},
"dependencies": {
"@babel/parser": "^7.23.0",
"@babel/traverse": "^7.23.0",
"@babel/types": "^7.23.0"
}
}
```
### Step 3: Use the Augmentation
```typescript
// Install
// npm install @yourcompany/brainy-react-analyzer
import { Brainy } from '@soulcraft/brainy'
import { ReactAnalyzerAugmentation } from '@yourcompany/brainy-react-analyzer'
const brain = new Brainy()
// Add augmentation
brain.addAugmentation(new ReactAnalyzerAugmentation())
await brain.init()
// Import React project
await brain.import({
type: 'directory',
path: '/path/to/react-project/src',
recursive: true
})
// Query components
const components = await brain.find({ type: 'ReactComponent' })
console.log(`Found ${components.length} React components`)
// Find component dependencies
const appComponent = await brain.findOne({ type: 'ReactComponent', name: 'App' })
const imports = await brain.getRelated(appComponent.id, 'Imports')
console.log(`App component imports:`, imports.map(c => c.name))
// Export the analyzed component graph as a portable PortableGraph document
const graph = await brain.data().then(d => d.export({ type: 'ReactComponent' }))
console.log(`Exported ${graph.stats.entityCount} components, ${graph.stats.relationCount} edges`)
```
### Step 4: Premium Licensing (Optional)
```typescript
// Add license checking
export class ReactAnalyzerAugmentation extends BaseAugmentation {
private licenseKey?: string
constructor(config: { licenseKey?: string } = {}) {
super(config)
this.licenseKey = config.licenseKey
}
async onInitialize(): Promise<void> {
if (!this.licenseKey) {
throw new Error('React Analyzer requires a license key. Get one at https://yourcompany.com/brainy-react')
}
// Verify license
const valid = await this.verifyLicense(this.licenseKey)
if (!valid) {
throw new Error('Invalid license key')
}
this.log('React Analyzer initialized successfully')
}
private async verifyLicense(key: string): Promise<boolean> {
// Check with your license server
const response = await fetch('https://api.yourcompany.com/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ key, product: 'brainy-react-analyzer' })
})
const data = await response.json()
return data.valid
}
}
// Usage with license
brain.addAugmentation(new ReactAnalyzerAugmentation({
licenseKey: 'YOUR-LICENSE-KEY'
}))
```
---
## Comparison: Handler vs Augmentation
| Feature | Simple Handler | Full Augmentation |
|---------|---------------|-------------------|
| **Import** | ✅ Yes (automatic) | ✅ Yes (with custom logic) |
| **Storage** | ✅ Automatic (Brainy core) | ✅ Custom logic + relationships |
| **Export** | ❌ No | ✅ Yes (custom formats) |
| **Relationships** | ❌ No | ✅ Yes (create custom relationships) |
| **Premium licensing** | ❌ Difficult | ✅ Easy (augmentation-level) |
| **Custom operations** | ❌ Import only | ✅ Any operation |
| **Complexity** | Low (50-100 lines) | Medium (200-500 lines) |
### When to use Handler:
- Just need to import a new file type
- Don't need custom export
- Don't need special relationships
- Simple use case
### When to use Augmentation:
- Need import + export workflow
- Need custom relationship logic
- Want premium licensing capability
- Complex business logic
- Multiple operations (import + export + query)
---
## More Examples
### Example: Python Project Analyzer
```typescript
class PythonAnalyzerAugmentation extends BaseAugmentation {
// Import Python files, extract classes/functions
// Create relationships between modules
// Export: Dependency diagram, call graph
}
```
### Example: Database Schema Sync
```typescript
class DatabaseSyncAugmentation extends BaseAugmentation {
// Import: Parse SQL schema
// Store: Tables, columns, relationships
// Export: Generate migration scripts
}
```
### Example: API Documentation Generator
```typescript
class APIDocAugmentation extends BaseAugmentation {
// Import: Parse TypeScript types
// Store: Endpoints, parameters, responses
// Export: OpenAPI spec, Markdown docs
}
```
---
## See Also
- [FORMAT_HANDLERS.md](./FORMAT_HANDLERS.md) - Creating format handlers
- [AUGMENTATIONS.md](./AUGMENTATIONS.md) - Augmentation system details
- [ImageHandler source](../../src/augmentations/intelligentImport/handlers/imageHandler.ts) - Handler example
- [IntelligentImportAugmentation source](../../src/augmentations/intelligentImport/IntelligentImportAugmentation.ts) - Augmentation example

View file

@ -0,0 +1,687 @@
# Creating Custom Format Handlers
**Version:** 5.2.0+
Format handlers enable you to import ANY file type into Brainy as structured knowledge graph data. This guide shows how to create custom format handlers for your specific file formats.
---
## Quick Start
```typescript
import { BaseFormatHandler, globalHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
import type { FormatHandlerOptions, ProcessedData } from '@soulcraft/brainy/augmentations/intelligentImport'
class MyFormatHandler extends BaseFormatHandler {
readonly format = 'myformat'
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
// Option 1: Check by MIME type
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, ['application/x-myformat'])
}
// Option 2: Check by magic bytes
if (Buffer.isBuffer(data)) {
return data[0] === 0x4D && data[1] === 0x59 // "MY" magic bytes
}
return false
}
async process(
data: Buffer | string,
options: FormatHandlerOptions
): Promise<ProcessedData> {
// Convert to Buffer
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Parse your format
const parsed = this.parseMyFormat(buffer)
// Return structured data
return {
format: 'myformat',
data: [
{
type: 'MyEntity',
name: parsed.name,
metadata: parsed.metadata
}
],
metadata: {
rowCount: 1,
fields: ['type', 'name', 'metadata'],
processingTime: Date.now() - startTime
}
}
}
private parseMyFormat(buffer: Buffer): any {
// Your parsing logic here
return { name: 'example', metadata: {} }
}
}
// Register globally
globalHandlerRegistry.registerHandler({
name: 'myformat',
mimeTypes: ['application/x-myformat'],
extensions: ['.myf', '.myfmt'],
loader: async () => new MyFormatHandler()
})
```
Now Brainy automatically handles your format:
```typescript
await brain.import({
type: 'file',
data: myFormatBuffer,
filename: 'data.myf'
})
// Automatically routes to MyFormatHandler!
```
---
## BaseFormatHandler
All format handlers should extend `BaseFormatHandler`, which provides:
### MIME Type Detection
```typescript
protected getMimeType(data: Buffer | string | { filename?: string }): string
```
Detects MIME type from filename or buffer. Uses Brainy's comprehensive MIME detection (2000+ types).
**Example:**
```typescript
const mimeType = this.getMimeType({ filename: 'data.dwg' })
// Returns: 'image/vnd.dwg'
```
### MIME Type Matching
```typescript
protected mimeTypeMatches(mimeType: string, patterns: string[]): boolean
```
Checks if MIME type matches patterns. Supports wildcards (`image/*`).
**Example:**
```typescript
if (this.mimeTypeMatches(mimeType, ['image/*', 'video/*'])) {
// Handle all images and videos
}
```
### Extension Detection
```typescript
protected detectExtension(data: string | Buffer | { filename?: string; ext?: string }): string | null
```
Extracts file extension for fallback detection.
---
## canHandle() Method
The `canHandle()` method determines if your handler can process the given data.
### Strategy 1: MIME Type Detection (Recommended)
```typescript
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, [
'application/x-myformat',
'application/myformat'
])
}
return false
}
```
**Pros:** Automatic, comprehensive, works with 2000+ types
**Cons:** Requires filename
### Strategy 2: Magic Byte Detection
```typescript
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
if (Buffer.isBuffer(data)) {
// Check magic bytes
return (
data[0] === 0x50 && // 'P'
data[1] === 0x4B && // 'K'
data[2] === 0x03 &&
data[3] === 0x04 // ZIP signature
)
}
return false
}
```
**Pros:** Works without filename, robust
**Cons:** Requires knowledge of format structure
### Strategy 3: Combined Approach
```typescript
canHandle(data: Buffer | string | { filename?: string; ext?: string }): boolean {
// Try MIME type first
if (typeof data === 'object' && 'filename' in data) {
const mimeType = this.getMimeType(data)
if (this.mimeTypeMatches(mimeType, ['application/x-myformat'])) {
return true
}
}
// Fallback to magic bytes
if (Buffer.isBuffer(data)) {
return this.checkMagicBytes(data)
}
return false
}
```
**Pros:** Robust, works in all scenarios
**Cons:** More complex
---
## process() Method
The `process()` method extracts structured data from the file.
### Return Format
```typescript
interface ProcessedData {
/** Format identifier */
format: string
/** Array of extracted entities */
data: Array<Record<string, any>>
/** Metadata about processing */
metadata: {
rowCount: number
fields: string[]
processingTime: number
[key: string]: any
}
/** Original filename (optional) */
filename?: string
}
```
### Example: CAD File Handler
```typescript
async process(data: Buffer | string, options: FormatHandlerOptions): Promise<ProcessedData> {
const startTime = Date.now()
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Parse CAD file
const cad = await this.parseCAD(buffer)
// Extract entities
const entities = []
// Main CAD document
entities.push({
type: 'CADDocument',
filename: options.filename,
units: cad.units,
bounds: cad.bounds
})
// Layers
for (const layer of cad.layers) {
entities.push({
type: 'CADLayer',
name: layer.name,
color: layer.color,
visible: layer.visible,
objectCount: layer.objects.length
})
}
// Objects
for (const obj of cad.objects) {
entities.push({
type: 'CADObject',
objectType: obj.type,
layer: obj.layer,
geometry: obj.geometry,
properties: obj.properties
})
}
return {
format: 'cad',
data: entities,
metadata: {
rowCount: entities.length,
fields: ['type', 'name', 'geometry', 'properties'],
processingTime: Date.now() - startTime,
layerCount: cad.layers.length,
objectCount: cad.objects.length,
units: cad.units
},
filename: options.filename
}
}
```
### Best Practices
1. **Always track processing time:**
```typescript
const startTime = Date.now()
// ... processing ...
metadata.processingTime = Date.now() - startTime
```
2. **Include rich metadata:**
```typescript
metadata: {
rowCount: entities.length,
fields: ['type', 'name', ...],
processingTime: 123,
// Format-specific metadata
layerCount: 5,
objectCount: 150,
version: '2.0'
}
```
3. **Handle errors gracefully:**
```typescript
try {
const parsed = this.parse(buffer)
return { format: 'myformat', data: parsed, ... }
} catch (error) {
throw new Error(
`Failed to parse myformat: ${error instanceof Error ? error.message : String(error)}`
)
}
```
4. **Support progress reporting (optional):**
```typescript
if (options.progressHooks?.onCurrentItem) {
options.progressHooks.onCurrentItem(`Processing layer ${i}/${total}`)
}
```
---
## FormatHandlerRegistry
### Global Registry
Use the global registry for application-wide handlers:
```typescript
import { globalHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
globalHandlerRegistry.registerHandler({
name: 'myformat',
mimeTypes: ['application/x-myformat'],
extensions: ['.myf'],
loader: async () => new MyFormatHandler()
})
```
### Local Registry
Create a local registry for scoped handlers:
```typescript
import { FormatHandlerRegistry } from '@soulcraft/brainy/augmentations/intelligentImport'
const registry = new FormatHandlerRegistry()
registry.registerHandler({ ... })
```
### Lazy Loading
Handlers are lazy-loaded for performance:
```typescript
globalHandlerRegistry.registerHandler({
name: 'heavy',
mimeTypes: ['application/x-heavy'],
extensions: ['.heavy'],
loader: async () => {
// Only loaded when first needed
const { HeavyHandler } = await import('./HeavyHandler.js')
return new HeavyHandler()
}
})
```
### Getting Handlers
```typescript
// By filename (automatic MIME detection)
const handler = await registry.getHandler('data.myf')
// By MIME type
const handler = await registry.getHandlerByMimeType('application/x-myformat')
// By extension
const handler = await registry.getHandlerByExtension('.myf')
// By name
const handler = await registry.getHandlerByName('myformat')
```
---
## Real-World Examples
### Example 1: Video Metadata Extractor
```typescript
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import ffmpeg from 'fluent-ffmpeg'
class VideoHandler extends BaseFormatHandler {
readonly format = 'video'
canHandle(data) {
const mimeType = this.getMimeType(data)
return this.mimeTypeMatches(mimeType, ['video/*'])
}
async process(data, options) {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
// Extract video metadata with ffmpeg
const metadata = await this.extractVideoMetadata(buffer)
return {
format: 'video',
data: [{
type: 'Video',
duration: metadata.duration,
codec: metadata.codec,
resolution: metadata.resolution,
frameRate: metadata.frameRate,
bitrate: metadata.bitrate,
audioTracks: metadata.audioTracks,
subtitles: metadata.subtitles
}],
metadata: {
rowCount: 1,
fields: ['type', 'duration', 'codec', 'resolution'],
processingTime: metadata.processingTime
}
}
}
private async extractVideoMetadata(buffer: Buffer) {
// Use ffmpeg to extract metadata
return new Promise((resolve, reject) => {
ffmpeg(buffer)
.ffprobe((err, data) => {
if (err) reject(err)
else resolve(this.parseFFmpegOutput(data))
})
})
}
}
```
### Example 2: Git Repository Parser
```typescript
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import { simpleGit } from 'simple-git'
class GitRepoHandler extends BaseFormatHandler {
readonly format = 'git-repo'
canHandle(data) {
// Check for .git directory
if (typeof data === 'object' && 'filename' in data) {
return data.filename?.includes('.git') || false
}
return false
}
async process(data, options) {
const repoPath = options.filename || ''
const git = simpleGit(repoPath)
// Extract commits
const log = await git.log()
const commits = log.all.map(commit => ({
type: 'GitCommit',
hash: commit.hash,
message: commit.message,
author: commit.author_name,
date: commit.date
}))
// Extract branches
const branchSummary = await git.branchLocal()
const branches = Object.keys(branchSummary.branches).map(name => ({
type: 'GitBranch',
name,
current: branchSummary.current === name
}))
return {
format: 'git-repo',
data: [...commits, ...branches],
metadata: {
rowCount: commits.length + branches.length,
fields: ['type', 'hash', 'message', 'author'],
processingTime: Date.now() - startTime,
commitCount: commits.length,
branchCount: branches.length
}
}
}
}
```
### Example 3: Database Schema Importer
```typescript
import { BaseFormatHandler } from '@soulcraft/brainy/augmentations/intelligentImport'
import { Client } from 'pg'
class PostgreSQLSchemaHandler extends BaseFormatHandler {
readonly format = 'postgres-schema'
canHandle(data) {
if (typeof data === 'object' && 'filename' in data) {
return data.filename?.endsWith('.sql') || false
}
return false
}
async process(data, options) {
const buffer = Buffer.isBuffer(data) ? data : Buffer.from(data)
const sql = buffer.toString('utf-8')
// Parse SQL or connect to database
const schema = await this.parseSchema(sql)
const entities = []
// Tables
for (const table of schema.tables) {
entities.push({
type: 'Table',
name: table.name,
schema: table.schema,
columnCount: table.columns.length
})
// Columns
for (const column of table.columns) {
entities.push({
type: 'Column',
name: column.name,
table: table.name,
dataType: column.dataType,
nullable: column.nullable,
primaryKey: column.primaryKey
})
}
}
// Foreign keys
for (const fk of schema.foreignKeys) {
entities.push({
type: 'ForeignKey',
from: `${fk.fromTable}.${fk.fromColumn}`,
to: `${fk.toTable}.${fk.toColumn}`
})
}
return {
format: 'postgres-schema',
data: entities,
metadata: {
rowCount: entities.length,
fields: ['type', 'name', 'table', 'dataType'],
processingTime: Date.now() - startTime,
tableCount: schema.tables.length,
columnCount: schema.tables.reduce((sum, t) => sum + t.columns.length, 0),
foreignKeyCount: schema.foreignKeys.length
}
}
}
}
```
---
## Creating Premium Augmentations
Package your handler as a premium augmentation:
```typescript
// @yourcompany/brainy-cad-importer
import { BaseAugmentation } from '@soulcraft/brainy'
import { CADHandler } from './CADHandler.js'
export class CADImportAugmentation extends BaseAugmentation {
readonly name = 'cad-import'
readonly timing = 'before'
readonly operations = ['import', 'importFile']
private handler: CADHandler
constructor(config = {}) {
super(config)
this.handler = new CADHandler()
}
async execute(operation, params, next) {
// Check if this is a CAD file
if (this.isCADFile(params)) {
const processed = await this.handler.process(params.data, params.options)
params.data = processed.data
params.metadata = { ...params.metadata, ...processed.metadata }
}
return next()
}
private isCADFile(params: any): boolean {
return this.handler.canHandle(params.data || params)
}
}
// Usage:
// npm install @yourcompany/brainy-cad-importer
// brain.addAugmentation(new CADImportAugmentation())
```
---
## Testing
```typescript
import { describe, it, expect } from 'vitest'
import { MyFormatHandler } from './MyFormatHandler.js'
describe('MyFormatHandler', () => {
let handler: MyFormatHandler
beforeEach(() => {
handler = new MyFormatHandler()
})
describe('canHandle', () => {
it('should handle .myf files', () => {
expect(handler.canHandle({ filename: 'data.myf' })).toBe(true)
})
it('should handle by MIME type', () => {
expect(handler.canHandle({ filename: 'data.myformat' })).toBe(true)
})
it('should reject non-myformat files', () => {
expect(handler.canHandle({ filename: 'data.txt' })).toBe(false)
})
})
describe('process', () => {
it('should extract structured data', async () => {
const testData = Buffer.from('MY format data')
const result = await handler.process(testData)
expect(result.format).toBe('myformat')
expect(result.data).toHaveLength(1)
expect(result.metadata.processingTime).toBeGreaterThan(0)
})
it('should handle errors gracefully', async () => {
const invalidData = Buffer.from('invalid')
await expect(handler.process(invalidData)).rejects.toThrow()
})
})
})
```
---
## Best Practices
1. **Always extend BaseFormatHandler** - provides MIME detection and utilities
2. **Use MIME types for routing** - automatic, comprehensive, maintainable
3. **Lazy load heavy dependencies** - better performance
4. **Extract rich metadata** - make data queryable in knowledge graph
5. **Handle errors gracefully** - fail fast with clear error messages
6. **Test thoroughly** - test canHandle() and process() with real data
7. **Document your format** - explain what data is extracted and how
8. **Follow ProcessedData format** - ensures compatibility with Brainy
---
## See Also
- [ImageHandler source](../../src/augmentations/intelligentImport/handlers/imageHandler.ts) - Reference implementation
- [BaseFormatHandler source](../../src/augmentations/intelligentImport/handlers/base.ts) - Base class
- [FormatHandlerRegistry source](../../src/augmentations/intelligentImport/FormatHandlerRegistry.ts) - Registry implementation
- [Augmentations Guide](./AUGMENTATIONS.md) - Creating augmentations

View file

@ -0,0 +1,204 @@
# 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 |
|-------------|-------------|--------|--------|
| **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 Brainy()
// Just register augmentations - they work automatically!
brain.augmentations.register(new EntityRegistryAugmentation())
brain.augmentations.register(new APIServerAugmentation())
await brain.init()
```
### With Configuration
```typescript
const brain = new Brainy()
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,429 @@
# 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.
**Bun** (recommended): No additional dependencies needed - use `Bun.serve()` directly.
**Node.js**: Install optional Express dependencies:
```bash
npm install express cors ws
```
## Zero-Config Usage
```typescript
import { Brainy } from 'brainy'
import { APIServerAugmentation } from 'brainy/augmentations'
const brain = new Brainy()
// 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
### Bun ✅ (Recommended)
Native performance with Bun.serve() - no Express required. Works with `bun --compile` for single-binary deployment.
```typescript
// Simple Bun server with Brainy
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
Bun.serve({
port: 3000,
async fetch(req) {
const url = new URL(req.url)
if (url.pathname === '/api/search') {
const { query } = await req.json()
const results = await brain.search(query)
return Response.json(results)
}
return new Response('Not Found', { status: 404 })
}
})
```
### 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 Brainy()
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 Brainy()
// Stack augmentations for complete system
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

@ -55,20 +55,20 @@ process opening the same directory:
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?
## What about Cortex?
Brainy + Cor compose cleanly under this model:
Brainy + Cortex compose cleanly under this model:
- Cor stores its column-index segments inside the same `rootDir` (under
- Cortex stores its column-index segments inside the same `rootDir` (under
`indexes/_column_index/{field}/`).
- Segments (`*.cidx` files) are **immutable** once written. Cor mmaps them
- Segments (`*.cidx` files) are **immutable** once written. Cortex 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
A reader process can safely mmap Cortex segments alongside a live writer
without coordination. The single Brainy writer lock at
`<rootDir>/locks/_writer.lock` covers Cor too, because Cor segment
`<rootDir>/locks/_writer.lock` covers Cortex too, because Cortex segment
writes happen on the writer's side.
## Stale-lock detection
@ -105,7 +105,7 @@ coexists with whatever the writer is doing:
```typescript
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', path: '/data/brain' }
storage: { type: 'filesystem', rootDirectory: '/data/brain' }
})
const stats = await reader.stats()
@ -119,7 +119,7 @@ you need fresher state, ask the writer to flush before opening:
```typescript
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', path: '/data/brain' }
storage: { type: 'filesystem', rootDirectory: '/data/brain' }
})
const acked = await reader.requestFlush({ timeoutMs: 5000 })
@ -135,13 +135,12 @@ The CLI `brainy inspect` subcommands all do this for you by default
## 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
- **Cloud storage backends** (S3, GCS, R2, Azure) do not currently enforce
multi-process locking. Two processes pointing at the same bucket 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. Lock semantics for cloud backends will land in a future release.
- **Long-running readers** do not automatically pick up new Cortex 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.
@ -150,4 +149,4 @@ The CLI `brainy inspect` subcommands all do this for you by default
- `Brainy.openReadOnly()` — [API reference](../api/brainy.md)
- `brainy inspect` — [inspection guide](../guides/inspection.md)
- Cor columnar storage — see `node_modules/@soulcraft/cor/README.md`
- Cortex columnar storage — see `node_modules/@soulcraft/cortex/README.md`

View file

@ -27,13 +27,13 @@ override, and the install-time failure modes that the defensive
```
BaseStorageAdapter (counts, batch ops, multi-tenancy hooks)
BaseStorage (type-statistics, lifecycle helpers, generation
hooks, default no-op multi-process methods)
BaseStorage (COW, type-statistics, lifecycle helpers,
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.)
<your plugin's storage> (Cortex's MmapFileSystemStorage, etc.)
```
When you `extend FileSystemStorage`, your adapter inherits every method on
@ -75,7 +75,7 @@ That's the full ceremony for inheriting multi-process safety.
## When NOT to extend FileSystemStorage
If your storage is **not filesystem-backed** (a custom
If your storage is **not filesystem-backed** (S3, GCS, R2, Azure, a custom
network backend), extend `BaseStorage` directly:
```typescript

View file

@ -0,0 +1,833 @@
# Brainy Cloud Deployment Guide
This guide provides production-ready deployment configurations for Brainy using S3CompatibleStorage (preferred) or FileSystemStorage across major cloud platforms. All examples are verified against the actual Brainy codebase.
## 🆕 Production Features
**Cost Optimization at Scale:**
- **Lifecycle Policies**: Automatic tier transitions for 96% cost savings
- **Intelligent-Tiering**: S3 Intelligent-Tiering and GCS Autoclass support
- **Batch Operations**: Efficient bulk delete (1000 objects per request)
- **Compression**: Gzip compression for 60-80% storage savings
**Example Impact (500TB dataset):**
- Before: $138,000/year
- With lifecycle policies: $5,940/year
- **Savings: $132,060/year (96%)**
See the [Cost Optimization](#cost-optimization-v40) section below for implementation details.
## Overview
The API Server augmentation provides a universal handler that works with standard Request/Response objects, making Brainy deployable on any JavaScript runtime.
## Storage Adapter
**S3CompatibleStorage** is the preferred storage adapter for cloud deployments. It works with:
- Amazon S3
- Cloudflare R2
- Google Cloud Storage
- Azure Blob Storage
- Any S3-compatible service
## Deployment Examples
### AWS Lambda
```javascript
// handler.js
const { Brainy } = require('@soulcraft/brainy')
const { S3CompatibleStorage } = require('@soulcraft/brainy/storage')
let brain
let handler
exports.handler = async (event) => {
if (!brain) {
const storage = new S3CompatibleStorage({
endpoint: 's3.amazonaws.com',
region: process.env.AWS_REGION,
bucket: process.env.BRAINY_BUCKET,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
prefix: 'brainy-data/'
})
brain = new Brainy({
storage,
augmentations: [{
name: 'api-server',
config: {
enabled: true,
auth: {
required: true,
apiKeys: [process.env.API_KEY]
}
}
}]
})
await brain.init()
// Get the universal handler from the augmentation
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
handler = apiAugmentation.createUniversalHandler()
}
// Convert Lambda event to Request
const url = `https://${event.requestContext.domainName}${event.rawPath}`
const request = new Request(url, {
method: event.requestContext.http.method,
headers: event.headers,
body: event.body
})
// Use the universal handler
const response = await handler(request)
return {
statusCode: response.status,
headers: Object.fromEntries(response.headers),
body: await response.text()
}
}
```
### Google Cloud Functions
```javascript
// index.js
const { Brainy } = require('@soulcraft/brainy')
const { S3CompatibleStorage } = require('@soulcraft/brainy/storage')
let brain
let handler
exports.brainyAPI = async (req, res) => {
if (!brain) {
const storage = new S3CompatibleStorage({
endpoint: 'storage.googleapis.com',
bucket: process.env.GCS_BUCKET,
accessKeyId: process.env.GCS_ACCESS_KEY,
secretAccessKey: process.env.GCS_SECRET_KEY,
prefix: 'brainy/',
forcePathStyle: false,
region: 'US'
})
brain = new Brainy({ storage })
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
handler = apiAugmentation.createUniversalHandler()
}
// Convert Express req/res to Request/Response
const request = new Request(`https://${req.hostname}${req.originalUrl}`, {
method: req.method,
headers: req.headers,
body: JSON.stringify(req.body)
})
const response = await handler(request)
res.status(response.status)
res.set(Object.fromEntries(response.headers))
res.send(await response.text())
}
```
### Google Cloud Run with Cloud Storage
Google Cloud Run is ideal for containerized deployments with automatic scaling. This example uses Google Cloud Storage via the S3-compatible API.
```javascript
// server.js
import { Brainy } from '@soulcraft/brainy'
import { S3CompatibleStorage } from '@soulcraft/brainy/storage'
import express from 'express'
const app = express()
app.use(express.json())
const PORT = process.env.PORT || 8080
let brain
let handler
async function initBrainy() {
// Google Cloud Storage is S3-compatible
const storage = new S3CompatibleStorage({
endpoint: 'storage.googleapis.com',
bucket: process.env.GCS_BUCKET || 'brainy-data',
accessKeyId: process.env.GCS_ACCESS_KEY,
secretAccessKey: process.env.GCS_SECRET_KEY,
prefix: 'brainy/',
forcePathStyle: false,
region: process.env.GCS_REGION || 'US'
})
brain = new Brainy({
storage,
augmentations: [{
name: 'api-server',
config: {
enabled: true,
port: PORT,
cors: {
origin: process.env.CORS_ORIGIN || '*'
},
auth: {
required: process.env.AUTH_REQUIRED === 'true',
apiKeys: process.env.API_KEY ? [process.env.API_KEY] : []
}
}
}]
})
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
handler = apiAugmentation.createUniversalHandler()
}
// Initialize on startup
await initBrainy()
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', service: 'brainy-api' })
})
// Universal handler for all API routes
app.use('*', async (req, res) => {
const request = new Request(`https://${req.hostname}${req.originalUrl}`, {
method: req.method,
headers: req.headers,
body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined
})
const response = await handler(request)
res.status(response.status)
res.set(Object.fromEntries(response.headers))
res.send(await response.text())
})
app.listen(PORT, () => {
console.log(`Brainy API running on port ${PORT}`)
})
```
```dockerfile
# Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY . .
EXPOSE 8080
CMD ["node", "server.js"]
```
```yaml
# cloudbuild.yaml
steps:
# Build the container image
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/brainy-api:$COMMIT_SHA', '.']
# Push to Container Registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/brainy-api:$COMMIT_SHA']
# Deploy to Cloud Run
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
entrypoint: gcloud
args:
- 'run'
- 'deploy'
- 'brainy-api'
- '--image'
- 'gcr.io/$PROJECT_ID/brainy-api:$COMMIT_SHA'
- '--region'
- 'us-central1'
- '--platform'
- 'managed'
- '--allow-unauthenticated'
- '--set-env-vars'
- 'GCS_BUCKET=brainy-storage,GCS_REGION=US'
- '--set-secrets'
- 'GCS_ACCESS_KEY=gcs-access-key:latest,GCS_SECRET_KEY=gcs-secret-key:latest'
- '--memory'
- '2Gi'
- '--cpu'
- '2'
- '--max-instances'
- '100'
- '--min-instances'
- '0'
images:
- 'gcr.io/$PROJECT_ID/brainy-api:$COMMIT_SHA'
```
**Deploy with gcloud CLI:**
```bash
# Build and submit to Cloud Build
gcloud builds submit --config cloudbuild.yaml
# Or deploy directly
gcloud run deploy brainy-api \
--source . \
--platform managed \
--region us-central1 \
--allow-unauthenticated \
--set-env-vars GCS_BUCKET=brainy-storage \
--set-secrets "GCS_ACCESS_KEY=gcs-access-key:latest,GCS_SECRET_KEY=gcs-secret-key:latest"
```
**Create GCS Bucket with S3-compatible access:**
```bash
# Create bucket
gsutil mb -p PROJECT_ID -c STANDARD -l US gs://brainy-storage/
# Enable interoperability
gsutil iam ch serviceAccount:SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com:objectAdmin gs://brainy-storage
# Generate HMAC keys for S3-compatible access
gsutil hmac create SERVICE_ACCOUNT@PROJECT_ID.iam.gserviceaccount.com
# Store the access key and secret in Secret Manager
echo -n "YOUR_ACCESS_KEY" | gcloud secrets create gcs-access-key --data-file=-
echo -n "YOUR_SECRET_KEY" | gcloud secrets create gcs-secret-key --data-file=-
```
### Microsoft Azure Functions
```javascript
// index.js
module.exports = async function (context, req) {
const { Brainy } = require('@soulcraft/brainy')
const { S3CompatibleStorage } = require('@soulcraft/brainy/storage')
const storage = new S3CompatibleStorage({
endpoint: `${process.env.AZURE_STORAGE_ACCOUNT}.blob.core.windows.net`,
bucket: 'brainy-data',
accessKeyId: process.env.AZURE_STORAGE_ACCOUNT,
secretAccessKey: process.env.AZURE_STORAGE_KEY,
prefix: 'entities/',
forcePathStyle: false
})
const brain = new Brainy({ storage })
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
const handler = apiAugmentation.createUniversalHandler()
const request = new Request(`https://${context.req.headers.host}${context.req.url}`, {
method: context.req.method,
headers: context.req.headers,
body: JSON.stringify(context.req.body)
})
const response = await handler(request)
context.res = {
status: response.status,
headers: Object.fromEntries(response.headers),
body: await response.text()
}
}
```
### Cloudflare Workers
```javascript
// worker.js
import { Brainy } from '@soulcraft/brainy'
import { R2Storage } from '@soulcraft/brainy/storage' // Alias for S3CompatibleStorage
let handler
export default {
async fetch(request, env, ctx) {
if (!handler) {
const storage = new R2Storage({
endpoint: `${env.ACCOUNT_ID}.r2.cloudflarestorage.com`,
bucket: 'brainy-data',
accessKeyId: env.R2_ACCESS_KEY_ID,
secretAccessKey: env.R2_SECRET_ACCESS_KEY,
region: 'auto',
forcePathStyle: true
})
const brain = new Brainy({
storage,
augmentations: [{
name: 'api-server',
config: {
enabled: true,
cors: { origin: '*' }
}
}]
})
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
handler = apiAugmentation.createUniversalHandler()
}
// The handler works directly with Request/Response!
return handler(request)
}
}
```
```toml
# wrangler.toml
name = "brainy-api"
main = "worker.js"
compatibility_date = "2024-01-01"
[[r2_buckets]]
binding = "R2"
bucket_name = "brainy-data"
[vars]
ACCOUNT_ID = "your-account-id"
[env.production.vars]
R2_ACCESS_KEY_ID = "your-access-key"
R2_SECRET_ACCESS_KEY = "your-secret-key"
```
### Vercel Edge Functions
```javascript
// api/brainy.js
import { Brainy } from '@soulcraft/brainy'
import { S3CompatibleStorage } from '@soulcraft/brainy/storage'
let handler
export const config = {
runtime: 'edge',
}
export default async (request) => {
if (!handler) {
const storage = new S3CompatibleStorage({
endpoint: 's3.amazonaws.com',
region: 'us-east-1',
bucket: process.env.S3_BUCKET,
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})
const brain = new Brainy({
storage,
augmentations: [{
name: 'api-server',
config: { enabled: true }
}]
})
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
handler = apiAugmentation.createUniversalHandler()
}
return handler(request)
}
```
```json
// vercel.json
{
"functions": {
"api/brainy.js": {
"maxDuration": 30,
"memory": 1024
}
},
"rewrites": [
{
"source": "/api/:path*",
"destination": "/api/brainy"
}
]
}
```
### Railway
```javascript
// server.js
import { Brainy } from '@soulcraft/brainy'
import { S3CompatibleStorage } from '@soulcraft/brainy/storage'
import express from 'express'
const app = express()
const PORT = process.env.PORT || 3000
let brain
let handler
async function init() {
const storage = new S3CompatibleStorage({
endpoint: process.env.S3_ENDPOINT || 's3.amazonaws.com',
region: process.env.S3_REGION || 'us-east-1',
bucket: process.env.S3_BUCKET,
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
prefix: 'brainy/'
})
brain = new Brainy({
storage,
augmentations: [{
name: 'api-server',
config: {
enabled: true,
port: PORT
}
}]
})
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
handler = apiAugmentation.createUniversalHandler()
}
await init()
// Universal handler for all routes
app.use('*', async (req, res) => {
const request = new Request(`http://localhost${req.originalUrl}`, {
method: req.method,
headers: req.headers,
body: JSON.stringify(req.body)
})
const response = await handler(request)
res.status(response.status)
res.set(Object.fromEntries(response.headers))
res.send(await response.text())
})
app.listen(PORT, () => {
console.log(`Brainy API running on port ${PORT}`)
})
```
```toml
# railway.toml
[build]
builder = "nixpacks"
buildCommand = "npm ci"
[deploy]
startCommand = "node server.js"
restartPolicyType = "always"
restartPolicyMaxRetries = 3
```
### Render
```javascript
// server.js (same as Railway example above)
// Use S3CompatibleStorage with your preferred object storage provider
```
```yaml
# render.yaml
services:
- type: web
name: brainy-api
runtime: node
buildCommand: npm install
startCommand: node server.js
envVars:
- key: S3_BUCKET
value: brainy-data
- key: S3_ENDPOINT
value: s3.amazonaws.com
- key: S3_REGION
value: us-east-1
- key: S3_ACCESS_KEY
sync: false
- key: S3_SECRET_KEY
sync: false
- key: API_KEY
generateValue: true
healthCheckPath: /health
autoDeploy: true
```
### Deno Deploy
```typescript
// main.ts
import { Brainy } from "npm:@soulcraft/brainy"
import { S3CompatibleStorage } from "npm:@soulcraft/brainy/storage"
const storage = new S3CompatibleStorage({
endpoint: Deno.env.get("S3_ENDPOINT") || "s3.amazonaws.com",
bucket: Deno.env.get("S3_BUCKET")!,
accessKeyId: Deno.env.get("S3_ACCESS_KEY")!,
secretAccessKey: Deno.env.get("S3_SECRET_KEY")!,
region: "auto"
})
const brain = new Brainy({
storage,
augmentations: [{
name: 'api-server',
config: { enabled: true }
}]
})
await brain.init()
const apiAugmentation = brain.augmentationRegistry.getAugmentation('api-server')
const handler = apiAugmentation.createUniversalHandler()
Deno.serve(handler)
```
## API Endpoints
The API Server augmentation provides these REST endpoints:
- `POST /api/brainy/add` - Add entity
- `GET /api/brainy/get?id=xxx` - Get entity by ID
- `PUT /api/brainy/update` - Update entity
- `DELETE /api/brainy/delete?id=xxx` - Delete entity
- `POST /api/brainy/find` - Search/find entities
- `POST /api/brainy/relate` - Create relationship
- `GET /api/brainy/insights` - Get statistics and insights
- `GET /health` - Health check
## WebSocket Support
The API Server augmentation includes WebSocket support for real-time updates through the `setupUniversalWebSocket()` method.
## MCP Support
Model Context Protocol (MCP) endpoints are available at `/mcp/*` for AI tool integration.
## Environment Variables
```bash
# Storage Configuration (S3Compatible)
S3_ENDPOINT=s3.amazonaws.com
S3_REGION=us-east-1
S3_BUCKET=brainy-data
S3_ACCESS_KEY=xxx
S3_SECRET_KEY=xxx
# API Configuration
API_KEY=your-secret-key
PORT=3000
# CORS
CORS_ORIGIN=*
# Rate Limiting
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX=100
```
## Client Usage
```javascript
// REST API Client
const response = await fetch('https://your-api.com/api/brainy/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
data: 'Your content here',
metadata: { type: 'document' }
})
})
const { id } = await response.json()
// Search
const searchResponse = await fetch('https://your-api.com/api/brainy/find', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_API_KEY'
},
body: JSON.stringify({
query: 'neural networks'
})
})
const results = await searchResponse.json()
// WebSocket Client
const ws = new WebSocket('wss://your-api.com/ws')
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'subscribe',
pattern: 'technology'
}))
}
ws.onmessage = (event) => {
const update = JSON.parse(event.data)
console.log('Real-time update:', update)
}
```
## Storage Adapter Configuration
S3CompatibleStorage constructor parameters (verified from source):
```javascript
{
endpoint: string, // Required (e.g., 's3.amazonaws.com')
bucket: string, // Required
accessKeyId: string, // Required
secretAccessKey: string, // Required
region?: string, // Optional (default: 'us-east-1')
prefix?: string, // Optional (e.g., 'brainy/')
forcePathStyle?: boolean, // Optional (needed for some S3-compatible services)
}
```
## Important Notes
1. All examples use the **real** Brainy 3.0 APIs
2. The `createUniversalHandler()` method is provided by the API Server augmentation
3. S3CompatibleStorage works with any S3-compatible service
4. Always call `brain.init()` before using Brainy
5. The handler can be cached across requests for better performance
6. R2Storage is an alias for S3CompatibleStorage (for Cloudflare R2)
## Security Best Practices
1. **Always use environment variables for sensitive data** (API keys, secrets)
2. **Enable authentication** in the API Server augmentation config
3. **Use HTTPS/TLS** for all production deployments
4. **Implement rate limiting** to prevent abuse
5. **Configure CORS** appropriately for your use case
## Cost Optimization
### Enable Lifecycle Policies
**AWS S3: Automatic tier transitions**
```javascript
// After initializing brain with S3CompatibleStorage
const storage = brain.storage
// Set lifecycle policy for automatic archival
await storage.setLifecyclePolicy({
rules: [{
id: 'archive-old-data',
prefix: 'entities/',
status: 'Enabled',
transitions: [
{ days: 30, storageClass: 'STANDARD_IA' }, // $0.0125/GB after 30 days
{ days: 90, storageClass: 'GLACIER' }, // $0.004/GB after 90 days
{ days: 365, storageClass: 'DEEP_ARCHIVE' } // $0.00099/GB after 1 year
]
}]
})
// Or enable Intelligent-Tiering for hands-off optimization
await storage.enableIntelligentTiering('entities/', 'auto-optimize')
```
**Cost Impact (500TB dataset):**
| Storage Class | Cost/GB/Month | 500TB/Year | Savings |
|---------------|---------------|------------|---------|
| Standard | $0.023 | $138,000 | Baseline |
| Intelligent-Tiering | Variable | $6,900 | **95%** |
| With lifecycle policy | Variable | $5,940 | **96%** |
### Enable Batch Operations
**Efficient bulk deletions:**
```javascript
// Batch delete (1000 objects per request)
const idsToDelete = [/* array of entity IDs */]
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) // Much faster than individual deletes
```
### Enable Compression (FileSystem)
**For local/server deployments:**
```javascript
const storage = new FileSystemStorage({
path: './data',
compression: true // 60-80% space savings
})
const brain = new Brainy({ storage })
```
## Performance Tips
1. **Cache the brain instance** - Initialize once and reuse across requests
2. **Use S3CompatibleStorage** for cloud deployments (better scalability)
3. **Enable the cache augmentation** for frequently accessed data
4. **Configure appropriate memory limits** for your runtime
5. Enable lifecycle policies to reduce storage costs by 96%
6. Use batch operations for cleanup tasks
## Troubleshooting
### Common Issues
1. **"Brainy not initialized"** - Make sure to call `await brain.init()` before use
2. **"augmentationRegistry.getAugmentation is not a function"** - The augmentation wasn't loaded properly
3. **S3 Access Denied** - Check your IAM permissions and credentials
4. **CORS errors** - Configure the CORS settings in the API Server augmentation
### Debug Mode
Enable debug logging by setting:
```javascript
const brain = new Brainy({
storage,
debug: true,
augmentations: [{
name: 'api-server',
config: {
enabled: true,
verbose: true
}
}]
})
```
## Support
- Documentation: https://github.com/soulcraft/brainy/docs
- Issues: https://github.com/soulcraft/brainy/issues
- NPM Package: https://www.npmjs.com/package/@soulcraft/brainy
---
This guide contains only verified, working code from the actual Brainy 3.0 codebase. No mock implementations, no stub methods, only production-ready code.

View file

@ -0,0 +1,505 @@
# AWS Deployment Guide for Brainy
## Overview
Deploy Brainy on AWS with automatic scaling, high availability, and zero-config dynamic adaptation. Brainy automatically adapts to your AWS environment without manual configuration.
## Quick Start (Zero-Config)
### Option 1: AWS Lambda (Serverless)
```bash
# Install Brainy
npm install @soulcraft/brainy
# Create handler.js
cat > handler.js << 'EOF'
const { Brainy } = require('@soulcraft/brainy')
let brain
exports.handler = async (event) => {
// Brainy auto-detects Lambda environment and configures accordingly
if (!brain) {
brain = new Brainy() // Zero config - auto-adapts to Lambda
await brain.init()
}
const { method, ...params } = JSON.parse(event.body)
switch(method) {
case 'add':
const id = await brain.add(params)
return { statusCode: 200, body: JSON.stringify({ id }) }
case 'find':
const results = await brain.find(params)
return { statusCode: 200, body: JSON.stringify({ results }) }
default:
return { statusCode: 400, body: 'Unknown method' }
}
}
EOF
# Deploy with AWS SAM
sam init --runtime nodejs20.x --name brainy-app
sam deploy --guided
```
### Option 2: ECS Fargate (Container)
```bash
# Build and push Docker image
aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin $ECR_URI
docker build -t brainy .
docker tag brainy:latest $ECR_URI/brainy:latest
docker push $ECR_URI/brainy:latest
# Deploy with minimal ECS task definition
cat > task-definition.json << 'EOF'
{
"family": "brainy",
"networkMode": "awsvpc",
"requiresCompatibilities": ["FARGATE"],
"cpu": "256",
"memory": "512",
"containerDefinitions": [{
"name": "brainy",
"image": "$ECR_URI/brainy:latest",
"environment": [
{"name": "NODE_ENV", "value": "production"}
],
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/brainy",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "ecs"
}
}
}]
}
EOF
# Brainy auto-detects ECS environment and uses S3 for storage
aws ecs register-task-definition --cli-input-json file://task-definition.json
aws ecs create-service --cluster default --service-name brainy --task-definition brainy --desired-count 2
```
### Option 3: EC2 Auto-Scaling
```bash
# User data script for EC2 instances
#!/bin/bash
curl -fsSL https://rpm.nodesource.com/setup_20.x | sudo bash -
sudo yum install -y nodejs git
# Clone and setup (or use your deployment method)
git clone https://github.com/yourorg/brainy-app.git /app
cd /app
npm install --production
# Create systemd service
cat > /etc/systemd/system/brainy.service << 'EOF'
[Unit]
Description=Brainy
After=network.target
[Service]
Type=simple
User=ec2-user
WorkingDirectory=/app
ExecStart=/usr/bin/node index.js
Restart=on-failure
Environment=NODE_ENV=production
[Install]
WantedBy=multi-user.target
EOF
systemctl start brainy
systemctl enable brainy
```
## Zero-Config Storage (Automatic)
Brainy automatically detects and uses the best available storage:
```javascript
// No configuration needed - Brainy auto-detects:
const brain = new Brainy()
// Auto-detection priority:
// 1. S3 (if IAM role has permissions)
// 2. EFS (if mounted at /mnt/efs)
// 3. EBS volume (if available)
// 4. Instance storage (fallback)
```
### Manual S3 Configuration (Optional)
```javascript
const brain = new Brainy({
storage: {
type: 's3',
options: {
bucket: process.env.S3_BUCKET || 'auto', // 'auto' creates bucket
region: process.env.AWS_REGION || 'auto', // 'auto' detects region
// IAM role provides credentials automatically
}
}
})
```
## Scaling Strategies
### 1. Horizontal Scaling (Recommended)
```yaml
# Auto-scaling policy
Resources:
AutoScalingTarget:
Type: AWS::ApplicationAutoScaling::ScalableTarget
Properties:
ServiceNamespace: ecs
ResourceId: service/default/brainy
ScalableDimension: ecs:service:DesiredCount
MinCapacity: 2
MaxCapacity: 100
AutoScalingPolicy:
Type: AWS::ApplicationAutoScaling::ScalingPolicy
Properties:
PolicyType: TargetTrackingScaling
TargetTrackingScalingPolicyConfiguration:
TargetValue: 70.0
PredefinedMetricSpecification:
PredefinedMetricType: ECSServiceAverageCPUUtilization
```
### 2. Vertical Scaling
Brainy automatically adapts to available memory:
- **256MB**: Minimal mode, optimized caching
- **512MB**: Standard mode, balanced performance
- **1GB+**: Full mode, maximum performance
## High Availability Setup
### Multi-AZ Deployment
```javascript
// Brainy automatically handles multi-AZ with S3
const brain = new Brainy({
distributed: {
enabled: true, // Auto-enables with S3 storage
coordinationMethod: 'auto' // Uses S3 for coordination
}
})
```
### Load Balancing
```bash
# Application Load Balancer with health checks
aws elbv2 create-load-balancer \
--name brainy-alb \
--subnets subnet-xxx subnet-yyy \
--security-groups sg-xxx
aws elbv2 create-target-group \
--name brainy-targets \
--protocol HTTP \
--port 3000 \
--vpc-id vpc-xxx \
--health-check-path /health \
--health-check-interval-seconds 30
```
## Monitoring & Observability
### CloudWatch Integration
Brainy automatically sends metrics when running on AWS:
```javascript
// Automatic CloudWatch metrics (no config needed)
// - Request count
// - Response time
// - Error rate
// - Storage usage
// - Memory usage
```
### Custom Metrics
```javascript
const brain = new Brainy({
monitoring: {
enabled: true,
customMetrics: {
namespace: 'Brainy/Production',
dimensions: [
{ Name: 'Environment', Value: 'production' },
{ Name: 'Service', Value: 'api' }
]
}
}
})
```
## Security Best Practices
### 1. IAM Role (Recommended)
```json
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:ListBucket"
],
"Resource": [
"arn:aws:s3:::brainy-*/*",
"arn:aws:s3:::brainy-*"
]
}
]
}
```
### 2. VPC Configuration
```bash
# Private subnets with NAT Gateway
aws ec2 create-vpc --cidr-block 10.0.0.0/16
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.1.0/24 --availability-zone us-east-1a
aws ec2 create-subnet --vpc-id vpc-xxx --cidr-block 10.0.2.0/24 --availability-zone us-east-1b
```
### 3. Encryption
```javascript
// Automatic encryption with S3
const brain = new Brainy({
storage: {
type: 's3',
options: {
encryption: 'auto' // Uses S3 SSE-S3 by default
}
}
})
```
## Cost Optimization
### 1. Spot Instances (70% savings)
```bash
aws ec2 request-spot-fleet --spot-fleet-request-config '{
"IamFleetRole": "arn:aws:iam::xxx:role/fleet-role",
"TargetCapacity": 2,
"SpotPrice": "0.05",
"LaunchSpecifications": [{
"ImageId": "ami-xxx",
"InstanceType": "t3.medium",
"UserData": "BASE64_ENCODED_STARTUP_SCRIPT"
}]
}'
```
### 2. S3 Intelligent-Tiering
```javascript
// Brainy automatically uses S3 Intelligent-Tiering
const brain = new Brainy({
storage: {
type: 's3',
options: {
storageClass: 'INTELLIGENT_TIERING' // Automatic cost optimization
}
}
})
```
### 3. Lambda Reserved Concurrency
```bash
aws lambda put-function-concurrency \
--function-name brainy-handler \
--reserved-concurrent-executions 10
```
## Deployment Automation
### GitHub Actions CI/CD
```yaml
name: Deploy to AWS
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-1
- name: Deploy to ECS
run: |
docker build -t brainy .
docker tag brainy:latest $ECR_URI/brainy:latest
docker push $ECR_URI/brainy:latest
aws ecs update-service --cluster default --service brainy --force-new-deployment
```
## Troubleshooting
### Common Issues
1. **Storage Auto-Detection Fails**
```javascript
// Explicitly specify storage
const brain = new Brainy({
storage: { type: 's3', options: { bucket: 'my-bucket' } }
})
```
2. **Memory Issues**
```javascript
// Optimize for low memory
const brain = new Brainy({
cache: { maxSize: 100 }, // Reduce cache size
index: { M: 8 } // Reduce HNSW connections
})
```
3. **Cold Starts (Lambda)**
**Progressive Initialization (Zero-Config)**
Brainy automatically detects Lambda environments (AWS_LAMBDA_FUNCTION_NAME)
and uses progressive initialization for <200ms cold starts:
```javascript
// Zero-config - Brainy auto-detects Lambda
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: {
bucketName: 'my-bucket',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
}
})
await brain.init() // Returns in <200ms
// First write validates bucket (lazy validation)
await brain.add('noun', { name: 'test' }) // Validates here
```
**Manual Override (if needed)**
```javascript
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: {
bucketName: 'my-bucket',
// Force specific mode
initMode: 'progressive' // 'auto' | 'progressive' | 'strict'
}
}
})
```
| Mode | Cold Start | Best For |
|------|------------|----------|
| `auto` (default) | <200ms in Lambda | Zero-config, auto-detects |
| `progressive` | <200ms always | Force fast init everywhere |
| `strict` | 100-500ms+ | Local dev, tests, debugging |
**Warm-up (Alternative)**
```javascript
exports.warmup = async () => {
if (!brain) {
brain = new Brainy({ warmup: true })
await brain.init()
}
}
```
**Readiness Detection**
Use the `brain.ready` Promise to ensure Brainy is initialized before handling requests:
```javascript
let brain
exports.handler = async (event) => {
if (!brain) {
brain = new Brainy({ storage: { type: 's3', ... } })
brain.init() // Fire and forget
}
// Wait for initialization to complete
await brain.ready
// Now safe to use brain methods
const results = await brain.find({ query: event.queryStringParameters.q })
return { statusCode: 200, body: JSON.stringify(results) }
}
```
For health checks, use `isFullyInitialized()` to verify all background tasks are complete:
```javascript
exports.healthCheck = async () => {
try {
await brain.ready
return {
statusCode: 200,
body: JSON.stringify({
status: 'ready',
fullyInitialized: brain.isFullyInitialized()
})
}
} catch (error) {
return {
statusCode: 503,
body: JSON.stringify({ status: 'initializing' })
}
}
}
```
## Production Checklist
- [ ] IAM roles configured with minimal permissions
- [ ] VPC with private subnets
- [ ] Auto-scaling configured
- [ ] CloudWatch alarms set up
- [ ] Backup strategy (S3 versioning enabled)
- [ ] SSL/TLS certificates configured
- [ ] Rate limiting enabled
- [ ] Health checks configured
- [ ] Monitoring dashboard created
- [ ] Cost alerts configured
## Support
- Documentation: https://brainy.soulcraft.ai/docs
- Issues: https://github.com/soulcraft/brainy/issues
- Community: https://discord.gg/brainy

View file

@ -0,0 +1,627 @@
# Google Cloud Platform Deployment Guide for Brainy
## Overview
Deploy Brainy on GCP with automatic scaling, global distribution, and zero-config dynamic adaptation. Brainy automatically detects and optimizes for GCP services.
## Quick Start (Zero-Config)
### Option 1: Cloud Run (Serverless Containers)
```bash
# Build and deploy with one command
gcloud run deploy brainy \
--source . \
--platform managed \
--region us-central1 \
--allow-unauthenticated
# Brainy auto-detects Cloud Run and configures:
# - Memory-optimized caching
# - GCS for storage (if available)
# - Cloud SQL for metadata (if available)
```
### Option 2: Cloud Functions (Serverless)
```javascript
// index.js
const { Brainy } = require('@soulcraft/brainy')
let brain
exports.brainyHandler = async (req, res) => {
// Zero-config - auto-adapts to Cloud Functions
if (!brain) {
brain = new Brainy() // Detects GCP environment automatically
await brain.init()
}
const { method, ...params } = req.body
try {
let result
switch(method) {
case 'add':
result = await brain.add(params)
break
case 'find':
result = await brain.find(params)
break
case 'relate':
result = await brain.relate(params)
break
default:
return res.status(400).json({ error: 'Unknown method' })
}
res.json({ result })
} catch (error) {
res.status(500).json({ error: error.message })
}
}
```
Deploy:
```bash
gcloud functions deploy brainy \
--runtime nodejs20 \
--trigger-http \
--entry-point brainyHandler \
--memory 512MB \
--timeout 60s
```
### Option 3: Google Kubernetes Engine (GKE)
```bash
# Create autopilot cluster (fully managed, zero-config)
gcloud container clusters create-auto brainy-cluster \
--region us-central1
# Deploy using Cloud Build
gcloud builds submit --tag gcr.io/$PROJECT_ID/brainy
# Apply Kubernetes manifest
kubectl apply -f - <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy
spec:
replicas: 3
selector:
matchLabels:
app: brainy
template:
metadata:
labels:
app: brainy
spec:
containers:
- name: brainy
image: gcr.io/$PROJECT_ID/brainy
resources:
requests:
memory: "512Mi"
cpu: "250m"
env:
- name: NODE_ENV
value: production
---
apiVersion: v1
kind: Service
metadata:
name: brainy-service
spec:
type: LoadBalancer
selector:
app: brainy
ports:
- port: 80
targetPort: 3000
EOF
```
## Zero-Config Storage (Automatic)
Brainy automatically detects and uses the best GCP storage:
```javascript
const brain = new Brainy()
// Auto-detection priority:
// 1. Firestore (if available)
// 2. Cloud Storage (GCS)
// 3. Cloud SQL
// 4. Persistent Disk
// 5. Memory (fallback)
```
### Cloud Storage Configuration (Optional)
```javascript
const brain = new Brainy({
storage: {
type: 's3', // GCS is S3-compatible
options: {
endpoint: 'https://storage.googleapis.com',
bucket: process.env.GCS_BUCKET || 'auto', // Auto-creates bucket
// Uses Application Default Credentials automatically
}
}
})
```
### Firestore Integration (Optional)
```javascript
const brain = new Brainy({
storage: {
type: 'firestore',
options: {
projectId: process.env.GCP_PROJECT || 'auto',
collection: 'brainy-data'
}
}
})
```
## Scaling Strategies
### 1. Cloud Run Auto-scaling
```yaml
# service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: brainy
annotations:
run.googleapis.com/execution-environment: gen2
spec:
template:
metadata:
annotations:
autoscaling.knative.dev/minScale: "1"
autoscaling.knative.dev/maxScale: "1000"
autoscaling.knative.dev/target: "80"
spec:
containerConcurrency: 100
containers:
- image: gcr.io/PROJECT_ID/brainy
resources:
limits:
cpu: "2"
memory: "2Gi"
```
### 2. GKE Horizontal Pod Autoscaling
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: brainy-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: brainy
minReplicas: 3
maxReplicas: 100
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
```
## Global Distribution
### Multi-Region Setup
```javascript
// Brainy automatically handles multi-region with GCS
const brain = new Brainy({
distributed: {
enabled: true,
regions: ['us-central1', 'europe-west1', 'asia-northeast1'],
replication: 'auto' // Automatic cross-region replication
}
})
```
### Traffic Director Configuration
```bash
# Global load balancing with Traffic Director
gcloud compute backend-services create brainy-global \
--global \
--load-balancing-scheme=INTERNAL_SELF_MANAGED \
--protocol=HTTP2
gcloud compute backend-services add-backend brainy-global \
--global \
--network-endpoint-group=brainy-neg \
--network-endpoint-group-region=us-central1
```
## Monitoring & Observability
### Cloud Monitoring (Automatic)
Brainy automatically sends metrics to Cloud Monitoring:
```javascript
// No configuration needed - automatic when running on GCP
const brain = new Brainy()
// Automatic metrics:
// - Request latency
// - Error rate
// - Storage operations
// - Cache hit rate
// - Memory usage
```
### Custom Metrics
```javascript
const { Monitoring } = require('@google-cloud/monitoring')
const monitoring = new Monitoring.MetricServiceClient()
const brain = new Brainy({
onMetric: async (metric) => {
// Send custom metrics to Cloud Monitoring
await monitoring.createTimeSeries({
name: monitoring.projectPath(projectId),
timeSeries: [{
metric: {
type: `custom.googleapis.com/brainy/${metric.name}`,
labels: metric.labels
},
points: [{
interval: { endTime: { seconds: Date.now() / 1000 } },
value: { doubleValue: metric.value }
}]
}]
})
}
})
```
### Cloud Trace Integration
```javascript
const brain = new Brainy({
tracing: {
enabled: true,
sampleRate: 0.1 // Sample 10% of requests
}
})
```
## Security Best Practices
### 1. Workload Identity (GKE)
```yaml
# Enable Workload Identity
apiVersion: v1
kind: ServiceAccount
metadata:
name: brainy-sa
annotations:
iam.gke.io/gcp-service-account: brainy@PROJECT_ID.iam.gserviceaccount.com
```
### 2. Binary Authorization
```yaml
# Ensure only signed container images
apiVersion: binaryauthorization.grafeas.io/v1beta1
kind: Policy
metadata:
name: brainy-policy
spec:
defaultAdmissionRule:
requireAttestationsBy:
- projects/PROJECT_ID/attestors/prod-attestor
```
### 3. VPC Service Controls
```bash
# Create VPC Service Perimeter
gcloud access-context-manager perimeters create brainy_perimeter \
--resources=projects/PROJECT_NUMBER \
--restricted-services=storage.googleapis.com \
--title="Brainy Security Perimeter"
```
## Cost Optimization
### 1. Preemptible VMs (80% savings)
```yaml
# GKE node pool with preemptible VMs
apiVersion: container.cnrm.cloud.google.com/v1beta1
kind: ContainerNodePool
metadata:
name: brainy-preemptible-pool
spec:
clusterRef:
name: brainy-cluster
config:
preemptible: true
machineType: n2-standard-2
autoscaling:
minNodeCount: 1
maxNodeCount: 10
```
### 2. Cloud CDN for Static Assets
```bash
# Enable Cloud CDN for frequently accessed data
gcloud compute backend-buckets create brainy-assets \
--gcs-bucket-name=brainy-static
gcloud compute backend-buckets update brainy-assets \
--enable-cdn \
--cache-mode=CACHE_ALL_STATIC
```
### 3. Committed Use Discounts
```bash
# Purchase committed use for predictable workloads
gcloud compute commitments create brainy-commitment \
--plan=TWELVE_MONTH \
--resources=vcpu=100,memory=400
```
## Deployment Automation
### Cloud Build CI/CD
```yaml
# cloudbuild.yaml
steps:
# Build container
- name: 'gcr.io/cloud-builders/docker'
args: ['build', '-t', 'gcr.io/$PROJECT_ID/brainy:$SHORT_SHA', '.']
# Push to registry
- name: 'gcr.io/cloud-builders/docker'
args: ['push', 'gcr.io/$PROJECT_ID/brainy:$SHORT_SHA']
# Deploy to Cloud Run
- name: 'gcr.io/cloud-builders/gcloud'
args:
- 'run'
- 'deploy'
- 'brainy'
- '--image=gcr.io/$PROJECT_ID/brainy:$SHORT_SHA'
- '--region=us-central1'
- '--platform=managed'
# Trigger on push to main
trigger:
branch:
name: main
```
### Terraform Infrastructure
```hcl
# main.tf
resource "google_cloud_run_service" "brainy" {
name = "brainy"
location = "us-central1"
template {
spec {
containers {
image = "gcr.io/${var.project_id}/brainy"
resources {
limits = {
cpu = "2"
memory = "2Gi"
}
}
env {
name = "NODE_ENV"
value = "production"
}
}
}
}
traffic {
percent = 100
latest_revision = true
}
}
resource "google_cloud_run_service_iam_member" "public" {
service = google_cloud_run_service.brainy.name
location = google_cloud_run_service.brainy.location
role = "roles/run.invoker"
member = "allUsers"
}
```
## Performance Optimization
### 1. Memory Store (Redis Compatible)
```javascript
// Brainy can use Memorystore for caching
const brain = new Brainy({
cache: {
type: 'redis',
options: {
host: process.env.REDIS_HOST || 'auto-detect',
port: 6379
}
}
})
```
### 2. Cloud Spanner for Global Consistency
```javascript
const brain = new Brainy({
metadata: {
type: 'spanner',
options: {
instance: 'brainy-instance',
database: 'brainy-db'
}
}
})
```
## Troubleshooting
### Common Issues
1. **Quota Exceeded**
```bash
# Check quotas
gcloud compute project-info describe --project=$PROJECT_ID
# Request increase
gcloud compute project-info add-metadata \
--metadata google-compute-default-region=us-central1
```
2. **Cold Starts**
**Progressive Initialization (Zero-Config)**
Brainy automatically detects Cloud Run and Cloud Functions environments
and uses progressive initialization for <200ms cold starts:
```javascript
// Zero-config - Brainy auto-detects Cloud Run (K_SERVICE env var)
const brain = new Brainy({
storage: {
type: 'gcs',
gcsNativeStorage: { bucketName: 'my-bucket' }
}
})
await brain.init() // Returns in <200ms
// First write validates bucket (lazy validation)
await brain.add('noun', { name: 'test' }) // Validates here
```
**Manual Override (if needed)**
```javascript
const brain = new Brainy({
storage: {
type: 'gcs',
gcsNativeStorage: {
bucketName: 'my-bucket',
// Force specific mode
initMode: 'progressive' // 'auto' | 'progressive' | 'strict'
}
}
})
```
| Mode | Cold Start | Best For |
|------|------------|----------|
| `auto` (default) | <200ms in cloud | Zero-config, auto-detects |
| `progressive` | <200ms always | Force fast init everywhere |
| `strict` | 100-500ms+ | Local dev, tests, debugging |
**Keep Warm (Alternative)**
```javascript
// Keep minimum instances warm
const brain = new Brainy({
warmup: {
enabled: true,
interval: 60000 // Ping every minute
}
})
```
**Readiness Detection**
Use the `brain.ready` Promise to ensure Brainy is initialized before handling requests:
```javascript
let brain
async function handleRequest(req, res) {
if (!brain) {
brain = new Brainy({ storage: { type: 'gcs', ... } })
brain.init() // Fire and forget
}
// Wait for initialization to complete
await brain.ready
// Now safe to use brain methods
const results = await brain.find({ query: req.query.q })
res.json(results)
}
```
For Cloud Run health checks, use `isFullyInitialized()` to verify all background tasks are complete:
```javascript
// Health check endpoint for Cloud Run
app.get('/health', async (req, res) => {
try {
await brain.ready
res.json({
status: 'ready',
fullyInitialized: brain.isFullyInitialized()
})
} catch (error) {
res.status(503).json({ status: 'initializing' })
}
})
```
3. **Memory Pressure**
```javascript
// Optimize for GCP memory constraints
const brain = new Brainy({
memory: {
mode: 'aggressive', // Aggressive garbage collection
maxHeap: 0.8 // Use 80% of available memory
}
})
```
## Production Checklist
- [ ] Enable Workload Identity for secure access
- [ ] Configure Cloud Armor for DDoS protection
- [ ] Set up Cloud KMS for encryption keys
- [ ] Enable VPC Service Controls
- [ ] Configure Cloud IAP for authentication
- [ ] Set up Cloud Monitoring dashboards
- [ ] Configure Error Reporting
- [ ] Enable Cloud Trace
- [ ] Set up budget alerts
- [ ] Configure backup and disaster recovery
## Support
- Documentation: https://brainy.soulcraft.ai/docs
- Issues: https://github.com/soulcraft/brainy/issues
- GCP Marketplace: https://console.cloud.google.com/marketplace/product/brainy

View file

@ -0,0 +1,727 @@
# Kubernetes Deployment Guide for Brainy
## Overview
Deploy Brainy on Kubernetes with automatic scaling, high availability, and zero-config dynamic adaptation. Works with any Kubernetes distribution (vanilla, EKS, GKE, AKS, OpenShift, etc.).
## Quick Start (Zero-Config)
### Basic Deployment
```yaml
# brainy-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy
labels:
app: brainy
spec:
replicas: 3
selector:
matchLabels:
app: brainy
template:
metadata:
labels:
app: brainy
spec:
containers:
- name: brainy
image: soulcraft/brainy:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
# Brainy auto-detects Kubernetes and configures accordingly
resources:
requests:
memory: "256Mi"
cpu: "100m"
limits:
memory: "1Gi"
cpu: "500m"
---
apiVersion: v1
kind: Service
metadata:
name: brainy-service
spec:
selector:
app: brainy
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
```
Deploy:
```bash
kubectl apply -f brainy-deployment.yaml
```
## Production-Grade Setup
### 1. StatefulSet with Persistent Storage
```yaml
apiVersion: v1
kind: StorageClass
metadata:
name: brainy-storage
provisioner: kubernetes.io/aws-ebs # Or your cloud provider
parameters:
type: gp3
iopsPerGB: "10"
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: brainy
spec:
serviceName: brainy-headless
replicas: 3
selector:
matchLabels:
app: brainy
template:
metadata:
labels:
app: brainy
spec:
containers:
- name: brainy
image: soulcraft/brainy:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
value: production
- name: BRAINY_STORAGE_TYPE
value: filesystem
- name: BRAINY_STORAGE_PATH
value: /data
volumeMounts:
- name: data
mountPath: /data
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 3000
initialDelaySeconds: 5
periodSeconds: 5
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: brainy-storage
resources:
requests:
storage: 10Gi
```
### 2. Horizontal Pod Autoscaler
```yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: brainy-hpa
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: brainy
minReplicas: 2
maxReplicas: 100
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
behavior:
scaleUp:
stabilizationWindowSeconds: 60
policies:
- type: Percent
value: 100
periodSeconds: 60
scaleDown:
stabilizationWindowSeconds: 300
policies:
- type: Percent
value: 10
periodSeconds: 60
```
### 3. Ingress Configuration
```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: brainy-ingress
annotations:
kubernetes.io/ingress.class: nginx
cert-manager.io/cluster-issuer: letsencrypt-prod
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
spec:
tls:
- hosts:
- api.brainy.example.com
secretName: brainy-tls
rules:
- host: api.brainy.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: brainy-service
port:
number: 80
```
## Zero-Config Storage Options
### Option 1: S3-Compatible Storage (Recommended)
```yaml
apiVersion: v1
kind: Secret
metadata:
name: brainy-s3-credentials
type: Opaque
data:
access-key: <base64-encoded-key>
secret-key: <base64-encoded-secret>
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy
spec:
template:
spec:
containers:
- name: brainy
env:
- name: BRAINY_STORAGE_TYPE
value: s3
- name: AWS_ACCESS_KEY_ID
valueFrom:
secretKeyRef:
name: brainy-s3-credentials
key: access-key
- name: AWS_SECRET_ACCESS_KEY
valueFrom:
secretKeyRef:
name: brainy-s3-credentials
key: secret-key
- name: S3_BUCKET
value: brainy-data
- name: AWS_REGION
value: us-east-1
```
### Option 2: MinIO (Self-Hosted S3)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: minio
spec:
replicas: 1
selector:
matchLabels:
app: minio
template:
metadata:
labels:
app: minio
spec:
containers:
- name: minio
image: minio/minio:latest
args:
- server
- /data
env:
- name: MINIO_ROOT_USER
value: brainy
- name: MINIO_ROOT_PASSWORD
value: brainy123456
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
persistentVolumeClaim:
claimName: minio-pvc
---
apiVersion: v1
kind: Service
metadata:
name: minio-service
spec:
selector:
app: minio
ports:
- port: 9000
targetPort: 9000
```
### Option 3: Shared NFS Storage
```yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: brainy-nfs-pv
spec:
capacity:
storage: 100Gi
accessModes:
- ReadWriteMany
nfs:
server: nfs-server.example.com
path: /export/brainy
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: brainy-nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Gi
```
## High Availability Configuration
### 1. Pod Anti-Affinity
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy
spec:
template:
spec:
affinity:
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- brainy
topologyKey: kubernetes.io/hostname
```
### 2. Pod Disruption Budget
```yaml
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
name: brainy-pdb
spec:
minAvailable: 2
selector:
matchLabels:
app: brainy
```
### 3. Multi-Zone Deployment
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: brainy
spec:
template:
spec:
topologySpreadConstraints:
- maxSkew: 1
topologyKey: topology.kubernetes.io/zone
whenUnsatisfiable: DoNotSchedule
labelSelector:
matchLabels:
app: brainy
```
## Monitoring & Observability
### 1. Prometheus Metrics
```yaml
apiVersion: v1
kind: Service
metadata:
name: brainy-metrics
annotations:
prometheus.io/scrape: "true"
prometheus.io/port: "9090"
prometheus.io/path: "/metrics"
spec:
selector:
app: brainy
ports:
- name: metrics
port: 9090
targetPort: 9090
```
### 2. Grafana Dashboard
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: brainy-dashboard
data:
dashboard.json: |
{
"dashboard": {
"title": "Brainy Metrics",
"panels": [
{
"title": "Request Rate",
"targets": [
{
"expr": "rate(brainy_requests_total[5m])"
}
]
},
{
"title": "Response Time",
"targets": [
{
"expr": "histogram_quantile(0.95, brainy_response_time)"
}
]
}
]
}
}
```
### 3. Logging with Fluentd
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: fluentd-config
data:
fluent.conf: |
<source>
@type tail
path /var/log/containers/brainy*.log
pos_file /var/log/fluentd-brainy.log.pos
tag brainy.*
<parse>
@type json
</parse>
</source>
<match brainy.**>
@type elasticsearch
host elasticsearch.logging.svc.cluster.local
port 9200
logstash_format true
logstash_prefix brainy
</match>
```
## Security Best Practices
### 1. Network Policies
```yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: brainy-netpol
spec:
podSelector:
matchLabels:
app: brainy
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 3000
egress:
- to:
- namespaceSelector: {}
ports:
- protocol: TCP
port: 443 # HTTPS
- protocol: TCP
port: 9000 # MinIO/S3
```
### 2. Pod Security Policy
```yaml
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: brainy-psp
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
volumes:
- 'configMap'
- 'emptyDir'
- 'projected'
- 'secret'
- 'persistentVolumeClaim'
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
fsGroup:
rule: 'RunAsAny'
```
### 3. RBAC Configuration
```yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: brainy-sa
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: brainy-role
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["secrets"]
verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: brainy-rolebinding
subjects:
- kind: ServiceAccount
name: brainy-sa
roleRef:
kind: Role
name: brainy-role
apiGroup: rbac.authorization.k8s.io
```
## GitOps with ArgoCD
```yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: brainy
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/yourorg/brainy-k8s
targetRevision: HEAD
path: manifests
destination:
server: https://kubernetes.default.svc
namespace: brainy
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true
```
## Helm Chart Installation
```bash
# Add Brainy Helm repository
helm repo add brainy https://charts.brainy.io
helm repo update
# Install with custom values
cat > values.yaml << EOF
replicaCount: 3
image:
repository: soulcraft/brainy
tag: latest
pullPolicy: IfNotPresent
service:
type: LoadBalancer
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: api.brainy.example.com
paths:
- path: /
pathType: Prefix
resources:
limits:
cpu: 1000m
memory: 1Gi
requests:
cpu: 100m
memory: 256Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 100
targetCPUUtilizationPercentage: 70
storage:
type: s3
s3:
bucket: brainy-data
region: us-east-1
EOF
helm install brainy brainy/brainy -f values.yaml
```
## Cost Optimization
### 1. Spot/Preemptible Nodes
```yaml
apiVersion: v1
kind: NodePool
metadata:
name: brainy-spot-pool
spec:
nodeSelector:
node.kubernetes.io/lifecycle: spot
taints:
- key: spot
value: "true"
effect: NoSchedule
tolerations:
- key: spot
operator: Equal
value: "true"
effect: NoSchedule
```
### 2. Vertical Pod Autoscaler
```yaml
apiVersion: autoscaling.k8s.io/v1
kind: VerticalPodAutoscaler
metadata:
name: brainy-vpa
spec:
targetRef:
apiVersion: apps/v1
kind: Deployment
name: brainy
updatePolicy:
updateMode: "Auto"
resourcePolicy:
containerPolicies:
- containerName: brainy
minAllowed:
cpu: 100m
memory: 128Mi
maxAllowed:
cpu: 2
memory: 2Gi
```
## Troubleshooting
### Common Issues
1. **Pod CrashLoopBackOff**
```bash
kubectl logs -f pod/brainy-xxx
kubectl describe pod brainy-xxx
```
2. **Storage Issues**
```bash
kubectl get pv,pvc
kubectl describe pvc brainy-data
```
3. **Network Connectivity**
```bash
kubectl exec -it pod/brainy-xxx -- curl http://brainy-service/health
kubectl get endpoints brainy-service
```
4. **Memory Pressure**
```bash
kubectl top pods -l app=brainy
kubectl describe node
```
## Production Checklist
- [ ] High availability with multiple replicas
- [ ] Pod disruption budgets configured
- [ ] Resource limits and requests set
- [ ] Horizontal and vertical autoscaling enabled
- [ ] Persistent storage configured
- [ ] Network policies in place
- [ ] RBAC properly configured
- [ ] Monitoring and alerting setup
- [ ] Backup and disaster recovery plan
- [ ] Security scanning enabled
- [ ] GitOps deployment pipeline
## Support
- Documentation: https://brainy.soulcraft.ai/docs
- Helm Charts: https://github.com/soulcraft/brainy-charts
- Issues: https://github.com/soulcraft/brainy/issues
- Slack: https://brainy-community.slack.com

View file

@ -11,7 +11,7 @@ next:
- getting-started/quick-start
---
# Brainy and Cor — Explained Simply
# Brainy and Cortex — Explained Simply
*A plain-language guide for anyone who wants to understand what this thing actually does.*
@ -59,19 +59,19 @@ Brainy can narrow any result set down by exact labels or ranges in the same brea
- **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.
- **Safe experiments.** You can branch your entire knowledge base — like branching code in version control — make changes on the branch, and either keep them or throw them away without ever touching the original.
- **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?
## What is Cortex?
Cor is a turbocharger for Brainy.
Cortex 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.
Technically, Cortex 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.
@ -83,9 +83,9 @@ 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.
- **Analytics** that aren't even possible in pure JavaScript — real-time anomaly detection, streaming percentile estimates, approximate unique counts — become available because Cortex brings the native capabilities required to run them efficiently.
If Brainy is what makes knowledge fast, Cor is what makes Brainy feel instant.
If Brainy is what makes knowledge fast, Cortex is what makes Brainy feel instant.
---
@ -101,11 +101,11 @@ Most applications that need to store and search knowledge end up stitching toget
- 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.
Search, graph, filter, files, branches, and imports — unified in a single query.
### What Each Tool Is Missing
| Tool | Search | Graph | Filter | VFS | Time travel | Import |
| Tool | Search | Graph | Filter | VFS | Branch | Import |
|---|:---:|:---:|:---:|:---:|:---:|:---:|
| **Brainy** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| *— Vector databases —* | | | | | | |
@ -133,9 +133,9 @@ Brainy is the only row with every box checked. And it runs all of them in a sing
### 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.
Brainy scales from a single laptop to billions of entities without changing a line of code. Small datasets live in memory. Larger ones spill to disk. At cloud scale, Brainy uses S3-compatible storage and automatically shards across nodes — the same API the whole way. Up to ten billion entities is fully implemented today.
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.
Add Cortex 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.
---
@ -147,7 +147,7 @@ Add Cor and you also unlock memory-mapped storage — aggregate state lives dire
- **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.
- **Safe experiments**Let teams branch the knowledge base, experiment independently, and merge when ready — just like branching code.
- **Unified business platforms** — Combine booking, CRM, inventory, and analytics in one queryable knowledge graph with no sync pipeline.
### What Brainy is good at

View file

@ -0,0 +1,413 @@
# 🚀 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
```typescript
// 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 Brainy() // 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 Brainy({ mode: 'reader' })
// Optimized for read-heavy workloads
// 80% cache ratio, aggressive prefetch
// 1 hour TTL, minimal writes
```
### Writer Mode ✅
```typescript
const brain = new Brainy({ mode: 'writer' })
// Optimized for write-heavy workloads
// Large write buffers, batch writes
// Minimal caching, fast ingestion
```
### Hybrid Mode ✅
```typescript
const brain = new Brainy({ 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.getStats()
// 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 (future GPU support)
if (device === 'cuda') {
// Future: GPU acceleration 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 Brainy()
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 { Brainy } from 'brainy'
// Zero config required!
const brain = new Brainy()
await brain.init()
// Add data (auto-detects type)
await brain.add('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

@ -0,0 +1,693 @@
# Instant Fork™
**Clone your entire Brainy database in 1-2 seconds. Test anything without fear.**
---
## The Problem
You need to test a risky migration. Or run an A/B experiment. Or let your team fork production data for development.
**Traditional approach:**
```javascript
// Export entire database (20 minutes)
await database.export('backup.json')
// Modify data (cross your fingers)
await database.updateAll(riskyTransformation)
// If it fails... restore from backup (another 20 minutes)
// Total downtime: 40+ minutes
```
**The pain:**
- ❌ Slow (hours for large datasets)
- ❌ Risky (one mistake = data loss)
- ❌ Expensive (full copy = 2x storage)
- ❌ Complex (manual backup/restore workflows)
---
## The Solution
**Brainy's Instant Fork**:
```javascript
// Clone entire database in 1-2 seconds
const experiment = await brain.fork('test-migration')
// Test your changes safely
await experiment.updateAll(riskyTransformation)
// Works? Great! Use the experimental branch.
// Failed? Just discard.
if (success) {
// Make experiment the new main branch
await brain.checkout('test-migration')
} else {
await experiment.destroy() // No harm done
}
```
**Benefits:**
- ✅ **Fast**: 1-2 seconds even with millions of entities
- ✅ **Safe**: Original data untouched
- ✅ **Cheap**: 70-90% storage savings (content-addressable blobs)
- ✅ **Simple**: One line of code
---
## How It Works
Brainy uses **Snowflake-style Copy-on-Write (COW)** for instant forking:
### Architecture
1. **HNSW Index COW** (The Performance Bottleneck):
- **Shallow Copy**: O(1) Map reference copying (~10ms for 1M+ nodes)
- **Lazy Deep Copy**: Nodes copied only when modified (`ensureCOW()`)
- **Write Isolation**: Fork modifications don't affect parent
- **Implementation**: `src/hnsw/hnswIndex.ts` lines 2088-2150
2. **Metadata & Graph Indexes** (Fast Rebuild):
- **Rebuild from Storage**: < 500ms total for both indexes
- **Shared Storage**: Both indexes read from COW-enabled storage layer
- **Acceptable Overhead**: Fast enough not to need in-memory COW
3. **Storage Layer** (Shared):
- **RefManager**: Manages branch references
- **BlobStorage**: Content-addressable with deduplication
- **All Adapters**: Memory, FileSystem, S3, R2, GCS, Azure, OPFS
**Performance**:
- **Fork time**: < 100ms @ 10K entities (MEASURED in tests)
- **Storage overhead**: 10-20% (shared blobs, only changed data duplicated)
- **Memory overhead**: 10-20% (shared HNSW nodes, only modified nodes duplicated)
**Technical Details**:
```typescript
// Shallow copy HNSW (instant)
clone.index.enableCOW(this.index) // O(1) Map reference copy
// Fast rebuild small indexes from shared storage
clone.metadataIndex = new MetadataIndexManager(clone.storage) // <100ms
clone.graphIndex = new GraphAdjacencyIndex(clone.storage) // <500ms
```
---
## Basic Usage
### 1. Create a Fork
```javascript
const brain = new Brainy({ storage: { adapter: 'memory' } })
await brain.init()
// Add some data
await brain.add({ noun: 'user', data: { name: 'Alice' } })
await brain.add({ noun: 'user', data: { name: 'Bob' } })
// Fork instantly
const fork = await brain.fork('experiment')
console.log('Fork created!', fork)
```
**What happens:**
- Original brain: unchanged
- Fork: exact copy at this moment
- Changes in fork: don't affect original
- Changes in original: don't affect fork
### 2. Work with the Fork
```javascript
// Fork is a full Brainy instance
await fork.add({ noun: 'user', data: { name: 'Charlie' } })
// All APIs work
const users = await fork.find({ noun: 'user' })
console.log(users.length) // 3 (Alice, Bob, Charlie)
// Original brain unchanged
const originalUsers = await brain.find({ noun: 'user' })
console.log(originalUsers.length) // 2 (Alice, Bob)
```
### 3. Discard When Done
```javascript
// Clean up fork when finished
await fork.destroy()
// Note: fork() creates independent branches for experimentation
// Use checkout() to switch between branches or keep them separate forever
```
---
## Use Cases
### 1. Safe Migrations
**Problem**: Migrating data is risky. One mistake = data corruption.
**Solution**: Test migration in fork first.
```javascript
const brain = new Brainy({ storage: { adapter: 'filesystem', path: './data' } })
await brain.init()
// Fork production data
const migration = await brain.fork('migration-test')
// Run migration on fork
const users = await migration.find({ noun: 'user' })
for (const user of users) {
await migration.update(user.id, {
email: user.data.email.toLowerCase(), // Normalize emails
verified: user.data.verified ?? false // Add missing field
})
}
// Validate migration
const allValid = (await migration.find({ noun: 'user' }))
.every(u => u.data.email === u.data.email.toLowerCase())
if (allValid) {
console.log('✅ Migration safe! Apply changes to production brain')
// Apply validated migration to main brain
const users = await brain.find({ noun: 'user' })
for (const user of users) {
await brain.update(user.id, {
email: user.data.email.toLowerCase(),
verified: user.data.verified ?? false
})
}
await migration.destroy()
} else {
console.log('❌ Migration failed! Discarding fork.')
await migration.destroy()
}
```
### 2. A/B Testing
**Problem**: Need to test two different algorithms on the same data.
**Solution**: Create two forks, run experiments in parallel.
```javascript
const brain = new Brainy({ storage: { adapter: 's3', bucket: 'production' } })
await brain.init()
// Create two variants
const variantA = await brain.fork('variant-a') // Control
const variantB = await brain.fork('variant-b') // Test
// Run different algorithms
await variantA.processWithAlgorithm('current')
await variantB.processWithAlgorithm('improved')
// Compare results
const metricsA = await variantA.getMetrics()
const metricsB = await variantB.getMetrics()
console.log('Variant A accuracy:', metricsA.accuracy)
console.log('Variant B accuracy:', metricsB.accuracy)
// Choose winner and update main brain
if (metricsB.accuracy > metricsA.accuracy) {
console.log('B wins! Apply algorithm B to production')
await brain.processWithAlgorithm('improved')
await variantA.destroy()
await variantB.destroy()
} else {
console.log('A wins! Keeping current algorithm.')
await variantA.destroy()
await variantB.destroy()
}
```
### 3. Distributed Development
**Problem**: Multiple developers need to work with production data.
**Solution**: Each developer gets their own fork.
```javascript
// Main production brain
const production = new Brainy({ storage: { adapter: 's3', bucket: 'prod' } })
await production.init()
// Alice's feature branch
const aliceBranch = await production.fork('alice-feature-x')
// Bob's feature branch
const bobBranch = await production.fork('bob-feature-y')
// Both work independently (zero conflicts!)
await aliceBranch.add({ noun: 'feature', data: { name: 'X' } })
await bobBranch.add({ noun: 'feature', data: { name: 'Y' } })
// When ready, manually copy validated changes to production
await production.add({ noun: 'feature', data: { name: 'X' } })
await production.add({ noun: 'feature', data: { name: 'Y' } })
await aliceBranch.destroy()
await bobBranch.destroy()
```
### 4. Instant Backup/Restore
**Problem**: Need fast backups before risky operations.
**Solution**: Fork as backup.
```javascript
const brain = new Brainy({ storage: { adapter: 'memory' } })
await brain.init()
// Work with production data
await brain.add({ noun: 'important', data: { value: 'critical' } })
// Instant backup (1-2 seconds)
const backup = await brain.fork('backup-before-delete')
// Do risky operation
const entities = await brain.find({ noun: 'important' })
await brain.delete(entities[0].id)
// Oops! Need to restore
// Just discard current, use backup
await brain.destroy()
// Restore from backup (or switch to backup branch)
const restored = await backup.fork('main')
console.log('✅ Data restored!')
```
### 5. Snapshot Testing
**Problem**: Need to test code against specific data states.
**Solution**: Create fork snapshots before making changes.
```javascript
const brain = new Brainy({ storage: { adapter: 'memory' } })
await brain.init()
// Create initial state
await brain.add({ noun: 'doc', data: { version: 1 } })
// Take snapshot before changes
const snapshot = await brain.fork('before-update')
// Make changes to main brain
const docs = await brain.find({ noun: 'doc' })
await brain.update(docs[0].id, { version: 2 })
// Test against original state
const originalData = await snapshot.find({ noun: 'doc' })
console.log(originalData[0].data.version) // 1 (original state!)
// Clean up
await snapshot.destroy()
// Note: Time-travel queries (asOf) Planned
```
---
## Advanced Features
### Fork Options
```javascript
// Custom branch name
const fork1 = await brain.fork('my-experiment')
// Auto-generated name (uses timestamp)
const fork2 = await brain.fork() // 'fork-1635789012345'
// Fork with metadata (for tracking)
const fork3 = await brain.fork('test', {
author: 'Alice',
message: 'Testing new feature'
})
```
### Branch Management
Full branch management now available!
```javascript
// List all branches
const branches = await brain.listBranches()
console.log(branches) // ['main', 'experiment', 'test']
// Get current branch
const current = await brain.getCurrentBranch()
console.log(current) // 'main'
// Switch between branches
await brain.checkout('experiment')
// Delete a branch
await brain.deleteBranch('old-experiment')
```
### Commit Tracking
Git-style commit tracking!
```javascript
// Create a commit (snapshot of current state)
await brain.add({ type: 'user', data: { name: 'Alice' } })
const commitHash = await brain.commit({
message: 'Add Alice user',
author: 'dev@example.com'
})
console.log(commitHash) // 'a3f2c1b9...'
```
### Commit History
View commit history!
```javascript
// Get commit history for current branch
const history = await brain.getHistory({ limit: 10 })
history.forEach(commit => {
console.log(`${commit.hash}: ${commit.message}`)
console.log(` By: ${commit.author} at ${new Date(commit.timestamp)}`)
})
## Performance Characteristics
### Fork Speed (Measured)
| Entities | Traditional Copy | Brainy Fork | Speedup |
|----------|------------------|-------------|---------|
| 1,000 | 2-5 seconds | 0.5 seconds | 4-10x |
| 10,000 | 20-40 seconds | 0.8 seconds | 25-50x |
| 100,000 | 3-5 minutes | 1.2 seconds | 150-250x |
| 1,000,000 | 30-60 minutes | 1.8 seconds | 1000-2000x |
### Storage Overhead
```
Scenario: 1M entities, 10 forks
Traditional: 10 full copies = 80GB × 10 = 800GB
Brainy: 1 base + 10% changes = 80GB + 8GB = 88GB
Savings: 89% less storage
```
### Memory Overhead
```
Scenario: 1M entities in memory
Traditional fork: 2x memory (10GB → 20GB)
Brainy fork: 1.2x memory (10GB → 12GB)
Savings: 40% less memory
```
---
## Zero Configuration
**Fork is enabled by default. No setup required.**
```javascript
// This is all you need:
const brain = new Brainy({ storage: { adapter: 'memory' } })
await brain.init()
// Fork is ready to use:
const fork = await brain.fork()
// That's it!
```
**Automatic optimizations:**
- ✅ Compression: zstd for metadata, none for vectors (automatic)
- ✅ Deduplication: content-addressable (automatic)
- ✅ Caching: LRU with memory limits (automatic)
- ✅ Garbage collection: cleanup unused blobs (automatic)
---
## Integration with Brainy Features
### Works with All Storage Adapters
```javascript
// Memory
await new Brainy({ storage: { adapter: 'memory' } }).fork()
// FileSystem
await new Brainy({ storage: { adapter: 'filesystem', path: './data' } }).fork()
// S3
await new Brainy({ storage: { adapter: 's3', bucket: 'my-data' } }).fork()
// All adapters supported: Memory, OPFS, FileSystem, S3, R2, GCS, Azure, TypeAware
```
### Works with find(), VFS, Triple Intelligence
```javascript
const brain = new Brainy({
storage: { adapter: 'memory' },
vfs: { enabled: true },
intelligence: { enabled: true }
})
await brain.init()
// Create VFS files
await brain.vfs.writeFile('/project/README.md', '# My Project')
// Add entities
await brain.add({ noun: 'user', data: { name: 'Alice' } })
// Fork everything
const fork = await brain.fork('test')
// All features work on fork:
await fork.vfs.readFile('/project/README.md') // ✅ VFS
await fork.find({ noun: 'user' }) // ✅ find()
await fork.query('users named Alice') // ✅ Triple Intelligence
```
### Works at Billion Scale
```javascript
// Tested at 1M entities, extrapolates to 1B
const brain = new Brainy({
storage: { adapter: 'gcs', bucket: 'billion-scale' },
hnsw: { typeAware: true } // 87% memory reduction
})
await brain.init()
// Fork 1B entities: still < 2 seconds
const fork = await brain.fork()
```
---
## FAQ
### Q: Does fork() copy all data?
**A: No.** Fork uses copy-on-write (COW). Unchanged data is shared between parent and fork via content-addressable blobs. Only modified data creates new blobs.
### Q: Is fork() safe for production?
**A: Yes.** Fork is battle-tested at scale. Uses proven Git-like COW technology. Zero risk to original data.
### Q: Does it work with all storage adapters?
**A: Yes.** Fork works with Memory, OPFS, FileSystem, S3, R2, GCS, Azure, and TypeAware adapters.
### Q: What happens to the fork if I modify the original?
**A: Nothing.** Fork is isolated. Changes in parent don't affect fork. Changes in fork don't affect parent.
### Q: Can I merge forks back to main?
**A: Use the "experimental branching" paradigm.** Instead of merging, either (1) make your experimental branch the new main with `checkout()`, or (2) manually copy specific entities you want. See CHANGELOG v6.0.0 for migration patterns.
### Q: How long are forks kept?
**A: Forever (or until you delete them).** Forks persist like branches. Delete with `fork.destroy()` or set retention policy (Enterprise).
### Q: What's the performance impact?
**A: Minimal.** Fork time: 1-2 seconds @ 1M entities. Storage: 10-20% overhead. Memory: 20-40% overhead.
### Q: Can I fork a fork?
**A: Yes.** Fork anything, anytime. Create branch trees as deep as needed.
---
## Comparison to Other Databases
### vs PostgreSQL
**PostgreSQL:**
```sql
-- Create copy (full table scan, minutes)
CREATE TABLE users_backup AS SELECT * FROM users;
-- Modify (risky!)
UPDATE users SET email = LOWER(email);
-- Restore (if failed)
DROP TABLE users;
ALTER TABLE users_backup RENAME TO users;
```
**Brainy:**
```javascript
const fork = await brain.fork('test')
await fork.updateAll({ email: (u) => u.email.toLowerCase() })
if (success) await brain.checkout('test') // Make test branch active
else await fork.destroy()
```
**Winner: Brainy** (1000x faster, safer)
### vs MongoDB
**MongoDB:**
```javascript
// No native fork/clone
// Must manually export/import
// Export (slow)
mongoexport --db mydb --collection users --out users.json
// Import to new collection (slow)
mongoimport --db mydb --collection users_backup --file users.json
```
**Brainy:**
```javascript
const fork = await brain.fork() // Done!
```
**Winner: Brainy** (100x faster, built-in)
### vs Pinecone/Weaviate
**Pinecone/Weaviate:**
```
❌ No fork/clone feature at all
❌ Manual backup/restore only
❌ Downtime required for testing
```
**Brainy:**
```javascript
✅ Fork in 1-2 seconds
✅ Zero downtime
✅ Zero risk
```
**Winner: Brainy** (only vector DB with instant fork)
---
## What's Implemented vs. What's Next
### ✅ Available in - ✅ `fork()` - Instant clone in <100ms
- ✅ `listBranches()` - List all forks
- ✅ `getCurrentBranch()` - Get active branch
- ✅ `checkout()` - Switch between branches
- ✅ `deleteBranch()` - Delete branches
- ✅ `commit()` - Create state snapshots
- ✅ `getHistory()` - View commit history
### 🔮 Planned for:
**Temporal Features:**
- `asOf(timestamp)` - Query data at specific time (✅ available)
- `rollback(commitHash)` - Restore to previous state
- Full audit trail for all changes
These features require additional temporal infrastructure and are being carefully designed
---
## CLI Support
All fork/merge/commit features are available via CLI:
```bash
# Fork (instant clone)
brainy fork feature-x --message "Testing new feature" --author "dev@example.com"
# List branches
brainy branch list
# Switch branches
brainy checkout feature-x
# Create commit
brainy commit --message "Add new feature" --author "dev@example.com"
# View history
brainy history --limit 10
# Merge branches
brainy merge feature-x main --strategy last-write-wins
# Delete branch
brainy branch delete old-feature --force
```
## Try It Now
```bash
npm install @soulcraft/brainy
```
```javascript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
await brain.add({ noun: 'test', data: { value: 1 } })
const fork = await brain.fork('experiment')
console.log('Fork created in < 2 seconds! 🚀')
```
**Zero config. Zero complexity. Pure power.**
---
## Learn More
- [COW Architecture](../architecture/copy-on-write.md)
- [Performance Benchmarks](../benchmarks/fork-performance.md)
- [Enterprise Features](../enterprise/temporal-cloning.md)
- [API Reference](../api/fork.md)
---
**Brainy** | [GitHub](https://github.com/soulcraftlabs/brainy) | [npm](https://npmjs.com/package/@soulcraft/brainy)

View file

@ -0,0 +1,296 @@
# 🚀 Brainy - Production-Ready Features
> **Status**: All features listed here are IMPLEMENTED and TESTED
## 📊 Performance Metrics
- **Search Latency**: <10ms for 10,000+ items
- **Write Throughput**: 10,000+ ops/sec
- **Memory Efficiency**: <500MB for 10K items
- **Concurrent Operations**: 100+ simultaneous operations
## 🧠 Core Intelligence Features
### Triple Intelligence System ✅
Unified query system combining three types of intelligence:
```typescript
const results = await brain.find({
like: 'AI research', // Vector similarity search
where: { year: 2024 }, // Metadata filtering
connected: { to: authorId } // Graph relationships
})
```
### Intelligent Type Mapping ✅
Prevents semantic degradation by intelligently inferring types:
```typescript
// Automatically infers 'person' from email field
brain.add({ name: "John", email: "john@example.com" }, 'entity')
// → Stored as type: 'person', not generic 'entity'
```
### Neural Query Understanding ✅
- 220+ embedded patterns for intent detection
- Natural language query processing
- Automatic query optimization
- Pattern-based query rewriting
## 🏢 Enterprise Features
### Distributed Coordination ✅
Raft consensus for multi-node deployments:
```typescript
import { DistributedCoordinator } from '@soulcraft/brainy'
const coordinator = createCoordinator({
nodeId: 'node-1',
peers: ['node-2', 'node-3'],
electionTimeout: 500
})
// Automatic leader election and failover
```
### Horizontal Sharding ✅
Consistent hashing for data distribution:
```typescript
import { ShardManager } from '@soulcraft/brainy'
const shards = createShardManager({
nodes: ['node-1', 'node-2', 'node-3'],
replicationFactor: 2,
virtualNodes: 150
})
// Automatic shard rebalancing on node changes
```
### Read/Write Separation ✅
Primary-replica architecture for scale:
```typescript
import { ReadWriteSeparation } from '@soulcraft/brainy'
const replication = createReadWriteSeparation({
role: 'auto', // Automatic primary/replica detection
consistencyLevel: 'strong', // or 'eventual'
readPreference: 'nearest'
})
```
### Cross-Instance Cache Sync ✅
Version vector-based cache synchronization:
```typescript
import { CacheSync } from '@soulcraft/brainy'
const cache = createCacheSync({
nodeId: 'node-1',
syncInterval: 100,
conflictResolution: 'version-vector'
})
```
## 🔐 Security & Compliance
### Rate Limiting ✅
Per-operation configurable limits:
```typescript
const rateLimiter = createRateLimitAugmentation({
limits: {
searches: 1000, // per minute
writes: 100,
reads: 5000,
deletes: 50
}
})
```
### Audit Logging ✅
Comprehensive operation tracking:
```typescript
const auditLogger = createAuditLogAugmentation({
logLevel: 'detailed',
retention: 90, // days
includeMetadata: true
})
// Query audit logs
const logs = auditLogger.queryLogs({
operation: 'add',
startTime: Date.now() - 3600000
})
```
## 📦 Storage & Persistence
Full crash recovery and replay:
```typescript
enabled: true,
checkpointInterval: 1000,
maxLogSize: 100 * 1024 * 1024 // 100MB
}))
```
### Multi-Tenancy ✅
Service-based data isolation:
```typescript
// Isolated data per service
await brain.add(data, 'document', { service: 'tenant-1' })
await brain.find('query', { service: 'tenant-1' })
```
### Write-Only Mode ✅
For dedicated write nodes:
```typescript
const brain = new Brainy({
mode: 'write-only',
storage: 's3://bucket/path'
})
```
## 🚀 Performance Features
### Batch Operations ✅
Optimized bulk processing:
```typescript
// Parallel processing with automatic batching
await brain.addMany(items) // <10ms per item
await brain.updateMany(updates)
await brain.deleteMany(filters)
```
### Request Deduplication ✅
Automatic duplicate request handling:
```typescript
brain.use(new RequestDeduplicatorAugmentation())
// Identical concurrent requests return same result
```
### Smart Caching ✅
Intelligent search result caching:
```typescript
brain.use(new CacheAugmentation({
maxSize: 10000,
ttl: 300000, // 5 minutes
invalidateOnWrite: true
}))
```
## 🔄 Data Processing
### Entity Registry ✅
Bloom filter-based deduplication:
```typescript
brain.use(new EntityRegistryAugmentation())
// Handles millions of entities with minimal memory
```
### Neural Import ✅
Intelligent data import with type inference:
```typescript
await brain.import({
source: 'data.json',
autoDetectTypes: true,
batchSize: 1000
})
```
### Streaming Pipeline ✅
Real-time data processing:
```typescript
brain.stream()
.pipe(transform)
.pipe(enrich)
.pipe(brain.writer())
```
## 📊 Analytics & Monitoring
### Metrics Collection ✅
Built-in performance metrics:
```typescript
const metrics = brain.getMetrics()
// {
// operations: { add: 1000, find: 5000 },
// performance: { p95: 8, p99: 12 },
// cache: { hits: 4500, misses: 500 }
// }
```
### Health Monitoring ✅
Automatic health checks:
```typescript
const health = brain.getHealth()
// {
// status: 'healthy',
// storage: 'connected',
// memory: { used: 245, limit: 512 }
// }
```
## 🛠️ Developer Experience
### Zero Configuration ✅
Works out of the box:
```typescript
import Brainy from '@soulcraft/brainy'
const brain = new Brainy() // Auto-configures everything
```
### TypeScript First ✅
Full type safety and inference:
```typescript
// Types are automatically inferred
const results = await brain.find<MyType>('query')
```
### Augmentation System ✅
Extensible plugin architecture:
```typescript
class CustomAugmentation extends BaseAugmentation {
execute(operation, params, next) {
// Your custom logic
return next()
}
}
```
## 🔧 Operational Features
### Graceful Shutdown ✅
Clean shutdown with data persistence:
```typescript
process.on('SIGTERM', async () => {
await brain.shutdown() // Saves all pending data
})
```
### Hot Reload ✅
Configuration updates without restart:
```typescript
brain.updateConfig({
cache: { enabled: false }
})
```
### Backup & Restore ✅
Full data backup capabilities:
```typescript
await brain.backup('backup.bin')
await brain.restore('backup.bin')
```
## 📈 Proven at Scale
- **10,000+ items**: Sub-10ms search
- **1M+ operations**: Stable memory usage
- **100+ concurrent users**: No performance degradation
- **Multi-node clusters**: Automatic failover
## 🚫 NOT Implemented (Planned)
These features are documented but NOT yet implemented:
- GraphQL API (use REST API instead)
- Kubernetes operators (use Docker)
- Some distributed features require manual configuration
---
*Last Updated: Latest Version*
*All features listed above are production-ready and tested*

View file

@ -11,13 +11,7 @@ No batch jobs. No scheduled recalculations. Aggregates stay current with every w
**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.
or after the data exists — including when a persisted brain reopens already populated.
## Quick Start
@ -493,7 +487,7 @@ Aggregate definitions and running state are automatically persisted:
## Native Acceleration
When [Cor](https://www.npmjs.com/package/@soulcraft/cor) is installed as a plugin, the aggregation engine automatically uses Rust-accelerated computation:
When [Cortex](https://github.com/soulcraftlabs/cortex) 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
@ -502,7 +496,7 @@ When [Cor](https://www.npmjs.com/package/@soulcraft/cor) is installed as a plugi
```typescript
const brain = new Brainy({
plugins: ['@soulcraft/cor']
plugins: ['@soulcraft/cortex']
})
await brain.init()
@ -583,7 +577,7 @@ brain.defineAggregate({
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:
With Cortex native acceleration:
| Operation | Throughput | Latency |
|-----------|-----------|---------|

View file

@ -0,0 +1,406 @@
# Distributed Brainy System Guide
## Overview
Brainy introduces a groundbreaking **zero-configuration distributed system** that transforms how vector databases scale. Unlike traditional distributed databases that require complex setup (Consul, etcd, Zookeeper), Brainy uses your existing storage (S3, GCS, R2) as the coordination layer.
## Key Innovation: Storage-Based Coordination
Instead of requiring separate infrastructure for coordination, Brainy leverages your storage backend:
```typescript
// Traditional distributed database setup:
// ❌ Setup Consul/etcd
// ❌ Configure node discovery
// ❌ Setup health checks
// ❌ Configure sharding
// ❌ Setup replication
// Brainy distributed setup:
const brain = new Brainy({
storage: {
type: 's3',
options: { bucket: 'my-data' }
},
distributed: true // ✅ That's it!
})
```
## Real-World Scenarios
### 1. Processing Multiple Streaming Data Sources (Bluesky, Twitter, etc.)
**Problem**: You need to ingest millions of posts from multiple social media firehoses simultaneously while maintaining search performance.
**Traditional Approach**:
- Separate ingestion and search clusters
- Complex queue systems (Kafka, RabbitMQ)
- Manual sharding configuration
- Complicated backpressure handling
**Brainy Solution**:
```typescript
// Node 1: Bluesky Ingestion
const ingestionNode1 = new Brainy({
storage: { type: 's3', options: { bucket: 'social-data' }},
distributed: true,
writeOnly: true // Optimized for writes
})
// Continuously ingest Bluesky firehose
blueskyStream.on('post', async (post) => {
await ingestionNode1.add({
content: post.text,
author: post.author,
timestamp: post.createdAt,
platform: 'bluesky'
}, 'social-post')
})
// Node 2: Twitter Ingestion (separate machine)
const ingestionNode2 = new Brainy({
storage: { type: 's3', options: { bucket: 'social-data' }},
distributed: true,
writeOnly: true
})
// Node 3-5: Search nodes (auto-balanced)
const searchNode = new Brainy({
storage: { type: 's3', options: { bucket: 'social-data' }},
distributed: true,
readOnly: true // Optimized for queries
})
// Search across ALL data from ALL sources
const results = await searchNode.find('AI trends', 100)
// Automatically queries all shards across all nodes!
```
**Benefits**:
- **Auto-sharding**: Data automatically distributed by content hash
- **No bottlenecks**: Each ingestion node writes directly to storage
- **Live search**: Search nodes see new data immediately
- **Auto-scaling**: Add nodes anytime, data rebalances automatically
### 2. Multi-Tenant SaaS Application
**Problem**: Each customer needs isolated, fast search across their documents, with ability to scale per customer.
**Brainy Solution**:
```typescript
// Spin up dedicated nodes per large customer
const enterpriseNode = new Brainy({
storage: {
type: 's3',
options: {
bucket: 'customer-data',
prefix: 'customer-123/' // Isolated data
}
},
distributed: true
})
// Shared nodes for smaller customers
const sharedNode = new Brainy({
storage: {
type: 's3',
options: { bucket: 'shared-customers' }
},
distributed: true
})
// Domain-based sharding ensures customer data stays together
await sharedNode.add(document, 'document', {
customerId: 'cust-456', // Used for shard assignment
domain: 'customer-456' // Keeps related data together
})
```
### 3. Global Knowledge Graph with Local Inference
**Problem**: Building a knowledge graph that needs both global connectivity and local LLM inference.
**Brainy Solution**:
```typescript
// Edge nodes near users (with GPU)
const edgeNode = new Brainy({
storage: { type: 's3', options: { bucket: 'knowledge-graph' }},
distributed: true,
models: {
embed: { model: 'BAAI/bge-base-en-v1.5' },
chat: { model: 'meta-llama/Llama-3.2-3B-Instruct' }
}
})
// Central nodes for graph operations
const graphNode = new Brainy({
storage: { type: 's3', options: { bucket: 'knowledge-graph' }},
distributed: true,
augmentations: ['graph', 'triple-intelligence']
})
// Local inference with global knowledge
const context = await edgeNode.find(userQuery, 10)
const response = await edgeNode.chat(userQuery, { context })
// Graph traversal across all nodes
const connections = await graphNode.traverse({
start: 'concept:AI',
depth: 3,
relationship: 'related-to'
})
```
## Competitive Advantages
### vs. Pinecone/Weaviate/Qdrant
| Feature | Traditional Vector DBs | Brainy Distributed |
|---------|----------------------|-------------------|
| Setup Complexity | High (k8s, operators) | Zero (just storage) |
| Minimum Nodes | 3-5 for HA | 1 (scale as needed) |
| Coordination | External (etcd, Consul) | Built-in (via storage) |
| Data Locality | Random sharding | Domain-aware sharding |
| Query Planning | Basic | Triple Intelligence |
| Cost at Scale | High (always-on clusters) | Low (scale to zero) |
### vs. Neo4j/ArangoDB (Graph Databases)
| Feature | Graph Databases | Brainy Distributed |
|---------|----------------|-------------------|
| Vector Search | Bolt-on/Limited | Native HNSW |
| Embedding Generation | External | Built-in (30+ models) |
| Distributed Transactions | Complex/Slow | Eventually consistent |
| Natural Language | No | Native (Triple Intelligence) |
| Setup | Very Complex | Zero config |
### vs. Elasticsearch/OpenSearch
| Feature | Elasticsearch | Brainy Distributed |
|---------|--------------|-------------------|
| Vector Support | Added later (slow) | Native (fast HNSW) |
| Cluster Management | Complex (master nodes) | Automatic (via storage) |
| Shard Rebalancing | Manual/Risky | Automatic/Safe |
| Memory Usage | Very High | Efficient |
| Query Language | Complex DSL | Natural language |
## Innovative Features
### 1. Domain-Aware Sharding
Unlike hash-based sharding, Brainy understands data relationships:
```typescript
// Documents about the same topic stay on the same shard
await brain.add(doc1, 'document', { domain: 'physics' })
await brain.add(doc2, 'document', { domain: 'physics' })
// Both documents on same shard = faster related queries
// Customer data stays together
await brain.add(order, 'order', { customerId: 'cust-123' })
await brain.add(invoice, 'invoice', { customerId: 'cust-123' })
// Same customer = same shard = better locality
```
### 2. Streaming Shard Migration
Zero-downtime data movement between nodes:
```typescript
// Automatically triggered when nodes join/leave
// Uses HTTP streaming for efficiency
// Validates data integrity
// Atomic ownership transfer
// No query downtime!
```
### 3. Storage-Based Consensus
No Raft/Paxos complexity:
```typescript
// Leader election via storage atomic operations
// Health monitoring via storage heartbeats
// Configuration consensus via storage CAS
// No split-brain issues!
```
### 4. Intelligent Query Planning
The distributed query planner understands:
- Which shards contain relevant data
- Node health and latency
- Data locality and caching
- Triple Intelligence scoring
```typescript
// Automatically optimizes query execution
const results = await brain.find('quantum physics')
// Planner knows:
// - Physics domain → shard-3
// - Node-2 has shard-3 cached
// - Route query to node-2
// - Merge results with Triple Intelligence
```
## Performance Characteristics
### Scalability
- **Horizontal**: Add nodes anytime
- **Vertical**: Nodes can differ in size
- **Geographic**: Nodes can be globally distributed
- **Elastic**: Scale to zero when idle
### Throughput
- **Writes**: Linear scaling with nodes
- **Reads**: Sub-linear (due to caching)
- **Mixed**: Read/write optimized nodes
### Latency
- **Local queries**: ~10ms p50
- **Distributed queries**: ~50ms p50
- **Shard migration**: Streaming (no bulk pause)
## Use Cases Where Brainy Excels
### ✅ Perfect For:
1. **Multi-source data ingestion** (social media, logs, events)
2. **Global search applications** (distributed teams)
3. **Multi-tenant SaaS** (customer isolation)
4. **Knowledge graphs** (with vector search)
5. **Edge AI applications** (local inference, global knowledge)
6. **Document intelligence** (contracts, research papers)
7. **Real-time analytics** (streaming + search)
### ⚠️ Consider Alternatives For:
1. **Strong consistency requirements** (use PostgreSQL)
2. **Sub-millisecond latency** (use Redis)
3. **Complex transactions** (use traditional RDBMS)
4. **Purely structured data** (use columnar stores)
## Migration from Other Systems
### From Pinecone/Weaviate:
```typescript
// Your existing vector search still works
const results = await brain.search(embedding, 10)
// But now you can scale horizontally!
// And add graph relationships!
// And use natural language!
```
### From Elasticsearch:
```typescript
// Import your documents
await brain.import('./elasticsearch-export.json')
// Queries are simpler
const results = await brain.find('user query')
// No complex DSL needed!
```
### From Neo4j:
```typescript
// Import your graph
await brain.importGraph('./neo4j-export.cypher')
// Now with vector search!
const similar = await brain.find('concepts like quantum computing')
```
## Deployment Patterns
### 1. Start Simple, Scale Later
```typescript
// Day 1: Single node
const brain = new Brainy({ storage: 's3' })
// Month 2: Growing data, add distribution
const brain = new Brainy({
storage: 's3',
distributed: true // Just add this!
})
// Month 6: Multiple nodes auto-balance
// No migration needed!
```
### 2. Geographic Distribution
```typescript
// US Node
const usNode = new Brainy({
storage: { region: 'us-east-1' },
distributed: true
})
// EU Node
const euNode = new Brainy({
storage: { region: 'eu-west-1' },
distributed: true
})
// They automatically coordinate!
```
### 3. Specialized Nodes
```typescript
// GPU nodes for embedding
const embedNode = new Brainy({
distributed: true,
writeOnly: true,
models: { embed: 'large-model' }
})
// CPU nodes for search
const searchNode = new Brainy({
distributed: true,
readOnly: true
})
```
## Monitoring & Operations
### Health Checks
```typescript
const health = await brain.getClusterHealth()
// {
// nodes: 5,
// healthy: 5,
// shards: 16,
// status: 'green'
// }
```
### Shard Distribution
```typescript
const shards = await brain.getShardDistribution()
// Shows which nodes own which shards
```
### Migration Status
```typescript
const migrations = await brain.getActiveMigrations()
// Shows ongoing shard movements
```
## Conclusion
Brainy's distributed system is **production-ready** and offers:
1. **True zero-configuration** - Just add `distributed: true`
2. **Storage-based coordination** - No external dependencies
3. **Intelligent sharding** - Domain-aware data placement
4. **Automatic operations** - Rebalancing, failover, scaling
5. **Unified interface** - Vector + Graph + Document + LLM
This is not just another distributed database. It's a fundamental rethinking of how distributed systems should work in the cloud era.
## Next Steps
1. [Try the distributed quick start](./distributed-quickstart.md)
2. [Read the architecture deep dive](../architecture/distributed-storage.md)
3. [View benchmarks](../benchmarks/distributed-performance.md)
4. [Deploy to production](./production-deployment.md)

View file

@ -165,28 +165,32 @@ 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 Brainy({
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

View file

@ -5,7 +5,7 @@ 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.
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. Covers vectors, edge policy, conflict handling, and id remapping.
next:
- guides/subtypes-and-facets
- api/README
@ -13,46 +13,38 @@ next:
# 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.
`brain.data()` exposes a **portable graph export/import** API. One method serializes a
graph — an item, a collection, a connected neighbourhood, a VFS subtree, a predicate
match, or the whole brain — into a single versioned JSON document (`PortableGraph`); the
inverse restores it.
```typescript
const graph = await brain.export() // whole brain → PortableGraph
await brain.import(graph) // restore (merge by id, re-embed if no vectors)
const data = await brain.data()
const graph = await data.export() // whole brain → PortableGraph
await data.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)
```
It is **portable** (human-readable JSON), **versioned** (`formatVersion`, so a 7.x export
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 graphs, cross-environment
moves, and version upgrades.
## 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) |
| A portable, partial-or-whole, cross-version graph document | **`brain.data().export()` / `import()`** (this guide) |
| 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).
The two are different operations that happen to share a verb: `import(file)` parses a
foreign document into new entities; `data().import(graph)` restores a graph this API
exported.
## Exporting
```typescript
brain.export(selector?, options?): Promise<PortableGraph>
// (also on any Db: brain.now().export(...), (await brain.asOf(g)).export(...))
export(selector?, options?): Promise<PortableGraph>
```
### Selectors — *what* to export
@ -65,19 +57,23 @@ Omit the selector to export the whole brain. Otherwise pick a node set:
| 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 }` |
| Everything matching a predicate | `{ type, subtype, where, service }` |
| The whole brain | *(omit)* |
The selector reuses `find()`'s grammar — *"export what `find()` would match, minus ranking
and limit."* Structural and predicate selectors **compose**:
and limit."* Structural and predicate selectors **compose** — a structural selector picks
the nodes, and predicate keys then filter them:
```typescript
// Members of a collection whose status is "open"
await brain.export({ collection: collectionId, where: { status: 'open' } })
await data.export({ collection: collectionId, where: { status: 'open' } })
```
// Already have find() results? Export exactly those with the ids selector
If you already have results from `find()`, export exactly those with the `ids` selector:
```typescript
const hits = await brain.find({ type: NounType.Document })
await brain.export({ ids: hits.map(r => r.id) })
await data.export({ ids: hits.map(r => r.id) })
```
### Options — *how* to serialize
@ -86,46 +82,56 @@ await brain.export({ ids: hits.map(r => r.id) })
|--------|---------|--------|
| `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. |
| `includeSystem` | `false` | Include `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). |
```typescript
// A self-contained subgraph with vectors, ready to restore elsewhere
const subset = await data.export({ ids }, { includeVectors: true })
// A VFS subtree including file bytes
const tree = await data.export({ vfsPath: '/docs' }, { includeContent: true })
```
## Importing
```typescript
brain.import(graph, options?): Promise<ImportResult>
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' })
const result = await brain.data().then(d => d.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'`. |
| `onConflict` | `'merge'` | `'merge'` (update existing id in place — assemble many 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 default `onConflict: 'merge'` is what lets you assemble one working graph from many
exported documents that share entity ids — re-importing an id merges rather than duplicates.
```typescript
// Clone a subgraph under fresh ids (a copy, not a move)
const remap = new Map(graph.entities.map(e => [e.id, crypto.randomUUID()]))
await data.import(graph, { remapIds: id => remap.get(id) ?? id })
```
## The `PortableGraph` format
```jsonc
{
"format": "brainy-portable-graph", // identifies the document type
"format": "brainy-portable-graph",
"formatVersion": 1, // import gates on this (cross-version migration)
"brainyVersion": "8.0.0",
"brainyVersion": "7.32.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",
"id": "…", "type": "Document", "subtype": "invoice",
"data": "…", // the embedding source
"confidence": 1, "weight": 1, "service": "…",
"vector": [ … ], // only with includeVectors
@ -142,32 +148,18 @@ documents that share entity ids — re-importing an id merges rather than duplic
}
```
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.
Standard fields (`subtype`, `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.
## Cross-version (7.x → 8.0)
Because the document is shared and versioned, a PortableGraph written by 7.x imports into 8.0:
Because the document is shared and versioned, a graph 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).
(or `true` carries vectors verbatim). The format is **current-state** — if you need a
whole-brain snapshot *with* generation history, that is a separate native facility
(`db.persist()` / `Brainy.load()` on 8.0).
## VFS
@ -175,7 +167,7 @@ VFS directories are `Collection` entities and files are entities linked by `Cont
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
await data.export({ vfsPath: '/' }, { includeContent: true }) // all VFS + bytes
await data.export({ vfsPath: '/docs' }, { includeContent: true }) // one directory
await data.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

@ -40,10 +40,6 @@ Brainy picks `maxLimit` from the first of these that's available:
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
@ -78,7 +74,7 @@ When the auto-config is wrong for your workload (e.g. you know your entities are
```typescript
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' },
storage: { type: 'filesystem', options: { path: './data' } },
maxQueryLimit: 50_000 // raises the cap; still hard-clamped at 100 000
})
```

View file

@ -1,17 +1,15 @@
# 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.
Brainy 3.0 is **framework-first** - designed from the ground up to work seamlessly with modern JavaScript frameworks. This guide shows you how to integrate Brainy into any framework.
> **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 Framework-First?
## 🎯 Why Server-Side?
Brainy embeds an HNSW vector index, a graph engine, and a filesystem-backed persistence layer. These belong on the server:
Traditional AI databases require complex browser polyfills and bundler configurations. Brainy 3.0 trusts your framework to handle this:
- **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
- **Framework responsibility**: Let Next.js, Vite, Webpack handle Node.js polyfills
- **Cleaner code**: No browser-specific entry points or conditional imports
- **Better DX**: Same API everywhere - browser, server, edge
## 🚀 Quick Start
@ -26,8 +24,7 @@ npm install @soulcraft/brainy
```javascript
import { Brainy } from '@soulcraft/brainy'
// Run on the server (API route, server component, backend service)
// new Brainy() auto-detects filesystem persistence on Node
// Works in any framework!
const brain = new Brainy()
await brain.init()
@ -44,48 +41,52 @@ 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'
import { useState, useEffect, useCallback } from 'react'
import { Brainy } from '@soulcraft/brainy'
function useBrainySearch(endpoint = '/api/search') {
const [results, setResults] = useState([])
const [loading, setLoading] = useState(false)
function useBrainy() {
const [brain, setBrain] = useState(null)
const [isReady, setIsReady] = 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 })
useEffect(() => {
const initBrain = async () => {
const newBrain = new Brainy({
storage: { type: 'opfs' } // Browser storage
})
const { results } = await res.json()
setResults(results)
} finally {
setLoading(false)
await newBrain.init()
setBrain(newBrain)
setIsReady(true)
}
}, [endpoint])
return { results, loading, search }
initBrain()
}, [])
return { brain, isReady }
}
// Usage in component
function SearchComponent() {
const { results, loading, search } = useBrainySearch()
const { brain, isReady } = useBrainy()
const [results, setResults] = useState([])
const handleSearch = useCallback(async (query) => {
if (!isReady) return
const searchResults = await brain.find(query)
setResults(searchResults)
}, [brain, isReady])
if (!isReady) return <div>Loading AI...</div>
return (
<div>
<input
type="text"
placeholder="Search..."
onChange={(e) => search(e.target.value)}
onChange={(e) => handleSearch(e.target.value)}
/>
{loading && <div>Searching...</div>}
<div>
{results.map(result => (
<div key={result.id}>
@ -99,34 +100,48 @@ function SearchComponent() {
}
```
### Shared Server Instance
### React Context Pattern
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
```jsx
import React, { createContext, useContext, useEffect, useState } from 'react'
import { Brainy } from '@soulcraft/brainy'
let brainPromise
const BrainyContext = createContext()
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
export function BrainyProvider({ children }) {
const [brain, setBrain] = useState(null)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
const initBrain = async () => {
const newBrain = new Brainy()
await newBrain.init()
setBrain(newBrain)
setIsReady(true)
}
initBrain()
}, [])
return (
<BrainyContext.Provider value={{ brain, isReady }}>
{children}
</BrainyContext.Provider>
)
}
export function useBrainContext() {
const context = useContext(BrainyContext)
if (!context) {
throw new Error('useBrainContext must be used within BrainyProvider')
}
return brainPromise
return context
}
```
## 🟢 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)
### Composition API
```vue
<template>
@ -140,70 +155,100 @@ Vue components call a server endpoint (see the Nuxt server route in the [Vue.js
</template>
<script setup>
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { Brainy } from '@soulcraft/brainy'
const brain = ref(null)
const isReady = ref(false)
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 })
onMounted(async () => {
brain.value = new Brainy({
storage: { type: 'opfs' }
})
results.value = (await res.json()).results
await brain.value.init()
isReady.value = true
})
const search = async () => {
if (!isReady.value || !query.value) return
results.value = await brain.value.find(query.value)
}
</script>
```
### Shared Server Instance
On the server, create one Brainy instance and reuse it across requests:
### Vue 3 Plugin
```javascript
// server/brain.js (server-only module)
// plugins/brainy.js
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export default {
install(app, options) {
const brain = new Brainy(options)
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
app.config.globalProperties.$brain = brain
app.provide('brain', brain)
// Initialize on app mount
brain.init()
}
return brainPromise
}
// main.js
import { createApp } from 'vue'
import BrainyPlugin from './plugins/brainy'
const app = createApp(App)
app.use(BrainyPlugin, {
storage: { type: 'opfs' }
})
```
## 🅰️ Angular Integration
The Angular service calls your backend over HTTP; Brainy lives in that backend, not in the browser.
### Service Pattern (calls the backend)
### Service Pattern
```typescript
// brainy.service.ts
import { Injectable } from '@angular/core'
import { HttpClient } from '@angular/common/http'
import { Observable } from 'rxjs'
import { BehaviorSubject, Observable } from 'rxjs'
import { Brainy } from '@soulcraft/brainy'
@Injectable({
providedIn: 'root'
})
export class BrainyService {
constructor(private http: HttpClient) {}
private brain: Brainy
private readySubject = new BehaviorSubject<boolean>(false)
search(query: string): Observable<{ results: any[] }> {
return this.http.post<{ results: any[] }>('/api/search', { query })
ready$: Observable<boolean> = this.readySubject.asObservable()
constructor() {
this.initBrain()
}
add(data: any, type: string, metadata?: any): Observable<{ id: string }> {
return this.http.post<{ id: string }>('/api/add', { data, type, metadata })
private async initBrain() {
this.brain = new Brainy({
storage: { type: 'opfs' }
})
await this.brain.init()
this.readySubject.next(true)
}
async search(query: string): Promise<any[]> {
if (!this.readySubject.value) {
throw new Error('Brain not ready')
}
return await this.brain.find(query)
}
async add(data: any, type: string, metadata?: any): Promise<string> {
if (!this.readySubject.value) {
throw new Error('Brain not ready')
}
return await this.brain.add({ data, type, metadata })
}
}
```
@ -235,52 +280,64 @@ export class SearchComponent {
constructor(private brainyService: BrainyService) {}
search() {
async search() {
if (!this.query) return
this.brainyService.search(this.query).subscribe(({ results }) => {
this.results = results
})
this.results = await this.brainyService.search(this.query)
}
}
```
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.
### App Router (Next.js 13+)
### Shared Server Instance
```javascript
// lib/brain.server.js (imported only by server code)
```jsx
// app/providers.jsx
'use client'
import { createContext, useContext, useEffect, useState } from 'react'
import { Brainy } from '@soulcraft/brainy'
let brainPromise
const BrainyContext = createContext()
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
})
await brain.init()
return brain
})()
}
return brainPromise
export function BrainyProvider({ children }) {
const [brain, setBrain] = useState(null)
const [isReady, setIsReady] = useState(false)
useEffect(() => {
const initBrain = async () => {
const newBrain = new Brainy()
await newBrain.init()
setBrain(newBrain)
setIsReady(true)
}
initBrain()
}, [])
return (
<BrainyContext.Provider value={{ brain, isReady }}>
{children}
</BrainyContext.Provider>
)
}
export const useBrainy = () => useContext(BrainyContext)
```
```jsx
// app/layout.jsx
import { BrainyProvider } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<BrainyProvider>
{children}
</BrainyProvider>
</body>
</html>
)
}
```
@ -288,78 +345,45 @@ export function getBrain() {
```javascript
// app/api/search/route.js
import { getBrain } from '@/lib/brain.server'
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
})
await brain.init()
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 Integration
```svelte
<!-- SearchComponent.svelte -->
<script>
import { onMount } from 'svelte'
import { Brainy } from '@soulcraft/brainy'
let brain = null
let isReady = false
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 })
onMount(async () => {
brain = new Brainy({
storage: { type: 'opfs' }
})
results = (await res.json()).results
await brain.init()
isReady = true
})
async function search() {
if (!isReady || !query) return
results = await brain.find(query)
}
</script>
@ -377,23 +401,27 @@ export async function POST({ request }) {
## 🌟 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'
import { createSignal, onMount } from 'solid-js'
import { Brainy } from '@soulcraft/brainy'
function SearchComponent() {
const [brain, setBrain] = createSignal(null)
const [isReady, setIsReady] = createSignal(false)
const [query, setQuery] = createSignal('')
const [results, setResults] = createSignal([])
onMount(async () => {
const newBrain = new Brainy()
await newBrain.init()
setBrain(newBrain)
setIsReady(true)
})
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)
if (!isReady() || !query()) return
const searchResults = await brain().find(query())
setResults(searchResults)
}
return (
@ -422,25 +450,57 @@ function SearchComponent() {
## 📦 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:
### Vite (Recommended)
```javascript
// vite.config.js (SSR build)
// vite.config.js
import { defineConfig } from 'vite'
export default defineConfig({
ssr: {
external: ['@soulcraft/brainy']
define: {
global: 'globalThis'
},
optimizeDeps: {
include: ['@soulcraft/brainy']
}
})
```
### Webpack
```javascript
// rollup.config.js (server bundle)
// webpack.config.js
module.exports = {
resolve: {
fallback: {
"fs": false,
"path": require.resolve("path-browserify"),
"crypto": require.resolve("crypto-browserify")
}
},
plugins: [
new webpack.ProvidePlugin({
global: 'global'
})
]
}
```
### Rollup
```javascript
// rollup.config.js
import { nodeResolve } from '@rollup/plugin-node-resolve'
import commonjs from '@rollup/plugin-commonjs'
export default {
external: ['@soulcraft/brainy', 'node:fs', 'node:path', 'node:crypto']
plugins: [
nodeResolve({
browser: true,
preferBuiltins: false
}),
commonjs()
]
}
```
@ -448,24 +508,30 @@ export default {
### 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'
// Check if running in browser
if (typeof window !== 'undefined') {
// Browser-only code
const brain = new Brainy({
storage: { type: 'opfs' }
})
}
export async function load({ url }) {
const brain = await getBrain()
const query = url.searchParams.get('q') ?? ''
const results = query ? await brain.find(query) : []
return { results }
// Or use dynamic imports
const initBrainForBrowser = async () => {
if (typeof window === 'undefined') return null
const { Brainy } = await import('@soulcraft/brainy')
const brain = new Brainy()
await brain.init()
return brain
}
```
### Static Site Generation
```javascript
// For build-time usage (runs in Node during the build)
// For build-time usage
import { Brainy } from '@soulcraft/brainy'
export async function generateStaticProps() {
@ -474,11 +540,11 @@ export async function generateStaticProps() {
})
await brain.init()
// Build search index (paginate with { limit, offset } for larger stores)
const allContent = await brain.find({ limit: 1000 })
// Build a search index — export the whole brain as a portable PortableGraph document
const graph = await brain.data().then(d => d.export())
return {
props: { searchIndex: allContent }
props: { searchIndex: graph.entities }
}
}
```
@ -486,46 +552,67 @@ export async function generateStaticProps() {
## 🔧 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
- Use `useCallback` for search functions to prevent re-renders
- Consider `useMemo` for expensive brain operations
- Implement cleanup in `useEffect` for proper memory management
### Vue
- Components call an endpoint; the shared instance lives in a server module
- Consider Pinia for caching results client-side
- Debounce reactive search queries
- Use `shallowRef` for the brain instance (it's not reactive data)
- Consider Pinia for global brain state management
- Use `watchEffect` for 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
- Implement proper dependency injection with services
- Use RxJS observables for reactive search
- Consider lazy loading brain 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
- Use dynamic imports for client-side only features
- Consider API routes for server-side brain operations
- Implement proper error boundaries
## 🚨 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: "crypto is not defined"
**Solution**: Your framework should handle this automatically. If not:
```javascript
// Add to your bundle config
define: {
global: 'globalThis'
}
```
### 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: "fs module not found"
**Solution**: This is expected in browsers. Use browser-compatible storage:
```javascript
const brain = new Brainy({
storage: { type: 'opfs' } // Or 'memory' for development
})
```
### Issue: Large bundle size
**Solution**: Use dynamic imports for optional features:
```javascript
const brain = await import('@soulcraft/brainy').then(m => new m.Brainy())
```
### 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.
**Solution**: Initialize brain only on client:
```javascript
useEffect(() => {
// Browser-only initialization
initBrain()
}, [])
```
## 🎯 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
1. **Initialize Once**: Create brain instance at app level, not component level
2. **Use Context**: Share brain instance across components with context/providers
3. **Handle Loading**: Always show loading states during brain initialization
4. **Error Boundaries**: Implement proper error handling for brain operations
5. **Memory Management**: Clean up brain instances on unmount
6. **Storage Strategy**: Choose appropriate storage for your deployment target
## 📚 Next Steps

View file

@ -49,23 +49,28 @@ await brain.import(csv, { format: 'csv' })
### 📊 Import Excel - Multi-Sheet Support
```javascript
// Import entire Excel workbook — every sheet is processed automatically
// Import entire Excel workbook
await brain.import('sales-report.xlsx')
// ✨ Processes all sheets, preserves structure, infers types!
// Mirror the workbook into the VFS, grouped by sheet
// Or specific sheets only
await brain.import('data.xlsx', {
vfsPath: '/imports/data',
groupBy: 'sheet'
excelSheets: ['Customers', 'Orders']
})
// ✨ Multi-sheet data becomes interconnected entities!
```
### 📑 Import PDF - Text & Tables
```javascript
// Import PDF documents — text and tables are extracted automatically
// Import PDF documents
await brain.import('research-paper.pdf')
// ✨ Extracts text, detects tables, preserves metadata!
// With table extraction
await brain.import('report.pdf', {
pdfExtractTables: true
})
// ✨ Converts PDF tables to structured data automatically!
```
### 📝 Import YAML - File or String
@ -260,7 +265,7 @@ Once imported, use Triple Intelligence to query:
```javascript
// Vector search
const similar = await brain.find('engineers')
const similar = await brain.search('engineers')
// Natural language
const results = await brain.find('people in engineering who joined this year')
@ -300,10 +305,7 @@ await brain.import(data, {
// 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.
// Note: Auto-disabled for imports >100 entities
// Performance
chunkSize: 100, // Batch size for processing (default: varies by operation)

View file

@ -1173,22 +1173,21 @@ await this.graphIndex.addEdge(
```
**Benefits**:
- **O(1) relationship lookups**: `related(entityId)` is instant
- **O(1) relationship lookups**: `getRelations(entityId)` is instant
- **Bidirectional traversal**: Find incoming and outgoing edges
- **Type filtering**: Get only `CreatedBy` relationships
- **Global statistics**: Count relationships by type
**Query Examples**:
```typescript
// What did Mona Lisa create? (outgoing edges)
const outgoing = await brain.related({ from: 'ent_mona_lisa_...' })
// What did Mona Lisa create?
const outgoing = await brain.getRelations('ent_mona_lisa_...', { direction: 'outgoing' })
// What created Mona Lisa? (incoming edges)
const incoming = await brain.related({ to: 'ent_mona_lisa_...' })
// What created Mona Lisa?
const incoming = await brain.getRelations('ent_mona_lisa_...', { direction: 'incoming' })
// Get only CreatedBy relationships
const createdBy = await brain.related({
from: 'ent_mona_lisa_...',
const createdBy = await brain.getRelations('ent_mona_lisa_...', {
type: VerbType.CreatedBy
})
```
@ -1309,7 +1308,7 @@ await this.history.recordImport(
```
**Use Cases**:
- List all imports: `coordinator.getHistory().getHistory()` (the `ImportHistory` entries shown above)
- List all imports: `brain.data().listImports()`
- Reimport with same settings
- Audit trail for compliance
- Rollback imports
@ -1688,11 +1687,11 @@ verbCounts['CreatedBy']
---
### 8. Storage Layout
### 8. Storage by Cloud Provider
Brainy 8.0 ships two adapters: filesystem and memory.
Brainy supports multiple storage adapters:
#### Filesystem (Default)
#### File System (Local)
```
.brainy/
├── nouns/
@ -1702,18 +1701,43 @@ Brainy 8.0 ships two adapters: filesystem and memory.
└── index.json
```
#### Google Cloud Storage (GCS)
```
gs://my-bucket/brainy/
├── nouns/
│ └── ent_mona_lisa_1730000000000.json
├── nouns-metadata/
│ └── ent_mona_lisa_1730000000000.json
└── ...
```
#### Amazon S3
```
s3://my-bucket/brainy/
├── nouns/
│ └── ent_mona_lisa_1730000000000.json
└── ...
```
#### Cloudflare R2
```
r2://my-bucket/brainy/
├── nouns/
│ └── ent_mona_lisa_1730000000000.json
└── ...
```
**Configuration**:
```typescript
const brain = await Brainy.create({
storage: {
type: 'filesystem',
path: './.brainy'
type: 'gcs',
bucket: 'my-bucket',
prefix: 'brainy/'
}
})
```
For off-site backup, snapshot `path` from your scheduler with `gsutil rsync`, `aws s3 sync`, `rclone`, or `tar`. Brainy itself doesn't reach out to object storage.
---
## Performance & Scale
@ -1743,7 +1767,7 @@ For off-site backup, snapshot `path` from your scheduler with `gsutil rsync`, `a
- HNSW Index: O(log n) search (1B entities = ~30 hops)
- Metadata Index: O(1) filtering
- Graph Adjacency: O(1) relationship lookups
- Storage: Bounded by the filesystem volume backing `path`
- Storage: Unlimited (cloud buckets)
### Optimization Tips
@ -1887,7 +1911,7 @@ groupBy: 'type'
- ✅ O(1) metadata filtering
- ✅ O(1) relationship traversal
- ✅ Human-readable VFS structure
- ✅ Filesystem-backed persistence (snapshot/sync the directory for off-site backup)
- ✅ Cloud storage support (GCS/S3/R2)
- ✅ Billion-scale performance
- ✅ Zero mocks, production-ready!

View file

@ -86,22 +86,11 @@ await brain.import(file, {
```typescript
await brain.import(file, {
enableDeduplication: true, // Check for duplicates (default: true)
enableDeduplication: true, // Check for duplicates (default: false)
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:
@ -362,7 +351,7 @@ const result = await brain.import(file)
const products = await brain.find({ type: 'Product' })
// Get entity relationships
const relations = await brain.related(products[0].id)
const relations = await brain.getRelations(products[0].id)
// Search VFS
const vfsFiles = await brain.vfs().find(result.vfs.rootPath + '/**/*.json')

View file

@ -17,7 +17,10 @@ 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.
**Never open a second writer on the same directory.** It will throw on
filesystem storage; on cloud storage it'll silently overwrite the live
writer's state. Use `Brainy.openReadOnly()` or the `brainy inspect` CLI
instead.
## The CLI is the fastest path
@ -94,7 +97,7 @@ $ brainy inspect health /data/brain
{
"overall": "warn",
"checks": [
{ "name": "index-parity", "status": "pass", "message": "Vector (1851) and metadata (1851) agree." },
{ "name": "index-parity", "status": "pass", "message": "HNSW (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...)." }
@ -111,7 +114,7 @@ check fails — useful for piping into monitoring or CI.
import { Brainy } from '@soulcraft/brainy'
const reader = await Brainy.openReadOnly({
storage: { type: 'filesystem', path: '/data/brain' }
storage: { type: 'filesystem', rootDirectory: '/data/brain' }
})
// Force the writer to flush before reading
@ -134,8 +137,8 @@ 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.
Every mutation method (`add`, `update`, `delete`, `relate`, `commit`,
`fork`, `branch`, ...) throws on a read-only instance with a clear message.
## Backups
@ -148,10 +151,9 @@ 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).
container scheduler. For point-in-time recovery, use Brainy's COW
`commit()` API — the snapshots there are content-addressed and never
overwritten.
## Comparing two stores
@ -166,34 +168,6 @@ 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`

View file

@ -5,10 +5,10 @@ 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.
description: Install Brainy with npm, bun, yarn, or pnpm. Works in Node.js 22+, Bun 1.0+, and browser (OPFS). TypeScript included.
next:
- getting-started/quick-start
- guides/storage-adapters
- concepts/zero-config
---
# Installation
@ -45,26 +45,36 @@ console.log('Brainy ready.')
## Native Acceleration (Optional)
For production workloads, add Cor for Rust-accelerated SIMD distance calculations and native embeddings:
For production workloads, add Cortex for Rust-accelerated SIMD distance calculations, vector quantization, and native embeddings:
```bash
npm install @soulcraft/cor
npm install @soulcraft/cortex
```
```typescript
import { Brainy } from '@soulcraft/brainy'
import { registerCortex } from '@soulcraft/cortex'
const brain = new Brainy({ plugins: ['@soulcraft/cor'] })
await brain.init() // native providers registered during init
registerCortex() // activates native acceleration globally
const brain = new Brainy()
await brain.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.
Cortex delivers a **5.2x geometric mean speedup** — see [Brainy vs Cortex](/docs/cortex/comparison) for measured benchmarks.
## Server-only since 8.0
## Browser (OPFS)
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.
Brainy works in the browser using the Origin Private File System:
```typescript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy({ storage: 'opfs' })
await brain.init()
```
No server required. Data persists across page refreshes in the browser's private storage.
## TypeScript
@ -86,4 +96,5 @@ const id = await brain.add({
## Next Steps
- [Quick Start](/docs/getting-started/quick-start) — build your first knowledge graph in 60 seconds
- [Zero Configuration](/docs/concepts/zero-config) — understand what Brainy auto-detects
- [Storage Adapters](/docs/guides/storage-adapters) — choose the right storage for your deployment

View file

@ -252,7 +252,7 @@ const result = await brain.import('./glossary.xlsx', {
// - "part_of"
// - "related_to"
const relations = await brain.related({ limit: 100 })
const relations = await brain.getRelations({ limit: 100 })
const types = new Set(relations.map(r => r.label))
console.log(types)
// Set { 'capital_of', 'guards', 'located_in', 'related_to' }

View file

@ -35,17 +35,14 @@ This single WASM file contains everything needed for sentence embeddings.
### Bun (Recommended)
```bash
# Bun as a runtime — supported and recommended
bun add @soulcraft/brainy
```typescript
// Works with Bun runtime
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.
// Works with bun --compile (single binary deployment!)
bun build --compile --target=bun server.ts
./server // Self-contained binary with embedded model
```
### Node.js
@ -167,7 +164,7 @@ await brain.init()
**What's new:**
- Faster initialization
- Bundler-friendly (pure WASM, no native binaries)
- Works with `bun --compile`
- No network requirements
### From Custom Embedding Functions
@ -219,8 +216,9 @@ export { brain }
### Deployment
```bash
# Option 1: Bun runtime
bun run server.ts
# Option 1: Bun compile (single binary)
bun build --compile server.ts
./server # Contains everything
# Option 2: Docker
docker build -t my-app .

371
docs/guides/neural-api.md Normal file
View file

@ -0,0 +1,371 @@
# Neural API Guide
> Semantic intelligence features for clustering, similarity, and analysis
## Overview
The Neural API provides advanced AI-powered features for understanding relationships and patterns in your data. Access it through `brain.neural()` (method call) after initializing Brainy.
## Quick Start
```javascript
import { Brainy } from '@soulcraft/brainy'
const brain = new Brainy()
await brain.init()
// Access Neural API (note: neural() is a method call)
const neural = brain.neural()
// Find similar items
const similarity = await neural.similar('text1', 'text2')
// Auto-cluster your data
const clusters = await neural.clusters()
```
## Core Features
### 1. Semantic Clustering
Automatically group related items based on their meaning:
```javascript
// Simple clustering - let Brainy decide
const clusters = await neural.clusters()
// Each cluster contains:
// - id: Unique identifier
// - members: Array of item IDs in this cluster
// - centroid: The "center" of the cluster
// - label: Optional descriptive label
// - confidence: How confident the clustering is
// Example: Organize customer feedback
const feedback = [
await brain.add("The app crashes when I upload photos"),
await brain.add("Photo upload feature is broken"),
await brain.add("Great customer service!"),
await brain.add("Support team was very helpful"),
await brain.add("Pricing is too high"),
await brain.add("Too expensive for what it offers")
]
const themes = await neural.clusters()
// Results in 3 clusters: bugs, support, pricing
```
#### Advanced Clustering Options
```javascript
// Control clustering behavior
const clusters = await neural.clusters({
algorithm: 'kmeans', // Algorithm to use
maxClusters: 5, // Maximum clusters to create
threshold: 0.7 // Minimum similarity within clusters
})
// Cluster specific items only
const techItems = ['id1', 'id2', 'id3', 'id4']
const techClusters = await neural.clusters(techItems)
// Find clusters near a specific item
const relatedClusters = await neural.clusters('central-item-id')
```
### 2. Similarity Calculation
Compare any two items to see how similar they are:
```javascript
// Compare by ID
const score = await neural.similar('item1-id', 'item2-id')
// Returns 0-1 (0 = completely different, 1 = identical)
// Compare text directly
const score = await neural.similar(
"Machine learning is fascinating",
"AI and deep learning are interesting"
)
// Returns ~0.75 (pretty similar)
// Compare vectors
const v1 = await brain.embed("concept 1")
const v2 = await brain.embed("concept 2")
const score = await neural.similar(v1, v2)
// Get detailed similarity analysis
const detailed = await neural.similar('id1', 'id2', {
detailed: true
})
// Returns: {
// score: 0.85,
// confidence: 0.92,
// explanation: "High semantic overlap in technology domain"
// }
```
### 3. Finding Neighbors
Discover items similar to a given item:
```javascript
// Find 5 most similar items
const neighbors = await neural.neighbors('item-id', 5)
// Each neighbor has:
// - id: The neighbor's ID
// - similarity: How similar (0-1)
// - data: The actual content
// Example: Recommend similar articles
const articleId = await brain.add("Guide to React Hooks")
const similar = await neural.neighbors(articleId, 3)
for (const article of similar) {
console.log(`${article.similarity * 100}% similar: ${article.data}`)
}
```
### 4. Semantic Hierarchy
Build a hierarchy showing relationships between items:
```javascript
const hierarchy = await neural.hierarchy('item-id')
// Returns structure like:
// {
// self: { id: 'item-id', type: 'article' },
// parent: { id: 'parent-id', similarity: 0.8 },
// siblings: [
// { id: 'sibling1', similarity: 0.75 },
// { id: 'sibling2', similarity: 0.72 }
// ],
// children: [
// { id: 'child1', similarity: 0.85 }
// ]
// }
// Use for navigation or breadcrumbs
const hier = await neural.hierarchy(currentDoc)
console.log(`You are here: ${hier.self.id}`)
if (hier.parent) {
console.log(`Parent topic: ${hier.parent.id}`)
}
```
### 5. Outlier Detection
Find unusual or anomalous items in your data:
```javascript
// Find items that don't fit patterns
const outliers = await neural.outliers(0.3)
// Returns array of IDs that are > 0.3 distance from others
// Example: Detect spam or unusual content
const messages = [
await brain.add("Meeting at 3pm"),
await brain.add("Lunch plans for tomorrow"),
await brain.add("BUY NOW!!! AMAZING DEALS!!!"),
await brain.add("Project deadline next week")
]
const suspicious = await neural.outliers(0.4)
// Returns the spam message ID
```
### 6. Visualization Support
Generate data for visualization libraries:
```javascript
// Create force-directed graph data
const vizData = await neural.visualize({
maxNodes: 100, // Limit nodes for performance
dimensions: 2, // 2D or 3D
algorithm: 'force' // Layout algorithm
})
// Returns:
// {
// nodes: [
// { id: 'n1', x: 10, y: 20, cluster: 'c1' },
// { id: 'n2', x: 30, y: 40, cluster: 'c1' }
// ],
// edges: [
// { source: 'n1', target: 'n2', weight: 0.8 }
// ],
// clusters: [
// { id: 'c1', color: '#ff6b6b', size: 15 }
// ]
// }
// Use with D3.js, Cytoscape, or other viz libraries
const data = await neural.visualize({ dimensions: 3 })
// Now feed to Three.js for 3D visualization
```
## Practical Examples
### Content Recommendation System
```javascript
// User reads an article
const currentArticle = 'article-123'
// Find similar content
const recommendations = await neural.neighbors(currentArticle, 5)
// Group all content into topics
const topics = await neural.clusters()
// Find which topic this article belongs to
const currentTopic = topics.find(t =>
t.members.includes(currentArticle)
)
// Recommend from same topic first, then similar items
const sameTopicArticles = currentTopic.members
.filter(id => id !== currentArticle)
.slice(0, 3)
```
### Customer Feedback Analysis
```javascript
import { NounType } from '@soulcraft/brainy'
// Add feedback with metadata
const feedbackIds = []
for (const feedback of customerFeedback) {
const id = await brain.add({
data: feedback.text,
type: NounType.Document,
metadata: {
rating: feedback.rating,
date: feedback.date,
product: feedback.product
}
})
feedbackIds.push(id)
}
// Cluster to find themes
const neural = brain.neural()
const themes = await neural.clusters(feedbackIds)
// Analyze each theme
for (const theme of themes) {
// Get items using Promise.all with brain.get()
const items = await Promise.all(
theme.members.map(id => brain.get(id))
)
const avgRating = items.reduce((sum, item) =>
sum + (item?.metadata?.rating || 0), 0) / items.length
console.log(`Theme with ${theme.members.length} items`)
console.log(`Average rating: ${avgRating}`)
// Find representative feedback for this theme
const centroidId = theme.members[0] // Closest to center
const example = await brain.get(centroidId)
console.log(`Example: "${example?.data}"`)
}
```
### Knowledge Base Organization
```javascript
import { NounType } from '@soulcraft/brainy'
// Analyze existing knowledge base - use find() to get documents
const allDocs = await brain.find({ type: NounType.Document, limit: 1000 })
// Access neural API
const neural = brain.neural()
// Find duplicate or highly similar content
const duplicates = []
for (let i = 0; i < allDocs.length; i++) {
for (let j = i + 1; j < allDocs.length; j++) {
const similarity = await neural.similar(
allDocs[i].id,
allDocs[j].id
)
if (similarity > 0.95) {
duplicates.push([allDocs[i].id, allDocs[j].id])
}
}
}
// Build topic hierarchy
const mainTopics = await neural.clusters({
maxClusters: 10,
algorithm: 'hierarchical'
})
// For each main topic, find subtopics
for (const topic of mainTopics) {
const subtopics = await neural.clusters(topic.members)
console.log(`Topic has ${subtopics.length} subtopics`)
}
```
## Performance Tips
1. **Caching**: Neural API automatically caches results. Repeated calls with same parameters are instant.
2. **Batch Operations**: Process multiple items together rather than one at a time.
3. **Sampling**: For large datasets, use sampling:
```javascript
const clusters = await neural.clusters({
algorithm: 'sample',
sampleSize: 1000 // Only analyze 1000 items
})
```
4. **Async Processing**: All neural operations are async and non-blocking.
## Error Handling
```javascript
try {
const similarity = await neural.similar('id1', 'id2')
} catch (error) {
// Handle errors
if (error.message.includes('not found')) {
console.log('One of the items does not exist')
}
}
// Safe clustering with empty data
const clusters = await neural.clusters([])
// Returns empty array, doesn't throw
// Non-existent IDs return 0 similarity
const sim = await neural.similar('fake-id-1', 'fake-id-2')
// Returns 0
```
## Advanced Configuration
```javascript
// Configure neural behavior at initialization
const brain = new Brainy({
neural: {
cacheSize: 1000, // Cache up to 1000 results
defaultAlgorithm: 'kmeans',
similarityMetric: 'cosine'
}
})
```
## Next Steps
- Explore [Triple Intelligence](../architecture/triple-intelligence.md) for combined vector + graph + metadata queries
- Learn about [Augmentations](../augmentations/README.md) to extend Neural API
- See [API Reference](../api/README.md) for complete method documentation

View file

@ -7,8 +7,8 @@ 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
- api/README
---
# Optimistic concurrency with `_rev`
@ -25,7 +25,7 @@ Brainy 7.31.0 adds a per-entity revision counter so multiple writers can coordin
| `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.
`_rev` is independent of `brain.versions.*` (named snapshots), of `brain.fork()` / branches (COW), and of VFS file versioning. They all coexist; `_rev` is the per-write counter used for CAS.
## The lock pattern
@ -134,81 +134,42 @@ await brain.addIfMissing({ // ← not a real API
})
```
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:
It's race-prone outside a transaction: two concurrent imports both see "not found," both insert, you get duplicates. Without a unique-index primitive (which Brainy doesn't have today), this pattern needs to live inside a transaction:
```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()
}
// The race-safe shape, available once brain.transact() ships in 8.0.
await brain.transact(async tx => {
const existing = await tx.find({
type: 'Person',
where: { email: 'x@y.com' },
limit: 1
})
if (existing.length === 0) {
await tx.add({ type: 'Person', data: '...', metadata: { email: 'x@y.com' } })
}
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.
For 7.31.0, lean on `ifAbsent` when you control the ID, and accept the inherent dedup race when you don't. The 8.0 `brain.transact()` is the natural home for the attribute-based variant.
## How `_rev` relates to generations
## How `_rev` interacts with the other versioning systems
Brainy 8.0 has exactly two write-coordination counters, at two granularities:
Brainy has several persistence primitives that all touch the word "version" in different ways. `_rev` is independent of every one of them:
| 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` |
| System | What it tracks | When it advances |
|---|---|---|
| **`_rev`** (new in 7.31.0) | Per-entity write counter | Auto, on every successful `update()` |
| **`brain.versions.save()`** | Named snapshots per entity | Explicit — you call `save()` with a tag |
| **`brain.fork()` / branches** | Whole-brain copy-on-write | Explicit — you call `fork(name)` |
| **VFS file versioning** | Per-VFS-file snapshots (Document entity) | Same as `brain.versions.save()` |
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).
If you're on a branch, each branch has its own copy of every entity (that's what COW means), so each branch has its own `_rev` per entity — same as every other field. Snapshots taken via `brain.versions.save()` capture the entity at that moment including its `_rev` at that time; the snapshot's own `version: number` is the snapshot index, distinct from `_rev`.
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.
## What's coming in 8.0
## The transact envelope: batch size, budget, and bulk imports
Brainy 8.0 ships a Datomic-style immutable `Db` API where `brain.transact(tx)` is the public multi-write atomicity primitive. Two things change for code written against 7.31.0's surface:
`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:
- `_rev` survives unchanged. It's part of the 8.0 entity shape; the auto-bump moves to the new `transact()` write path. Code that uses `update({ ifRev })` keeps working.
- A new `brain.transact(tx, { ifAtGeneration: prev.generation })` adds generation-based CAS for whole-transaction atomicity, layered on top of `_rev`. Per-entity for single-record patterns (the lock above), generation-based for "did the world move under me."
```
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`.
If you adopt `_rev` + `ifRev` in 7.31.0, no migration work is needed for 8.0.

View file

@ -60,17 +60,17 @@ const nextId: string = await brain.add({
await brain.relate({
from: nextId,
to: reactId,
type: VerbType.DependsOn
type: VerbType.BuiltOn
})
```
## 5. Query with Triple Intelligence
```typescript
import type { Result } from '@soulcraft/brainy'
import type { FindResult } from '@soulcraft/brainy'
// All three search paradigms in one call
const results: Result[] = await brain.find({
const results: FindResult[] = await brain.find({
query: 'modern frontend frameworks', // Vector similarity search
where: { year: { greaterThan: 2015 } }, // Metadata filtering
connected: { to: reactId, depth: 2 } // Graph traversal
@ -108,4 +108,4 @@ await brain.find({ query: 'people who work at Anthropic' })
- [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
- [Storage Adapters](/docs/guides/storage-adapters) — S3, GCS, Azure, filesystem, OPFS

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,6 +1,6 @@
# 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.
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 automatic backup, resume support, and error tracking.
---
@ -48,13 +48,15 @@ When `brain.init()` runs:
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 })`.
1. **Backup** — creates an instant COW branch (`pre-migration-7.17.0`) tagged with `system:backup` metadata. Rollback is possible by switching to this branch.
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.
2. **Transform main branch** — 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.
3. **Transform other branches** — switches to each user branch, runs the same transforms. Inherited (already-migrated) entities return `null` and are skipped automatically.
4. **Rebuild indexes** — if any entities were modified, rebuilds the MetadataIndex.
4. **Save state** — records each completed migration ID so it never re-runs.
5. **Rebuild indexes** — if any entities were modified, rebuilds the MetadataIndex.
---
@ -76,7 +78,7 @@ interface Migration {
- **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 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 branch iterations may re-encounter inherited 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.
@ -108,9 +110,9 @@ const preview = await brain.migrate({ dryRun: true })
// 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
// Apply migrations
const result = await brain.migrate()
// result.backupBranch — name of COW backup branch, or null
// result.migrationsApplied — array of migration IDs that ran
// result.entitiesProcessed — total entities scanned
// result.entitiesModified — entities actually changed
@ -162,20 +164,20 @@ 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)):
Before modifying any data, `brain.migrate()` calls `brain.fork()` to create a COW snapshot. This is instant regardless of dataset size — it's a pointer copy, not a data copy.
The backup branch is named `pre-migration-{version}` and tagged with metadata:
- `type: 'system:backup'`
- `migrationVersion: '7.17.0'`
- `author: 'brainy-migration'`
To roll back, switch to the backup branch:
```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)
await brain.checkout('pre-migration-7.17.0')
```
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.
Old backup branches from previous migrations are cleaned up automatically before each new migration run.
---
@ -271,7 +273,7 @@ Progress is reported after each batch (batch size is determined by the storage a
## 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.
Migrations work identically across all storage backends (Memory, FileSystem, S3, R2, GCS, OPFS). 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.

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

@ -5,158 +5,197 @@ 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."
description: "Six adapters for every deployment: in-memory, OPFS (browser), filesystem, S3, Google Cloud Storage, Azure Blob, and Cloudflare R2. All support copy-on-write branching."
next:
- guides/plugins
- concepts/consistency-model
- concepts/zero-config
---
# Storage Adapters
# Storage Adapters Guide
Brainy 8.0 ships **two storage adapters**:
## Overview
Brainy supports 6 storage adapters for different deployment environments. All adapters implement the same StorageAdapter interface and support copy-on-write branching.
- **`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.
## Adapters
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).
### 1. MemoryStorage
- **File**: `src/storage/adapters/memoryStorage.ts`
- **Use case**: Development, testing, prototyping
- **Configuration**: None required
- **Persistence**: None (data lost on restart)
- **Batch config**: 1000 batch size, 0ms delay, 1000 concurrent ops, 100k ops/sec
## Quick start
```typescript
const brain = new Brainy({ storage: { type: 'memory' } })
```
```ts
import { Brainy } from '@soulcraft/brainy'
### 2. FileSystemStorage
- **File**: `src/storage/adapters/fileSystemStorage.ts`
- **Use case**: Node.js local persistence, single-server deployments
- **Configuration**: `basePath` (required), `readOnly` (optional)
- **Features**: zlib compression, atomic writes via temp files, UUID-based sharding
// Filesystem (recommended for any persistent workload):
```typescript
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
### 3. S3CompatibleStorage
- **File**: `src/storage/adapters/s3CompatibleStorage.ts`
- **Use case**: AWS S3, MinIO, DigitalOcean Spaces, any S3-compatible service
- **Configuration**: `bucketName`, `region`, `accessKeyId`, `secretAccessKey`, `endpoint?` (for custom endpoints), `s3ForcePathStyle?`
- **Features**: Write buffering, request coalescing, throttling detection (429/503), progressive initialization
- **Batch config**: 1000 batch size, 150 concurrent ops, 5000 ops/sec burst
| 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
```typescript
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: {
bucketName: 'my-brainy-data',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
}
| StorageAdapter // e.g. storage: new MemoryStorage()
}
})
// For MinIO or other S3-compatible services:
const brain = new Brainy({
storage: {
type: 's3',
s3Storage: {
bucketName: 'my-data',
region: 'us-east-1',
endpoint: 'http://localhost:9000',
s3ForcePathStyle: true,
accessKeyId: 'minio-key',
secretAccessKey: 'minio-secret'
}
}
})
```
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.
### 4. R2Storage (Cloudflare)
- **File**: `src/storage/adapters/r2Storage.ts`
- **Use case**: Zero egress fees, cost-effective cloud storage
- **Configuration**: `bucketName`, `accountId`, `accessKeyId`, `secretAccessKey`, `cacheConfig?`
- **Features**: Zero egress fees, aggressive caching, write buffering, request coalescing
- **Batch config**: 1000 batch size, 150 concurrent ops, 6000 ops/sec
## 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 })
```typescript
const brain = new Brainy({
storage: {
type: 'r2',
r2Storage: {
accountId: process.env.CF_ACCOUNT_ID,
bucketName: 'my-brainy-data',
accessKeyId: process.env.CF_ACCESS_KEY_ID,
secretAccessKey: process.env.CF_SECRET_ACCESS_KEY
}
}
})
```
## Migration from 7.x cloud adapters
### 5. GcsStorage (Google Cloud)
- **File**: `src/storage/adapters/gcsStorage.ts`
- **Use case**: Google Cloud ecosystem, Cloud Run deployments
- **Authentication priority**:
1. Service Account Key File (`keyFilename`)
2. Credentials Object (`credentials`)
3. HMAC Keys (backward compat)
4. Application Default Credentials (automatic in Cloud Run)
- **Features**: Progressive initialization (fast cold starts <200ms), Autoclass lifecycle, bucket validation
- **Batch config**: 1000 batch size, 100 concurrent ops, 1000 ops/sec
7.x consumers of `OPFSStorage`, `GcsStorage`, `R2Storage`, `S3CompatibleStorage`,
or `AzureBlobStorage` need to migrate to `FileSystemStorage` plus operator
backup tooling. The recipe:
```typescript
// With explicit credentials
const brain = new Brainy({
storage: {
type: 'gcs',
gcsStorage: {
bucketName: 'my-brainy-data',
keyFilename: './service-account.json'
}
}
})
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)`.
// In Cloud Run (uses Application Default Credentials automatically)
const brain = new Brainy({
storage: {
type: 'gcs',
gcsStorage: {
bucketName: 'my-brainy-data'
}
}
})
```
Same data, same APIs, no library-side cloud code.
**Progressive Initialization Modes:**
- `'strict'` (default locally): Full validation during init (100-500ms+)
- `'progressive'` (default in Cloud Run/Lambda): Fast init <200ms, background validation
- `'auto'`: Auto-detects environment
### 6. AzureBlobStorage
- **File**: `src/storage/adapters/azureBlobStorage.ts`
- **Use case**: Azure ecosystem, enterprise deployments
- **Authentication priority**:
1. DefaultAzureCredential (Managed Identity - automatic in Azure)
2. Connection String
3. Account Name + Account Key
4. SAS Token
- **Features**: Progressive initialization, lifecycle management, container validation
- **Batch config**: 1000 batch size, 100 concurrent ops, 3000 ops/sec
```typescript
// With connection string
const brain = new Brainy({
storage: {
type: 'azure',
azureStorage: {
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: 'brainy-data'
}
}
})
// With Managed Identity (automatic in Azure App Service/Functions)
const brain = new Brainy({
storage: {
type: 'azure',
azureStorage: {
containerName: 'brainy-data'
// DefaultAzureCredential used automatically
}
}
})
```
## Choosing an Adapter
| Scenario | Recommended Adapter |
|----------|-------------------|
| Development/Testing | MemoryStorage |
| Local persistence | FileSystemStorage |
| AWS deployment | S3CompatibleStorage |
| Google Cloud / Cloud Run | GcsStorage |
| Azure deployment | AzureBlobStorage |
| Cost-sensitive (high egress) | R2Storage |
| Self-hosted (MinIO) | S3CompatibleStorage |
## Common Configuration
All cloud adapters support:
- **Progressive initialization** for fast serverless cold starts
- **Read-only mode** (`readOnly: true`)
- **Cache configuration** (`cacheConfig: { hotCacheMaxSize, warmCacheTTL }`)
- **Copy-on-write branching** for git-style data management
## See Also
- [Cloud Deployment Guide](../deployment/CLOUD_DEPLOYMENT_GUIDE.md)
- [Cost Optimization: AWS S3](../operations/cost-optimization-aws-s3.md)
- [Cost Optimization: GCS](../operations/cost-optimization-gcs.md)
- [Cost Optimization: Azure](../operations/cost-optimization-azure.md)
- [Cost Optimization: R2](../operations/cost-optimization-cloudflare-r2.md)

View file

@ -242,7 +242,7 @@ 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' } })
const brain = new Brainy({ storage: { type: 'filesystem', options: { path: './brain-data' } } })
await brain.init()
// 1. Migrate any existing metadata.kind convention to the new top-level subtype
@ -301,14 +301,14 @@ await brain.relate({
```typescript
// Direct reports — fast path filter (column-store hit, not metadata fallback)
const direct = await brain.related({
const direct = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: 'direct'
})
// Set membership
const allReports = await brain.related({
const allReports = await brain.getRelations({
from: ceoId,
type: VerbType.ReportsTo,
subtype: ['direct', 'dotted-line']
@ -332,7 +332,7 @@ await brain.updateRelation({ id: relationId, weight: 0.5, confidence: 0.9 })
`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)
// All direct reports two hops deep (will be supported in Cortex; for now depth-1)
const directChain = await brain.find({
connected: {
from: ceoId,
@ -343,7 +343,7 @@ const directChain = await brain.find({
})
```
Multi-hop subtype filtering (`depth > 1`) lights up on the Cor native path; the JS path throws today rather than return incorrect partial results.
Multi-hop subtype filtering (`depth > 1`) lights up on the Cortex native path; the JS path throws today rather than return incorrect partial results.
### Counts
@ -552,8 +552,8 @@ Brainy 8.0 ships:
- `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.getRelations({ subtype })` — filter (fast path)
- `brain.getRelations({ 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

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.

View file

@ -2,8 +2,6 @@
Complete guide to integrating Brainy with Vue.js applications, covering Vue 3, Nuxt.js, Composition API, Options API, and advanced patterns.
> **Runtime**: Brainy 8.0 runs on Node.js 22+ and Bun, server-side only — it is not a browser library. In Vue apps, Brainy lives behind an HTTP endpoint (a Nuxt server route, or any Node/Bun backend), and your components call that endpoint. The composables, plugin, and store below are wrappers around `fetch`; the single Brainy instance is created once on the server (see [Nuxt Server Route](#nuxt-server-route)).
## 🚀 Quick Start
### Installation
@ -17,47 +15,34 @@ npm install @soulcraft/brainy
### Basic Setup
The composable talks to a Brainy-backed endpoint (see [Nuxt Server Route](#nuxt-server-route)). It is always "ready" because there is no client-side initialization — the server owns the Brainy instance.
```javascript
// src/composables/useBrainy.js
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { Brainy } from '@soulcraft/brainy'
export function useBrainy(endpoint = '/api/brain') {
const error = ref(null)
const brain = ref(null)
const isReady = ref(false)
const error = ref(null)
const search = async (query, options = {}) => {
error.value = null
export function useBrainy() {
onMounted(async () => {
try {
const res = await fetch(`${endpoint}/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, options })
brain.value = new Brainy({
storage: { type: 'opfs' } // Browser storage
})
if (!res.ok) throw new Error(`Search failed: ${res.status}`)
return (await res.json()).results
await brain.value.init()
isReady.value = true
} catch (err) {
error.value = err.message
console.error('Brainy search failed:', err)
return []
console.error('Brainy initialization failed:', err)
}
}
})
const add = async (data, type, metadata) => {
const res = await fetch(`${endpoint}/add`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data, type, metadata })
})
return (await res.json()).id
return {
brain: readonly(brain),
isReady: readonly(isReady),
error: readonly(error)
}
const stats = async () => {
const res = await fetch(`${endpoint}/stats`)
return await res.json()
}
return { search, add, stats, error: readonly(error) }
}
```
@ -69,36 +54,43 @@ export function useBrainy(endpoint = '/api/brain') {
<!-- src/components/Search.vue -->
<template>
<div class="search-container">
<div class="search-input">
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
class="input"
/>
<div v-if="!isReady" class="loading">
<div class="spinner"></div>
<span>Initializing AI...</span>
</div>
<div v-if="loading" class="loading">
Searching...
</div>
<div v-else class="results">
<div
v-for="result in results"
:key="result.id"
class="result-item"
>
<h3>{{ result.data }}</h3>
<div class="result-meta">
<span>Score: {{ (result.score * 100).toFixed(1) }}%</span>
<span v-if="result.metadata?.type">
Type: {{ result.metadata.type }}
</span>
</div>
<div v-else>
<div class="search-input">
<input
v-model="query"
@input="handleSearch"
placeholder="Search with AI..."
class="input"
/>
</div>
<div v-if="query && !loading && results.length === 0" class="no-results">
No results found for "{{ query }}"
<div v-if="loading" class="loading">
Searching...
</div>
<div v-else class="results">
<div
v-for="result in results"
:key="result.id"
class="result-item"
>
<h3>{{ result.data }}</h3>
<div class="result-meta">
<span>Score: {{ (result.score * 100).toFixed(1) }}%</span>
<span v-if="result.metadata?.type">
Type: {{ result.metadata.type }}
</span>
</div>
</div>
<div v-if="query && !loading && results.length === 0" class="no-results">
No results found for "{{ query }}"
</div>
</div>
</div>
</div>
@ -109,21 +101,22 @@ import { ref, watch } from 'vue'
import { useBrainy } from '../composables/useBrainy'
import { debounce } from '../utils/debounce'
const { search: searchBrain } = useBrainy()
const { brain, isReady } = useBrainy()
const query = ref('')
const results = ref([])
const loading = ref(false)
const search = async (searchQuery) => {
if (!searchQuery.trim()) {
if (!isReady.value || !searchQuery.trim()) {
results.value = []
return
}
loading.value = true
try {
results.value = await searchBrain(searchQuery)
const searchResults = await brain.value.find(searchQuery)
results.value = searchResults
} catch (error) {
console.error('Search error:', error)
results.value = []
@ -235,7 +228,11 @@ watch(query, (newQuery) => {
<div class="data-manager">
<h2>Data Management</h2>
<div>
<div v-if="!isReady" class="loading">
Initializing...
</div>
<div v-else>
<!-- Add Data Form -->
<form @submit.prevent="addData" class="add-form">
<h3>Add New Data</h3>
@ -292,7 +289,7 @@ watch(query, (newQuery) => {
import { ref, reactive, onMounted } from 'vue'
import { useBrainy } from '../composables/useBrainy'
const { add, stats: fetchStats } = useBrainy()
const { brain, isReady } = useBrainy()
const newItem = reactive({
data: '',
@ -302,7 +299,18 @@ const newItem = reactive({
const stats = ref(null)
onMounted(loadStats)
onMounted(async () => {
if (isReady.value) {
await loadStats()
}
})
// Watch for brain readiness
watch(isReady, async (ready) => {
if (ready) {
await loadStats()
}
})
const addData = async () => {
try {
@ -311,7 +319,11 @@ const addData = async () => {
tags: newItem.tags.split(',').map(tag => tag.trim()).filter(Boolean)
}
await add(newItem.data, newItem.type, metadata)
await brain.value.add({
data: newItem.data,
type: newItem.type,
metadata
})
// Reset form
newItem.data = ''
@ -327,7 +339,7 @@ const addData = async () => {
const loadStats = async () => {
try {
stats.value = await fetchStats()
stats.value = await brain.value.stats()
} catch (error) {
console.error('Failed to load stats:', error)
}
@ -423,55 +435,73 @@ button:disabled {
<!-- src/components/SearchOptions.vue -->
<template>
<div class="search-container">
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
class="search-input"
/>
<div v-if="!isReady" class="loading">
Initializing AI...
</div>
<div v-if="loading" class="loading">Searching...</div>
<div v-else>
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
class="search-input"
/>
<div class="results">
<div
v-for="result in results"
:key="result.id"
class="result-item"
>
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
<div v-if="loading" class="loading">Searching...</div>
<div class="results">
<div
v-for="result in results"
:key="result.id"
class="result-item"
>
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
</div>
</div>
</div>
</div>
</template>
<script>
import { Brainy } from '@soulcraft/brainy'
import { debounce } from '../utils/debounce'
export default {
name: 'SearchOptions',
data() {
return {
brain: null,
isReady: false,
query: '',
results: [],
loading: false
}
},
async mounted() {
await this.initBrain()
},
methods: {
async initBrain() {
try {
this.brain = new Brainy({
storage: { type: 'opfs' }
})
await this.brain.init()
this.isReady = true
} catch (error) {
console.error('Brain initialization failed:', error)
}
},
async search(query) {
if (!query.trim()) {
if (!this.isReady || !query.trim()) {
this.results = []
return
}
this.loading = true
try {
const res = await fetch('/api/brain/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query })
})
this.results = (await res.json()).results
this.results = await this.brain.find(query)
} catch (error) {
console.error('Search error:', error)
this.results = []
@ -490,6 +520,10 @@ export default {
this.loading = false
}
}
},
beforeUnmount() {
// Cleanup if needed
this.brain = null
}
}
</script>
@ -499,22 +533,31 @@ export default {
### Global Brainy Plugin
The plugin exposes a global `$searchBrain` helper that calls your Brainy-backed endpoint. Brainy itself runs on the server (see [Nuxt Server Route](#nuxt-server-route)).
```javascript
// src/plugins/brainy.js
import { Brainy } from '@soulcraft/brainy'
export default {
install(app, options = {}) {
const endpoint = options.endpoint ?? '/api/brain'
const defaultOptions = {
storage: { type: 'opfs' },
...options
}
// Add global search method (calls the server endpoint)
app.config.globalProperties.$searchBrain = async (query, searchOptions) => {
const res = await fetch(`${endpoint}/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, options: searchOptions })
})
return (await res.json()).results
const brain = new Brainy(defaultOptions)
// Make brain available globally
app.config.globalProperties.$brain = brain
app.provide('brain', brain)
// Auto-initialize
brain.init().catch(error => {
console.error('Brainy plugin initialization failed:', error)
})
// Add global method
app.config.globalProperties.$searchBrain = async (query, options) => {
return await brain.find(query, options)
}
}
}
@ -531,7 +574,7 @@ import BrainyPlugin from './plugins/brainy'
const app = createApp(App)
app.use(BrainyPlugin, {
endpoint: '/api/brain'
storage: { type: 'opfs' }
})
app.mount('#app')
@ -568,58 +611,24 @@ export default {
## 🏰 Nuxt.js Integration
Nuxt's server engine (Nitro) is the natural home for Brainy: it runs on Node/Bun, so the Brainy instance lives in `server/`, and pages/components call its routes.
### Nuxt Server Route
### Nuxt Plugin
```javascript
// server/utils/brain.js (server-only — Nitro never bundles this into the client)
// plugins/brainy.client.js
import { Brainy } from '@soulcraft/brainy'
let brainPromise
export default defineNuxtPlugin(async () => {
const brain = new Brainy({
storage: { type: 'opfs' }
})
export function getBrain() {
if (!brainPromise) {
brainPromise = (async () => {
// new Brainy() auto-detects filesystem persistence on Node
const brain = new Brainy()
await brain.init()
return brain
})()
await brain.init()
return {
provide: {
brain
}
}
return brainPromise
}
```
```javascript
// server/api/brain/search.post.js
import { getBrain } from '../../utils/brain'
export default defineEventHandler(async (event) => {
const { query, options } = await readBody(event)
const brain = await getBrain()
return { results: await brain.find(query, options) }
})
```
```javascript
// server/api/brain/add.post.js
import { getBrain } from '../../utils/brain'
export default defineEventHandler(async (event) => {
const { data, type, metadata } = await readBody(event)
const brain = await getBrain()
return { id: await brain.add({ data, type, metadata }) }
})
```
```javascript
// server/api/brain/stats.get.js
import { getBrain } from '../../utils/brain'
export default defineEventHandler(async () => {
const brain = await getBrain()
return await brain.stats()
})
```
@ -628,25 +637,26 @@ export default defineEventHandler(async () => {
```javascript
// composables/useBrainy.js
export const useBrainy = () => {
const { $brain } = useNuxtApp()
const search = async (query, options = {}) => {
return (await $fetch('/api/brain/search', {
method: 'POST',
body: { query, options }
})).results
return await $brain.find(query, options)
}
const add = async (data, type, metadata) => {
return (await $fetch('/api/brain/add', {
method: 'POST',
body: { data, type, metadata }
})).id
return await $brain.add({ data, type, metadata })
}
const stats = async () => {
return await $fetch('/api/brain/stats')
return await $brain.stats()
}
return { search, add, stats }
return {
brain: $brain,
search,
add,
stats
}
}
```
@ -656,20 +666,26 @@ export const useBrainy = () => {
<!-- pages/search.vue -->
<template>
<div>
<h1>Search</h1>
<h1>AI Search</h1>
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
/>
<div v-if="pending" class="loading">
Initializing AI...
</div>
<div v-if="searching" class="loading">Searching...</div>
<div v-else>
<input
v-model="query"
@input="handleSearch"
placeholder="Search..."
/>
<div class="results">
<div v-for="result in results" :key="result.id" class="result">
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
<div v-if="searching" class="loading">Searching...</div>
<div class="results">
<div v-for="result in results" :key="result.id" class="result">
<h3>{{ result.data }}</h3>
<p>Score: {{ (result.score * 100).toFixed(1) }}%</p>
</div>
</div>
</div>
</div>
@ -681,6 +697,12 @@ const { search } = useBrainy()
const query = ref('')
const results = ref([])
const searching = ref(false)
const pending = ref(true)
// Initialize
onMounted(() => {
pending.value = false
})
const handleSearch = debounce(async () => {
if (!query.value.trim()) {
@ -700,58 +722,82 @@ const handleSearch = debounce(async () => {
</script>
```
### Nuxt Configuration
```javascript
// nuxt.config.ts
export default defineNuxtConfig({
ssr: false, // Disable SSR for browser-only features
// Or use client-side hydration
nitro: {
experimental: {
wasm: true
}
},
vite: {
define: {
global: 'globalThis'
}
}
})
```
## 🛠️ Advanced Patterns
### Global State Management with Pinia
The store caches results and stats client-side; all Brainy work happens behind the server endpoints.
```javascript
// stores/brainy.js
import { defineStore } from 'pinia'
import { Brainy } from '@soulcraft/brainy'
export const useBrainyStore = defineStore('brainy', () => {
const brain = ref(null)
const isReady = ref(false)
const error = ref(null)
const stats = ref(null)
const search = async (query, options = {}) => {
error.value = null
const init = async (options = {}) => {
try {
const res = await fetch('/api/brain/search', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, options })
})
return (await res.json()).results
brain.value = new Brainy(options)
await brain.value.init()
isReady.value = true
await loadStats()
} catch (err) {
error.value = err.message
throw err
console.error('Brainy initialization failed:', err)
}
}
const search = async (query, options = {}) => {
if (!isReady.value) throw new Error('Brain not ready')
return await brain.value.find(query, options)
}
const add = async (data, type, metadata) => {
const res = await fetch('/api/brain/add', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ data, type, metadata })
})
const { id } = await res.json()
if (!isReady.value) throw new Error('Brain not ready')
const id = await brain.value.add({ data, type, metadata })
await loadStats() // Refresh stats
return id
}
const loadStats = async () => {
if (!isReady.value) return
try {
const res = await fetch('/api/brain/stats')
stats.value = await res.json()
stats.value = await brain.value.stats()
} catch (err) {
console.error('Failed to load stats:', err)
}
}
return {
brain: readonly(brain),
isReady: readonly(isReady),
error: readonly(error),
stats: readonly(stats),
init,
search,
add,
loadStats
@ -821,7 +867,7 @@ const showResults = ref(false)
const selectedIndex = ref(-1)
const search = async (searchQuery) => {
if (!searchQuery.trim()) {
if (!brainyStore.isReady || !searchQuery.trim()) {
results.value = []
return
}
@ -1120,23 +1166,21 @@ onMounted(() => {
### Component Testing with Vitest
The component talks to the server over `fetch`, so mock the endpoint, not Brainy itself.
```javascript
// tests/components/Search.test.js
import { mount } from '@vue/test-utils'
import { describe, it, expect, vi, beforeEach } from 'vitest'
import Search from '../src/components/Search.vue'
// Mock the server endpoint
beforeEach(() => {
global.fetch = vi.fn().mockResolvedValue({
ok: true,
json: async () => ({
results: [{ id: '1', data: 'Test result', score: 0.9 }]
})
})
})
// Mock Brainy
vi.mock('@soulcraft/brainy', () => ({
Brainy: vi.fn().mockImplementation(() => ({
init: vi.fn().mockResolvedValue(undefined),
find: vi.fn().mockResolvedValue([
{ id: '1', data: 'Test result', score: 0.9 }
])
}))
}))
describe('Search Component', () => {
let wrapper
@ -1149,7 +1193,14 @@ describe('Search Component', () => {
expect(wrapper.find('input').exists()).toBe(true)
})
it('shows loading state initially', () => {
expect(wrapper.text()).toContain('Initializing AI')
})
it('performs search when input changes', async () => {
// Wait for brain to initialize
await wrapper.vm.$nextTick()
const input = wrapper.find('input')
await input.setValue('test query')
await input.trigger('input')
@ -1157,7 +1208,6 @@ describe('Search Component', () => {
// Wait for debounced search
await new Promise(resolve => setTimeout(resolve, 350))
expect(global.fetch).toHaveBeenCalled()
expect(wrapper.text()).toContain('Test result')
})
})
@ -1191,8 +1241,6 @@ test('search functionality works', async ({ page }) => {
### Bundle Optimization
Keep Brainy out of the client bundle — it belongs to the server build only. Mark it external for SSR so the bundler resolves it at runtime instead of inlining it.
```javascript
// vite.config.js
import { defineConfig } from 'vite'
@ -1200,46 +1248,63 @@ import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
ssr: {
external: ['@soulcraft/brainy']
define: {
global: 'globalThis'
},
optimizeDeps: {
include: ['@soulcraft/brainy']
},
build: {
rollupOptions: {
output: {
manualChunks: {
'brainy': ['@soulcraft/brainy']
}
}
}
}
})
```
### Error Handling
Wrap the endpoint call with retry/backoff so transient network failures don't surface to the user.
```javascript
// src/composables/useBrainyWithErrorHandling.js
import { ref } from 'vue'
import { ref, onMounted } from 'vue'
import { Brainy } from '@soulcraft/brainy'
export function useBrainyWithErrorHandling(endpoint = '/api/brain') {
export function useBrainyWithErrorHandling() {
const brain = ref(null)
const isReady = ref(false)
const error = ref(null)
const retryCount = ref(0)
const search = async (query, options = {}, maxRetries = 3) => {
error.value = null
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
const res = await fetch(`${endpoint}/search`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ query, options })
})
if (!res.ok) throw new Error(`Search failed: ${res.status}`)
return (await res.json()).results
} catch (err) {
error.value = err.message
if (attempt === maxRetries) throw err
// Exponential backoff before retrying
await new Promise(resolve => setTimeout(resolve, 2000 * (attempt + 1)))
const init = async () => {
try {
brain.value = new Brainy()
await brain.value.init()
isReady.value = true
error.value = null
retryCount.value = 0
} catch (err) {
error.value = err.message
console.error('Brainy initialization failed:', err)
// Retry logic
if (retryCount.value < 3) {
retryCount.value++
setTimeout(init, 2000 * retryCount.value)
}
}
}
onMounted(init)
return {
brain: readonly(brain),
isReady: readonly(isReady),
error: readonly(error),
search
retry: init
}
}
```
@ -1250,13 +1315,6 @@ Here's a complete Vue 3 application structure:
```
my-brainy-vue-app/
├── server/ # Server-side (Node/Bun) — hosts Brainy
│ ├── utils/
│ │ └── brain.js # Shared Brainy instance (getBrain)
│ └── api/brain/
│ ├── search.post.js
│ ├── add.post.js
│ └── stats.get.js
├── src/
│ ├── components/
│ │ ├── Search.vue
@ -1278,7 +1336,7 @@ my-brainy-vue-app/
└── package.json
```
This provides a complete, production-ready Vue.js application: Brainy runs on the server, and the client talks to it over HTTP.
This provides a complete, production-ready Vue.js application with comprehensive Brainy integration.
## 📚 Next Steps

View file

@ -219,6 +219,7 @@ Actual cache size: ~40GB (prevents waste on 128GB systems)
**Recommendations:**
- ✅ Use FP32 model for maximum accuracy
- ✅ Enable distributed storage (S3/GCS)
- ✅ Monitor fairness violations (HNSW shouldn't dominate cache)
- ✅ Consider sharding beyond 50M entities
- ✅ Implement application-level caching for hot queries
@ -451,7 +452,7 @@ const euWestBrain = new Brainy({
// Route queries based on user location
async function search(query, userRegion) {
const brain = userRegion === 'US' ? usEastBrain : euWestBrain
return await brain.find(query)
return await brain.search(query)
}
```
@ -500,7 +501,7 @@ if (stats.fairness.fairnessViolation) {
```typescript
// Track search latency
console.time('search')
const results = await brain.find('query')
const results = await brain.search('query')
console.timeEnd('search') // Target: <10ms for hot queries
```

View file

@ -0,0 +1,138 @@
# Cloud Run + Filestore (NFS) Deployment Guide
> Eliminate cloud storage rate limiting by using brainy's FileSystem adapter with a Filestore NFS mount on Cloud Run.
## Why Filestore?
Brainy's metadata index generates 26-40 file writes per `brain.add()` call. On GCS, each write is a cloud API call subject to rate limiting (~1 write/sec per object), causing HTTP 429 errors and 61-second latency for a single add operation.
With Filestore:
- **Local disk speed**: Each write is ~1ms (vs 50-300ms for cloud API)
- **No rate limits**: NFS has no per-object write throttling
- **Zero code changes**: Use brainy's existing `FileSystemStorage` adapter
- **Same Cloud Run deployment**: Just add a volume mount
## Prerequisites
- Google Cloud project with Filestore API enabled
- Cloud Run service (gen2 execution environment required for NFS)
- VPC connector or Direct VPC egress configured
## Step 1: Create a Filestore Instance
```bash
gcloud filestore instances create brainy-store \
--zone=us-central1-b \
--tier=BASIC_SSD \
--file-share=name=brainy_data,capacity=1TB \
--network=name=default
```
**Tier recommendations:**
- **BASIC_SSD** (~$370/month for 1 TiB): Good balance of performance and cost
- **BASIC_HDD** (~$204/month for 1 TiB): Lower cost, sufficient for moderate workloads
- **ENTERPRISE** or **ZONAL**: Higher IOPS for heavy workloads
Note the IP address from the output — you'll need it for the Cloud Run mount.
## Step 2: Configure Cloud Run NFS Volume Mount
```yaml
# service.yaml
apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: my-brainy-service
spec:
template:
metadata:
annotations:
run.googleapis.com/execution-environment: gen2
run.googleapis.com/vpc-access-connector: projects/PROJECT/locations/REGION/connectors/CONNECTOR
spec:
containers:
- image: gcr.io/PROJECT/my-brainy-app
volumeMounts:
- name: brainy-nfs
mountPath: /mnt/brainy
volumes:
- name: brainy-nfs
nfs:
server: FILESTORE_IP # e.g., 10.0.0.2
path: /brainy_data
```
Deploy:
```bash
gcloud run services replace service.yaml --region=us-central1
```
## Step 3: Configure Brainy to Use FileSystem Adapter
```typescript
import { Brainy } from '@soulcraft/brainy'
import { FileSystemStorage } from '@soulcraft/brainy/storage'
const storage = new FileSystemStorage({
basePath: '/mnt/brainy/brain-data'
})
const brain = new Brainy({ storage })
await brain.init()
```
That's it. No GCS adapter, no rate limits, no retry logic needed.
## Caveats
### Single-Writer Recommendation
NFS does not provide strong file locking guarantees on Cloud Run. For best results:
- Use a **single Cloud Run instance** (max-instances=1) for write operations
- Multiple read-only instances can safely read from the same Filestore mount
- For multi-writer scenarios, use the GCS adapter with the write buffer (rate limit protection built in)
### Filestore Permissions
Cloud Run gen2 instances run as a specific service account. Ensure the Filestore instance allows access from your VPC network. No additional IAM permissions are needed — Filestore uses NFS network-level access.
### Cost Comparison
| Solution | Monthly Cost (1 TiB) | Write Latency | Rate Limits |
|----------|---------------------|---------------|-------------|
| GCS Standard | ~$20 storage + API ops | 50-300ms/write | 1 write/sec/object |
| GCS HNS | ~$20 storage + API ops | 50-300ms/write | 8,000 writes/sec |
| Filestore SSD | ~$370 fixed | ~1ms/write | None |
| Filestore HDD | ~$204 fixed | ~5ms/write | None |
Filestore costs more for storage but eliminates all rate limiting issues and provides significantly lower write latency.
### When to Choose Filestore vs GCS
**Choose Filestore when:**
- Write-heavy workloads (frequent `brain.add()`, chat applications)
- Low-latency requirements
- Single-writer architecture is acceptable
**Choose GCS (with write buffer) when:**
- Multi-instance deployments need shared storage
- Cost optimization for large datasets (lifecycle policies, Autoclass)
- Read-heavy workloads with infrequent writes
## Verification
After deploying, verify the mount works:
```bash
# In Cloud Run container
ls -la /mnt/brainy/
# Should show the Filestore share contents
# Test write performance
dd if=/dev/zero of=/mnt/brainy/test bs=1M count=100
# Expected: ~100MB/s+ for SSD tier
```
Then run your brainy application and confirm `brain.add()` completes without rate limit errors.

View file

@ -0,0 +1,399 @@
# AWS S3 Cost Optimization Guide for Brainy
> **Cost Impact**: Reduce storage costs from $138k/year to $5.9k/year at 500TB scale (**96% savings**)
## Overview
Brainy provides enterprise-grade cost optimization features for AWS S3 storage, enabling automatic tier transitions that dramatically reduce storage costs while maintaining performance.
## Cost Breakdown (Before Optimization)
### Standard S3 Storage Costs (500TB Dataset)
```
Storage: 500TB × $0.023/GB/month × 12 months = $138,000/year
Operations: ~$5,000/year (PUT, GET, LIST requests)
Total: $143,000/year
```
## S3 Storage Tiers
| Tier | Cost/GB/Month | Retrieval Fee | Use Case |
|------|---------------|---------------|----------|
| **Standard** | $0.023 | None | Frequently accessed |
| **Standard-IA** | $0.0125 | $0.01/GB | Infrequently accessed (30+ days) |
| **Intelligent-Tiering** | $0.023-0.00099 | None | Automatic optimization |
| **Glacier Instant** | $0.004 | $0.03/GB | Rare access, instant retrieval |
| **Glacier Flexible** | $0.0036 | $0.01/GB + time | Archive (minutes-hours retrieval) |
| **Glacier Deep Archive** | $0.00099 | $0.02/GB + time | Long-term archive (12 hours retrieval) |
## Strategy 1: Lifecycle Policies (Recommended)
### Setup: Automatic Tier Transitions
**Best for**: Predictable access patterns, batch workloads, archival data
```typescript
import { Brainy } from '@soulcraft/brainy'
import { S3CompatibleStorage } from '@soulcraft/brainy/storage'
// Initialize Brainy with S3 storage
const storage = new S3CompatibleStorage({
bucket: 'my-brainy-data',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})
const brain = new Brainy({ storage })
await brain.init()
// Set lifecycle policy for automatic archival
await storage.setLifecyclePolicy({
rules: [{
id: 'optimize-vectors',
prefix: 'entities/nouns/vectors/',
status: 'Enabled',
transitions: [
{ days: 30, storageClass: 'STANDARD_IA' }, // Infrequent Access after 30 days
{ days: 90, storageClass: 'GLACIER' }, // Glacier after 90 days
{ days: 365, storageClass: 'DEEP_ARCHIVE' } // Deep Archive after 1 year
]
}, {
id: 'optimize-metadata',
prefix: 'entities/nouns/metadata/',
status: 'Enabled',
transitions: [
{ days: 30, storageClass: 'STANDARD_IA' },
{ days: 180, storageClass: 'GLACIER' }
]
}]
})
// Verify lifecycle policy
const policy = await storage.getLifecyclePolicy()
console.log('Lifecycle policy active:', policy.rules.length, 'rules')
```
### Cost Calculation (500TB with Lifecycle Policy)
**Assumptions:**
- 40% of data accessed in last 30 days (Standard)
- 30% of data 30-90 days old (Standard-IA)
- 20% of data 90-365 days old (Glacier)
- 10% of data 365+ days old (Deep Archive)
```
Standard (200TB): 200TB × $0.023/GB × 12 = $55,200/year
Standard-IA (150TB): 150TB × $0.0125/GB × 12 = $22,500/year
Glacier (100TB): 100TB × $0.004/GB × 12 = $4,800/year
Deep Archive (50TB): 50TB × $0.00099/GB × 12 = $594/year
Total Storage Cost: $83,094/year (instead of $138,000)
Total with Operations: ~$88,000/year
Savings: $55,000/year (40%)
```
**But we can do better with Intelligent-Tiering...**
## Strategy 2: Intelligent-Tiering (Most Recommended)
### Setup: Automatic Access-Based Optimization
**Best for**: Unpredictable access patterns, mixed workloads, maximum automation
```typescript
// Enable Intelligent-Tiering for automatic optimization
await storage.enableIntelligentTiering('entities/', 'brainy-auto-optimize')
// Benefits:
// - Automatically moves objects between tiers based on access patterns
// - No retrieval fees (unlike Glacier)
// - Transitions happen within 24-48 hours of last access
// - Supports Archive Access tier (90+ days) and Deep Archive Access tier (180+ days)
```
### Intelligent-Tiering Tiers
Intelligent-Tiering automatically moves objects between:
1. **Frequent Access**: $0.023/GB/month (0-30 days)
2. **Infrequent Access**: $0.0125/GB/month (30-90 days)
3. **Archive Access**: $0.004/GB/month (90-180 days)
4. **Deep Archive Access**: $0.00099/GB/month (180+ days)
**Monitoring Fee**: $0.0025 per 1000 objects (minimal)
### Cost Calculation (500TB with Intelligent-Tiering)
**Realistic distribution after 1 year:**
- 15% Frequent Access (hot data)
- 20% Infrequent Access
- 35% Archive Access
- 30% Deep Archive Access
```
Frequent (75TB): 75TB × $0.023/GB × 12 = $20,700/year
Infrequent (100TB): 100TB × $0.0125/GB × 12 = $15,000/year
Archive (175TB): 175TB × $0.004/GB × 12 = $8,400/year
Deep Archive (150TB): 150TB × $0.00099/GB × 12 = $1,782/year
Monitoring: ~$300/year (minimal)
Total Storage Cost: $46,182/year
Total with Operations: ~$51,000/year
Savings vs Standard: $92,000/year (67%)
```
## Strategy 3: Hybrid Approach (Maximum Savings)
### Setup: Lifecycle + Intelligent-Tiering
**Best for**: Maximum cost optimization with fine-grained control
```typescript
// Enable Intelligent-Tiering for vectors (frequently searched)
await storage.enableIntelligentTiering('entities/nouns/vectors/', 'vectors-auto')
await storage.enableIntelligentTiering('entities/verbs/vectors/', 'verbs-auto')
// Set lifecycle policy for metadata (less frequently accessed)
await storage.setLifecyclePolicy({
rules: [{
id: 'archive-old-metadata',
prefix: 'entities/nouns/metadata/',
status: 'Enabled',
transitions: [
{ days: 30, storageClass: 'STANDARD_IA' },
{ days: 60, storageClass: 'GLACIER' },
{ days: 180, storageClass: 'DEEP_ARCHIVE' }
]
}, {
id: 'cleanup-old-system-data',
prefix: '_system/',
status: 'Enabled',
expiration: { days: 365 } // Delete old statistics
}]
})
```
### Cost Calculation (500TB Hybrid Approach)
**Vectors (300TB with Intelligent-Tiering):**
```
Frequent (45TB): 45TB × $0.023/GB × 12 = $12,420/year
Infrequent (60TB): 60TB × $0.0125/GB × 12 = $9,000/year
Archive (105TB): 105TB × $0.004/GB × 12 = $5,040/year
Deep Archive (90TB): 90TB × $0.00099/GB × 12 = $1,069/year
Subtotal: $27,529/year
```
**Metadata (200TB with Lifecycle Policy):**
```
Standard (60TB): 60TB × $0.023/GB × 12 = $16,560/year
Standard-IA (40TB): 40TB × $0.0125/GB × 12 = $6,000/year
Glacier (60TB): 60TB × $0.004/GB × 12 = $2,880/year
Deep Archive (40TB): 40TB × $0.00099/GB × 12 = $475/year
Subtotal: $25,915/year
```
**Total Cost: $53,444/year + operations (~$58,500/year total)**
**Savings vs Standard: $84,500/year (61%)**
## Strategy 4: Aggressive Archival (Maximum Savings)
### Setup: Fast Archival for Cold Data
**Best for**: Archival workloads, historical data, compliance
```typescript
await storage.setLifecyclePolicy({
rules: [{
id: 'aggressive-archival',
prefix: 'entities/',
status: 'Enabled',
transitions: [
{ days: 14, storageClass: 'STANDARD_IA' }, // IA after 2 weeks
{ days: 30, storageClass: 'GLACIER' }, // Glacier after 1 month
{ days: 90, storageClass: 'DEEP_ARCHIVE' } // Deep Archive after 3 months
]
}]
})
```
### Cost Calculation (500TB Aggressive Archival)
**After 1 year:**
```
Standard (50TB): 50TB × $0.023/GB × 12 = $13,800/year
Standard-IA (50TB): 50TB × $0.0125/GB × 12 = $7,500/year
Glacier (100TB): 100TB × $0.004/GB × 12 = $4,800/year
Deep Archive (300TB): 300TB × $0.00099/GB × 12 = $3,564/year
Total Storage Cost: $29,664/year
Total with Operations: ~$34,000/year
Savings: $109,000/year (76%)
Note: Retrieval costs may be significant if archived data is accessed frequently
```
## Comparison Table: All Strategies
| Strategy | Annual Cost | Savings | Retrieval Speed | Best For |
|----------|-------------|---------|-----------------|----------|
| **No Optimization** | $143,000 | 0% | Instant | N/A |
| **Lifecycle Policy** | $88,000 | 38% | Varies | Predictable patterns |
| **Intelligent-Tiering** | $51,000 | 64% | Instant (no retrieval fees) | **Recommended** |
| **Hybrid Approach** | $58,500 | 59% | Instant for vectors | Fine-grained control |
| **Aggressive Archival** | $34,000 | 76% | Hours to 12 hours | Cold data, compliance |
## Batch Delete Operations
### Efficient Cleanup
```typescript
// Batch delete (1000 objects per request)
const idsToDelete = [/* array of entity IDs */]
// Generate paths for both vector and metadata files
const paths = idsToDelete.flatMap(id => {
const shard = id.substring(0, 2)
return [
`entities/nouns/vectors/${shard}/${id}.json`,
`entities/nouns/metadata/${shard}/${id}.json`
]
})
// Batch delete (much faster and cheaper than individual deletes)
await storage.batchDelete(paths)
// Cost impact:
// - Individual deletes: 1M objects × $0.005 per 1000 = $5,000
// - Batch deletes: 1M/1000 × $0.005 = $5 (1000x cheaper!)
```
## Monitoring and Optimization
### Get Current Lifecycle Policy
```typescript
const policy = await storage.getLifecyclePolicy()
console.log('Active rules:', policy.rules)
// Example output:
// {
// rules: [
// {
// id: 'optimize-vectors',
// prefix: 'entities/nouns/vectors/',
// status: 'Enabled',
// transitions: [...]
// }
// ]
// }
```
### Remove Lifecycle Policy (if needed)
```typescript
// Remove all lifecycle rules
await storage.removeLifecyclePolicy()
```
### Check Intelligent-Tiering Configurations
```typescript
const configs = await storage.getIntelligentTieringConfigs()
console.log('Active configurations:', configs)
```
### Disable Intelligent-Tiering
```typescript
await storage.disableIntelligentTiering('brainy-auto-optimize')
```
## AWS Cost Explorer Analysis
### Track Your Savings
1. **Enable Cost Explorer** in AWS Console
2. **Group by Storage Class** to see tier distribution
3. **Set up Cost Anomaly Detection** for unexpected spikes
4. **Create Budget Alerts** for monthly storage costs
### Expected Metrics After 6 Months
```
Standard storage: 15-20% of total data
Standard-IA: 20-25%
Archive tiers: 55-65%
Monthly cost trend: Decreasing 5-10% per month as data ages into cheaper tiers
```
## Best Practices
1. ✅ **Start with Intelligent-Tiering** - No retrieval fees, automatic optimization
2. ✅ **Use batch operations** for deletions - 1000x cheaper than individual deletes
3. ✅ **Monitor storage class distribution** monthly via Cost Explorer
4. ✅ **Set lifecycle policies** for predictable archival (metadata, logs)
5. ✅ **Enable S3 Storage Lens** for detailed storage analytics
6. ✅ **Use S3 Select** for querying archived data without full retrieval
7. ✅ **Consider S3 Batch Operations** for large-scale tier changes
## Troubleshooting
### Issue: Data not transitioning to cheaper tiers
**Solution:**
```typescript
// Check if lifecycle policy is active
const policy = await storage.getLifecyclePolicy()
console.log('Policy status:', policy.rules.map(r => r.status))
// Ensure objects are old enough
// S3 requires objects to be at least 30 days old for IA transition
```
### Issue: High retrieval costs from Glacier
**Solution:**
```typescript
// Switch to Intelligent-Tiering (no retrieval fees)
await storage.disableIntelligentTiering('old-config')
await storage.enableIntelligentTiering('entities/', 'new-config')
// Or use Glacier Instant Retrieval instead of Glacier Flexible
```
### Issue: Unexpected monitoring fees
**Solution:**
- Intelligent-Tiering has $0.0025 per 1000 objects monitoring fee
- For 1 billion objects: $2,500/month monitoring
- If cost is high, use lifecycle policies instead (no monitoring fee)
## Summary
**Recommended Strategy for Most Use Cases:**
- **Intelligent-Tiering** for vectors and frequently queried data
- **Lifecycle policies** for metadata and system files
- **Batch operations** for efficient cleanup
**Expected Savings:**
- **Year 1**: 40-50% reduction in storage costs
- **Year 2+**: 60-70% reduction as more data ages into archive tiers
- **Long-term**: 75-85% reduction for mature datasets
**500TB Example (Intelligent-Tiering):**
- Before: $143,000/year
- After: $51,000/year
- **Savings: $92,000/year (64%)**
**1PB Example (Intelligent-Tiering):**
- Before: $286,000/year
- After: $102,000/year
- **Savings: $184,000/year (64%)**
---
**Last Updated**: 2025-10-17
**Cloud Provider**: AWS S3

View file

@ -0,0 +1,552 @@
# Azure Blob Storage Cost Optimization Guide for Brainy
> **Cost Impact**: Reduce storage costs from $107k/year to $5k/year at 500TB scale (**95% savings**)
## Overview
Brainy provides enterprise-grade cost optimization features for Azure Blob Storage, including manual tier management, lifecycle policies, and batch operations.
## Cost Breakdown (Before Optimization)
### Hot Tier Azure Storage Costs (500TB Dataset)
```
Storage: 500TB × $0.0184/GB/month × 12 months = $107,520/year
Operations: ~$5,000/year (write/read operations)
Total: $112,520/year
```
## Azure Blob Storage Tiers
| Tier | Cost/GB/Month | Retrieval | Early Deletion | Use Case |
|------|---------------|-----------|----------------|----------|
| **Hot** | $0.0184 | None | None | Frequent access |
| **Cool** | $0.0115 | $0.01/GB | 30 days | Infrequent access |
| **Archive** | $0.00099 | $0.02/GB + rehydration time | 180 days | Long-term archive |
**Key Difference from AWS/GCS:**
- Azure has only 3 tiers (vs AWS 6 tiers, GCS 4 classes)
- Archive tier requires rehydration (hours to 15 hours) before access
- No "Intelligent-Tiering" equivalent - must use lifecycle policies or manual management
## Strategy 1: Manual Tier Management (Immediate Savings)
### Setup: Change Blob Tiers Manually
**Best for**: Quick wins, specific files, immediate cost reduction
```typescript
import { Brainy } from '@soulcraft/brainy'
import { AzureBlobStorage } from '@soulcraft/brainy/storage'
// Initialize Brainy with Azure storage
const storage = new AzureBlobStorage({
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: 'brainy-data'
})
const brain = new Brainy({ storage })
await brain.init()
// Change tier for a single blob
await storage.changeBlobTier(
'entities/nouns/vectors/00/00123456-uuid.json',
'Cool'
)
// Batch tier changes (efficient for thousands of blobs)
const blobsToMove = [
'entities/nouns/vectors/01/...',
'entities/nouns/vectors/02/...',
// ... up to thousands of blobs
]
await storage.batchChangeTier(blobsToMove, 'Cool') // Hot → Cool
await storage.batchChangeTier(oldBlobs, 'Archive') // Cool → Archive
```
### Immediate Cost Impact
Moving 400TB from Hot to Cool:
```
Before (Hot): 400TB × $0.0184/GB × 12 = $86,016/year
After (Cool): 400TB × $0.0115/GB × 12 = $53,760/year
Savings: $32,256/year (37% savings on moved data)
```
Moving 100TB from Hot to Archive:
```
Before (Hot): 100TB × $0.0184/GB × 12 = $21,504/year
After (Archive): 100TB × $0.00099/GB × 12 = $1,158/year
Savings: $20,346/year (95% savings on moved data)
```
## Strategy 2: Lifecycle Policies (Automated)
### Setup: Automatic Tier Transitions
**Best for**: Predictable patterns, automatic management
```typescript
// Set lifecycle policy for automatic tier management
await storage.setLifecyclePolicy({
rules: [{
name: 'optimizeVectors',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/vectors/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 90 }
}
}
}
}, {
name: 'optimizeMetadata',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/metadata/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 180 }
}
}
}
}, {
name: 'cleanupOldSystemFiles',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['_system/']
},
actions: {
baseBlob: {
delete: { daysAfterModificationGreaterThan: 365 }
}
}
}
}]
})
// Verify lifecycle policy
const policy = await storage.getLifecyclePolicy()
console.log('Lifecycle policy active:', policy.rules.length, 'rules')
```
### Cost Calculation (500TB with Lifecycle Policy)
**Assumptions:**
- 30% of data in Hot tier (< 30 days old)
- 40% of data in Cool tier (30-90 days old)
- 30% of data in Archive tier (90+ days old)
```
Hot (150TB): 150TB × $0.0184/GB × 12 = $32,256/year
Cool (200TB): 200TB × $0.0115/GB × 12 = $26,880/year
Archive (150TB): 150TB × $0.00099/GB × 12 = $1,732/year
Total Storage Cost: $60,868/year
Total with Operations: ~$65,500/year
Savings: $47,000/year (42%)
```
## Strategy 3: Aggressive Archival (Maximum Savings)
### Setup: Fast Archival for Cold Data
**Best for**: Archival workloads, compliance, historical data
```typescript
await storage.setLifecyclePolicy({
rules: [{
name: 'aggressiveArchival',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 14 },
tierToArchive: { daysAfterModificationGreaterThan: 30 }
}
}
}
}]
})
```
### Cost Calculation (500TB Aggressive Archival)
**After 6 months:**
```
Hot (50TB): 50TB × $0.0184/GB × 12 = $10,752/year
Cool (100TB): 100TB × $0.0115/GB × 12 = $13,440/year
Archive (350TB): 350TB × $0.00099/GB × 12 = $4,039/year
Total Storage Cost: $28,231/year
Total with Operations: ~$33,000/year
Savings: $79,500/year (71%)
Warning: Archive rehydration takes 1-15 hours
```
## Strategy 4: Hybrid Approach (Balanced)
### Setup: Different Policies for Different Data Types
```typescript
await storage.setLifecyclePolicy({
rules: [{
// Vectors: Keep in Hot/Cool for search performance
name: 'vectors-moderate',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/vectors/', 'entities/verbs/vectors/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 60 }
// Don't archive vectors - keep searchable
}
}
}
}, {
// Metadata: Aggressive archival
name: 'metadata-aggressive',
enabled: true,
type: 'Lifecycle',
definition: {
filters: {
blobTypes: ['blockBlob'],
prefixMatch: ['entities/nouns/metadata/', 'entities/verbs/metadata/']
},
actions: {
baseBlob: {
tierToCool: { daysAfterModificationGreaterThan: 30 },
tierToArchive: { daysAfterModificationGreaterThan: 90 }
}
}
}
}]
})
```
### Cost Calculation (500TB Hybrid Approach)
**Vectors (300TB):**
```
Hot (90TB): 90TB × $0.0184/GB × 12 = $19,354/year
Cool (210TB): 210TB × $0.0115/GB × 12 = $28,980/year
Subtotal: $48,334/year
```
**Metadata (200TB):**
```
Hot (30TB): 30TB × $0.0184/GB × 12 = $6,451/year
Cool (70TB): 70TB × $0.0115/GB × 12 = $9,660/year
Archive (100TB): 100TB × $0.00099/GB × 12 = $1,158/year
Subtotal: $17,269/year
```
**Total Cost: $65,603/year + operations (~$70,500/year total)**
**Savings vs Hot: $42,000/year (37%)**
## Comparison Table: All Strategies
| Strategy | Annual Cost | Savings | Archive % | Best For |
|----------|-------------|---------|-----------|----------|
| **No Optimization** | $112,500 | 0% | 0% | N/A |
| **Manual Tier Mgmt** | $75,000 | 33% | 20% | Immediate savings |
| **Lifecycle Policy** | $65,500 | 42% | 30% | Automated management |
| **Hybrid Approach** | $70,500 | 37% | 20% | Balance performance/cost |
| **Aggressive Archival** | $33,000 | **71%** | 70% | Cold data, compliance |
## Archive Rehydration (Important!)
### Rehydrate from Archive Tier
**Required before accessing archived blobs:**
```typescript
// Rehydrate blob from Archive to Hot (high priority)
await storage.rehydrateBlob(
'entities/nouns/vectors/00/00123456-uuid.json',
'High' // 'Standard' or 'High' priority
)
// Rehydration time:
// - High priority: 1 hour
// - Standard priority: up to 15 hours
// Check rehydration status
const metadata = await storage.getBlobMetadata(blobPath)
console.log('Archive status:', metadata.archiveStatus)
// Output: 'rehydrate-pending-to-hot', 'rehydrate-pending-to-cool', or undefined (done)
```
### Batch Rehydration
```typescript
// Rehydrate multiple blobs (useful for planned access)
const blobsToRehydrate = [/* array of blob paths */]
for (const blobPath of blobsToRehydrate) {
await storage.rehydrateBlob(blobPath, 'High')
}
// Wait for rehydration to complete (1-15 hours)
// Then access blobs normally
```
### Cost Impact of Rehydration
```
Retrieval fee: $0.02/GB
High priority: Additional $0.10/GB
Examples:
- 1GB blob (standard): $0.02 + wait 15 hours
- 1GB blob (high priority): $0.12 + wait 1 hour
- 1TB batch (high priority): $122.88 + wait 1 hour
```
## Batch Operations
### Efficient Bulk Deletions
```typescript
// Batch delete (256 blobs per request)
const idsToDelete = [/* array of entity IDs */]
const paths = idsToDelete.flatMap(id => {
const shard = id.substring(0, 2)
return [
`entities/nouns/vectors/${shard}/${id}.json`,
`entities/nouns/metadata/${shard}/${id}.json`
]
})
// Batch delete via BlobBatchClient
await storage.batchDelete(paths)
// Cost impact:
// - Individual deletes: 1M operations × $0.0005 per 10k = $50
// - Batch deletes: 4k batches × $0.0005 = $2 (25x cheaper!)
```
### Batch Tier Changes
```typescript
// Change tier for thousands of blobs efficiently
const vectorPaths = [/* 10,000 blob paths */]
// Batch operation (256 blobs per batch)
await storage.batchChangeTier(vectorPaths, 'Cool')
// Much faster than individual changeBlobTier() calls
// Azure automatically batches internally for efficiency
```
## Monitoring and Management
### Get Current Lifecycle Policy
```typescript
const policy = await storage.getLifecyclePolicy()
console.log('Active rules:', policy.rules)
// Example output:
// {
// rules: [
// {
// name: 'optimizeVectors',
// enabled: true,
// type: 'Lifecycle',
// definition: {...}
// }
// ]
// }
```
### Remove Lifecycle Policy
```typescript
await storage.removeLifecyclePolicy()
```
### Get Storage Status
```typescript
const status = await storage.getStorageStatus()
console.log('Storage type:', status.type)
console.log('Container:', status.details.container)
console.log('Account:', status.details.account)
```
## Azure Portal Monitoring
### Track Your Savings
1. **Storage Account****Insights** → View tier distribution
2. **Cost Management****Cost Analysis** → Filter by storage account
3. **Monitoring****Metrics** → Track blob count by tier
4. **Lifecycle Management** → View policy execution logs
### Expected Metrics After 6 Months
```
Hot tier: 20-30% of total data
Cool tier: 40-50%
Archive tier: 20-40%
Monthly cost trend: Decreasing 5-8% per month as data transitions
```
## Best Practices
1. ✅ **Use lifecycle policies** for automatic management
2. ✅ **Archive cold data** aggressively (95% cost savings!)
3. ✅ **Use batch operations** for tier changes and deletions
4. ✅ **Plan rehydration** ahead of time (1-15 hour delay)
5. ✅ **Monitor early deletion charges** (Cool: 30 days, Archive: 180 days)
6. ✅ **Use Cool tier for infrequent access** (no rehydration needed)
7. ✅ **Consider ZRS or GRS** for critical data redundancy
## Storage Redundancy Options
| Option | Cost Multiplier | Copies | Availability | Use Case |
|--------|-----------------|--------|--------------|----------|
| **LRS** | 1x | 3 (same datacenter) | 11 nines | Cost-optimized |
| **ZRS** | 1.25x | 3 (different zones) | 12 nines | High availability |
| **GRS** | 2x | 6 (secondary region) | 16 nines | Disaster recovery |
| **RA-GRS** | 2.5x | 6 (read access) | 16 nines | Global read access |
**Recommendation for Brainy:**
- **Production**: GRS (geo-redundancy for disaster recovery)
- **Cost-optimized**: LRS (lowest cost, still very reliable)
## Troubleshooting
### Issue: Data not transitioning tiers
**Solution:**
```typescript
// Check lifecycle policy status
const policy = await storage.getLifecyclePolicy()
console.log('Policy rules:', policy.rules.map(r => ({
name: r.name,
enabled: r.enabled
})))
// Azure lifecycle policies run once per day
// Transitions may take 24-48 hours to execute
```
### Issue: Access denied on archived blob
**Solution:**
```typescript
// Archived blobs cannot be accessed directly
// Must rehydrate first:
await storage.rehydrateBlob(blobPath, 'High')
// Wait for rehydration (check status)
let status
do {
await new Promise(resolve => setTimeout(resolve, 60000)) // Wait 1 minute
const metadata = await storage.getBlobMetadata(blobPath)
status = metadata.archiveStatus
} while (status && status.includes('pending'))
// Now access the blob
const data = await storage.get(blobPath)
```
### Issue: High early deletion charges
**Solution:**
- Cool tier: 30-day minimum storage
- Archive tier: 180-day minimum storage
- Early deletion incurs pro-rated charges
- Use lifecycle policies instead of manual changes to avoid early deletion fees
## Authentication
### Connection String (Simple)
```typescript
const storage = new AzureBlobStorage({
connectionString: process.env.AZURE_STORAGE_CONNECTION_STRING,
containerName: 'brainy-data'
})
```
### Account Key (Explicit)
```typescript
const storage = new AzureBlobStorage({
accountName: process.env.AZURE_STORAGE_ACCOUNT,
accountKey: process.env.AZURE_STORAGE_KEY,
containerName: 'brainy-data'
})
```
### SAS Token (Granular Access)
```typescript
const storage = new AzureBlobStorage({
sasToken: process.env.AZURE_STORAGE_SAS_TOKEN,
accountName: process.env.AZURE_STORAGE_ACCOUNT,
containerName: 'brainy-data'
})
```
## Summary
**Recommended Strategy for Most Use Cases:**
- **Lifecycle policies** for automatic tier management
- **Batch operations** for efficient tier changes and deletions
- **Cool tier** for infrequently accessed data (no rehydration delay)
- **Archive tier** for long-term storage (1-15 hour rehydration)
**Expected Savings:**
- **Year 1**: 40-50% reduction in storage costs
- **Year 2+**: 60-70% reduction as more data ages into Archive
- **Long-term**: 75-85% reduction for mature datasets
**500TB Example (Lifecycle Policy):**
- Before: $112,500/year
- After: $65,500/year
- **Savings: $47,000/year (42%)**
**500TB Example (Aggressive Archival):**
- Before: $112,500/year
- After: $33,000/year
- **Savings: $79,500/year (71%)**
**1PB Example (Lifecycle Policy):**
- Before: $225,000/year
- After: $131,000/year
- **Savings: $94,000/year (42%)**
---
**Last Updated**: 2025-10-17
**Cloud Provider**: Azure Blob Storage

View file

@ -0,0 +1,450 @@
# Cloudflare R2 Cost Optimization Guide for Brainy
> **Cost Impact**: $0 egress fees + 96% storage savings = Lowest cloud storage costs
## Overview
Cloudflare R2 is an S3-compatible object storage with **zero egress fees**, making it ideal for high-traffic applications. Brainy fully supports R2 with lifecycle policies and batch operations.
## Cost Breakdown
### R2 Pricing (As of 2025)
```
Storage: $0.015/GB/month
Class A Operations (write): $4.50 per million
Class B Operations (read): $0.36 per million
Egress: $0.00 (FREE!)
```
### Comparison to AWS S3 (500TB Dataset)
**AWS S3 Standard:**
```
Storage: 500TB × $0.023/GB × 12 = $138,000/year
Egress (assume 100TB/month): 1.2PB × $0.09/GB = $108,000/year
Operations: $5,000/year
Total: $251,000/year
```
**Cloudflare R2 Standard:**
```
Storage: 500TB × $0.015/GB × 12 = $90,000/year
Egress: $0 (FREE!)
Operations: $5,000/year
Total: $95,000/year
Savings vs AWS: $156,000/year (62%)
```
**R2's Zero Egress Advantage:**
- **High-traffic apps**: Save $100k-$1M/year in egress fees
- **Video/media delivery**: No CDN egress costs
- **API responses**: Unlimited reads at no extra cost
## R2 Storage Classes (Coming Soon)
**Current State (2025):**
- R2 currently has only **one storage class** (Standard)
- No lifecycle policies or tier transitions yet
- Cloudflare plans to add infrequent access tiers
**When lifecycle features arrive:**
- Brainy is already prepared with `setLifecyclePolicy()` support
- Will work seamlessly once Cloudflare enables lifecycle management
## Strategy 1: Use R2 Standard (Current Best Practice)
### Setup: S3-Compatible API
```typescript
import { Brainy } from '@soulcraft/brainy'
import { S3CompatibleStorage } from '@soulcraft/brainy/storage'
// R2 uses S3-compatible API
const storage = new S3CompatibleStorage({
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
bucket: 'my-brainy-data',
region: 'auto', // R2 uses 'auto' region
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY
})
const brain = new Brainy({ storage })
await brain.init()
```
### Cost Calculation (500TB on R2)
```
Storage: 500TB × $0.015/GB × 12 = $90,000/year
Class A ops (10M writes): $45/year
Class B ops (100M reads): $36/year
Egress: $0
Total: $90,081/year
```
**Compared to AWS S3 (with egress):**
- AWS: $251,000/year
- R2: $90,081/year
- **Savings: $160,919/year (64%)**
## Strategy 2: R2 + Workers (Edge Computing)
### Setup: Compute at the Edge
```typescript
// Cloudflare Worker (runs at edge)
export default {
async fetch(request, env) {
// Initialize Brainy with R2
const storage = new S3CompatibleStorage({
endpoint: env.R2_ENDPOINT,
bucket: env.R2_BUCKET,
accessKeyId: env.R2_ACCESS_KEY,
secretAccessKey: env.R2_SECRET_KEY,
region: 'auto'
})
const brain = new Brainy({ storage })
await brain.init()
// Process at edge (no origin server needed)
const results = await brain.search(request.query)
return new Response(JSON.stringify(results))
}
}
```
### Cost Calculation (Workers + R2)
```
R2 Storage (500TB): $90,000/year
Workers (10M requests/day):
- First 100k requests/day: FREE
- Additional 350M requests/month: $1,750/year
- CPU time (50ms avg): $5,000/year
Total: $96,750/year
vs Traditional Setup (AWS S3 + EC2 + CloudFront):
- S3: $138,000/year
- EC2 (t3.xlarge × 4): $24,000/year
- CloudFront egress: $50,000/year
- Load balancer: $3,000/year
Total: $215,000/year
Savings: $118,250/year (55%)
```
## Strategy 3: Hybrid Multi-Cloud (R2 + S3)
### Setup: R2 for Hot Data, S3 for Archives
```typescript
// Use R2 for frequently accessed data (zero egress)
const hotStorage = new S3CompatibleStorage({
endpoint: `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com`,
bucket: 'brainy-hot',
region: 'auto',
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY
})
// Use AWS S3 with Intelligent-Tiering for cold data
const coldStorage = new S3CompatibleStorage({
endpoint: 's3.amazonaws.com',
bucket: 'brainy-archive',
region: 'us-east-1',
accessKeyId: process.env.AWS_ACCESS_KEY_ID,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY
})
// Initialize separate Brainy instances or implement tiering logic
```
### Cost Calculation (300TB R2 + 200TB S3 Archive)
```
R2 Hot Data (300TB):
Storage: 300TB × $0.015/GB × 12 = $54,000/year
Egress: $0
S3 Cold Data (200TB with lifecycle → Deep Archive):
Storage: 200TB × $0.00099/GB × 12 = $2,376/year
Ops: $1,000/year
Total: $57,376/year
vs All AWS S3:
- S3 storage + egress: $251,000/year
Savings: $193,624/year (77%)
```
## Batch Operations
### Efficient Bulk Deletions
```typescript
// R2 supports S3 batch delete API
const idsToDelete = [/* array of entity IDs */]
const paths = idsToDelete.flatMap(id => {
const shard = id.substring(0, 2)
return [
`entities/nouns/vectors/${shard}/${id}.json`,
`entities/nouns/metadata/${shard}/${id}.json`
]
})
// Batch delete (1000 objects per request)
await storage.batchDelete(paths)
// Cost impact:
// - Individual deletes: 1M × $4.50/1M = $4.50
// - Batch deletes: 1k batches × $4.50/1M = $0.0045 (1000x cheaper!)
```
## R2 Advanced Features
### 1. R2 Custom Domains
**Free custom domains for R2 buckets:**
```bash
# Configure custom domain in Cloudflare dashboard
# Then access via your domain
https://storage.yourdomain.com/entities/nouns/vectors/...
# Benefits:
# - No additional cost
# - Automatic SSL/TLS
# - Global CDN included
# - DDoS protection
```
### 2. R2 Event Notifications
**Trigger Workers on object events:**
```typescript
// Worker triggered on R2 object upload
export default {
async fetch(request, env) {
// Process new objects automatically
// E.g., index new entities, generate thumbnails, etc.
}
}
// Cost: Only pay for Worker execution (no polling needed)
```
### 3. R2 Presigned URLs
```typescript
// Generate presigned URL for direct browser uploads
const url = await storage.getPresignedUrl('upload-path', 3600) // 1 hour expiry
// Client uploads directly to R2 (no server bandwidth used)
```
## Monitoring and Management
### Get Storage Status
```typescript
const status = await storage.getStorageStatus()
console.log('Storage type:', status.type) // 's3-compatible'
console.log('Bucket:', status.details.bucket)
console.log('Endpoint:', status.details.endpoint)
```
### Cloudflare Dashboard Monitoring
1. **R2 Dashboard** → View bucket metrics
2. **Analytics** → Track requests, storage, and bandwidth
3. **Workers Analytics** → Monitor edge compute usage
4. **Logs** → Real-time logs with Logpush
### Expected Metrics
```
Storage: Growing with your data
Class A ops: Writes (higher cost)
Class B ops: Reads (minimal cost)
Egress: Always $0 (R2's advantage)
```
## Comparison Table: R2 vs Other Providers
| Feature | R2 | AWS S3 | GCS | Azure |
|---------|-----|--------|-----|-------|
| **Storage** | $0.015/GB | $0.023/GB | $0.020/GB | $0.0184/GB |
| **Egress** | **$0** | $0.09/GB | $0.12/GB | $0.087/GB |
| **Lifecycle Tiers** | Coming soon | 6 tiers | 4 classes | 3 tiers |
| **S3 API Compatible** | ✅ Yes | ✅ Native | ⚠️ Via interop | ⚠️ Via SDK |
| **CDN Included** | ✅ Yes | ❌ Extra cost | ❌ Extra cost | ❌ Extra cost |
| **Edge Compute** | ✅ Workers | ❌ Lambda@Edge | ❌ Cloud Functions | ❌ Functions |
## R2 Free Tier
**Generous free tier:**
```
Storage: 10 GB free per month
Class A ops: 1 million free per month
Class B ops: 10 million free per month
Egress: Unlimited (always free)
```
**Perfect for:**
- Development and testing
- Small applications (<10GB)
- Prototypes
## Best Practices
1. ✅ **Use R2 for high-egress workloads** - Zero egress fees
2. ✅ **Combine with Workers** - Edge compute included
3. ✅ **Use custom domains** - Free branded URLs
4. ✅ **Batch operations** for deletions - 1000x cheaper
5. ✅ **Use presigned URLs** - Direct client uploads
6. ✅ **Monitor with Analytics** - Built-in dashboarding
7. ✅ **Consider hybrid approach** - R2 hot + S3 archive cold
## Migration from S3 to R2
### Using rclone
```bash
# Install rclone
brew install rclone # or apt-get install rclone
# Configure S3 source
rclone config create s3-source s3 \
access_key_id=$AWS_ACCESS_KEY \
secret_access_key=$AWS_SECRET_KEY \
region=us-east-1
# Configure R2 destination
rclone config create r2-dest s3 \
access_key_id=$R2_ACCESS_KEY \
secret_access_key=$R2_SECRET_KEY \
endpoint=https://$R2_ACCOUNT_ID.r2.cloudflarestorage.com \
region=auto
# Copy data
rclone copy s3-source:my-bucket r2-dest:my-bucket --progress
# Verify
rclone check s3-source:my-bucket r2-dest:my-bucket
```
### Cost of Migration
```
Data transfer out from S3: 500TB × $0.09/GB = $45,000
Data transfer into R2: $0 (ingress is free)
One-time migration cost: $45,000
Monthly savings after migration:
S3 storage + egress: $20,833/month
R2 storage: $7,500/month
Savings: $13,333/month
ROI: 3.4 months
```
## Future: R2 Lifecycle Policies (When Available)
### Prepared for Future Features
```typescript
// Brainy is ready for R2 lifecycle features
await storage.setLifecyclePolicy({
rules: [{
id: 'archive-old-data',
prefix: 'entities/',
status: 'Enabled',
transitions: [
{ days: 30, storageClass: 'INFREQUENT_ACCESS' }, // When available
{ days: 90, storageClass: 'ARCHIVE' }
]
}]
})
// Expected cost impact (when lifecycle is available):
// Standard: $0.015/GB
// Infrequent: ~$0.008/GB (estimated)
// Archive: ~$0.002/GB (estimated)
```
## Troubleshooting
### Issue: Connection errors
**Solution:**
```typescript
// Ensure correct endpoint format
const storage = new S3CompatibleStorage({
endpoint: `https://${accountId}.r2.cloudflarestorage.com`,
// NOT: `https://r2.cloudflarestorage.com/${accountId}`
region: 'auto', // R2 requires 'auto'
forcePathStyle: false // R2 uses virtual-hosted-style
})
```
### Issue: High Class A operation costs
**Solution:**
- Use batch operations (writes are most expensive)
- Cache frequently written data
- Consolidate small writes into larger batches
- Consider Workers KV for high-frequency writes
### Issue: Need lifecycle management now
**Solution:**
- Manually move old data to S3 Deep Archive
- Use hybrid approach: R2 for hot, S3 for cold
- Wait for R2 lifecycle features (planned)
## Summary
**R2 Advantages:**
- ✅ **Zero egress fees** - Unlimited reads at no cost
- ✅ **Lower storage costs** - $0.015/GB vs $0.023/GB (AWS)
- ✅ **S3-compatible API** - Drop-in replacement for S3
- ✅ **Global CDN included** - No additional CDN costs
- ✅ **Edge Workers** - Compute at the edge
- ✅ **Free custom domains** - Branded URLs
- ✅ **No minimums** - No minimum storage duration
**R2 Limitations (Current):**
- ⚠️ Single storage class (for now)
- ⚠️ No lifecycle policies yet (coming soon)
- ⚠️ Less mature than S3/GCS/Azure
**Recommended Use Cases:**
- 🎯 High-traffic APIs (zero egress fees!)
- 🎯 Video/media delivery (massive savings)
- 🎯 User-generated content
- 🎯 Web application assets
- 🎯 Hot data storage
**500TB Example (R2 vs AWS S3 with 100TB/month egress):**
- AWS S3: $251,000/year
- Cloudflare R2: $90,000/year
- **Savings: $161,000/year (64%)**
**1PB Example (R2 vs AWS S3 with 200TB/month egress):**
- AWS S3: $686,000/year
- Cloudflare R2: $180,000/year
- **Savings: $506,000/year (74%)**
---
**Last Updated**: 2025-10-17
**Cloud Provider**: Cloudflare R2
**Key Advantage**: **$0 egress fees forever**

View file

@ -0,0 +1,465 @@
# Google Cloud Storage Cost Optimization Guide for Brainy
> **Cost Impact**: Reduce storage costs from $138k/year to $8.3k/year at 500TB scale (**94% savings**)
> **Disclaimer**: Cost savings percentages are PROJECTED based on published cloud provider pricing at 500TB scale. Actual savings depend on access patterns, data lifecycle, and workload characteristics. These are not measured benchmarks.
## Overview
Brainy provides enterprise-grade cost optimization features for Google Cloud Storage, including lifecycle policies and Autoclass for automatic tier management.
## Cost Breakdown (Before Optimization)
### Standard GCS Storage Costs (500TB Dataset)
```
Storage: 500TB × $0.023/GB/month × 12 months = $138,000/year
Operations: ~$5,000/year (Class A/B operations)
Total: $143,000/year
```
## GCS Storage Classes
| Class | Cost/GB/Month | Retrieval Fee | Minimum Storage | Use Case |
|-------|---------------|---------------|-----------------|----------|
| **Standard** | $0.020 | None | None | Frequent access |
| **Nearline** | $0.010 | $0.01/GB | 30 days | Once per month |
| **Coldline** | $0.004 | $0.02/GB | 90 days | Once per quarter |
| **Archive** | $0.0012 | $0.05/GB | 365 days | Long-term archive |
## Strategy 1: Lifecycle Policies (Manual Control)
### Setup: Automatic Tier Transitions
```typescript
import { Brainy } from '@soulcraft/brainy'
import { GcsStorage } from '@soulcraft/brainy/storage'
// Initialize Brainy with GCS storage
const storage = new GcsStorage({
bucketName: 'my-brainy-data',
keyFilename: './service-account.json' // Or use ADC
})
const brain = new Brainy({ storage })
await brain.init()
// Set lifecycle policy for automatic archival
await storage.setLifecyclePolicy({
rules: [{
condition: { age: 30 },
action: { type: 'SetStorageClass', storageClass: 'NEARLINE' }
}, {
condition: { age: 90 },
action: { type: 'SetStorageClass', storageClass: 'COLDLINE' }
}, {
condition: { age: 365 },
action: { type: 'SetStorageClass', storageClass: 'ARCHIVE' }
}]
})
// Verify lifecycle policy
const policy = await storage.getLifecyclePolicy()
console.log('Lifecycle policy active:', policy.rules.length, 'rules')
```
### Cost Calculation (500TB with Lifecycle Policy)
**Assumptions:**
- 40% of data accessed in last 30 days (Standard)
- 30% of data 30-90 days old (Nearline)
- 20% of data 90-365 days old (Coldline)
- 10% of data 365+ days old (Archive)
```
Standard (200TB): 200TB × $0.020/GB × 12 = $48,000/year
Nearline (150TB): 150TB × $0.010/GB × 12 = $18,000/year
Coldline (100TB): 100TB × $0.004/GB × 12 = $4,800/year
Archive (50TB): 50TB × $0.0012/GB × 12 = $720/year
Total Storage Cost: $71,520/year
Total with Operations: ~$76,500/year
Savings: $66,500/year (46%)
```
## Strategy 2: Autoclass (Recommended)
### Setup: Automatic Class Optimization
**Best for**: Maximum automation, unpredictable access patterns
```typescript
// Enable Autoclass for automatic tier management
await storage.enableAutoclass({
terminalStorageClass: 'ARCHIVE' // Optional: Set lowest tier
})
// Benefits:
// - Automatically moves objects between classes based on access patterns
// - No data retrieval delays (unlike AWS Glacier)
// - Transparent tier transitions within 24 hours
// - Supports all storage classes including Archive
// - No extra monitoring fees
```
### How Autoclass Works
1. **Initial Placement**: New objects start in Standard class
2. **Automatic Demotion**: Objects move to Nearline (30 days) → Coldline (90 days) → Archive (365 days)
3. **Automatic Promotion**: Accessed objects move back to Standard class
4. **Access-Pattern Learning**: Uses 90-day access history for optimization
### Cost Calculation (500TB with Autoclass)
**Realistic distribution after 1 year:**
- 10% Standard (hot data, frequently accessed)
- 15% Nearline (warm data)
- 35% Coldline (cool data)
- 40% Archive (cold data)
```
Standard (50TB): 50TB × $0.020/GB × 12 = $12,000/year
Nearline (75TB): 75TB × $0.010/GB × 12 = $9,000/year
Coldline (175TB): 175TB × $0.004/GB × 12 = $8,400/year
Archive (200TB): 200TB × $0.0012/GB × 12 = $2,880/year
Total Storage Cost: $32,280/year
Total with Operations: ~$37,000/year
Savings vs Standard: $106,000/year (74%)
```
## Strategy 3: Hybrid Approach (Maximum Savings)
### Setup: Autoclass + Lifecycle Policies
```typescript
// Enable Autoclass for vectors (frequently searched)
await storage.enableAutoclass({
terminalStorageClass: 'COLDLINE' // Don't archive vectors deeply
})
// Set lifecycle policy for metadata (less frequently accessed)
await storage.setLifecyclePolicy({
rules: [{
condition: { age: 30, matchesPrefix: ['entities/nouns/metadata/'] },
action: { type: 'SetStorageClass', storageClass: 'NEARLINE' }
}, {
condition: { age: 60, matchesPrefix: ['entities/nouns/metadata/'] },
action: { type: 'SetStorageClass', storageClass: 'COLDLINE' }
}, {
condition: { age: 180, matchesPrefix: ['entities/nouns/metadata/'] },
action: { type: 'SetStorageClass', storageClass: 'ARCHIVE' }
}, {
condition: { age: 730, matchesPrefix: ['_system/'] },
action: { type: 'Delete' } // Delete old system files after 2 years
}]
})
```
### Cost Calculation (500TB Hybrid Approach)
**Vectors (300TB with Autoclass):**
```
Standard (30TB): 30TB × $0.020/GB × 12 = $7,200/year
Nearline (45TB): 45TB × $0.010/GB × 12 = $5,400/year
Coldline (225TB): 225TB × $0.004/GB × 12 = $10,800/year
Subtotal: $23,400/year
```
**Metadata (200TB with Lifecycle Policy):**
```
Standard (30TB): 30TB × $0.020/GB × 12 = $7,200/year
Nearline (40TB): 40TB × $0.010/GB × 12 = $4,800/year
Coldline (80TB): 80TB × $0.004/GB × 12 = $3,840/year
Archive (50TB): 50TB × $0.0012/GB × 12 = $720/year
Subtotal: $16,560/year
```
**Total Cost: $39,960/year + operations (~$45,000/year total)**
**Savings vs Standard: $98,000/year (69%)**
## Strategy 4: Aggressive Archival (Maximum Savings)
### Setup: Fast Archival for Cold Data
```typescript
await storage.setLifecyclePolicy({
rules: [{
condition: { age: 14 },
action: { type: 'SetStorageClass', storageClass: 'NEARLINE' }
}, {
condition: { age: 30 },
action: { type: 'SetStorageClass', storageClass: 'COLDLINE' }
}, {
condition: { age: 90 },
action: { type: 'SetStorageClass', storageClass: 'ARCHIVE' }
}]
})
// Note: Archive class has 365-day minimum storage duration
// Early deletion incurs pro-rated charges for remaining days
```
### Cost Calculation (500TB Aggressive Archival)
**After 1 year:**
```
Standard (25TB): 25TB × $0.020/GB × 12 = $6,000/year
Nearline (50TB): 50TB × $0.010/GB × 12 = $6,000/year
Coldline (75TB): 75TB × $0.004/GB × 12 = $3,600/year
Archive (350TB): 350TB × $0.0012/GB × 12 = $5,040/year
Total Storage Cost: $20,640/year
Total with Operations: ~$25,500/year
Savings: $117,500/year (82%)
Warning: High retrieval costs if archived data is accessed frequently
```
## Comparison Table: All Strategies
| Strategy | Annual Cost | Savings | Best For |
|----------|-------------|---------|----------|
| **No Optimization** | $143,000 | 0% | N/A |
| **Lifecycle Policy** | $76,500 | 46% | Predictable patterns |
| **Autoclass** | $37,000 | **74%** | **Recommended** |
| **Hybrid Approach** | $45,000 | 69% | Fine-grained control |
| **Aggressive Archival** | $25,500 | 82% | Cold data, compliance |
## Autoclass vs Lifecycle Policies
| Feature | Autoclass | Lifecycle Policies |
|---------|-----------|-------------------|
| **Automation** | Fully automatic | Rule-based |
| **Access-pattern learning** | Yes (90-day history) | No |
| **Promotion to Standard** | Automatic on access | Manual only |
| **Terminal class** | Configurable | Fixed by rules |
| **Complexity** | Single command | Multiple rules |
| **Cost** | Lower (smarter) | Moderate |
| **Best for** | Unpredictable patterns | Predictable patterns |
## Batch Delete Operations
### Efficient Cleanup
```typescript
// Batch delete (100 objects per request for GCS)
const idsToDelete = [/* array of entity IDs */]
const paths = idsToDelete.flatMap(id => {
const shard = id.substring(0, 2)
return [
`entities/nouns/vectors/${shard}/${id}.json`,
`entities/nouns/metadata/${shard}/${id}.json`
]
})
// Batch delete
await storage.batchDelete(paths)
// Cost impact:
// - Individual deletes: 1M operations × $0.005 per 10k = $500
// - Batch deletes: 10k batches × $0.005 = $5 (100x cheaper!)
```
## Monitoring and Management
### Check Autoclass Status
```typescript
const status = await storage.getAutoclassStatus()
console.log('Autoclass enabled:', status.enabled)
console.log('Terminal class:', status.terminalStorageClass)
// Example output:
// {
// enabled: true,
// terminalStorageClass: 'ARCHIVE',
// toggleTime: '2025-01-15T10:30:00Z'
// }
```
### Disable Autoclass
```typescript
// Disable Autoclass (objects remain in current class)
await storage.disableAutoclass()
```
### Get Current Lifecycle Policy
```typescript
const policy = await storage.getLifecyclePolicy()
console.log('Active rules:', policy.rules)
```
### Remove Lifecycle Policy
```typescript
await storage.removeLifecyclePolicy()
```
## GCS Cloud Console Monitoring
### Track Your Savings
1. **Storage Browser** → View storage class distribution
2. **Monitoring** → Create custom dashboards for storage metrics
3. **Cloud Logging** → Track class transition events
4. **Cloud Billing Reports** → Compare storage costs month-over-month
### Expected Metrics After 6 Months (Autoclass)
```
Standard: 10-15% of total data
Nearline: 15-20%
Coldline: 30-40%
Archive: 30-45%
Monthly cost trend: Decreasing 8-12% per month as data ages into cheaper classes
```
## Best Practices
1. ✅ **Start with Autoclass** - Simplest and most effective
2. ✅ **Set terminal class to ARCHIVE** for maximum savings
3. ✅ **Use lifecycle policies for system files** - Predictable archival
4. ✅ **Monitor class distribution** monthly in Cloud Console
5. ✅ **Use batch operations** for deletions - 100x cheaper
6. ✅ **Enable Object Lifecycle Management logging** for auditing
7. ✅ **Consider Turbo Replication** for multi-region redundancy
## Troubleshooting
### Issue: Data not transitioning to cheaper classes
**Solution:**
```typescript
// Check Autoclass status
const status = await storage.getAutoclassStatus()
if (!status.enabled) {
await storage.enableAutoclass({ terminalStorageClass: 'ARCHIVE' })
}
// Autoclass requires 24-48 hours for initial transitions
```
### Issue: High retrieval costs
**Solution:**
- GCS has lower retrieval fees than AWS Glacier ($0.01-0.05/GB vs $0.01-0.20/GB)
- Autoclass automatically promotes frequently accessed objects to Standard
- Use Coldline for occasional access (better than Archive)
### Issue: Minimum storage duration charges
**Solution:**
- Nearline: 30-day minimum
- Coldline: 90-day minimum
- Archive: 365-day minimum
- Early deletion incurs pro-rated charges
- Use Autoclass to avoid manual class changes that might trigger early deletion fees
## ADC (Application Default Credentials) Setup
### Production Best Practice
```typescript
// Use ADC instead of service account key file
const storage = new GcsStorage({
bucketName: 'my-brainy-data'
// No keyFilename needed - uses ADC automatically
})
// ADC authentication order:
// 1. GOOGLE_APPLICATION_CREDENTIALS environment variable
// 2. gcloud CLI credentials
// 3. Compute Engine/Cloud Run service account
```
### Set up ADC
```bash
# For local development
gcloud auth application-default login
# For production (use service account)
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"
# For Cloud Run/GKE/Compute Engine (automatic)
# Service account is automatically available
```
## Summary
**Recommended Strategy for Most Use Cases:**
- **Autoclass** for automatic optimization (simplest, most effective)
- **Lifecycle policies** for predictable archival (system files, logs)
- **Batch operations** for efficient cleanup
**Expected Savings:**
- **Year 1**: 50-60% reduction in storage costs
- **Year 2+**: 70-80% reduction as more data ages into archive classes
- **Long-term**: 85-90% reduction for mature datasets
**500TB Example (Autoclass):**
- Before: $143,000/year
- After: $37,000/year
- **Savings: $106,000/year (74%)**
**1PB Example (Autoclass):**
- Before: $286,000/year
- After: $74,000/year
- **Savings: $212,000/year (74%)**
**10PB Example (Autoclass):**
- Before: $2,860,000/year
- After: $740,000/year
- **Savings: $2,120,000/year (74%)**
## Strategy 5: HNS Buckets for Write-Heavy Workloads
### When to Use HNS (Hierarchical Namespace)
If you experience HTTP 429 rate limit errors during `brain.add()` operations (especially with many metadata fields), GCS Hierarchical Namespace buckets provide significantly higher write throughput with zero code changes.
### Why It Helps
Standard GCS buckets enforce ~1 write/sec per object path. Brainy's metadata index writes multiple chunk and sparse index files per `brain.add()` call, which can exceed these limits during burst writes.
HNS buckets provide:
- **8x higher initial QPS** (8,000 writes/sec vs 1,000 for standard buckets)
- **Better handling of hierarchical key patterns** (which brainy uses for sharded storage)
- **No code changes required** — create a new HNS-enabled bucket and point brainy at it
### Setup
```bash
# Create HNS-enabled bucket
gcloud storage buckets create gs://my-brainy-hns \
--location=us-central1 \
--uniform-bucket-level-access \
--enable-hierarchical-namespace
```
Then configure brainy to use the new bucket:
```typescript
const storage = new GcsStorage({
bucketName: 'my-brainy-hns'
})
```
### Trade-offs
- HNS buckets do not support object versioning (irrelevant for brainy — uses its own COW versioning)
- HNS is available in most GCS regions
- Pricing is the same as standard buckets
### Alternative: Cloud Run with Filestore/NFS
For the highest write throughput with zero rate limiting, see the [Cloud Run Filestore Guide](cloud-run-filestore-guide.md) — mount a Filestore NFS volume and use brainy's FileSystem adapter instead of GCS.
---
**Last Updated**: 2025-10-17
**Cloud Provider**: Google Cloud Storage

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,16 +1,3 @@
---
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
@ -23,19 +10,19 @@ Brainy's transaction system provides **atomic operations** with automatic rollba
- **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
- **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(), brain.transact(), etc.)
User Code (brain.add(), brain.update(), etc.)
Transaction Manager (orchestration)
Operations (SaveNounMetadataOperation, SaveNounOperation, etc.)
Storage Adapter (sharding, ID-first routing)
Storage Adapter (COW, sharding, type-aware routing)
```
### How It Works
@ -87,41 +74,31 @@ class SaveNounMetadataOperation {
## Compatibility with Advanced Features
### Multi-Write Batches: `brain.transact()`
### COW (Copy-on-Write)
✅ **The 8.0 path for atomic multi-entity writes**
✅ **Fully Compatible**
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:
Transactions work transparently with COW branches:
```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' } })
// Create branch
await brain.cow.createBranch('feature-branch')
await brain.cow.checkout('feature-branch')
db.receipt.ids // resolved id per operation, in input order
// 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:**
- 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.
- 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
@ -177,27 +154,37 @@ await brain.update({
- 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)
- 40x faster on cloud storage (eliminates 42-type search)
### Storage Adapter Interface
### Distributed Storage
✅ **Fully Compatible**
Transactions go through the `StorageAdapter` interface, so both shipped adapters (filesystem, memory) and any custom plugin adapter inherit the same atomicity guarantees:
Transactions work with distributed/remote storage:
```typescript
// Works with S3, Azure, GCS, etc.
const brain = new Brainy({
storage: { type: 'filesystem', path: './data' }
storage: {
type: 's3Compatible',
config: { /* S3 config */ }
}
})
await brain.add({ data: { name: 'Entity' }, type: NounType.Thing })
// 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
- 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
- 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
@ -296,7 +283,7 @@ await brain.relate({
})
// Delete person (atomic - deletes entity + relationships)
await brain.remove(personId)
await brain.delete(personId)
// If delete fails, both entity and relationships remain
```
@ -329,31 +316,37 @@ try {
### 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)
**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
### Auditing Committed Batches
Every committed `brain.transact()` batch is recorded in the transaction
log, newest first:
### Statistics and Monitoring
```typescript
await brain.transact(ops, { meta: { author: 'import-job' } })
const entries = await brain.transactionLog({ limit: 10 })
// [{ generation: 1042, timestamp: 1765432100000, meta: { author: 'import-job' } }]
// Get transaction statistics
const stats = brain.transactionManager?.getStats()
console.log(stats)
// {
// totalTransactions: 1234,
// successfulTransactions: 1200,
// failedTransactions: 34,
// rollbacks: 34,
// averageOperationsPerTransaction: 4.2
// }
```
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.
**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
@ -363,7 +356,7 @@ for the history-granularity contract.
// ✅ Recommended: Use Brainy's API (transactions automatic)
await brain.add({ data, type })
await brain.update({ id, data })
await brain.remove(id)
await brain.delete(id)
// ❌ Avoid: Direct storage access bypasses transactions
await brain.storage.saveNoun(noun) // No transaction protection
@ -393,34 +386,35 @@ if (!isValidVector(vector, brain.dimension)) {
await brain.add({ data, type, vector })
```
### 4. Batch Related Writes with `transact()`
### 4. Monitor Transaction Statistics
```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
// ✅ 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 a single Brainy process
- ✅ Atomicity within single Brainy instance
- ✅ Consistent state across all indexes
- ✅ Automatic rollback on failure
- ✅ Works with all storage adapters (filesystem, memory, custom plugin adapters)
- ✅ Works with all storage adapters (local, remote, COW, sharded)
**What Transactions DON'T Provide:**
- ❌ Two-phase commit across multiple Brainy instances
- ❌ Distributed locking across processes
- ❌ Distributed locking across nodes
- ❌ 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.
**Design:** Transactions ensure atomicity at the **write coordinator level**. For multi-instance scenarios, use `DistributedCoordinator`.
## Testing Transactions
@ -456,18 +450,16 @@ describe('Transaction Tests', () => {
### Integration Tests
See `tests/transaction/integration/` for comprehensive integration tests covering:
- COW integration (`cow-transactions.test.ts`)
- 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`.
- TypeAware integration (`typeaware-transactions.test.ts`)
- Distributed storage integration (`distributed-transactions.test.ts`)
## Troubleshooting
### High Rollback Rate
**Symptom:** a high share of writes throw and roll back
**Symptom:** `failedTransactions` / `totalTransactions` > 5%
**Possible Causes:**
1. Invalid vector dimensions
@ -497,6 +489,21 @@ through the real recovery path — are proven in
- 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
@ -545,23 +552,32 @@ interface StorageAdapter {
}
```
**Key Insight:** Both shipped storage adapters (filesystem, memory) — and any custom plugin adapter — implement this interface. Transactions work with **any** storage adapter automatically.
**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`, `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)
- **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
- 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. 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.
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 for single operations
- ✅ **Atomic**: All operations succeed or all rollback — per operation and per `transact()` batch
- ✅ **Automatic**: No manual transaction management needed
- ✅ **Atomic**: All operations succeed or all rollback
- ✅ **Compatible**: Works with all storage adapters and features
- ✅ **Coordinated**: Per-entity `ifRev` and whole-store `ifAtGeneration` CAS reject conflicting batches before anything is staged
- ✅ **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.remove()`, and `brain.relate()` — and reach for `brain.transact()` whenever several writes must land together.
Start using transactions today - they're already built into `brain.add()`, `brain.update()`, `brain.delete()`, and `brain.relate()`!

View file

@ -162,7 +162,7 @@ const brain = new Brainy({
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 +175,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
})
@ -220,14 +219,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
})
@ -366,7 +364,7 @@ try {
await brain.init()
const id = await brain.add("health check", { nounType: 'content' })
const results = await brain.find("health")
const results = await brain.search("health")
console.log('✅ Brainy is working correctly')
console.log(`Added item: ${id}`)

View file

@ -380,7 +380,7 @@ displayAug.configure({
```typescript
// Precompute display fields for better performance
const entities = await brainy.find({ limit: 100 })
const entities = await brainy.search('*', { limit: 100 })
await displayAug.precomputeBatch(
entities.map(e => ({ id: e.id, data: e.metadata }))
)

View file

@ -80,16 +80,17 @@ const brain = new Brainy({
}
})
// ✅ Pattern 2: Filesystem (production default)
// ✅ Pattern 2: Cloud storage (production)
const brain = new Brainy({
storage: {
type: 'filesystem',
path: '/var/lib/brainy'
type: 's3',
bucket: 'my-vfs-data',
region: 'us-west-2'
}
})
// ✅ Pattern 3: Auto-detection (recommended)
const brain = new Brainy() // Picks filesystem on Node, memory in browser
const brain = new Brainy() // Automatically chooses best storage
```
## 🔍 Search Patterns
@ -562,7 +563,7 @@ function vfsMiddleware() {
| ❌ **Avoid These Patterns** | ✅ **Use These Instead** |
|---------------------------|------------------------|
| Manual tree filtering | `vfs.getDirectChildren()` |
| Memory storage for files | Filesystem (snapshot off-site for backup) |
| Memory storage for files | Filesystem or cloud storage |
| Sequential file operations | Parallel processing with limits |
| Manual relationship tracking | Built-in `vfs.addRelationship()` |
| Loading entire directories | Paginated/lazy loading |

View file

@ -289,7 +289,7 @@ export class SizeProjection extends BaseProjectionStrategy {
where: {
vfsType: 'file',
size: {
gte: min,
greaterEqual: min,
lessThan: max
}
},
@ -305,7 +305,7 @@ export class SizeProjection extends BaseProjectionStrategy {
where: {
vfsType: 'file',
size: {
gte: min,
greaterEqual: min,
lessThan: max
}
},
@ -359,7 +359,7 @@ export class StatusProjection extends BaseProjectionStrategy {
const results = await brain.find({
where: {
vfsType: 'file',
modified: { gte: oneDayAgo },
modified: { greaterEqual: oneDayAgo },
reviewStatus: { missing: true } // No review status set
},
limit: 1000
@ -387,7 +387,7 @@ export class StatusProjection extends BaseProjectionStrategy {
vfsType: 'file',
anyOf: [
{ reviewStatus: { exists: true } },
{ modified: { gte: Date.now() - 86400000 } }
{ modified: { greaterEqual: Date.now() - 86400000 } }
]
},
limit
@ -415,7 +415,7 @@ Projection strategies use **Brainy Field Operators** (BFO), not MongoDB-style op
{ size: { $gte: 1000, $lte: 5000 } }
// ✅ BFO style (CORRECT)
{ size: { gte: 1000, lte: 5000 } }
{ size: { greaterEqual: 1000, lessEqual: 5000 } }
```
### Logical Operators
@ -451,9 +451,9 @@ Projection strategies use **Brainy Field Operators** (BFO), not MongoDB-style op
// Comparison
{ field: value } // Exact match
{ field: { greaterThan: 10 } } // >
{ field: { gte: 10 } } // >=
{ field: { greaterEqual: 10 } } // >=
{ field: { lessThan: 10 } } // <
{ field: { lte: 10 } } // <=
{ field: { lessEqual: 10 } } // <=
{ field: { not: value } } // !=
// Logical
@ -485,7 +485,7 @@ All metadata fields are automatically indexed. Use direct equality or range quer
```typescript
// ✅ Fast: Direct index lookup (O(log n))
{ priority: 'high' }
{ size: { gte: 1000 } }
{ size: { greaterEqual: 1000 } }
// ⚠️ Slower: Must scan results
{ path: { matches: /complex-regex/ } }
@ -668,7 +668,7 @@ async resolve(brain, vfs, period: string) {
const results = await brain.find({
where: {
modified: { gte: since }
modified: { greaterEqual: since }
}
})
return this.extractIds(results)

View file

@ -8,7 +8,7 @@
- **[VFS Core](VFS_CORE.md)** - Complete filesystem architecture and API
- **[Semantic VFS](SEMANTIC_VFS.md)** - Multi-dimensional file access (6 semantic dimensions)
- **[Neural Extraction](NEURAL_EXTRACTION.md)** - AI-powered concept and entity extraction
- **[Common Patterns](COMMON_PATTERNS.md)** - Real-world use cases and code
- **[Examples & Scenarios](VFS_EXAMPLES_SCENARIOS.md)** - Real-world use cases and code
- **[VFS API Guide](VFS_API_GUIDE.md)** - Complete API reference
## What is Brainy VFS?
@ -332,7 +332,7 @@ const urgent = await vfs.search('', {
## Advanced Features
> **Note:** See [VFS ROADMAP](./ROADMAP.md) for planned advanced features like version history and more.
> **Note:** See [VFS ROADMAP](./ROADMAP.md) for planned advanced features like version history, distributed filesystem, and more.
## Integration Possibilities
@ -351,6 +351,7 @@ Brainy VFS is designed for speed and scale:
**PROJECTED at larger scales (not yet tested):**
- **Vector search** <100ms for millions of files (projected)
- **Streaming support** for files of any size (architecture supports, see [limitations in ROADMAP](./ROADMAP.md))
- **Distributed sharding** for billions of files (architecture supports, not tested at scale)
See tests in `tests/vfs/` for actual measured performance.
@ -376,7 +377,7 @@ Brainy VFS fully leverages Brainy's revolutionary Triple Intelligence system:
| Manual organization | Self-organizing with intelligent fusion |
| No relationships | Rich knowledge graph with traversal |
| Static metadata | Dynamic, queryable metadata with field intelligence |
| Manual scaling | Horizontal read scaling — many readers, one writer |
| Single server | Distributed & federated |
## Installation
@ -386,7 +387,8 @@ npm install @soulcraft/brainy
## Requirements
- Node.js 22+ or Bun (server-only)
- Node.js 18+ (for server/desktop)
- Modern browser (for web apps)
- Brainy 3.0+
## API Reference
@ -436,10 +438,10 @@ The VFS is built with production scalability in mind:
- Parent-child relationship caching
- Hot path detection and optimization
2. **Horizontal Read Scaling**
- On-disk store partitioned into 256 hex directory buckets
- Many reader processes against one shared store
- Single writer keeps the store consistent
2. **Distributed Architecture**
- Sharding by path prefix
- Read replicas for hot directories
- CDN integration for static files
3. **Intelligent Indexing**
- Compound indexes on (parent, name)
@ -587,6 +589,49 @@ vfs.on('file:added', async (path) => {
})
```
#### **5. Distributed Team Workspace**
Collaborative file management:
```javascript
// Track file ownership and access
await vfs.writeFile('/projects/alpha/spec.md', content, {
metadata: {
owner: userId,
team: 'engineering',
permissions: {
[userId]: 'rw',
'team:engineering': 'r',
'others': '-'
}
}
})
// Add collaborative features
await vfs.addTodo('/projects/alpha/spec.md', {
task: 'Review security section',
assignee: 'alice@company.com',
due: '2024-02-01',
priority: 'high'
})
// Track who's working on what
await vfs.setxattr('/projects/alpha/spec.md', 'locks', {
section3: {
user: 'bob@company.com',
since: Date.now()
}
})
// Find all files assigned to a user
const assigned = await vfs.search('', {
where: {
'todos.assignee': 'alice@company.com',
'todos.status': 'pending'
}
})
```
### Monitoring & Operations (Planned)
> **Note:** Production monitoring features are planned for v1.1. See [ROADMAP](./ROADMAP.md).
@ -620,11 +665,27 @@ await vfs.init({
})
```
#### **Distributed Cluster**
```javascript
const vfs = new VirtualFileSystem()
await vfs.init({
distributed: true,
nodes: [
'vfs1.internal:8080',
'vfs2.internal:8080',
'vfs3.internal:8080'
],
replication: 3,
consistency: 'eventual'
})
```
## Roadmap & Future Features
See [VFS ROADMAP](./ROADMAP.md) for planned features including:
- Enhanced streaming support (v1.1)
- Version history (v1.2)
- Distributed filesystem (v1.2)
- AI-powered automation (v2.0)
- FUSE driver (v2.0 research)
- And more community-requested features

View file

@ -65,6 +65,27 @@ const diff = await vfs.diffVersions('/important-doc.md', v1, v2)
**Status:** Planned
**Effort:** 4-5 weeks
### Distributed Filesystem
Mount remote Brainy instances for federated file access.
```typescript
// Planned API (not yet implemented)
await vfs.mount('/remote-team', {
type: 'brainy-remote',
url: 'https://team-brainy.example.com',
credentials: {...}
})
// Federated search across mounted instances
const results = await vfs.search('project docs', { includeMounted: true })
// Sync directories
await vfs.sync('/local/docs', '/remote-team/docs')
```
**Status:** Planned
**Effort:** 6-8 weeks
### Backup & Recovery API
Built-in backup and recovery operations.
@ -194,6 +215,12 @@ Vite integration with VFS for semantic module resolution.
## Far Future (v5.0+)
### Automatic Node Discovery
Zero-config multi-node setup with automatic discovery via UDP broadcast, Kubernetes DNS, or cloud provider APIs.
### Automatic Failover
Health monitoring and automatic failover in distributed deployments.
### Cloud Provider Auto-Detection
```typescript
// Far future concept

View file

@ -47,18 +47,16 @@ const authFiles = await vfs.readdir('/by-concept/authentication')
const aliceFiles = await vfs.readdir('/by-author/alice')
```
### 2. **Change Tracking by Date**
List the files that were modified on any given day:
### 2. **Time Travel**
See your codebase as it existed at any point:
```typescript
// Files that changed on March 15th
const changed = await vfs.readdir('/as-of/2024-03-15')
// Code from March 15th
const snapshot = await vfs.readdir('/as-of/2024-03-15')
// Everything under /src right now
// Compare with today
const current = await vfs.readdir('/src')
```
`/as-of/<date>` selects by *modification date* — it reads the files' current content, not historical versions. For true point-in-time queries over entity state, use the Db API (`brain.asOf(generation)`).
### 3. **Knowledge Graph Navigation**
Navigate by semantic relationships:
```typescript
@ -120,14 +118,13 @@ await vfs.stat('/by-author/alice/config.ts')
### 4. By Time (Temporal) ✅ **Production**
```typescript
await vfs.readdir('/as-of/2024-03-15')
// Files modified on March 15, 2024 (24-hour window)
// Files modified on March 15, 2024
await vfs.readFile('/as-of/2024-03-15/auth.ts')
// Current content of auth.ts, addressed by modification date —
// the path only resolves if auth.ts was modified that day
await vfs.readFile('/as-of/2024-03-15/src/auth.ts')
// Read auth.ts as it existed that day
```
**How it works:** Tracks the `modified` timestamp on every file and runs a range query (`gte`/`lte`) over one 24-hour window for O(log n) performance. The VFS does not store historical file contents — `/as-of/` filters by *when a file last changed*; reads return the current bytes. For point-in-time state, use the Db API (`brain.asOf(generation)`).
**How it works:** Tracks `modified` timestamp. Uses B-tree range queries (`greaterEqual`/`lessEqual`) for O(log n) performance.
**Status:** ✅ Fully implemented and tested at 10K file scale
@ -225,7 +222,7 @@ console.log(authFiles)
// ['login.ts', 'signup.ts', 'oauth.ts']
```
### Example 2: Changes by Day
### Example 2: Time Travel
```typescript
// See what changed today
const today = new Date().toISOString().split('T')[0]
@ -235,8 +232,8 @@ const todaysFiles = await vfs.readdir(`/as-of/${today}`)
const yesterday = new Date(Date.now() - 86400000).toISOString().split('T')[0]
const yesterdaysFiles = await vfs.readdir(`/as-of/${yesterday}`)
const onlyToday = todaysFiles.filter(f => !yesterdaysFiles.includes(f))
console.log('Changed today (untouched yesterday):', onlyToday)
const newFiles = todaysFiles.filter(f => !yesterdaysFiles.includes(f))
console.log('New files today:', newFiles)
```
### Example 3: Graph Navigation
@ -400,9 +397,9 @@ Semantic VFS is built on Brainy's Triple Intelligence™:
### Real Implementations, Zero Mocks
Every component uses **production Brainy APIs**:
- `brain.find()` - Real metadata queries
- `brain.similar()` - Real HNSW search
- `brain.related()` - Real graph traversal
- `brain.find()` - Real metadata queries (brainy.ts:580)
- `brain.similar()` - Real HNSW search (brainy.ts:680)
- `brain.getRelations()` - Real graph traversal (brainy.ts:803)
- `MetadataIndexManager` - Real B-tree indexes
- `GraphAdjacencyIndex` - Real graph storage
- `HNSW Index` - Real vector search
@ -435,17 +432,25 @@ await vfs.writeFile(path, code, {
})
```
### 3. Combine Dimensions
### 3. Optimize for Your Scale
```typescript
// Find security files Alice changed on a given day
// (each /as-of/<date> path covers exactly that one day)
// For < 100K files: Post-filtering is fine
// For > 100K files: Use flattened indexes
// Force index refresh after bulk operations
await brain.storage.rebuildIndexes()
```
### 4. Combine Dimensions
```typescript
// Find security files Alice worked on this week
const aliceFiles = await vfs.readdir('/by-author/alice')
const securityFiles = await vfs.readdir('/by-tag/security')
const changedThatDay = await vfs.readdir('/as-of/2024-03-15')
const thisWeek = await vfs.readdir(`/as-of/${weekAgo}`)
const intersection = aliceFiles
.filter(f => securityFiles.includes(f))
.filter(f => changedThatDay.includes(f))
.filter(f => thisWeek.includes(f))
```
---
@ -464,13 +469,13 @@ console.log(entity.metadata.concepts)
### Slow Queries on Large Datasets
```typescript
// Check if indexes are built and populated
const stats = await brain.getIndexStats()
// Check if indexes are built
const stats = await brain.storage.getIndexStats()
console.log(stats)
```
If an index looks empty or inconsistent, rebuild from raw storage with the CLI
(stop the live writer first): `brainy inspect repair <data-dir>`.
// Rebuild if needed
await brain.storage.rebuildIndexes()
```
### Semantic Path Returns Empty
```typescript

View file

@ -183,7 +183,7 @@ console.log('VFS type:', entity.metadata.vfsType)
### 3. Check Relationships
```javascript
const parentId = await vfs.resolvePath('/directory')
const relations = await brain.related({
const relations = await brain.getRelations({
from: parentId,
type: VerbType.Contains
})
@ -246,7 +246,7 @@ const tree = await vfs.getTreeStructure('/', {
If you encounter issues not covered here:
1. Check the [VFS API Guide](./VFS_API_GUIDE.md)
2. Review [Common Patterns](./COMMON_PATTERNS.md)
2. Review [VFS Examples](./VFS_EXAMPLES_SCENARIOS.md)
3. Look at [test files](../../tests/vfs/) for working examples
4. Report issues at [GitHub Issues](https://github.com/soulcraftlabs/brainy/issues)

728
docs/vfs/USER_FUNCTIONS.md Normal file
View file

@ -0,0 +1,728 @@
# VFS User Functions - Templates and Examples
This document provides template functions that you can implement for domain-specific needs. These functions combine VFS primitives to solve common problems.
## Table of Contents
1. [Code Analysis Functions](#code-analysis-functions)
2. [Export Format Functions](#export-format-functions)
3. [Project Management Functions](#project-management-functions)
4. [Creative Writing Functions](#creative-writing-functions)
5. [Game Development Functions](#game-development-functions)
## Code Analysis Functions
### Get Dependency Graph
```javascript
/**
* Build a dependency graph for JavaScript/TypeScript projects
*/
async function getDependencyGraph(vfs, srcPath) {
const files = await vfs.readdir(srcPath, { recursive: true })
const graph = {}
for (const file of files) {
const filePath = `${srcPath}/${file}`
// Only process JS/TS files
if (file.match(/\.(js|ts|jsx|tsx)$/)) {
const content = await vfs.readFile(filePath)
const text = content.toString()
// Parse imports (basic regex, use proper AST parser for production)
const imports = []
const importRegex = /import\s+.*?\s+from\s+['"](.+?)['"]/g
const requireRegex = /require\(['"](.+?)['"]\)/g
let match
while ((match = importRegex.exec(text)) !== null) {
imports.push(match[1])
}
while ((match = requireRegex.exec(text)) !== null) {
imports.push(match[1])
}
graph[filePath] = imports
}
}
return graph
}
// Use it
const deps = await getDependencyGraph(vfs, '/src')
```
### Find Circular Dependencies
```javascript
/**
* Detect circular dependencies in your code
*/
async function findCircularDependencies(vfs, srcPath) {
const graph = await getDependencyGraph(vfs, srcPath)
const cycles = []
function detectCycle(node, visited = new Set(), stack = []) {
if (stack.includes(node)) {
const cycleStart = stack.indexOf(node)
cycles.push(stack.slice(cycleStart))
return
}
if (visited.has(node)) return
visited.add(node)
stack.push(node)
const dependencies = graph[node] || []
for (const dep of dependencies) {
// Resolve relative imports
const resolvedDep = resolvePath(node, dep)
if (graph[resolvedDep]) {
detectCycle(resolvedDep, visited, [...stack])
}
}
}
Object.keys(graph).forEach(node => detectCycle(node))
return cycles
}
```
### Find Untested Code
```javascript
/**
* Find source files without corresponding test files
*/
async function findUntestedCode(vfs, srcPath, testPath = null) {
testPath = testPath || srcPath.replace('/src', '/tests')
const sourceFiles = await vfs.readdir(srcPath, { recursive: true })
const testFiles = await vfs.readdir(testPath, { recursive: true }).catch(() => [])
const untestedFiles = []
for (const sourceFile of sourceFiles) {
if (!sourceFile.match(/\.(js|ts|jsx|tsx)$/)) continue
// Look for corresponding test file
const baseName = sourceFile.replace(/\.(js|ts|jsx|tsx)$/, '')
const hasTest = testFiles.some(testFile =>
testFile.includes(baseName) &&
testFile.match(/\.(test|spec)\.(js|ts|jsx|tsx)$/)
)
if (!hasTest) {
untestedFiles.push(`${srcPath}/${sourceFile}`)
}
}
return untestedFiles
}
```
### Find Similar Code (Duplicate Detection)
```javascript
/**
* Find potentially duplicate code using similarity scoring
*/
async function findSimilarCode(vfs, filePath, options = {}) {
const threshold = options.threshold || 0.8
const searchPath = options.searchPath || '/'
// Get the reference file content
const referenceContent = await vfs.readFile(filePath)
const referenceText = referenceContent.toString()
// Use VFS's semantic search
const similar = await vfs.findSimilar(filePath, {
limit: 10,
threshold
})
// Additionally, do structural comparison
const results = []
for (const match of similar) {
const matchContent = await vfs.readFile(match.path)
const matchText = matchContent.toString()
// Simple line-based similarity (use better algorithms in production)
const similarity = calculateSimilarity(referenceText, matchText)
if (similarity > threshold) {
results.push({
path: match.path,
similarity,
semanticScore: match.score
})
}
}
return results.sort((a, b) => b.similarity - a.similarity)
}
function calculateSimilarity(text1, text2) {
// Simple Jaccard similarity on lines
const lines1 = new Set(text1.split('\n').map(l => l.trim()).filter(l => l))
const lines2 = new Set(text2.split('\n').map(l => l.trim()).filter(l => l))
const intersection = new Set([...lines1].filter(x => lines2.has(x)))
const union = new Set([...lines1, ...lines2])
return intersection.size / union.size
}
```
## Export Format Functions
### Export to EPUB (for novels)
```javascript
/**
* Export a directory of markdown files to EPUB format
*/
async function exportToEpub(vfs, path, metadata = {}) {
// First get the markdown export
const markdown = await vfs.exportToMarkdown(path)
// You'll need an EPUB library like epub-gen
const Epub = require('epub-gen')
// Convert markdown chapters to EPUB format
const chapters = []
const files = await vfs.readdir(path, { recursive: true })
for (const file of files.sort()) {
if (file.endsWith('.md')) {
const content = await vfs.readFile(`${path}/${file}`)
const title = file.replace('.md', '').replace(/-/g, ' ')
chapters.push({
title: title,
data: content.toString()
})
}
}
const options = {
title: metadata.title || 'My Book',
author: metadata.author || 'Author',
chapters: chapters
}
return new Epub(options)
}
```
### Export to Static Site
```javascript
/**
* Export VFS content to static HTML site
*/
async function exportToStaticSite(vfs, sourcePath, options = {}) {
const json = await vfs.exportToJSON(sourcePath)
const html = []
html.push('<!DOCTYPE html>')
html.push('<html><head>')
html.push(`<title>${options.title || 'Documentation'}</title>`)
html.push('<style>/* Add your styles */</style>')
html.push('</head><body>')
function renderNode(node, name, depth = 0) {
const indent = ' '.repeat(depth)
if (node._meta?.type === 'file') {
html.push(`${indent}<article>`)
html.push(`${indent} <h${Math.min(depth + 2, 6)}>${name}</h${Math.min(depth + 2, 6)}>`)
if (typeof node._content === 'string') {
// Convert markdown to HTML if needed
html.push(`${indent} <pre>${escapeHtml(node._content)}</pre>`)
}
html.push(`${indent}</article>`)
} else if (node._meta?.type === 'directory') {
html.push(`${indent}<section>`)
html.push(`${indent} <h${Math.min(depth + 1, 6)}>${name}</h${Math.min(depth + 1, 6)}>`)
for (const [childName, childNode] of Object.entries(node)) {
if (!childName.startsWith('_')) {
renderNode(childNode, childName, depth + 1)
}
}
html.push(`${indent}</section>`)
}
}
renderNode(json, options.title || 'Root')
html.push('</body></html>')
return html.join('\n')
}
```
### Export to GraphQL Schema
```javascript
/**
* Generate GraphQL schema from VFS entities
*/
async function exportToGraphQLSchema(vfs) {
const entities = await vfs.listEntities()
const types = new Map()
// Group entities by type
for (const entity of entities) {
const type = entity.type || 'Unknown'
if (!types.has(type)) {
types.set(type, [])
}
types.get(type).push(entity)
}
// Generate schema
let schema = 'type Query {\n'
for (const [typeName, entities] of types) {
schema += ` get${typeName}(id: ID!): ${typeName}\n`
schema += ` list${typeName}s: [${typeName}!]!\n`
}
schema += '}\n\n'
// Generate types
for (const [typeName, entities] of types) {
schema += `type ${typeName} {\n`
schema += ' id: ID!\n'
// Infer fields from first entity
if (entities.length > 0) {
const sample = entities[0]
for (const [key, value] of Object.entries(sample)) {
if (key !== 'id') {
const fieldType = inferGraphQLType(value)
schema += ` ${key}: ${fieldType}\n`
}
}
}
schema += '}\n\n'
}
return schema
}
```
## Project Management Functions
### Get Project Insights
```javascript
/**
* Analyze project for insights and patterns
*/
async function getProjectInsights(vfs, projectPath) {
const stats = await vfs.getProjectStats(projectPath)
const todos = await vfs.getAllTodos(projectPath)
const timeline = await vfs.getTimeline({ limit: 100 })
// Analyze activity patterns
const activityByDay = {}
const activityByUser = {}
const activityByFile = {}
for (const event of timeline) {
const day = event.timestamp.toISOString().split('T')[0]
activityByDay[day] = (activityByDay[day] || 0) + 1
const user = event.user || 'system'
activityByUser[user] = (activityByUser[user] || 0) + 1
activityByFile[event.path] = (activityByFile[event.path] || 0) + 1
}
// Find hotspots (most edited files)
const hotspots = Object.entries(activityByFile)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
.map(([path, count]) => ({ path, edits: count }))
// Todo analysis
const todosByPriority = {}
const todosByStatus = {}
for (const todo of todos) {
todosByPriority[todo.priority] = (todosByPriority[todo.priority] || 0) + 1
todosByStatus[todo.status] = (todosByStatus[todo.status] || 0) + 1
}
return {
stats,
activity: {
byDay: activityByDay,
byUser: activityByUser,
hotspots
},
todos: {
total: todos.length,
byPriority: todosByPriority,
byStatus: todosByStatus,
highPriority: todos.filter(t => t.priority === 'high' && t.status === 'pending')
},
recommendations: generateRecommendations(stats, todos, hotspots)
}
}
function generateRecommendations(stats, todos, hotspots) {
const recommendations = []
if (stats.largestFile && stats.largestFile.size > 1024 * 1024) {
recommendations.push({
type: 'refactor',
message: `Consider splitting ${stats.largestFile.path} (${Math.round(stats.largestFile.size / 1024)}KB)`
})
}
if (todos.filter(t => t.priority === 'high' && t.status === 'pending').length > 5) {
recommendations.push({
type: 'priority',
message: 'You have many high-priority pending todos'
})
}
if (hotspots.length > 0 && hotspots[0].edits > 50) {
recommendations.push({
type: 'stability',
message: `${hotspots[0].path} changes frequently, consider stabilizing`
})
}
return recommendations
}
```
### Generate Sprint Report
```javascript
/**
* Generate a report for the current sprint
*/
async function generateSprintReport(vfs, sprintStart, sprintEnd = new Date()) {
const timeline = await vfs.getTimeline({
from: sprintStart,
to: sprintEnd
})
const todos = await vfs.getAllTodos()
// Group work by user
const workByUser = {}
for (const event of timeline) {
const user = event.user || 'system'
if (!workByUser[user]) {
workByUser[user] = {
commits: 0,
filesModified: new Set(),
linesChanged: 0
}
}
workByUser[user].commits++
workByUser[user].filesModified.add(event.path)
}
// Calculate completion rate
const completedTodos = todos.filter(t => t.status === 'completed').length
const totalTodos = todos.length
const completionRate = totalTodos > 0 ? (completedTodos / totalTodos * 100).toFixed(1) : 0
return {
period: {
start: sprintStart,
end: sprintEnd,
days: Math.ceil((sprintEnd - sprintStart) / (1000 * 60 * 60 * 24))
},
team: Object.entries(workByUser).map(([user, work]) => ({
user,
commits: work.commits,
filesModified: work.filesModified.size
})),
todos: {
completed: completedTodos,
total: totalTodos,
completionRate: `${completionRate}%`,
remaining: todos.filter(t => t.status === 'pending')
},
velocity: {
commitsPerDay: (timeline.length / 7).toFixed(1),
todosPerDay: (completedTodos / 7).toFixed(1)
}
}
}
```
## Creative Writing Functions
### Track Character Arcs
```javascript
/**
* Track how characters evolve throughout a story
*/
async function trackCharacterArc(vfs, characterName, storyPath = '/') {
// Find the character entity
const entities = await vfs.searchEntities({
type: 'character',
name: characterName
})
if (entities.length === 0) {
throw new Error(`Character ${characterName} not found`)
}
const character = entities[0]
const occurrences = await vfs.findEntityOccurrences(character.id)
// Analyze each appearance
const arc = []
for (const occurrence of occurrences) {
const content = await vfs.readFile(occurrence.path)
const text = content.toString()
// Find mentions of the character (basic approach)
const mentions = text.split('\n').filter(line =>
line.toLowerCase().includes(characterName.toLowerCase())
)
arc.push({
chapter: occurrence.path,
mentions: mentions.length,
// Analyze emotional tone (simplified)
mood: analyzeMood(mentions),
// Extract key actions
actions: extractActions(mentions, characterName)
})
}
return {
character: character.metadata,
arc: arc,
summary: summarizeArc(arc)
}
}
function analyzeMood(mentions) {
const positiveWords = ['smiled', 'laughed', 'happy', 'joy', 'love', 'success']
const negativeWords = ['cried', 'angry', 'sad', 'fear', 'fail', 'death']
let positive = 0, negative = 0
for (const mention of mentions) {
const lower = mention.toLowerCase()
positive += positiveWords.filter(w => lower.includes(w)).length
negative += negativeWords.filter(w => lower.includes(w)).length
}
if (positive > negative) return 'positive'
if (negative > positive) return 'negative'
return 'neutral'
}
```
### Generate Story Bible
```javascript
/**
* Create a comprehensive reference for your story universe
*/
async function generateStoryBible(vfs, storyPath) {
const characters = await vfs.listEntities({ type: 'character' })
const locations = await vfs.listEntities({ type: 'location' })
const concepts = await vfs.findConcepts({ domain: 'narrative' })
const bible = {
title: 'Story Bible',
generated: new Date(),
characters: {},
locations: {},
plotThreads: {},
timeline: []
}
// Document characters
for (const char of characters) {
const occurrences = await vfs.findEntityOccurrences(char.id)
bible.characters[char.metadata.name] = {
...char.metadata,
appearances: occurrences.map(o => o.path),
relationships: await vfs.getEntityGraph(char.id, { depth: 1 })
}
}
// Document locations
for (const loc of locations) {
bible.locations[loc.metadata.name] = {
...loc.metadata,
scenes: await vfs.findEntityOccurrences(loc.id)
}
}
// Plot threads from concepts
for (const concept of concepts) {
if (concept.type === 'plot') {
bible.plotThreads[concept.name] = {
description: concept.description,
keywords: concept.keywords,
chapters: await vfs.findByConcept(concept.name)
}
}
}
// Generate timeline
const events = await vfs.getTimeline({ limit: 1000 })
bible.timeline = events.map(e => ({
date: e.timestamp,
event: e.description,
chapter: e.path
}))
return bible
}
```
## Game Development Functions
### Validate Game Data
```javascript
/**
* Validate game configuration files for consistency
*/
async function validateGameData(vfs, gamePath) {
const errors = []
const warnings = []
// Load all game data
const gameData = await vfs.exportToJSON(gamePath)
// Check quest references
if (gameData.quests) {
for (const [questName, quest] of Object.entries(gameData.quests)) {
// Check NPC references
if (quest._content?.questGiver) {
const npcPath = `${gamePath}/npcs/${quest._content.questGiver}.json`
const exists = await vfs.exists(npcPath)
if (!exists) {
errors.push(`Quest ${questName} references missing NPC: ${quest._content.questGiver}`)
}
}
// Check item rewards
if (quest._content?.rewards?.items) {
for (const item of quest._content.rewards.items) {
const itemPath = `${gamePath}/items/${item}.json`
const exists = await vfs.exists(itemPath)
if (!exists) {
warnings.push(`Quest ${questName} rewards missing item: ${item}`)
}
}
}
}
}
// Check balance
if (gameData.items) {
const itemPowers = []
for (const [itemName, item] of Object.entries(gameData.items)) {
if (item._content?.stats) {
const totalPower = Object.values(item._content.stats)
.reduce((a, b) => a + b, 0)
itemPowers.push({ name: itemName, power: totalPower })
}
}
// Find outliers
const avgPower = itemPowers.reduce((a, b) => a + b.power, 0) / itemPowers.length
const outliers = itemPowers.filter(i => Math.abs(i.power - avgPower) > avgPower * 2)
for (const outlier of outliers) {
warnings.push(`Item ${outlier.name} may be imbalanced (power: ${outlier.power}, avg: ${avgPower})`)
}
}
return { errors, warnings, valid: errors.length === 0 }
}
```
### Generate Loot Tables
```javascript
/**
* Generate weighted loot tables from item definitions
*/
async function generateLootTables(vfs, itemsPath) {
const items = await vfs.exportToJSON(itemsPath)
const tables = {
common: [],
uncommon: [],
rare: [],
epic: [],
legendary: []
}
for (const [itemName, item] of Object.entries(items)) {
if (item._meta?.type === 'file' && item._content?.rarity) {
const entry = {
name: itemName.replace('.json', ''),
weight: getWeight(item._content.rarity),
data: item._content
}
tables[item._content.rarity.toLowerCase()].push(entry)
}
}
// Normalize weights
for (const table of Object.values(tables)) {
const totalWeight = table.reduce((a, b) => a + b.weight, 0)
for (const entry of table) {
entry.probability = (entry.weight / totalWeight * 100).toFixed(2) + '%'
}
}
return tables
}
function getWeight(rarity) {
const weights = {
common: 100,
uncommon: 50,
rare: 20,
epic: 5,
legendary: 1
}
return weights[rarity.toLowerCase()] || 10
}
```
## Using These Functions
All these functions are templates that you can customize for your specific needs. To use them:
1. Copy the function you need
2. Modify it for your specific requirements
3. Use it with your VFS instance:
```javascript
import { Brainy } from '@soulcraft/brainy'
// Initialize VFS
const brain = new Brainy()
await brain.init()
const vfs = brain.vfs()
await vfs.init()
// Use your custom function
const insights = await getProjectInsights(vfs, '/my-project')
console.log(insights.recommendations)
// Combine multiple functions
const deps = await getDependencyGraph(vfs, '/src')
const cycles = await findCircularDependencies(vfs, '/src')
const untested = await findUntestedCode(vfs, '/src', '/tests')
```
Remember: These are starting points. The power of VFS is that you can combine its primitives to build exactly what you need for your domain!

View file

@ -632,7 +632,7 @@ VFS works with all built-in Brainy storage adapters:
// Memory (testing/development)
const brain = new Brainy({ storage: { type: 'memory' } })
// Filesystem (production default)
// Filesystem (local development)
const brain = new Brainy({
storage: {
type: 'filesystem',
@ -640,12 +640,52 @@ const brain = new Brainy({
}
})
// Both work identically with VFS
// S3-compatible storage (production)
const brain = new Brainy({
storage: {
type: 's3',
bucket: 'my-bucket',
region: 'us-east-1'
}
})
// Cloudflare R2 (production)
const brain = new Brainy({
storage: {
type: 'r2',
accountId: 'your-account-id',
bucket: 'my-bucket'
}
})
// Google Cloud Storage (production)
const brain = new Brainy({
storage: {
type: 'gcs',
bucket: 'my-bucket',
projectId: 'my-project'
}
})
// Azure Blob Storage (production)
const brain = new Brainy({
storage: {
type: 'azure',
accountName: 'myaccount',
containerName: 'my-container'
}
})
// OPFS (browser-native persistence)
const brain = new Brainy({ storage: { type: 'opfs' } })
// TypeAware storage (optimized for entity types)
const brain = new Brainy({ storage: { type: 'typeaware' } })
// All work identically with VFS
const vfs = new VirtualFileSystem(brain)
```
For off-site backup of the filesystem artifact, snapshot `path` from your scheduler (`gsutil rsync`, `aws s3 sync`, `rclone`, `tar`).
**Custom Storage Adapters:** Redis, PostgreSQL, and other databases can be added via the [extension system](../api/EXTENSIBILITY.md). See `src/config/extensibleConfig.ts` for examples.
### Best Practices

View file

@ -358,6 +358,7 @@ VFS scales to millions of files:
- O(1) path lookup with caching
- Efficient graph traversal for directories
- Chunked storage for large files
- Distributed storage backend support
- Vector search scales with HNSW index
## Method Availability

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