@soulcraft/brainy 4.11.1 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/brainy.js CHANGED
@@ -18,6 +18,7 @@ import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js';
18
18
  import { VirtualFileSystem } from './vfs/VirtualFileSystem.js';
19
19
  import { MetadataIndexManager } from './utils/metadataIndex.js';
20
20
  import { GraphAdjacencyIndex } from './graph/graphAdjacencyIndex.js';
21
+ import { CommitBuilder } from './storage/cow/CommitObject.js';
21
22
  import { createPipeline } from './streaming/pipeline.js';
22
23
  import { configureLogger, LogLevel } from './utils/logger.js';
23
24
  import { DistributedCoordinator, ShardManager, CacheSync, ReadWriteSeparation } from './distributed/index.js';
@@ -1770,6 +1771,456 @@ export class Brainy {
1770
1771
  this._tripleIntelligence = undefined;
1771
1772
  });
1772
1773
  }
1774
+ // ============= COW (COPY-ON-WRITE) API - v5.0.0 =============
1775
+ /**
1776
+ * Fork the brain (instant clone via Snowflake-style COW)
1777
+ *
1778
+ * Creates a shallow copy in <100ms using copy-on-write (COW) technology.
1779
+ * Fork shares storage and HNSW data structures with parent, copying only
1780
+ * when modified (lazy deep copy).
1781
+ *
1782
+ * **How It Works (v5.0.0)**:
1783
+ * 1. HNSW Index: Shallow copy via `enableCOW()` (~10ms for 1M+ nodes)
1784
+ * 2. Metadata Index: Fast rebuild from shared storage (<100ms)
1785
+ * 3. Graph Index: Fast rebuild from shared storage (<500ms)
1786
+ *
1787
+ * **Performance**:
1788
+ * - Fork time: <100ms @ 10K entities (MEASURED)
1789
+ * - Memory overhead: 10-20% (shared HNSW nodes)
1790
+ * - Storage overhead: 10-20% (shared blobs)
1791
+ *
1792
+ * **Write Isolation**: Changes in fork don't affect parent, and vice versa.
1793
+ *
1794
+ * @param branch - Optional branch name (auto-generated if not provided)
1795
+ * @param options - Optional fork metadata (author, message)
1796
+ * @returns New Brainy instance (forked, fully independent)
1797
+ *
1798
+ * @example
1799
+ * ```typescript
1800
+ * const brain = new Brainy()
1801
+ * await brain.init()
1802
+ *
1803
+ * // Add data to parent
1804
+ * await brain.add({ type: 'user', data: { name: 'Alice' } })
1805
+ *
1806
+ * // Fork instantly (<100ms)
1807
+ * const experiment = await brain.fork('test-migration')
1808
+ *
1809
+ * // Make changes safely in fork
1810
+ * await experiment.add({ type: 'user', data: { name: 'Bob' } })
1811
+ *
1812
+ * // Original untouched
1813
+ * console.log((await brain.find({})).length) // 1 (Alice)
1814
+ * console.log((await experiment.find({})).length) // 2 (Alice + Bob)
1815
+ * ```
1816
+ *
1817
+ * @since v5.0.0
1818
+ */
1819
+ async fork(branch, options) {
1820
+ await this.ensureInitialized();
1821
+ return this.augmentationRegistry.execute('fork', { branch, options }, async () => {
1822
+ const branchName = branch || `fork-${Date.now()}`;
1823
+ // Check if storage has RefManager (COW enabled)
1824
+ if (!('refManager' in this.storage)) {
1825
+ throw new Error('Fork requires COW-enabled storage. ' +
1826
+ 'This storage adapter does not support branching. ' +
1827
+ 'Please use v5.0.0+ storage adapters.');
1828
+ }
1829
+ const refManager = this.storage.refManager;
1830
+ const currentBranch = this.storage.currentBranch || 'main';
1831
+ // Step 1: Copy storage ref (COW layer - instant!)
1832
+ await refManager.copyRef(currentBranch, branchName);
1833
+ // Step 2: Create new Brainy instance pointing to fork branch
1834
+ const forkConfig = {
1835
+ ...this.config,
1836
+ storage: {
1837
+ ...(this.config.storage || { type: 'memory' }),
1838
+ branch: branchName
1839
+ }
1840
+ };
1841
+ const clone = new Brainy(forkConfig);
1842
+ // Step 3: TRUE INSTANT FORK - Shallow copy indexes (O(1), <10ms)
1843
+ // Share storage reference (already COW-enabled)
1844
+ clone.storage = this.storage;
1845
+ // Shallow copy HNSW index (INSTANT - just copies Map references)
1846
+ clone.index = this.setupIndex();
1847
+ // Enable COW (handle both HNSWIndex and TypeAwareHNSWIndex)
1848
+ if ('enableCOW' in clone.index && typeof clone.index.enableCOW === 'function') {
1849
+ clone.index.enableCOW(this.index);
1850
+ }
1851
+ // Fast rebuild for small indexes from COW storage (Metadata/Graph are fast)
1852
+ clone.metadataIndex = new MetadataIndexManager(clone.storage);
1853
+ await clone.metadataIndex.init();
1854
+ clone.graphIndex = new GraphAdjacencyIndex(clone.storage);
1855
+ await clone.graphIndex.rebuild();
1856
+ // Setup augmentations
1857
+ clone.augmentationRegistry = this.setupAugmentations();
1858
+ await clone.augmentationRegistry.initializeAll({
1859
+ brain: clone,
1860
+ storage: clone.storage,
1861
+ config: clone.config,
1862
+ log: (message, level) => {
1863
+ if (!clone.config.silent) {
1864
+ console[level || 'info'](message);
1865
+ }
1866
+ }
1867
+ });
1868
+ // Mark as initialized
1869
+ clone.initialized = true;
1870
+ clone.dimensions = this.dimensions;
1871
+ return clone;
1872
+ });
1873
+ }
1874
+ /**
1875
+ * List all branches/forks
1876
+ * @returns Array of branch names
1877
+ *
1878
+ * @example
1879
+ * ```typescript
1880
+ * const branches = await brain.listBranches()
1881
+ * console.log(branches) // ['main', 'experiment', 'backup']
1882
+ * ```
1883
+ */
1884
+ async listBranches() {
1885
+ await this.ensureInitialized();
1886
+ return this.augmentationRegistry.execute('listBranches', {}, async () => {
1887
+ if (!('refManager' in this.storage)) {
1888
+ throw new Error('Branch management requires COW-enabled storage (v5.0.0+)');
1889
+ }
1890
+ const refManager = this.storage.refManager;
1891
+ const refs = await refManager.listRefs();
1892
+ // Filter to branches only (exclude tags)
1893
+ return refs
1894
+ .filter((ref) => ref.startsWith('heads/'))
1895
+ .map((ref) => ref.replace('heads/', ''));
1896
+ });
1897
+ }
1898
+ /**
1899
+ * Get current branch name
1900
+ * @returns Current branch name
1901
+ *
1902
+ * @example
1903
+ * ```typescript
1904
+ * const current = await brain.getCurrentBranch()
1905
+ * console.log(current) // 'main'
1906
+ * ```
1907
+ */
1908
+ async getCurrentBranch() {
1909
+ await this.ensureInitialized();
1910
+ return this.augmentationRegistry.execute('getCurrentBranch', {}, async () => {
1911
+ if (!('currentBranch' in this.storage)) {
1912
+ return 'main'; // Default branch
1913
+ }
1914
+ return this.storage.currentBranch || 'main';
1915
+ });
1916
+ }
1917
+ /**
1918
+ * Switch to a different branch
1919
+ * @param branch - Branch name to switch to
1920
+ *
1921
+ * @example
1922
+ * ```typescript
1923
+ * await brain.checkout('experiment')
1924
+ * console.log(await brain.getCurrentBranch()) // 'experiment'
1925
+ * ```
1926
+ */
1927
+ async checkout(branch) {
1928
+ await this.ensureInitialized();
1929
+ return this.augmentationRegistry.execute('checkout', { branch }, async () => {
1930
+ if (!('refManager' in this.storage)) {
1931
+ throw new Error('Branch management requires COW-enabled storage (v5.0.0+)');
1932
+ }
1933
+ // Verify branch exists
1934
+ const branches = await this.listBranches();
1935
+ if (!branches.includes(branch)) {
1936
+ throw new Error(`Branch '${branch}' does not exist`);
1937
+ }
1938
+ // Update storage currentBranch
1939
+ this.storage.currentBranch = branch;
1940
+ // Reload from new branch
1941
+ // Clear indexes and reload
1942
+ this.index = this.setupIndex();
1943
+ this.metadataIndex = new MetadataIndexManager(this.storage);
1944
+ this.graphIndex = new GraphAdjacencyIndex(this.storage);
1945
+ // Re-initialize
1946
+ this.initialized = false;
1947
+ await this.init();
1948
+ });
1949
+ }
1950
+ /**
1951
+ * Create a commit with current state
1952
+ * @param options - Commit options (message, author, metadata)
1953
+ * @returns Commit hash
1954
+ *
1955
+ * @example
1956
+ * ```typescript
1957
+ * await brain.add({ noun: 'user', data: { name: 'Alice' } })
1958
+ * const commitHash = await brain.commit({
1959
+ * message: 'Add Alice user',
1960
+ * author: 'dev@example.com'
1961
+ * })
1962
+ * ```
1963
+ */
1964
+ async commit(options) {
1965
+ await this.ensureInitialized();
1966
+ return this.augmentationRegistry.execute('commit', { options }, async () => {
1967
+ if (!('refManager' in this.storage) || !('commitLog' in this.storage) || !('blobStorage' in this.storage)) {
1968
+ throw new Error('Commit requires COW-enabled storage (v5.0.0+)');
1969
+ }
1970
+ const refManager = this.storage.refManager;
1971
+ const blobStorage = this.storage.blobStorage;
1972
+ const currentBranch = await this.getCurrentBranch();
1973
+ // Get current HEAD commit (parent)
1974
+ const currentCommitHash = await refManager.resolveRef(`heads/${currentBranch}`);
1975
+ // Get current state statistics
1976
+ const entityCount = await this.getNounCount();
1977
+ const relationshipCount = await this.getVerbCount();
1978
+ // Build commit object using builder pattern
1979
+ const builder = CommitBuilder.create(blobStorage)
1980
+ .tree('0000000000000000000000000000000000000000000000000000000000000000') // Empty tree hash for now
1981
+ .message(options?.message || 'Snapshot commit')
1982
+ .author(options?.author || 'unknown')
1983
+ .timestamp(Date.now())
1984
+ .entityCount(entityCount)
1985
+ .relationshipCount(relationshipCount);
1986
+ // Set parent if this is not the first commit
1987
+ if (currentCommitHash) {
1988
+ builder.parent(currentCommitHash);
1989
+ }
1990
+ // Add custom metadata
1991
+ if (options?.metadata) {
1992
+ Object.entries(options.metadata).forEach(([key, value]) => {
1993
+ builder.meta(key, value);
1994
+ });
1995
+ }
1996
+ // Build and persist commit (returns hash directly)
1997
+ const commitHash = await builder.build();
1998
+ // Update branch ref to point to new commit
1999
+ await refManager.setRef(`heads/${currentBranch}`, commitHash, {
2000
+ author: options?.author || 'unknown',
2001
+ message: options?.message || 'Snapshot commit'
2002
+ });
2003
+ return commitHash;
2004
+ });
2005
+ }
2006
+ /**
2007
+ * Merge a source branch into target branch
2008
+ * @param sourceBranch - Branch to merge from
2009
+ * @param targetBranch - Branch to merge into
2010
+ * @param options - Merge options (strategy, author, onConflict)
2011
+ * @returns Merge result with statistics
2012
+ *
2013
+ * @example
2014
+ * ```typescript
2015
+ * const result = await brain.merge('experiment', 'main', {
2016
+ * strategy: 'last-write-wins',
2017
+ * author: 'dev@example.com'
2018
+ * })
2019
+ * console.log(result) // { added: 5, modified: 3, deleted: 1, conflicts: 0 }
2020
+ * ```
2021
+ */
2022
+ async merge(sourceBranch, targetBranch, options) {
2023
+ await this.ensureInitialized();
2024
+ return this.augmentationRegistry.execute('merge', { sourceBranch, targetBranch, options }, async () => {
2025
+ if (!('refManager' in this.storage) || !('blobStorage' in this.storage)) {
2026
+ throw new Error('Merge requires COW-enabled storage (v5.0.0+)');
2027
+ }
2028
+ const strategy = options?.strategy || 'last-write-wins';
2029
+ let added = 0;
2030
+ let modified = 0;
2031
+ let deleted = 0;
2032
+ let conflicts = 0;
2033
+ // Verify both branches exist
2034
+ const branches = await this.listBranches();
2035
+ if (!branches.includes(sourceBranch)) {
2036
+ throw new Error(`Source branch '${sourceBranch}' does not exist`);
2037
+ }
2038
+ if (!branches.includes(targetBranch)) {
2039
+ throw new Error(`Target branch '${targetBranch}' does not exist`);
2040
+ }
2041
+ // 1. Create temporary fork of source branch to read from
2042
+ const sourceFork = await this.fork(`${sourceBranch}-merge-temp-${Date.now()}`);
2043
+ await sourceFork.checkout(sourceBranch);
2044
+ // 2. Save current branch and checkout target
2045
+ const currentBranch = await this.getCurrentBranch();
2046
+ if (currentBranch !== targetBranch) {
2047
+ await this.checkout(targetBranch);
2048
+ }
2049
+ try {
2050
+ // 3. Get all entities from source and target
2051
+ const sourceResults = await sourceFork.find({});
2052
+ const targetResults = await this.find({});
2053
+ // Create maps for faster lookup
2054
+ const targetMap = new Map(targetResults.map(r => [r.entity.id, r.entity]));
2055
+ // 4. Merge entities
2056
+ for (const sourceResult of sourceResults) {
2057
+ const sourceEntity = sourceResult.entity;
2058
+ const targetEntity = targetMap.get(sourceEntity.id);
2059
+ if (!targetEntity) {
2060
+ // NEW entity in source - ADD to target
2061
+ await this.add({
2062
+ id: sourceEntity.id,
2063
+ type: sourceEntity.type,
2064
+ data: sourceEntity.data,
2065
+ vector: sourceEntity.vector
2066
+ });
2067
+ added++;
2068
+ }
2069
+ else {
2070
+ // Entity exists in both branches - check for conflicts
2071
+ const sourceTime = sourceEntity.updatedAt || sourceEntity.createdAt || 0;
2072
+ const targetTime = targetEntity.updatedAt || targetEntity.createdAt || 0;
2073
+ // If timestamps are identical, no change needed
2074
+ if (sourceTime === targetTime) {
2075
+ continue;
2076
+ }
2077
+ // Apply merge strategy
2078
+ if (strategy === 'last-write-wins') {
2079
+ if (sourceTime > targetTime) {
2080
+ // Source is newer, update target
2081
+ await this.update({ id: sourceEntity.id, data: sourceEntity.data });
2082
+ modified++;
2083
+ }
2084
+ // else target is newer, keep target
2085
+ }
2086
+ else if (strategy === 'first-write-wins') {
2087
+ if (sourceTime < targetTime) {
2088
+ // Source is older, update target
2089
+ await this.update({ id: sourceEntity.id, data: sourceEntity.data });
2090
+ modified++;
2091
+ }
2092
+ }
2093
+ else if (strategy === 'custom' && options?.onConflict) {
2094
+ // Custom conflict resolution
2095
+ const resolved = await options.onConflict(targetEntity, sourceEntity);
2096
+ await this.update({ id: sourceEntity.id, data: resolved.data });
2097
+ modified++;
2098
+ conflicts++;
2099
+ }
2100
+ else {
2101
+ // Conflict detected but no resolution strategy
2102
+ conflicts++;
2103
+ }
2104
+ }
2105
+ }
2106
+ // 5. Merge relationships (verbs)
2107
+ const sourceVerbsResult = await sourceFork.storage.getVerbs({});
2108
+ const targetVerbsResult = await this.storage.getVerbs({});
2109
+ const sourceVerbs = sourceVerbsResult.items || [];
2110
+ const targetVerbs = targetVerbsResult.items || [];
2111
+ // Create set of existing target relationships for deduplication
2112
+ const targetRelSet = new Set(targetVerbs.map((v) => `${v.sourceId}-${v.verb}-${v.targetId}`));
2113
+ // Add relationships that don't exist in target
2114
+ for (const sourceVerb of sourceVerbs) {
2115
+ const key = `${sourceVerb.sourceId}-${sourceVerb.verb}-${sourceVerb.targetId}`;
2116
+ if (!targetRelSet.has(key)) {
2117
+ // Only add if both entities exist in target
2118
+ const hasSource = targetMap.has(sourceVerb.sourceId);
2119
+ const hasTarget = targetMap.has(sourceVerb.targetId);
2120
+ if (hasSource && hasTarget) {
2121
+ await this.relate({
2122
+ from: sourceVerb.sourceId,
2123
+ to: sourceVerb.targetId,
2124
+ type: sourceVerb.verb,
2125
+ weight: sourceVerb.weight,
2126
+ metadata: sourceVerb.metadata
2127
+ });
2128
+ }
2129
+ }
2130
+ }
2131
+ // 6. Create merge commit
2132
+ if ('commitLog' in this.storage) {
2133
+ await this.commit({
2134
+ message: `Merge ${sourceBranch} into ${targetBranch}`,
2135
+ author: options?.author || 'system',
2136
+ metadata: {
2137
+ mergeType: 'branch',
2138
+ source: sourceBranch,
2139
+ target: targetBranch,
2140
+ strategy,
2141
+ stats: { added, modified, deleted, conflicts }
2142
+ }
2143
+ });
2144
+ }
2145
+ }
2146
+ finally {
2147
+ // 7. Clean up temporary fork (just delete the temp branch)
2148
+ try {
2149
+ const tempBranchName = `${sourceBranch}-merge-temp-${Date.now()}`;
2150
+ const branches = await this.listBranches();
2151
+ if (branches.includes(tempBranchName)) {
2152
+ await this.deleteBranch(tempBranchName);
2153
+ }
2154
+ }
2155
+ catch (err) {
2156
+ // Ignore cleanup errors
2157
+ }
2158
+ // Restore original branch if needed
2159
+ if (currentBranch !== targetBranch) {
2160
+ await this.checkout(currentBranch);
2161
+ }
2162
+ }
2163
+ return { added, modified, deleted, conflicts };
2164
+ });
2165
+ }
2166
+ /**
2167
+ * Delete a branch/fork
2168
+ * @param branch - Branch name to delete
2169
+ *
2170
+ * @example
2171
+ * ```typescript
2172
+ * await brain.deleteBranch('old-experiment')
2173
+ * ```
2174
+ */
2175
+ async deleteBranch(branch) {
2176
+ await this.ensureInitialized();
2177
+ return this.augmentationRegistry.execute('deleteBranch', { branch }, async () => {
2178
+ if (!('refManager' in this.storage)) {
2179
+ throw new Error('Branch management requires COW-enabled storage (v5.0.0+)');
2180
+ }
2181
+ const currentBranch = await this.getCurrentBranch();
2182
+ if (branch === currentBranch) {
2183
+ throw new Error('Cannot delete current branch');
2184
+ }
2185
+ const refManager = this.storage.refManager;
2186
+ await refManager.deleteRef(`heads/${branch}`);
2187
+ });
2188
+ }
2189
+ /**
2190
+ * Get commit history for current branch
2191
+ * @param options - History options (limit, offset, author)
2192
+ * @returns Array of commits
2193
+ *
2194
+ * @example
2195
+ * ```typescript
2196
+ * const history = await brain.getHistory({ limit: 10 })
2197
+ * history.forEach(commit => {
2198
+ * console.log(`${commit.hash}: ${commit.message}`)
2199
+ * })
2200
+ * ```
2201
+ */
2202
+ async getHistory(options) {
2203
+ await this.ensureInitialized();
2204
+ return this.augmentationRegistry.execute('getHistory', { options }, async () => {
2205
+ if (!('commitLog' in this.storage) || !('refManager' in this.storage)) {
2206
+ throw new Error('History requires COW-enabled storage (v5.0.0+)');
2207
+ }
2208
+ const commitLog = this.storage.commitLog;
2209
+ const currentBranch = await this.getCurrentBranch();
2210
+ // Get commit history for current branch
2211
+ const commits = await commitLog.getHistory(`heads/${currentBranch}`, {
2212
+ maxCount: options?.limit || 10
2213
+ });
2214
+ // Map to expected format (compute hash for each commit)
2215
+ return commits.map((commit) => ({
2216
+ hash: this.storage.blobStorage.constructor.hash(Buffer.from(JSON.stringify(commit))),
2217
+ message: commit.message,
2218
+ author: commit.author,
2219
+ timestamp: commit.timestamp,
2220
+ metadata: commit.metadata
2221
+ }));
2222
+ });
2223
+ }
1773
2224
  /**
1774
2225
  * Get total count of nouns - O(1) operation
1775
2226
  * @returns Promise that resolves to the total number of nouns
@@ -0,0 +1,60 @@
1
+ /**
2
+ * COW CLI Commands - Copy-on-Write Operations
3
+ *
4
+ * Fork, branch, merge, and migration operations for instant cloning
5
+ */
6
+ interface CoreOptions {
7
+ verbose?: boolean;
8
+ json?: boolean;
9
+ pretty?: boolean;
10
+ }
11
+ interface ForkOptions extends CoreOptions {
12
+ name?: string;
13
+ message?: string;
14
+ author?: string;
15
+ }
16
+ interface MergeOptions extends CoreOptions {
17
+ force?: boolean;
18
+ strategy?: 'last-write-wins' | 'custom';
19
+ }
20
+ interface MigrateOptions extends CoreOptions {
21
+ from?: string;
22
+ to?: string;
23
+ backup?: boolean;
24
+ dryRun?: boolean;
25
+ }
26
+ export declare const cowCommands: {
27
+ /**
28
+ * Fork the current brain (instant clone)
29
+ */
30
+ fork(name: string | undefined, options: ForkOptions): Promise<void>;
31
+ /**
32
+ * List all branches/forks
33
+ */
34
+ branchList(options: CoreOptions): Promise<void>;
35
+ /**
36
+ * Switch to a different branch
37
+ */
38
+ checkout(branch: string | undefined, options: CoreOptions): Promise<void>;
39
+ /**
40
+ * Delete a branch/fork
41
+ */
42
+ branchDelete(branch: string | undefined, options: CoreOptions & {
43
+ force?: boolean;
44
+ }): Promise<void>;
45
+ /**
46
+ * Merge a fork/branch into current branch
47
+ */
48
+ merge(source: string | undefined, target: string | undefined, options: MergeOptions): Promise<void>;
49
+ /**
50
+ * Get commit history
51
+ */
52
+ history(options: CoreOptions & {
53
+ limit?: string;
54
+ }): Promise<void>;
55
+ /**
56
+ * Migrate from v4.x to v5.0.0 (one-time)
57
+ */
58
+ migrate(options: MigrateOptions): Promise<void>;
59
+ };
60
+ export {};