@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 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
- // CRITICAL: Reset COW state to prevent automatic reinitialization
863
- // When COW data is cleared, we must also clear the COW managers
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
- // Remove all files in the nouns directory
863
- await removeDirectoryContents(this.nounsDir);
864
- // Remove all files in the verbs directory
865
- await removeDirectoryContents(this.verbsDir);
866
- // Remove all files in the metadata directory
867
- await removeDirectoryContents(this.metadataDir);
868
- // Remove all files in the noun metadata directory
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
- // CRITICAL: Reset COW state to prevent automatic reinitialization
885
- // When COW data is cleared, we must also clear the COW managers
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 all data directories
776
- await deleteObjectsWithPrefix(this.nounPrefix);
777
- await deleteObjectsWithPrefix(this.verbPrefix);
778
- await deleteObjectsWithPrefix(this.metadataPrefix);
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
- // CRITICAL: Reset COW state to prevent automatic reinitialization
786
- // When COW data is cleared, we must also clear the COW managers
787
- // Otherwise initializeCOW() will auto-recreate initial commit on next operation
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
- // CRITICAL: Reset COW state to prevent automatic reinitialization
397
- // When COW data is cleared, we must also clear the COW managers
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
- // Clear all prefixes (v5.6.1: includes _cow/ for version control data)
775
- // _cow/ stores all git-like versioning data (commits, trees, blobs, refs)
776
- // Must be deleted to fully clear all data including version history
777
- for (const prefix of [this.nounPrefix, this.verbPrefix, this.metadataPrefix, this.verbMetadataPrefix, this.systemPrefix, '_cow/']) {
778
- const objects = await this.listObjectsUnderPath(prefix);
779
- for (const key of objects) {
780
- await this.deleteObjectFromPath(key);
781
- }
782
- }
783
- // CRITICAL: Reset COW state to prevent automatic reinitialization
784
- // When COW data is cleared, we must also clear the COW managers
785
- // Otherwise initializeCOW() will auto-recreate initial commit on next operation
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
- // Delete all objects in the nouns directory
1615
- await deleteObjectsWithPrefix(this.nounPrefix);
1616
- // Delete all objects in the verbs directory
1617
- await deleteObjectsWithPrefix(this.verbPrefix);
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
- // CRITICAL: Reset COW state to prevent automatic reinitialization
1629
- // When COW data is cleared, we must also clear the COW managers
1630
- // Otherwise initializeCOW() will auto-recreate initial commit on next operation
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
- this.cowEnabled = false;
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.6.1: If COW was explicitly disabled (e.g., via clear()), don't reinitialize
216
- // This prevents automatic recreation of COW data after clear() operations
217
- if (this.cowEnabled === false) {
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
- // Check if RefManager already initialized (full COW setup complete)
221
- if (this.refManager) {
222
- return;
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
- this.cowEnabled = true;
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
- if (!this.cowEnabled) {
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
  *
@@ -494,6 +494,8 @@ export interface BrainyConfig {
494
494
  disableAutoOptimize?: boolean;
495
495
  batchWrites?: boolean;
496
496
  maxConcurrentOperations?: number;
497
+ maxQueryLimit?: number;
498
+ reservedQueryMemory?: number;
497
499
  verbose?: boolean;
498
500
  silent?: boolean;
499
501
  }
@@ -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 module
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 module not available
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
- // If queries are consistently fast with large results, increase limits
67
- if (this.avgQueryTime < 100 && resultCount > this.maxLimit * 0.8) {
68
- this.maxLimit = Math.min(this.maxLimit * 1.5, 100000);
69
- }
70
- // If queries are slow, reduce limits
71
- if (this.avgQueryTime > 1000) {
72
- this.maxLimit = Math.max(this.maxLimit * 0.8, 1000);
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.10.3",
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",