@soulcraft/brainy 5.10.3 → 5.11.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/CHANGELOG.md +5 -0
- package/dist/brainy.d.ts +89 -0
- package/dist/brainy.js +158 -1
- package/dist/storage/adapters/azureBlobStorage.d.ts +10 -0
- package/dist/storage/adapters/azureBlobStorage.js +12 -4
- package/dist/storage/adapters/fileSystemStorage.d.ts +10 -0
- package/dist/storage/adapters/fileSystemStorage.js +19 -14
- package/dist/storage/adapters/gcsStorage.d.ts +10 -0
- package/dist/storage/adapters/gcsStorage.js +18 -13
- package/dist/storage/adapters/historicalStorageAdapter.d.ts +10 -0
- package/dist/storage/adapters/historicalStorageAdapter.js +10 -0
- package/dist/storage/adapters/memoryStorage.d.ts +10 -0
- package/dist/storage/adapters/memoryStorage.js +10 -0
- package/dist/storage/adapters/opfsStorage.d.ts +10 -0
- package/dist/storage/adapters/opfsStorage.js +12 -4
- package/dist/storage/adapters/r2Storage.js +18 -13
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +10 -0
- package/dist/storage/adapters/s3CompatibleStorage.js +18 -17
- package/dist/storage/baseStorage.d.ts +10 -1
- package/dist/storage/baseStorage.js +16 -32
- package/dist/storage/cow/CommitLog.d.ts +24 -0
- package/dist/storage/cow/CommitLog.js +37 -0
- package/dist/types/brainy.types.d.ts +2 -0
- package/dist/utils/paramValidation.d.ts +43 -0
- package/dist/utils/paramValidation.js +135 -22
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
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.
|
|
4
4
|
|
|
5
|
+
### [5.10.4](https://github.com/soulcraftlabs/brainy/compare/v5.10.3...v5.10.4) (2025-11-17)
|
|
6
|
+
|
|
7
|
+
- fix: critical clear() data persistence regression (v5.10.4) (aba1563)
|
|
8
|
+
|
|
9
|
+
|
|
5
10
|
### [5.10.3](https://github.com/soulcraftlabs/brainy/compare/v5.10.2...v5.10.3) (2025-11-14)
|
|
6
11
|
|
|
7
12
|
- docs: add production service architecture guide to public docs (759e7fa)
|
package/dist/brainy.d.ts
CHANGED
|
@@ -1009,6 +1009,51 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1009
1009
|
timestamp: number;
|
|
1010
1010
|
metadata?: Record<string, any>;
|
|
1011
1011
|
}>>;
|
|
1012
|
+
/**
|
|
1013
|
+
* Stream commit history (memory-efficient)
|
|
1014
|
+
*
|
|
1015
|
+
* Use this for large commit histories (1000s of snapshots) where memory
|
|
1016
|
+
* efficiency is critical. Yields commits one at a time without accumulating
|
|
1017
|
+
* them in memory.
|
|
1018
|
+
*
|
|
1019
|
+
* For small histories (< 100 commits), use getHistory() for simpler API.
|
|
1020
|
+
*
|
|
1021
|
+
* @param options - History options
|
|
1022
|
+
* @param options.limit - Maximum number of commits to stream
|
|
1023
|
+
* @param options.since - Only include commits after this timestamp
|
|
1024
|
+
* @param options.until - Only include commits before this timestamp
|
|
1025
|
+
* @param options.author - Filter by author name
|
|
1026
|
+
*
|
|
1027
|
+
* @yields Commit metadata in reverse chronological order (newest first)
|
|
1028
|
+
*
|
|
1029
|
+
* @example
|
|
1030
|
+
* ```typescript
|
|
1031
|
+
* // Stream all commits without memory accumulation
|
|
1032
|
+
* for await (const commit of brain.streamHistory({ limit: 10000 })) {
|
|
1033
|
+
* console.log(`${commit.timestamp}: ${commit.message}`)
|
|
1034
|
+
* }
|
|
1035
|
+
*
|
|
1036
|
+
* // Stream with filtering
|
|
1037
|
+
* for await (const commit of brain.streamHistory({
|
|
1038
|
+
* author: 'alice',
|
|
1039
|
+
* since: Date.now() - 86400000 // Last 24 hours
|
|
1040
|
+
* })) {
|
|
1041
|
+
* // Process commit
|
|
1042
|
+
* }
|
|
1043
|
+
* ```
|
|
1044
|
+
*/
|
|
1045
|
+
streamHistory(options?: {
|
|
1046
|
+
limit?: number;
|
|
1047
|
+
since?: number;
|
|
1048
|
+
until?: number;
|
|
1049
|
+
author?: string;
|
|
1050
|
+
}): AsyncIterableIterator<{
|
|
1051
|
+
hash: string;
|
|
1052
|
+
message: string;
|
|
1053
|
+
author: string;
|
|
1054
|
+
timestamp: number;
|
|
1055
|
+
metadata?: Record<string, any>;
|
|
1056
|
+
}>;
|
|
1012
1057
|
/**
|
|
1013
1058
|
* Get total count of nouns - O(1) operation
|
|
1014
1059
|
* @returns Promise that resolves to the total number of nouns
|
|
@@ -1019,6 +1064,50 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
1019
1064
|
* @returns Promise that resolves to the total number of verbs
|
|
1020
1065
|
*/
|
|
1021
1066
|
getVerbCount(): Promise<number>;
|
|
1067
|
+
/**
|
|
1068
|
+
* Get memory statistics and limits (v5.11.0)
|
|
1069
|
+
*
|
|
1070
|
+
* Returns detailed memory information including:
|
|
1071
|
+
* - Current heap usage
|
|
1072
|
+
* - Container memory limits (if detected)
|
|
1073
|
+
* - Query limits and how they were calculated
|
|
1074
|
+
* - Memory allocation recommendations
|
|
1075
|
+
*
|
|
1076
|
+
* Use this to debug why query limits are low or to understand
|
|
1077
|
+
* memory allocation in production environments.
|
|
1078
|
+
*
|
|
1079
|
+
* @returns Memory statistics and configuration
|
|
1080
|
+
*
|
|
1081
|
+
* @example
|
|
1082
|
+
* ```typescript
|
|
1083
|
+
* const stats = brain.getMemoryStats()
|
|
1084
|
+
* console.log(`Query limit: ${stats.limits.maxQueryLimit}`)
|
|
1085
|
+
* console.log(`Basis: ${stats.limits.basis}`)
|
|
1086
|
+
* console.log(`Free memory: ${Math.round(stats.memory.free / 1024 / 1024)}MB`)
|
|
1087
|
+
* ```
|
|
1088
|
+
*/
|
|
1089
|
+
getMemoryStats(): {
|
|
1090
|
+
memory: {
|
|
1091
|
+
heapUsed: number;
|
|
1092
|
+
heapTotal: number;
|
|
1093
|
+
external: number;
|
|
1094
|
+
rss: number;
|
|
1095
|
+
free: number;
|
|
1096
|
+
total: number;
|
|
1097
|
+
containerLimit: number | null;
|
|
1098
|
+
};
|
|
1099
|
+
limits: {
|
|
1100
|
+
maxQueryLimit: number;
|
|
1101
|
+
maxQueryLength: number;
|
|
1102
|
+
maxVectorDimensions: number;
|
|
1103
|
+
basis: 'override' | 'reservedMemory' | 'containerMemory' | 'freeMemory';
|
|
1104
|
+
};
|
|
1105
|
+
config: {
|
|
1106
|
+
maxQueryLimit?: number;
|
|
1107
|
+
reservedQueryMemory?: number;
|
|
1108
|
+
};
|
|
1109
|
+
recommendations?: string[];
|
|
1110
|
+
};
|
|
1022
1111
|
/**
|
|
1023
1112
|
* Neural API - Advanced AI operations
|
|
1024
1113
|
*/
|
package/dist/brainy.js
CHANGED
|
@@ -25,6 +25,7 @@ import { NULL_HASH } from './storage/cow/constants.js';
|
|
|
25
25
|
import { createPipeline } from './streaming/pipeline.js';
|
|
26
26
|
import { configureLogger, LogLevel } from './utils/logger.js';
|
|
27
27
|
import { TransactionManager } from './transaction/TransactionManager.js';
|
|
28
|
+
import { ValidationConfig } from './utils/paramValidation.js';
|
|
28
29
|
import { SaveNounMetadataOperation, SaveNounOperation, AddToTypeAwareHNSWOperation, AddToHNSWOperation, AddToMetadataIndexOperation, SaveVerbMetadataOperation, SaveVerbOperation, AddToGraphIndexOperation, RemoveFromHNSWOperation, RemoveFromTypeAwareHNSWOperation, RemoveFromMetadataIndexOperation, RemoveFromGraphIndexOperation, UpdateNounMetadataOperation, DeleteNounMetadataOperation, DeleteVerbMetadataOperation } from './transaction/operations/index.js';
|
|
29
30
|
import { DistributedCoordinator, ShardManager, CacheSync, ReadWriteSeparation } from './distributed/index.js';
|
|
30
31
|
import { NounType } from './types/graphTypes.js';
|
|
@@ -45,6 +46,14 @@ export class Brainy {
|
|
|
45
46
|
this.lazyRebuildPromise = null;
|
|
46
47
|
// Normalize configuration with defaults
|
|
47
48
|
this.config = this.normalizeConfig(config);
|
|
49
|
+
// Configure memory limits (v5.11.0)
|
|
50
|
+
// This must happen early, before any validation occurs
|
|
51
|
+
if (this.config.maxQueryLimit !== undefined || this.config.reservedQueryMemory !== undefined) {
|
|
52
|
+
ValidationConfig.reconfigure({
|
|
53
|
+
maxQueryLimit: this.config.maxQueryLimit,
|
|
54
|
+
reservedQueryMemory: this.config.reservedQueryMemory
|
|
55
|
+
});
|
|
56
|
+
}
|
|
48
57
|
// Setup core components
|
|
49
58
|
this.distance = cosineDistance;
|
|
50
59
|
this.embedder = this.setupEmbedder();
|
|
@@ -1829,6 +1838,10 @@ export class Brainy {
|
|
|
1829
1838
|
// Recreate index if no clear method
|
|
1830
1839
|
this.index = this.setupIndex();
|
|
1831
1840
|
}
|
|
1841
|
+
// v5.10.4: Recreate metadata index to clear cached data
|
|
1842
|
+
// Bug: Metadata index cache was not being cleared, causing find() with type filters to return stale data
|
|
1843
|
+
this.metadataIndex = new MetadataIndexManager(this.storage);
|
|
1844
|
+
await this.metadataIndex.init();
|
|
1832
1845
|
// Reset dimensions
|
|
1833
1846
|
this.dimensions = undefined;
|
|
1834
1847
|
// Clear any cached sub-APIs
|
|
@@ -2684,6 +2697,67 @@ export class Brainy {
|
|
|
2684
2697
|
}));
|
|
2685
2698
|
});
|
|
2686
2699
|
}
|
|
2700
|
+
/**
|
|
2701
|
+
* Stream commit history (memory-efficient)
|
|
2702
|
+
*
|
|
2703
|
+
* Use this for large commit histories (1000s of snapshots) where memory
|
|
2704
|
+
* efficiency is critical. Yields commits one at a time without accumulating
|
|
2705
|
+
* them in memory.
|
|
2706
|
+
*
|
|
2707
|
+
* For small histories (< 100 commits), use getHistory() for simpler API.
|
|
2708
|
+
*
|
|
2709
|
+
* @param options - History options
|
|
2710
|
+
* @param options.limit - Maximum number of commits to stream
|
|
2711
|
+
* @param options.since - Only include commits after this timestamp
|
|
2712
|
+
* @param options.until - Only include commits before this timestamp
|
|
2713
|
+
* @param options.author - Filter by author name
|
|
2714
|
+
*
|
|
2715
|
+
* @yields Commit metadata in reverse chronological order (newest first)
|
|
2716
|
+
*
|
|
2717
|
+
* @example
|
|
2718
|
+
* ```typescript
|
|
2719
|
+
* // Stream all commits without memory accumulation
|
|
2720
|
+
* for await (const commit of brain.streamHistory({ limit: 10000 })) {
|
|
2721
|
+
* console.log(`${commit.timestamp}: ${commit.message}`)
|
|
2722
|
+
* }
|
|
2723
|
+
*
|
|
2724
|
+
* // Stream with filtering
|
|
2725
|
+
* for await (const commit of brain.streamHistory({
|
|
2726
|
+
* author: 'alice',
|
|
2727
|
+
* since: Date.now() - 86400000 // Last 24 hours
|
|
2728
|
+
* })) {
|
|
2729
|
+
* // Process commit
|
|
2730
|
+
* }
|
|
2731
|
+
* ```
|
|
2732
|
+
*/
|
|
2733
|
+
async *streamHistory(options) {
|
|
2734
|
+
await this.ensureInitialized();
|
|
2735
|
+
if (!('commitLog' in this.storage) || !('refManager' in this.storage)) {
|
|
2736
|
+
throw new Error('History streaming requires COW-enabled storage (v5.0.0+)');
|
|
2737
|
+
}
|
|
2738
|
+
const commitLog = this.storage.commitLog;
|
|
2739
|
+
const currentBranch = await this.getCurrentBranch();
|
|
2740
|
+
const blobStorage = this.storage.blobStorage;
|
|
2741
|
+
// Stream commits from CommitLog
|
|
2742
|
+
for await (const commit of commitLog.streamHistory(currentBranch, {
|
|
2743
|
+
maxCount: options?.limit,
|
|
2744
|
+
since: options?.since,
|
|
2745
|
+
until: options?.until
|
|
2746
|
+
})) {
|
|
2747
|
+
// Filter by author if specified
|
|
2748
|
+
if (options?.author && commit.author !== options.author) {
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
// Map to expected format (compute hash for commit)
|
|
2752
|
+
yield {
|
|
2753
|
+
hash: blobStorage.constructor.hash(Buffer.from(JSON.stringify(commit))),
|
|
2754
|
+
message: commit.message,
|
|
2755
|
+
author: commit.author,
|
|
2756
|
+
timestamp: commit.timestamp,
|
|
2757
|
+
metadata: commit.metadata
|
|
2758
|
+
};
|
|
2759
|
+
}
|
|
2760
|
+
}
|
|
2687
2761
|
/**
|
|
2688
2762
|
* Get total count of nouns - O(1) operation
|
|
2689
2763
|
* @returns Promise that resolves to the total number of nouns
|
|
@@ -2700,6 +2774,86 @@ export class Brainy {
|
|
|
2700
2774
|
await this.ensureInitialized();
|
|
2701
2775
|
return this.storage.getVerbCount();
|
|
2702
2776
|
}
|
|
2777
|
+
/**
|
|
2778
|
+
* Get memory statistics and limits (v5.11.0)
|
|
2779
|
+
*
|
|
2780
|
+
* Returns detailed memory information including:
|
|
2781
|
+
* - Current heap usage
|
|
2782
|
+
* - Container memory limits (if detected)
|
|
2783
|
+
* - Query limits and how they were calculated
|
|
2784
|
+
* - Memory allocation recommendations
|
|
2785
|
+
*
|
|
2786
|
+
* Use this to debug why query limits are low or to understand
|
|
2787
|
+
* memory allocation in production environments.
|
|
2788
|
+
*
|
|
2789
|
+
* @returns Memory statistics and configuration
|
|
2790
|
+
*
|
|
2791
|
+
* @example
|
|
2792
|
+
* ```typescript
|
|
2793
|
+
* const stats = brain.getMemoryStats()
|
|
2794
|
+
* console.log(`Query limit: ${stats.limits.maxQueryLimit}`)
|
|
2795
|
+
* console.log(`Basis: ${stats.limits.basis}`)
|
|
2796
|
+
* console.log(`Free memory: ${Math.round(stats.memory.free / 1024 / 1024)}MB`)
|
|
2797
|
+
* ```
|
|
2798
|
+
*/
|
|
2799
|
+
getMemoryStats() {
|
|
2800
|
+
const config = ValidationConfig.getInstance();
|
|
2801
|
+
const heapStats = process.memoryUsage ? process.memoryUsage() : {
|
|
2802
|
+
heapUsed: 0,
|
|
2803
|
+
heapTotal: 0,
|
|
2804
|
+
external: 0,
|
|
2805
|
+
rss: 0
|
|
2806
|
+
};
|
|
2807
|
+
// Get system memory info
|
|
2808
|
+
let freeMemory = 0;
|
|
2809
|
+
let totalMemory = 0;
|
|
2810
|
+
if (typeof window === 'undefined') {
|
|
2811
|
+
try {
|
|
2812
|
+
const os = require('node:os');
|
|
2813
|
+
freeMemory = os.freemem();
|
|
2814
|
+
totalMemory = os.totalmem();
|
|
2815
|
+
}
|
|
2816
|
+
catch (e) {
|
|
2817
|
+
// OS module not available
|
|
2818
|
+
}
|
|
2819
|
+
}
|
|
2820
|
+
const stats = {
|
|
2821
|
+
memory: {
|
|
2822
|
+
heapUsed: heapStats.heapUsed,
|
|
2823
|
+
heapTotal: heapStats.heapTotal,
|
|
2824
|
+
external: heapStats.external,
|
|
2825
|
+
rss: heapStats.rss,
|
|
2826
|
+
free: freeMemory,
|
|
2827
|
+
total: totalMemory,
|
|
2828
|
+
containerLimit: config.detectedContainerLimit
|
|
2829
|
+
},
|
|
2830
|
+
limits: {
|
|
2831
|
+
maxQueryLimit: config.maxLimit,
|
|
2832
|
+
maxQueryLength: config.maxQueryLength,
|
|
2833
|
+
maxVectorDimensions: config.maxVectorDimensions,
|
|
2834
|
+
basis: config.limitBasis
|
|
2835
|
+
},
|
|
2836
|
+
config: {
|
|
2837
|
+
maxQueryLimit: this.config.maxQueryLimit,
|
|
2838
|
+
reservedQueryMemory: this.config.reservedQueryMemory
|
|
2839
|
+
},
|
|
2840
|
+
recommendations: []
|
|
2841
|
+
};
|
|
2842
|
+
// Generate recommendations based on stats
|
|
2843
|
+
if (stats.limits.basis === 'freeMemory' && stats.memory.containerLimit) {
|
|
2844
|
+
stats.recommendations.push(`Container detected (${Math.round(stats.memory.containerLimit / 1024 / 1024)}MB) but limits based on free memory. ` +
|
|
2845
|
+
`Consider setting reservedQueryMemory config option for better limits.`);
|
|
2846
|
+
}
|
|
2847
|
+
if (stats.limits.maxQueryLimit < 5000 && stats.memory.containerLimit && stats.memory.containerLimit > 2 * 1024 * 1024 * 1024) {
|
|
2848
|
+
stats.recommendations.push(`Query limit is low (${stats.limits.maxQueryLimit}) despite ${Math.round(stats.memory.containerLimit / 1024 / 1024 / 1024)}GB container. ` +
|
|
2849
|
+
`Consider: new Brainy({ reservedQueryMemory: 1073741824 }) to reserve 1GB for queries.`);
|
|
2850
|
+
}
|
|
2851
|
+
if (stats.limits.basis === 'override') {
|
|
2852
|
+
stats.recommendations.push(`Using explicit maxQueryLimit override (${stats.limits.maxQueryLimit}). ` +
|
|
2853
|
+
`Auto-detection bypassed.`);
|
|
2854
|
+
}
|
|
2855
|
+
return stats;
|
|
2856
|
+
}
|
|
2703
2857
|
// ============= SUB-APIS =============
|
|
2704
2858
|
/**
|
|
2705
2859
|
* Neural API - Advanced AI operations
|
|
@@ -3989,7 +4143,10 @@ export class Brainy {
|
|
|
3989
4143
|
disableMetrics: config?.disableMetrics ?? false,
|
|
3990
4144
|
disableAutoOptimize: config?.disableAutoOptimize ?? false,
|
|
3991
4145
|
batchWrites: config?.batchWrites ?? true,
|
|
3992
|
-
maxConcurrentOperations: config?.maxConcurrentOperations ?? 10
|
|
4146
|
+
maxConcurrentOperations: config?.maxConcurrentOperations ?? 10,
|
|
4147
|
+
// Memory management options (v5.11.0)
|
|
4148
|
+
maxQueryLimit: config?.maxQueryLimit ?? undefined,
|
|
4149
|
+
reservedQueryMemory: config?.reservedQueryMemory ?? undefined
|
|
3993
4150
|
};
|
|
3994
4151
|
}
|
|
3995
4152
|
/**
|
|
@@ -240,6 +240,16 @@ export declare class AzureBlobStorage extends BaseStorage {
|
|
|
240
240
|
quota: number | null;
|
|
241
241
|
details?: Record<string, any>;
|
|
242
242
|
}>;
|
|
243
|
+
/**
|
|
244
|
+
* Check if COW has been explicitly disabled via clear()
|
|
245
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
246
|
+
* @returns true if marker blob exists, false otherwise
|
|
247
|
+
* @protected
|
|
248
|
+
*/
|
|
249
|
+
/**
|
|
250
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
251
|
+
* COW is now always enabled - marker files are no longer used
|
|
252
|
+
*/
|
|
243
253
|
/**
|
|
244
254
|
* Save statistics data to storage
|
|
245
255
|
*/
|
|
@@ -859,13 +859,11 @@ export class AzureBlobStorage extends BaseStorage {
|
|
|
859
859
|
await blockBlobClient.delete();
|
|
860
860
|
}
|
|
861
861
|
}
|
|
862
|
-
//
|
|
863
|
-
//
|
|
864
|
-
// Otherwise initializeCOW() will auto-recreate initial commit on next operation
|
|
862
|
+
// v5.11.0: Reset COW managers (but don't disable COW - it's always enabled)
|
|
863
|
+
// COW will re-initialize automatically on next use
|
|
865
864
|
this.refManager = undefined;
|
|
866
865
|
this.blobStorage = undefined;
|
|
867
866
|
this.commitLog = undefined;
|
|
868
|
-
this.cowEnabled = false;
|
|
869
867
|
// Clear caches
|
|
870
868
|
this.nounCacheManager.clear();
|
|
871
869
|
this.verbCacheManager.clear();
|
|
@@ -908,6 +906,16 @@ export class AzureBlobStorage extends BaseStorage {
|
|
|
908
906
|
};
|
|
909
907
|
}
|
|
910
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Check if COW has been explicitly disabled via clear()
|
|
911
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
912
|
+
* @returns true if marker blob exists, false otherwise
|
|
913
|
+
* @protected
|
|
914
|
+
*/
|
|
915
|
+
/**
|
|
916
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
917
|
+
* COW is now always enabled - marker files are no longer used
|
|
918
|
+
*/
|
|
911
919
|
/**
|
|
912
920
|
* Save statistics data to storage
|
|
913
921
|
*/
|
|
@@ -171,6 +171,16 @@ export declare class FileSystemStorage extends BaseStorage {
|
|
|
171
171
|
* Provides progress tracking, backup options, and instance name confirmation
|
|
172
172
|
*/
|
|
173
173
|
clearEnhanced(options?: import('../enhancedClearOperations.js').ClearOptions): Promise<import('../enhancedClearOperations.js').ClearResult>;
|
|
174
|
+
/**
|
|
175
|
+
* Check if COW has been explicitly disabled via clear()
|
|
176
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
177
|
+
* @returns true if marker file exists, false otherwise
|
|
178
|
+
* @protected
|
|
179
|
+
*/
|
|
180
|
+
/**
|
|
181
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
182
|
+
* COW is now always enabled - marker files are no longer used
|
|
183
|
+
*/
|
|
174
184
|
/**
|
|
175
185
|
* Get information about storage usage and capacity
|
|
176
186
|
*/
|
|
@@ -859,16 +859,13 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
859
859
|
}
|
|
860
860
|
}
|
|
861
861
|
};
|
|
862
|
-
//
|
|
863
|
-
|
|
864
|
-
//
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
await removeDirectoryContents(this.nounMetadataDir);
|
|
870
|
-
// Remove all files in the verb metadata directory
|
|
871
|
-
await removeDirectoryContents(this.verbMetadataDir);
|
|
862
|
+
// v5.10.4: Clear the entire branches/ directory (branch-based storage)
|
|
863
|
+
// Bug fix: Data is stored in branches/main/entities/, not just entities/
|
|
864
|
+
// The branch-based structure was introduced for COW support
|
|
865
|
+
const branchesDir = path.join(this.rootDir, 'branches');
|
|
866
|
+
if (await this.directoryExists(branchesDir)) {
|
|
867
|
+
await removeDirectoryContents(branchesDir);
|
|
868
|
+
}
|
|
872
869
|
// Remove all files in both system directories
|
|
873
870
|
await removeDirectoryContents(this.systemDir);
|
|
874
871
|
if (await this.directoryExists(this.indexDir)) {
|
|
@@ -881,13 +878,11 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
881
878
|
if (await this.directoryExists(cowDir)) {
|
|
882
879
|
// Delete the entire _cow/ directory (not just contents)
|
|
883
880
|
await fs.promises.rm(cowDir, { recursive: true, force: true });
|
|
884
|
-
//
|
|
885
|
-
//
|
|
886
|
-
// Otherwise initializeCOW() will auto-recreate initial commit on next operation
|
|
881
|
+
// v5.11.0: Reset COW managers (but don't disable COW - it's always enabled)
|
|
882
|
+
// COW will re-initialize automatically on next use
|
|
887
883
|
this.refManager = undefined;
|
|
888
884
|
this.blobStorage = undefined;
|
|
889
885
|
this.commitLog = undefined;
|
|
890
|
-
this.cowEnabled = false;
|
|
891
886
|
}
|
|
892
887
|
// Clear the statistics cache
|
|
893
888
|
this.statisticsCache = null;
|
|
@@ -915,6 +910,16 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
915
910
|
}
|
|
916
911
|
return result;
|
|
917
912
|
}
|
|
913
|
+
/**
|
|
914
|
+
* Check if COW has been explicitly disabled via clear()
|
|
915
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
916
|
+
* @returns true if marker file exists, false otherwise
|
|
917
|
+
* @protected
|
|
918
|
+
*/
|
|
919
|
+
/**
|
|
920
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
921
|
+
* COW is now always enabled - marker files are no longer used
|
|
922
|
+
*/
|
|
918
923
|
/**
|
|
919
924
|
* Get information about storage usage and capacity
|
|
920
925
|
*/
|
|
@@ -228,6 +228,16 @@ export declare class GcsStorage extends BaseStorage {
|
|
|
228
228
|
quota: number | null;
|
|
229
229
|
details?: Record<string, any>;
|
|
230
230
|
}>;
|
|
231
|
+
/**
|
|
232
|
+
* Check if COW has been explicitly disabled via clear()
|
|
233
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
234
|
+
* @returns true if marker object exists, false otherwise
|
|
235
|
+
* @protected
|
|
236
|
+
*/
|
|
237
|
+
/**
|
|
238
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
239
|
+
* COW is now always enabled - marker files are no longer used
|
|
240
|
+
*/
|
|
231
241
|
/**
|
|
232
242
|
* Save statistics data to storage
|
|
233
243
|
*/
|
|
@@ -772,23 +772,18 @@ export class GcsStorage extends BaseStorage {
|
|
|
772
772
|
await file.delete();
|
|
773
773
|
}
|
|
774
774
|
};
|
|
775
|
-
// Clear
|
|
776
|
-
|
|
777
|
-
await deleteObjectsWithPrefix(
|
|
778
|
-
|
|
779
|
-
await deleteObjectsWithPrefix(this.verbMetadataPrefix);
|
|
780
|
-
await deleteObjectsWithPrefix(this.systemPrefix);
|
|
781
|
-
// v5.6.1: Clear COW (copy-on-write) version control data
|
|
782
|
-
// This includes all git-like versioning data (commits, trees, blobs, refs)
|
|
783
|
-
// Must be deleted to fully clear all data including version history
|
|
775
|
+
// v5.11.0: Clear ALL data using correct paths
|
|
776
|
+
// Delete entire branches/ directory (includes ALL entities, ALL types, ALL VFS data, ALL forks)
|
|
777
|
+
await deleteObjectsWithPrefix('branches/');
|
|
778
|
+
// Delete COW version control data
|
|
784
779
|
await deleteObjectsWithPrefix('_cow/');
|
|
785
|
-
//
|
|
786
|
-
|
|
787
|
-
//
|
|
780
|
+
// Delete system metadata
|
|
781
|
+
await deleteObjectsWithPrefix('_system/');
|
|
782
|
+
// v5.11.0: Reset COW managers (but don't disable COW - it's always enabled)
|
|
783
|
+
// COW will re-initialize automatically on next use
|
|
788
784
|
this.refManager = undefined;
|
|
789
785
|
this.blobStorage = undefined;
|
|
790
786
|
this.commitLog = undefined;
|
|
791
|
-
this.cowEnabled = false;
|
|
792
787
|
// Clear caches
|
|
793
788
|
this.nounCacheManager.clear();
|
|
794
789
|
this.verbCacheManager.clear();
|
|
@@ -834,6 +829,16 @@ export class GcsStorage extends BaseStorage {
|
|
|
834
829
|
};
|
|
835
830
|
}
|
|
836
831
|
}
|
|
832
|
+
/**
|
|
833
|
+
* Check if COW has been explicitly disabled via clear()
|
|
834
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
835
|
+
* @returns true if marker object exists, false otherwise
|
|
836
|
+
* @protected
|
|
837
|
+
*/
|
|
838
|
+
/**
|
|
839
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
840
|
+
* COW is now always enabled - marker files are no longer used
|
|
841
|
+
*/
|
|
837
842
|
/**
|
|
838
843
|
* Save statistics data to storage
|
|
839
844
|
*/
|
|
@@ -101,6 +101,16 @@ export declare class HistoricalStorageAdapter extends BaseStorage {
|
|
|
101
101
|
quota: number | null;
|
|
102
102
|
details?: Record<string, any>;
|
|
103
103
|
}>;
|
|
104
|
+
/**
|
|
105
|
+
* Check if COW has been explicitly disabled via clear()
|
|
106
|
+
* v5.10.4: No-op for HistoricalStorageAdapter (read-only, doesn't manage COW)
|
|
107
|
+
* @returns Always false (read-only adapter doesn't manage COW state)
|
|
108
|
+
* @protected
|
|
109
|
+
*/
|
|
110
|
+
/**
|
|
111
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
112
|
+
* COW is now always enabled - marker files are no longer used
|
|
113
|
+
*/
|
|
104
114
|
/**
|
|
105
115
|
* WRITE BLOCKED: Historical storage is read-only
|
|
106
116
|
*/
|
|
@@ -226,6 +226,16 @@ export class HistoricalStorageAdapter extends BaseStorage {
|
|
|
226
226
|
}
|
|
227
227
|
};
|
|
228
228
|
}
|
|
229
|
+
/**
|
|
230
|
+
* Check if COW has been explicitly disabled via clear()
|
|
231
|
+
* v5.10.4: No-op for HistoricalStorageAdapter (read-only, doesn't manage COW)
|
|
232
|
+
* @returns Always false (read-only adapter doesn't manage COW state)
|
|
233
|
+
* @protected
|
|
234
|
+
*/
|
|
235
|
+
/**
|
|
236
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
237
|
+
* COW is now always enabled - marker files are no longer used
|
|
238
|
+
*/
|
|
229
239
|
// ============= Override Write Methods (Read-Only) =============
|
|
230
240
|
/**
|
|
231
241
|
* WRITE BLOCKED: Historical storage is read-only
|
|
@@ -79,6 +79,16 @@ export declare class MemoryStorage extends BaseStorage {
|
|
|
79
79
|
quota: number | null;
|
|
80
80
|
details?: Record<string, any>;
|
|
81
81
|
}>;
|
|
82
|
+
/**
|
|
83
|
+
* Check if COW has been explicitly disabled via clear()
|
|
84
|
+
* v5.10.4: No-op for MemoryStorage (doesn't persist)
|
|
85
|
+
* @returns Always false (marker doesn't persist in memory)
|
|
86
|
+
* @protected
|
|
87
|
+
*/
|
|
88
|
+
/**
|
|
89
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
90
|
+
* COW is now always enabled - marker files are no longer used
|
|
91
|
+
*/
|
|
82
92
|
/**
|
|
83
93
|
* Save statistics data to storage
|
|
84
94
|
* @param statistics The statistics data to save
|
|
@@ -158,6 +158,16 @@ export class MemoryStorage extends BaseStorage {
|
|
|
158
158
|
}
|
|
159
159
|
};
|
|
160
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* Check if COW has been explicitly disabled via clear()
|
|
163
|
+
* v5.10.4: No-op for MemoryStorage (doesn't persist)
|
|
164
|
+
* @returns Always false (marker doesn't persist in memory)
|
|
165
|
+
* @protected
|
|
166
|
+
*/
|
|
167
|
+
/**
|
|
168
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
169
|
+
* COW is now always enabled - marker files are no longer used
|
|
170
|
+
*/
|
|
161
171
|
/**
|
|
162
172
|
* Save statistics data to storage
|
|
163
173
|
* @param statistics The statistics data to save
|
|
@@ -94,6 +94,16 @@ export declare class OPFSStorage extends BaseStorage {
|
|
|
94
94
|
* Clear all data from storage
|
|
95
95
|
*/
|
|
96
96
|
clear(): Promise<void>;
|
|
97
|
+
/**
|
|
98
|
+
* Check if COW has been explicitly disabled via clear()
|
|
99
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
100
|
+
* @returns true if marker file exists, false otherwise
|
|
101
|
+
* @protected
|
|
102
|
+
*/
|
|
103
|
+
/**
|
|
104
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
105
|
+
* COW is now always enabled - marker files are no longer used
|
|
106
|
+
*/
|
|
97
107
|
private quotaWarningThreshold;
|
|
98
108
|
private quotaCriticalThreshold;
|
|
99
109
|
private lastQuotaCheck;
|
|
@@ -42,6 +42,16 @@ export class OPFSStorage extends BaseStorage {
|
|
|
42
42
|
this.statistics = null;
|
|
43
43
|
this.activeLocks = new Set();
|
|
44
44
|
this.lockPrefix = 'opfs-lock-';
|
|
45
|
+
/**
|
|
46
|
+
* Check if COW has been explicitly disabled via clear()
|
|
47
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
48
|
+
* @returns true if marker file exists, false otherwise
|
|
49
|
+
* @protected
|
|
50
|
+
*/
|
|
51
|
+
/**
|
|
52
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
53
|
+
* COW is now always enabled - marker files are no longer used
|
|
54
|
+
*/
|
|
45
55
|
// Quota monitoring configuration (v4.0.0)
|
|
46
56
|
this.quotaWarningThreshold = 0.8; // Warn at 80% usage
|
|
47
57
|
this.quotaCriticalThreshold = 0.95; // Critical at 95% usage
|
|
@@ -393,13 +403,11 @@ export class OPFSStorage extends BaseStorage {
|
|
|
393
403
|
try {
|
|
394
404
|
// Delete the entire _cow/ directory (not just contents)
|
|
395
405
|
await this.rootDir.removeEntry('_cow', { recursive: true });
|
|
396
|
-
//
|
|
397
|
-
//
|
|
398
|
-
// Otherwise initializeCOW() will auto-recreate initial commit on next operation
|
|
406
|
+
// v5.11.0: Reset COW managers (but don't disable COW - it's always enabled)
|
|
407
|
+
// COW will re-initialize automatically on next use
|
|
399
408
|
this.refManager = undefined;
|
|
400
409
|
this.blobStorage = undefined;
|
|
401
410
|
this.commitLog = undefined;
|
|
402
|
-
this.cowEnabled = false;
|
|
403
411
|
}
|
|
404
412
|
catch (error) {
|
|
405
413
|
// Ignore if _cow directory doesn't exist (not all instances use COW)
|
|
@@ -771,22 +771,27 @@ export class R2Storage extends BaseStorage {
|
|
|
771
771
|
async clear() {
|
|
772
772
|
await this.ensureInitialized();
|
|
773
773
|
prodLog.info('🧹 R2: Clearing all data from bucket...');
|
|
774
|
-
//
|
|
775
|
-
//
|
|
776
|
-
|
|
777
|
-
for (const
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
//
|
|
774
|
+
// v5.11.0: Clear ALL data using correct paths
|
|
775
|
+
// Delete entire branches/ directory (includes ALL entities, ALL types, ALL VFS data, ALL forks)
|
|
776
|
+
const branchObjects = await this.listObjectsUnderPath('branches/');
|
|
777
|
+
for (const key of branchObjects) {
|
|
778
|
+
await this.deleteObjectFromPath(key);
|
|
779
|
+
}
|
|
780
|
+
// Delete COW version control data
|
|
781
|
+
const cowObjects = await this.listObjectsUnderPath('_cow/');
|
|
782
|
+
for (const key of cowObjects) {
|
|
783
|
+
await this.deleteObjectFromPath(key);
|
|
784
|
+
}
|
|
785
|
+
// Delete system metadata
|
|
786
|
+
const systemObjects = await this.listObjectsUnderPath('_system/');
|
|
787
|
+
for (const key of systemObjects) {
|
|
788
|
+
await this.deleteObjectFromPath(key);
|
|
789
|
+
}
|
|
790
|
+
// v5.11.0: Reset COW managers (but don't disable COW - it's always enabled)
|
|
791
|
+
// COW will re-initialize automatically on next use
|
|
786
792
|
this.refManager = undefined;
|
|
787
793
|
this.blobStorage = undefined;
|
|
788
794
|
this.commitLog = undefined;
|
|
789
|
-
this.cowEnabled = false;
|
|
790
795
|
this.nounCacheManager.clear();
|
|
791
796
|
this.verbCacheManager.clear();
|
|
792
797
|
this.totalNounCount = 0;
|
|
@@ -373,6 +373,16 @@ export declare class S3CompatibleStorage extends BaseStorage {
|
|
|
373
373
|
quota: number | null;
|
|
374
374
|
details?: Record<string, any>;
|
|
375
375
|
}>;
|
|
376
|
+
/**
|
|
377
|
+
* Check if COW has been explicitly disabled via clear()
|
|
378
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
379
|
+
* @returns true if marker object exists, false otherwise
|
|
380
|
+
* @protected
|
|
381
|
+
*/
|
|
382
|
+
/**
|
|
383
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
384
|
+
* COW is now always enabled - marker files are no longer used
|
|
385
|
+
*/
|
|
376
386
|
protected statisticsBatchUpdateTimerId: NodeJS.Timeout | null;
|
|
377
387
|
protected statisticsModified: boolean;
|
|
378
388
|
protected lastStatisticsFlushTime: number;
|
|
@@ -89,6 +89,16 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
89
89
|
this.hnswLocks = new Map();
|
|
90
90
|
// Node cache to avoid redundant API calls
|
|
91
91
|
this.nodeCache = new Map();
|
|
92
|
+
/**
|
|
93
|
+
* Check if COW has been explicitly disabled via clear()
|
|
94
|
+
* v5.10.4: Fixes bug where clear() doesn't persist across instance restarts
|
|
95
|
+
* @returns true if marker object exists, false otherwise
|
|
96
|
+
* @protected
|
|
97
|
+
*/
|
|
98
|
+
/**
|
|
99
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() methods
|
|
100
|
+
* COW is now always enabled - marker files are no longer used
|
|
101
|
+
*/
|
|
92
102
|
// Batch update timer ID
|
|
93
103
|
this.statisticsBatchUpdateTimerId = null;
|
|
94
104
|
// Flag to indicate if statistics have been modified since last save
|
|
@@ -1611,27 +1621,18 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1611
1621
|
}
|
|
1612
1622
|
}
|
|
1613
1623
|
};
|
|
1614
|
-
//
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
// Delete all objects in the noun metadata directory
|
|
1619
|
-
await deleteObjectsWithPrefix(this.metadataPrefix);
|
|
1620
|
-
// Delete all objects in the verb metadata directory
|
|
1621
|
-
await deleteObjectsWithPrefix(this.verbMetadataPrefix);
|
|
1622
|
-
// Delete all objects in the index directory
|
|
1623
|
-
await deleteObjectsWithPrefix(this.indexPrefix);
|
|
1624
|
-
// v5.6.1: Delete COW (copy-on-write) version control data
|
|
1625
|
-
// This includes all git-like versioning data (commits, trees, blobs, refs)
|
|
1626
|
-
// Must be deleted to fully clear all data including version history
|
|
1624
|
+
// v5.11.0: Clear ALL data using correct paths
|
|
1625
|
+
// Delete entire branches/ directory (includes ALL entities, ALL types, ALL VFS data, ALL forks)
|
|
1626
|
+
await deleteObjectsWithPrefix('branches/');
|
|
1627
|
+
// Delete COW version control data
|
|
1627
1628
|
await deleteObjectsWithPrefix('_cow/');
|
|
1628
|
-
//
|
|
1629
|
-
|
|
1630
|
-
//
|
|
1629
|
+
// Delete system metadata
|
|
1630
|
+
await deleteObjectsWithPrefix('_system/');
|
|
1631
|
+
// v5.11.0: Reset COW managers (but don't disable COW - it's always enabled)
|
|
1632
|
+
// COW will re-initialize automatically on next use
|
|
1631
1633
|
this.refManager = undefined;
|
|
1632
1634
|
this.blobStorage = undefined;
|
|
1633
1635
|
this.commitLog = undefined;
|
|
1634
|
-
this.cowEnabled = false;
|
|
1635
1636
|
// Clear the statistics cache
|
|
1636
1637
|
this.statisticsCache = null;
|
|
1637
1638
|
this.statisticsModified = false;
|
|
@@ -58,7 +58,6 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
58
58
|
blobStorage?: BlobStorage;
|
|
59
59
|
commitLog?: CommitLog;
|
|
60
60
|
currentBranch: string;
|
|
61
|
-
protected cowEnabled: boolean;
|
|
62
61
|
protected nounCountsByType: Uint32Array<ArrayBuffer>;
|
|
63
62
|
protected verbCountsByType: Uint32Array<ArrayBuffer>;
|
|
64
63
|
protected nounTypeCache: Map<string, NounType>;
|
|
@@ -88,6 +87,8 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
88
87
|
* Called during init() to ensure all data is stored with branch prefixes from the start
|
|
89
88
|
* RefManager/BlobStorage/CommitLog are lazy-initialized on first fork()
|
|
90
89
|
* @param branch - Branch name to use (default: 'main')
|
|
90
|
+
*
|
|
91
|
+
* v5.11.0: COW is always enabled - this method now just sets the branch name (idempotent)
|
|
91
92
|
*/
|
|
92
93
|
enableCOWLightweight(branch?: string): void;
|
|
93
94
|
/**
|
|
@@ -119,6 +120,8 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
119
120
|
* Read object with inheritance from parent branches (COW layer)
|
|
120
121
|
* Tries current branch first, then walks commit history
|
|
121
122
|
* @protected - Available to subclasses for COW implementation
|
|
123
|
+
*
|
|
124
|
+
* v5.11.0: COW is always enabled - always use branch-scoped paths with inheritance
|
|
122
125
|
*/
|
|
123
126
|
protected readWithInheritance(path: string, branch?: string): Promise<any | null>;
|
|
124
127
|
/**
|
|
@@ -137,6 +140,8 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
137
140
|
* This enables fork to see parent's data in pagination operations
|
|
138
141
|
*
|
|
139
142
|
* Simplified approach: All branches inherit from main
|
|
143
|
+
*
|
|
144
|
+
* v5.11.0: COW is always enabled - always use inheritance
|
|
140
145
|
*/
|
|
141
146
|
protected listObjectsWithInheritance(prefix: string, branch?: string): Promise<string[]>;
|
|
142
147
|
/**
|
|
@@ -327,6 +332,10 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
327
332
|
* This method should be implemented by each specific adapter
|
|
328
333
|
*/
|
|
329
334
|
abstract clear(): Promise<void>;
|
|
335
|
+
/**
|
|
336
|
+
* v5.11.0: Removed checkClearMarker() and createClearMarker() abstract methods
|
|
337
|
+
* COW is now always enabled - marker files are no longer used
|
|
338
|
+
*/
|
|
330
339
|
/**
|
|
331
340
|
* Get information about storage usage and capacity
|
|
332
341
|
* This method should be implemented by each specific adapter
|
|
@@ -83,7 +83,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
83
83
|
// Memory footprint: Bounded by batch size (typically <1000 items during imports)
|
|
84
84
|
this.writeCache = new Map();
|
|
85
85
|
this.currentBranch = 'main';
|
|
86
|
-
|
|
86
|
+
// v5.11.0: Removed cowEnabled flag - COW is ALWAYS enabled (mandatory, cannot be disabled)
|
|
87
87
|
// Type-first indexing support (v5.4.0)
|
|
88
88
|
// Built into all storage adapters for billion-scale efficiency
|
|
89
89
|
this.nounCountsByType = new Uint32Array(NOUN_TYPE_COUNT); // 168 bytes (Stage 3: 42 types)
|
|
@@ -191,13 +191,11 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
191
191
|
* Called during init() to ensure all data is stored with branch prefixes from the start
|
|
192
192
|
* RefManager/BlobStorage/CommitLog are lazy-initialized on first fork()
|
|
193
193
|
* @param branch - Branch name to use (default: 'main')
|
|
194
|
+
*
|
|
195
|
+
* v5.11.0: COW is always enabled - this method now just sets the branch name (idempotent)
|
|
194
196
|
*/
|
|
195
197
|
enableCOWLightweight(branch = 'main') {
|
|
196
|
-
if (this.cowEnabled) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
198
|
this.currentBranch = branch;
|
|
200
|
-
this.cowEnabled = true;
|
|
201
199
|
// RefManager/BlobStorage/CommitLog remain undefined until first fork()
|
|
202
200
|
}
|
|
203
201
|
/**
|
|
@@ -212,19 +210,15 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
212
210
|
* @returns Promise that resolves when COW is initialized
|
|
213
211
|
*/
|
|
214
212
|
async initializeCOW(options) {
|
|
215
|
-
// v5.
|
|
216
|
-
//
|
|
217
|
-
if
|
|
213
|
+
// v5.11.0: COW is ALWAYS enabled - idempotent initialization only
|
|
214
|
+
// Removed marker file check (cowEnabled flag removed, COW is mandatory)
|
|
215
|
+
// Check if RefManager already initialized (idempotent)
|
|
216
|
+
if (this.refManager && this.blobStorage && this.commitLog) {
|
|
218
217
|
return;
|
|
219
218
|
}
|
|
220
|
-
//
|
|
221
|
-
if (
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
// Enable lightweight COW if not already enabled
|
|
225
|
-
if (!this.cowEnabled) {
|
|
226
|
-
this.currentBranch = options?.branch || 'main';
|
|
227
|
-
this.cowEnabled = true;
|
|
219
|
+
// Set current branch if provided
|
|
220
|
+
if (options?.branch) {
|
|
221
|
+
this.currentBranch = options.branch;
|
|
228
222
|
}
|
|
229
223
|
// Create COWStorageAdapter bridge
|
|
230
224
|
// This adapts BaseStorage's methods to the simple key-value interface
|
|
@@ -326,7 +320,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
326
320
|
await this.refManager.setHead(this.currentBranch);
|
|
327
321
|
}
|
|
328
322
|
}
|
|
329
|
-
|
|
323
|
+
// v5.11.0: COW is always enabled - no flag to set
|
|
330
324
|
}
|
|
331
325
|
/**
|
|
332
326
|
* Resolve branch-scoped path for COW isolation
|
|
@@ -340,9 +334,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
340
334
|
if (basePath.startsWith('_cow/')) {
|
|
341
335
|
return basePath; // COW metadata is global across all branches
|
|
342
336
|
}
|
|
343
|
-
|
|
344
|
-
return basePath; // COW disabled, use direct path
|
|
345
|
-
}
|
|
337
|
+
// v5.11.0: COW is always enabled - always use branch-scoped paths
|
|
346
338
|
const targetBranch = branch || this.currentBranch || 'main';
|
|
347
339
|
// Branch-scoped path: branches/<branch>/<basePath>
|
|
348
340
|
return `branches/${targetBranch}/${basePath}`;
|
|
@@ -366,17 +358,10 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
366
358
|
* Read object with inheritance from parent branches (COW layer)
|
|
367
359
|
* Tries current branch first, then walks commit history
|
|
368
360
|
* @protected - Available to subclasses for COW implementation
|
|
361
|
+
*
|
|
362
|
+
* v5.11.0: COW is always enabled - always use branch-scoped paths with inheritance
|
|
369
363
|
*/
|
|
370
364
|
async readWithInheritance(path, branch) {
|
|
371
|
-
if (!this.cowEnabled) {
|
|
372
|
-
// COW disabled: check write cache, then direct read
|
|
373
|
-
// v5.7.2: Check cache first for read-after-write consistency
|
|
374
|
-
const cachedData = this.writeCache.get(path);
|
|
375
|
-
if (cachedData !== undefined) {
|
|
376
|
-
return cachedData;
|
|
377
|
-
}
|
|
378
|
-
return this.readObjectFromPath(path);
|
|
379
|
-
}
|
|
380
365
|
const targetBranch = branch || this.currentBranch || 'main';
|
|
381
366
|
const branchPath = this.resolveBranchPath(path, targetBranch);
|
|
382
367
|
// v5.7.2: Check write cache FIRST (synchronous, instant)
|
|
@@ -453,11 +438,10 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
453
438
|
* This enables fork to see parent's data in pagination operations
|
|
454
439
|
*
|
|
455
440
|
* Simplified approach: All branches inherit from main
|
|
441
|
+
*
|
|
442
|
+
* v5.11.0: COW is always enabled - always use inheritance
|
|
456
443
|
*/
|
|
457
444
|
async listObjectsWithInheritance(prefix, branch) {
|
|
458
|
-
if (!this.cowEnabled) {
|
|
459
|
-
return this.listObjectsInBranch(prefix, branch);
|
|
460
|
-
}
|
|
461
445
|
const targetBranch = branch || this.currentBranch || 'main';
|
|
462
446
|
// Collect paths from current branch
|
|
463
447
|
const pathsSet = new Set();
|
|
@@ -115,6 +115,30 @@ export declare class CommitLog {
|
|
|
115
115
|
since?: number;
|
|
116
116
|
until?: number;
|
|
117
117
|
}): Promise<CommitObject[]>;
|
|
118
|
+
/**
|
|
119
|
+
* Stream commit history (memory-efficient for large histories)
|
|
120
|
+
*
|
|
121
|
+
* Yields commits one at a time without accumulating in memory.
|
|
122
|
+
* Use this for large commit histories (1000s of commits) where
|
|
123
|
+
* memory efficiency is important.
|
|
124
|
+
*
|
|
125
|
+
* @param ref - Starting ref
|
|
126
|
+
* @param options - Walk options
|
|
127
|
+
* @yields Commits in reverse chronological order (newest first)
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* // Stream all commits without memory accumulation
|
|
132
|
+
* for await (const commit of commitLog.streamHistory('main', { maxCount: 10000 })) {
|
|
133
|
+
* console.log(commit.message)
|
|
134
|
+
* }
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
streamHistory(ref: string, options?: {
|
|
138
|
+
maxCount?: number;
|
|
139
|
+
since?: number;
|
|
140
|
+
until?: number;
|
|
141
|
+
}): AsyncIterableIterator<CommitObject>;
|
|
118
142
|
/**
|
|
119
143
|
* Count commits between two commits
|
|
120
144
|
*
|
|
@@ -183,6 +183,43 @@ export class CommitLog {
|
|
|
183
183
|
}
|
|
184
184
|
return commits;
|
|
185
185
|
}
|
|
186
|
+
/**
|
|
187
|
+
* Stream commit history (memory-efficient for large histories)
|
|
188
|
+
*
|
|
189
|
+
* Yields commits one at a time without accumulating in memory.
|
|
190
|
+
* Use this for large commit histories (1000s of commits) where
|
|
191
|
+
* memory efficiency is important.
|
|
192
|
+
*
|
|
193
|
+
* @param ref - Starting ref
|
|
194
|
+
* @param options - Walk options
|
|
195
|
+
* @yields Commits in reverse chronological order (newest first)
|
|
196
|
+
*
|
|
197
|
+
* @example
|
|
198
|
+
* ```typescript
|
|
199
|
+
* // Stream all commits without memory accumulation
|
|
200
|
+
* for await (const commit of commitLog.streamHistory('main', { maxCount: 10000 })) {
|
|
201
|
+
* console.log(commit.message)
|
|
202
|
+
* }
|
|
203
|
+
* ```
|
|
204
|
+
*/
|
|
205
|
+
async *streamHistory(ref, options) {
|
|
206
|
+
let count = 0;
|
|
207
|
+
for await (const commit of this.walk(ref, {
|
|
208
|
+
maxDepth: options?.maxCount,
|
|
209
|
+
until: options?.until
|
|
210
|
+
})) {
|
|
211
|
+
// Filter by since timestamp if provided
|
|
212
|
+
if (options?.since && commit.timestamp < options.since) {
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
215
|
+
yield commit;
|
|
216
|
+
count++;
|
|
217
|
+
// Stop after maxCount commits
|
|
218
|
+
if (options?.maxCount && count >= options.maxCount) {
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
186
223
|
/**
|
|
187
224
|
* Count commits between two commits
|
|
188
225
|
*
|
|
@@ -5,6 +5,49 @@
|
|
|
5
5
|
* Only enforces universal truths, learns everything else
|
|
6
6
|
*/
|
|
7
7
|
import { FindParams, AddParams, UpdateParams, RelateParams } from '../types/brainy.types.js';
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for ValidationConfig
|
|
10
|
+
*/
|
|
11
|
+
export interface ValidationConfigOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Explicit maximum query limit override
|
|
14
|
+
* Bypasses all auto-detection
|
|
15
|
+
*/
|
|
16
|
+
maxQueryLimit?: number;
|
|
17
|
+
/**
|
|
18
|
+
* Memory reserved for query operations (in bytes)
|
|
19
|
+
* Bypasses auto-detection but still applies safety limits
|
|
20
|
+
*/
|
|
21
|
+
reservedQueryMemory?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Auto-configured limits based on system resources
|
|
25
|
+
* These adapt to available memory and observed performance
|
|
26
|
+
*/
|
|
27
|
+
export declare class ValidationConfig {
|
|
28
|
+
private static instance;
|
|
29
|
+
maxLimit: number;
|
|
30
|
+
maxQueryLength: number;
|
|
31
|
+
maxVectorDimensions: number;
|
|
32
|
+
limitBasis: 'override' | 'reservedMemory' | 'containerMemory' | 'freeMemory';
|
|
33
|
+
detectedContainerLimit: number | null;
|
|
34
|
+
private avgQueryTime;
|
|
35
|
+
private queryCount;
|
|
36
|
+
private constructor();
|
|
37
|
+
static getInstance(options?: ValidationConfigOptions): ValidationConfig;
|
|
38
|
+
/**
|
|
39
|
+
* Reset singleton (for testing or reconfiguration)
|
|
40
|
+
*/
|
|
41
|
+
static reset(): void;
|
|
42
|
+
/**
|
|
43
|
+
* Reconfigure with new options
|
|
44
|
+
*/
|
|
45
|
+
static reconfigure(options: ValidationConfigOptions): ValidationConfig;
|
|
46
|
+
/**
|
|
47
|
+
* Learn from actual usage to adjust limits
|
|
48
|
+
*/
|
|
49
|
+
recordQuery(duration: number, resultCount: number): void;
|
|
50
|
+
}
|
|
8
51
|
/**
|
|
9
52
|
* Universal validations - things that are always invalid
|
|
10
53
|
* These are mathematical/logical truths, not configuration
|
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
* Only enforces universal truths, learns everything else
|
|
6
6
|
*/
|
|
7
7
|
import { NounType, VerbType } from '../types/graphTypes.js';
|
|
8
|
-
// Dynamic import for Node.js os
|
|
8
|
+
// Dynamic import for Node.js os and fs modules
|
|
9
9
|
let os = null;
|
|
10
|
+
let fs = null;
|
|
10
11
|
if (typeof window === 'undefined') {
|
|
11
12
|
try {
|
|
12
13
|
os = await import('node:os');
|
|
14
|
+
fs = await import('node:fs');
|
|
13
15
|
}
|
|
14
16
|
catch (e) {
|
|
15
|
-
// OS
|
|
17
|
+
// OS/FS modules not available
|
|
16
18
|
}
|
|
17
19
|
}
|
|
18
20
|
// Browser-safe memory detection
|
|
@@ -30,46 +32,157 @@ const getAvailableMemory = () => {
|
|
|
30
32
|
// Browser fallback: assume 2GB available
|
|
31
33
|
return 2 * 1024 * 1024 * 1024;
|
|
32
34
|
};
|
|
35
|
+
/**
|
|
36
|
+
* Detect container memory limit (Docker/Kubernetes/Cloud Run)
|
|
37
|
+
*
|
|
38
|
+
* Production-grade detection for containerized environments.
|
|
39
|
+
* Supports:
|
|
40
|
+
* - cgroup v1 (legacy Docker/K8s)
|
|
41
|
+
* - cgroup v2 (modern systems)
|
|
42
|
+
* - Environment variables (Cloud Run, GCP, AWS, Azure)
|
|
43
|
+
*
|
|
44
|
+
* @returns Container memory limit in bytes, or null if not containerized
|
|
45
|
+
*/
|
|
46
|
+
const getContainerMemoryLimit = () => {
|
|
47
|
+
// Not in Node.js environment
|
|
48
|
+
if (!fs) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
// 1. Check environment variables first (fastest, most reliable for Cloud Run)
|
|
53
|
+
// Google Cloud Run
|
|
54
|
+
if (process.env.CLOUD_RUN_MEMORY) {
|
|
55
|
+
// Format: "512Mi", "1Gi", "2Gi", "4Gi"
|
|
56
|
+
const match = process.env.CLOUD_RUN_MEMORY.match(/^(\d+)(Mi|Gi)$/);
|
|
57
|
+
if (match) {
|
|
58
|
+
const value = parseInt(match[1]);
|
|
59
|
+
const unit = match[2];
|
|
60
|
+
return unit === 'Gi' ? value * 1024 * 1024 * 1024 : value * 1024 * 1024;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Generic MEMORY_LIMIT env var (bytes)
|
|
64
|
+
if (process.env.MEMORY_LIMIT) {
|
|
65
|
+
const limit = parseInt(process.env.MEMORY_LIMIT);
|
|
66
|
+
if (!isNaN(limit) && limit > 0) {
|
|
67
|
+
return limit;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
// 2. Check cgroup v2 (modern Docker/K8s)
|
|
71
|
+
try {
|
|
72
|
+
const cgroupV2Path = '/sys/fs/cgroup/memory.max';
|
|
73
|
+
const cgroupV2Content = fs.readFileSync(cgroupV2Path, 'utf8').trim();
|
|
74
|
+
// "max" means no limit, otherwise it's bytes
|
|
75
|
+
if (cgroupV2Content !== 'max') {
|
|
76
|
+
const limit = parseInt(cgroupV2Content);
|
|
77
|
+
if (!isNaN(limit) && limit > 0) {
|
|
78
|
+
return limit;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
// cgroup v2 not available, try v1
|
|
84
|
+
}
|
|
85
|
+
// 3. Check cgroup v1 (legacy Docker/K8s)
|
|
86
|
+
try {
|
|
87
|
+
const cgroupV1Path = '/sys/fs/cgroup/memory/memory.limit_in_bytes';
|
|
88
|
+
const cgroupV1Content = fs.readFileSync(cgroupV1Path, 'utf8').trim();
|
|
89
|
+
const limit = parseInt(cgroupV1Content);
|
|
90
|
+
// Very large values (> 1 PB) indicate no limit
|
|
91
|
+
const ONE_PETABYTE = 1024 * 1024 * 1024 * 1024 * 1024;
|
|
92
|
+
if (!isNaN(limit) && limit > 0 && limit < ONE_PETABYTE) {
|
|
93
|
+
return limit;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
// cgroup v1 not available
|
|
98
|
+
}
|
|
99
|
+
// Not containerized or no limit set
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
// Error reading cgroup files
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
33
107
|
/**
|
|
34
108
|
* Auto-configured limits based on system resources
|
|
35
109
|
* These adapt to available memory and observed performance
|
|
36
110
|
*/
|
|
37
|
-
class ValidationConfig {
|
|
38
|
-
constructor() {
|
|
111
|
+
export class ValidationConfig {
|
|
112
|
+
constructor(options) {
|
|
39
113
|
// Performance observations
|
|
40
114
|
this.avgQueryTime = 0;
|
|
41
115
|
this.queryCount = 0;
|
|
42
|
-
// Auto-configure based on system resources
|
|
43
|
-
const totalMemory = getSystemMemory();
|
|
44
|
-
const availableMemory = getAvailableMemory();
|
|
45
|
-
// Scale limits based on available memory
|
|
46
|
-
// 1GB = 10K limit, 8GB = 80K limit, etc.
|
|
47
|
-
this.maxLimit = Math.min(100000, // Absolute max for safety
|
|
48
|
-
Math.floor(availableMemory / (1024 * 1024 * 100)) * 1000);
|
|
49
|
-
// Query length scales with memory too
|
|
50
|
-
this.maxQueryLength = Math.min(50000, Math.floor(availableMemory / (1024 * 1024 * 10)) * 1000);
|
|
51
116
|
// Vector dimensions (standard for all-MiniLM-L6-v2)
|
|
52
117
|
this.maxVectorDimensions = 384;
|
|
118
|
+
// Detect container memory limit
|
|
119
|
+
this.detectedContainerLimit = getContainerMemoryLimit();
|
|
120
|
+
// Priority 1: Explicit override (highest priority)
|
|
121
|
+
if (options?.maxQueryLimit !== undefined) {
|
|
122
|
+
this.maxLimit = Math.min(options.maxQueryLimit, 100000); // Still cap at 100k for safety
|
|
123
|
+
this.limitBasis = 'override';
|
|
124
|
+
// Scale query length with limit
|
|
125
|
+
this.maxQueryLength = Math.min(50000, this.maxLimit * 5);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Priority 2: Reserved memory specified
|
|
129
|
+
if (options?.reservedQueryMemory !== undefined) {
|
|
130
|
+
this.maxLimit = Math.min(100000, Math.floor(options.reservedQueryMemory / (1024 * 1024 * 100)) * 1000);
|
|
131
|
+
this.limitBasis = 'reservedMemory';
|
|
132
|
+
this.maxQueryLength = Math.min(50000, Math.floor(options.reservedQueryMemory / (1024 * 1024 * 10)) * 1000);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
// Priority 3: Container detected (smart containerized behavior)
|
|
136
|
+
if (this.detectedContainerLimit) {
|
|
137
|
+
// In containers, assume 75% used by graph data (EXPECTED)
|
|
138
|
+
// Reserve 25% for query operations
|
|
139
|
+
const queryMemory = this.detectedContainerLimit * 0.25;
|
|
140
|
+
this.maxLimit = Math.min(100000, Math.floor(queryMemory / (1024 * 1024 * 100)) * 1000);
|
|
141
|
+
this.limitBasis = 'containerMemory';
|
|
142
|
+
this.maxQueryLength = Math.min(50000, Math.floor(queryMemory / (1024 * 1024 * 10)) * 1000);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Priority 4: Free memory (fallback, current behavior)
|
|
146
|
+
const availableMemory = getAvailableMemory();
|
|
147
|
+
this.maxLimit = Math.min(100000, Math.floor(availableMemory / (1024 * 1024 * 100)) * 1000);
|
|
148
|
+
this.limitBasis = 'freeMemory';
|
|
149
|
+
this.maxQueryLength = Math.min(50000, Math.floor(availableMemory / (1024 * 1024 * 10)) * 1000);
|
|
53
150
|
}
|
|
54
|
-
static getInstance() {
|
|
151
|
+
static getInstance(options) {
|
|
55
152
|
if (!ValidationConfig.instance) {
|
|
56
|
-
ValidationConfig.instance = new ValidationConfig();
|
|
153
|
+
ValidationConfig.instance = new ValidationConfig(options);
|
|
57
154
|
}
|
|
58
155
|
return ValidationConfig.instance;
|
|
59
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* Reset singleton (for testing or reconfiguration)
|
|
159
|
+
*/
|
|
160
|
+
static reset() {
|
|
161
|
+
ValidationConfig.instance = null;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Reconfigure with new options
|
|
165
|
+
*/
|
|
166
|
+
static reconfigure(options) {
|
|
167
|
+
ValidationConfig.instance = new ValidationConfig(options);
|
|
168
|
+
return ValidationConfig.instance;
|
|
169
|
+
}
|
|
60
170
|
/**
|
|
61
171
|
* Learn from actual usage to adjust limits
|
|
62
172
|
*/
|
|
63
173
|
recordQuery(duration, resultCount) {
|
|
64
174
|
this.queryCount++;
|
|
65
175
|
this.avgQueryTime = (this.avgQueryTime * (this.queryCount - 1) + duration) / this.queryCount;
|
|
66
|
-
//
|
|
67
|
-
if (this.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
176
|
+
// Only auto-adjust if not using explicit overrides
|
|
177
|
+
if (this.limitBasis !== 'override') {
|
|
178
|
+
// If queries are consistently fast with large results, increase limits
|
|
179
|
+
if (this.avgQueryTime < 100 && resultCount > this.maxLimit * 0.8) {
|
|
180
|
+
this.maxLimit = Math.min(this.maxLimit * 1.5, 100000);
|
|
181
|
+
}
|
|
182
|
+
// If queries are slow, reduce limits
|
|
183
|
+
if (this.avgQueryTime > 1000) {
|
|
184
|
+
this.maxLimit = Math.max(this.maxLimit * 0.8, 1000);
|
|
185
|
+
}
|
|
73
186
|
}
|
|
74
187
|
}
|
|
75
188
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.11.0",
|
|
4
4
|
"description": "Universal Knowledge Protocol™ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. Stage 3 CANONICAL: 42 nouns × 127 verbs covering 96-97% of all human knowledge.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|