@soulcraft/brainy 0.38.0 → 0.39.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/README.md +182 -105
- package/dist/brainyData.d.ts +61 -1
- package/dist/coreTypes.d.ts +17 -0
- package/dist/unified.js +717 -11
- package/dist/unified.min.js +991 -991
- package/dist/utils/cacheAutoConfig.d.ts +63 -0
- package/dist/utils/cacheAutoConfig.d.ts.map +1 -0
- package/dist/utils/searchCache.d.ts +93 -0
- package/dist/utils/searchCache.d.ts.map +1 -0
- package/package.json +1 -1
package/dist/unified.js
CHANGED
|
@@ -16601,6 +16601,515 @@ class HealthMonitor {
|
|
|
16601
16601
|
}
|
|
16602
16602
|
}
|
|
16603
16603
|
|
|
16604
|
+
/**
|
|
16605
|
+
* SearchCache - Caches search results for improved performance
|
|
16606
|
+
*/
|
|
16607
|
+
class SearchCache {
|
|
16608
|
+
constructor(config = {}) {
|
|
16609
|
+
this.cache = new Map();
|
|
16610
|
+
// Cache statistics
|
|
16611
|
+
this.hits = 0;
|
|
16612
|
+
this.misses = 0;
|
|
16613
|
+
this.evictions = 0;
|
|
16614
|
+
this.maxAge = config.maxAge ?? 5 * 60 * 1000; // 5 minutes
|
|
16615
|
+
this.maxSize = config.maxSize ?? 100;
|
|
16616
|
+
this.enabled = config.enabled ?? true;
|
|
16617
|
+
this.hitCountWeight = config.hitCountWeight ?? 0.3;
|
|
16618
|
+
}
|
|
16619
|
+
/**
|
|
16620
|
+
* Generate cache key from search parameters
|
|
16621
|
+
*/
|
|
16622
|
+
getCacheKey(query, k, options = {}) {
|
|
16623
|
+
// Create a normalized key that ignores order of options
|
|
16624
|
+
const normalizedOptions = Object.keys(options)
|
|
16625
|
+
.sort()
|
|
16626
|
+
.reduce((acc, key) => {
|
|
16627
|
+
// Skip cache-related options
|
|
16628
|
+
if (key === 'skipCache' || key === 'useStreaming')
|
|
16629
|
+
return acc;
|
|
16630
|
+
acc[key] = options[key];
|
|
16631
|
+
return acc;
|
|
16632
|
+
}, {});
|
|
16633
|
+
return JSON.stringify({
|
|
16634
|
+
query: typeof query === 'object' ? JSON.stringify(query) : query,
|
|
16635
|
+
k,
|
|
16636
|
+
...normalizedOptions
|
|
16637
|
+
});
|
|
16638
|
+
}
|
|
16639
|
+
/**
|
|
16640
|
+
* Get cached results if available and not expired
|
|
16641
|
+
*/
|
|
16642
|
+
get(key) {
|
|
16643
|
+
if (!this.enabled)
|
|
16644
|
+
return null;
|
|
16645
|
+
const entry = this.cache.get(key);
|
|
16646
|
+
if (!entry) {
|
|
16647
|
+
this.misses++;
|
|
16648
|
+
return null;
|
|
16649
|
+
}
|
|
16650
|
+
// Check if expired
|
|
16651
|
+
if (Date.now() - entry.timestamp > this.maxAge) {
|
|
16652
|
+
this.cache.delete(key);
|
|
16653
|
+
this.misses++;
|
|
16654
|
+
return null;
|
|
16655
|
+
}
|
|
16656
|
+
// Update hit count and statistics
|
|
16657
|
+
entry.hits++;
|
|
16658
|
+
this.hits++;
|
|
16659
|
+
return entry.results;
|
|
16660
|
+
}
|
|
16661
|
+
/**
|
|
16662
|
+
* Cache search results
|
|
16663
|
+
*/
|
|
16664
|
+
set(key, results) {
|
|
16665
|
+
if (!this.enabled)
|
|
16666
|
+
return;
|
|
16667
|
+
// Evict if cache is full
|
|
16668
|
+
if (this.cache.size >= this.maxSize) {
|
|
16669
|
+
this.evictOldest();
|
|
16670
|
+
}
|
|
16671
|
+
this.cache.set(key, {
|
|
16672
|
+
results: [...results], // Deep copy to prevent mutations
|
|
16673
|
+
timestamp: Date.now(),
|
|
16674
|
+
hits: 0
|
|
16675
|
+
});
|
|
16676
|
+
}
|
|
16677
|
+
/**
|
|
16678
|
+
* Evict the oldest entry based on timestamp and hit count
|
|
16679
|
+
*/
|
|
16680
|
+
evictOldest() {
|
|
16681
|
+
let oldestKey = null;
|
|
16682
|
+
let oldestScore = Infinity;
|
|
16683
|
+
const now = Date.now();
|
|
16684
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
16685
|
+
// Score combines age and inverse hit count
|
|
16686
|
+
const age = now - entry.timestamp;
|
|
16687
|
+
const hitScore = entry.hits > 0 ? 1 / entry.hits : 1;
|
|
16688
|
+
const score = age + (hitScore * this.hitCountWeight * this.maxAge);
|
|
16689
|
+
if (score < oldestScore) {
|
|
16690
|
+
oldestScore = score;
|
|
16691
|
+
oldestKey = key;
|
|
16692
|
+
}
|
|
16693
|
+
}
|
|
16694
|
+
if (oldestKey) {
|
|
16695
|
+
this.cache.delete(oldestKey);
|
|
16696
|
+
this.evictions++;
|
|
16697
|
+
}
|
|
16698
|
+
}
|
|
16699
|
+
/**
|
|
16700
|
+
* Clear all cached results
|
|
16701
|
+
*/
|
|
16702
|
+
clear() {
|
|
16703
|
+
this.cache.clear();
|
|
16704
|
+
this.hits = 0;
|
|
16705
|
+
this.misses = 0;
|
|
16706
|
+
this.evictions = 0;
|
|
16707
|
+
}
|
|
16708
|
+
/**
|
|
16709
|
+
* Invalidate cache entries that might be affected by data changes
|
|
16710
|
+
*/
|
|
16711
|
+
invalidate(pattern) {
|
|
16712
|
+
if (!pattern) {
|
|
16713
|
+
this.clear();
|
|
16714
|
+
return;
|
|
16715
|
+
}
|
|
16716
|
+
const keysToDelete = [];
|
|
16717
|
+
for (const key of this.cache.keys()) {
|
|
16718
|
+
const shouldDelete = typeof pattern === 'string'
|
|
16719
|
+
? key.includes(pattern)
|
|
16720
|
+
: pattern.test(key);
|
|
16721
|
+
if (shouldDelete) {
|
|
16722
|
+
keysToDelete.push(key);
|
|
16723
|
+
}
|
|
16724
|
+
}
|
|
16725
|
+
keysToDelete.forEach(key => this.cache.delete(key));
|
|
16726
|
+
}
|
|
16727
|
+
/**
|
|
16728
|
+
* Smart invalidation for real-time data updates
|
|
16729
|
+
* Only clears cache if it's getting stale or if data changes significantly
|
|
16730
|
+
*/
|
|
16731
|
+
invalidateOnDataChange(changeType) {
|
|
16732
|
+
// For now, clear all caches on data changes to ensure consistency
|
|
16733
|
+
// In the future, we could implement more sophisticated invalidation
|
|
16734
|
+
// based on the type of change and affected data
|
|
16735
|
+
this.clear();
|
|
16736
|
+
}
|
|
16737
|
+
/**
|
|
16738
|
+
* Check if cache entries have expired and remove them
|
|
16739
|
+
* This is especially important in distributed scenarios where
|
|
16740
|
+
* real-time updates might be delayed or missed
|
|
16741
|
+
*/
|
|
16742
|
+
cleanupExpiredEntries() {
|
|
16743
|
+
const now = Date.now();
|
|
16744
|
+
const keysToDelete = [];
|
|
16745
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
16746
|
+
if (now - entry.timestamp > this.maxAge) {
|
|
16747
|
+
keysToDelete.push(key);
|
|
16748
|
+
}
|
|
16749
|
+
}
|
|
16750
|
+
keysToDelete.forEach(key => this.cache.delete(key));
|
|
16751
|
+
return keysToDelete.length;
|
|
16752
|
+
}
|
|
16753
|
+
/**
|
|
16754
|
+
* Get cache statistics
|
|
16755
|
+
*/
|
|
16756
|
+
getStats() {
|
|
16757
|
+
const total = this.hits + this.misses;
|
|
16758
|
+
return {
|
|
16759
|
+
hits: this.hits,
|
|
16760
|
+
misses: this.misses,
|
|
16761
|
+
evictions: this.evictions,
|
|
16762
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
16763
|
+
size: this.cache.size,
|
|
16764
|
+
maxSize: this.maxSize,
|
|
16765
|
+
enabled: this.enabled
|
|
16766
|
+
};
|
|
16767
|
+
}
|
|
16768
|
+
/**
|
|
16769
|
+
* Enable or disable caching
|
|
16770
|
+
*/
|
|
16771
|
+
setEnabled(enabled) {
|
|
16772
|
+
Object.defineProperty(this, 'enabled', { value: enabled, writable: false });
|
|
16773
|
+
if (!enabled) {
|
|
16774
|
+
this.clear();
|
|
16775
|
+
}
|
|
16776
|
+
}
|
|
16777
|
+
/**
|
|
16778
|
+
* Get memory usage estimate in bytes
|
|
16779
|
+
*/
|
|
16780
|
+
getMemoryUsage() {
|
|
16781
|
+
let totalSize = 0;
|
|
16782
|
+
for (const [key, entry] of this.cache.entries()) {
|
|
16783
|
+
// Estimate key size
|
|
16784
|
+
totalSize += key.length * 2; // UTF-16 characters
|
|
16785
|
+
// Estimate entry size
|
|
16786
|
+
totalSize += JSON.stringify(entry.results).length * 2;
|
|
16787
|
+
totalSize += 16; // timestamp + hits (8 bytes each)
|
|
16788
|
+
}
|
|
16789
|
+
return totalSize;
|
|
16790
|
+
}
|
|
16791
|
+
/**
|
|
16792
|
+
* Get current cache configuration
|
|
16793
|
+
*/
|
|
16794
|
+
getConfig() {
|
|
16795
|
+
return {
|
|
16796
|
+
enabled: this.enabled,
|
|
16797
|
+
maxSize: this.maxSize,
|
|
16798
|
+
maxAge: this.maxAge,
|
|
16799
|
+
hitCountWeight: this.hitCountWeight
|
|
16800
|
+
};
|
|
16801
|
+
}
|
|
16802
|
+
/**
|
|
16803
|
+
* Update cache configuration dynamically
|
|
16804
|
+
*/
|
|
16805
|
+
updateConfig(newConfig) {
|
|
16806
|
+
if (newConfig.enabled !== undefined) {
|
|
16807
|
+
this.enabled = newConfig.enabled;
|
|
16808
|
+
}
|
|
16809
|
+
if (newConfig.maxSize !== undefined) {
|
|
16810
|
+
this.maxSize = newConfig.maxSize;
|
|
16811
|
+
// Trigger eviction if current size exceeds new limit
|
|
16812
|
+
this.evictIfNeeded();
|
|
16813
|
+
}
|
|
16814
|
+
if (newConfig.maxAge !== undefined) {
|
|
16815
|
+
this.maxAge = newConfig.maxAge;
|
|
16816
|
+
// Clean up entries that are now expired with new TTL
|
|
16817
|
+
this.cleanupExpiredEntries();
|
|
16818
|
+
}
|
|
16819
|
+
if (newConfig.hitCountWeight !== undefined) {
|
|
16820
|
+
this.hitCountWeight = newConfig.hitCountWeight;
|
|
16821
|
+
}
|
|
16822
|
+
}
|
|
16823
|
+
/**
|
|
16824
|
+
* Evict entries if cache exceeds maxSize
|
|
16825
|
+
*/
|
|
16826
|
+
evictIfNeeded() {
|
|
16827
|
+
if (this.cache.size <= this.maxSize) {
|
|
16828
|
+
return;
|
|
16829
|
+
}
|
|
16830
|
+
// Calculate eviction score for each entry (same logic as existing eviction)
|
|
16831
|
+
const entries = Array.from(this.cache.entries()).map(([key, entry]) => {
|
|
16832
|
+
const age = Date.now() - entry.timestamp;
|
|
16833
|
+
const hitCount = entry.hits;
|
|
16834
|
+
// Eviction score: lower is more likely to be evicted
|
|
16835
|
+
// Combines age and hit count (weighted by hitCountWeight)
|
|
16836
|
+
const ageScore = age / this.maxAge;
|
|
16837
|
+
const hitScore = 1 / (hitCount + 1); // Inverse of hits (more hits = lower score)
|
|
16838
|
+
const score = ageScore * (1 - this.hitCountWeight) + hitScore * this.hitCountWeight;
|
|
16839
|
+
return { key, entry, score };
|
|
16840
|
+
});
|
|
16841
|
+
// Sort by score (lowest first - these will be evicted)
|
|
16842
|
+
entries.sort((a, b) => a.score - b.score);
|
|
16843
|
+
// Evict entries until we're under the limit
|
|
16844
|
+
const toEvict = entries.slice(0, this.cache.size - this.maxSize);
|
|
16845
|
+
toEvict.forEach(({ key }) => {
|
|
16846
|
+
this.cache.delete(key);
|
|
16847
|
+
this.evictions++;
|
|
16848
|
+
});
|
|
16849
|
+
}
|
|
16850
|
+
}
|
|
16851
|
+
|
|
16852
|
+
/**
|
|
16853
|
+
* Intelligent cache auto-configuration system
|
|
16854
|
+
* Adapts cache settings based on environment, usage patterns, and storage type
|
|
16855
|
+
*/
|
|
16856
|
+
class CacheAutoConfigurator {
|
|
16857
|
+
constructor() {
|
|
16858
|
+
this.stats = {
|
|
16859
|
+
totalQueries: 0,
|
|
16860
|
+
repeatQueries: 0,
|
|
16861
|
+
avgQueryTime: 50,
|
|
16862
|
+
memoryPressure: 0,
|
|
16863
|
+
storageType: 'memory',
|
|
16864
|
+
isDistributed: false,
|
|
16865
|
+
changeFrequency: 0,
|
|
16866
|
+
readWriteRatio: 10,
|
|
16867
|
+
};
|
|
16868
|
+
this.configHistory = [];
|
|
16869
|
+
this.lastOptimization = 0;
|
|
16870
|
+
}
|
|
16871
|
+
/**
|
|
16872
|
+
* Auto-detect optimal cache configuration based on current conditions
|
|
16873
|
+
*/
|
|
16874
|
+
autoDetectOptimalConfig(storageConfig, currentStats) {
|
|
16875
|
+
// Update stats with current information
|
|
16876
|
+
if (currentStats) {
|
|
16877
|
+
this.stats = { ...this.stats, ...currentStats };
|
|
16878
|
+
}
|
|
16879
|
+
// Detect environment characteristics
|
|
16880
|
+
this.detectEnvironment(storageConfig);
|
|
16881
|
+
// Generate optimal configuration
|
|
16882
|
+
const result = this.generateOptimalConfig();
|
|
16883
|
+
// Store for learning
|
|
16884
|
+
this.configHistory.push(result);
|
|
16885
|
+
this.lastOptimization = Date.now();
|
|
16886
|
+
return result;
|
|
16887
|
+
}
|
|
16888
|
+
/**
|
|
16889
|
+
* Dynamically adjust configuration based on runtime performance
|
|
16890
|
+
*/
|
|
16891
|
+
adaptConfiguration(currentConfig, performanceMetrics) {
|
|
16892
|
+
const reasoning = [];
|
|
16893
|
+
let needsUpdate = false;
|
|
16894
|
+
// Check if we should update (don't over-optimize)
|
|
16895
|
+
if (Date.now() - this.lastOptimization < 60000) {
|
|
16896
|
+
return null; // Wait at least 1 minute between optimizations
|
|
16897
|
+
}
|
|
16898
|
+
// Analyze performance patterns
|
|
16899
|
+
const adaptations = {};
|
|
16900
|
+
// Low hit rate → adjust cache size or TTL
|
|
16901
|
+
if (performanceMetrics.hitRate < 0.3) {
|
|
16902
|
+
if (performanceMetrics.externalChangesDetected > 5) {
|
|
16903
|
+
// Too many external changes → shorter TTL
|
|
16904
|
+
adaptations.maxAge = Math.max(60000, currentConfig.maxAge * 0.7);
|
|
16905
|
+
reasoning.push('Reduced cache TTL due to frequent external changes');
|
|
16906
|
+
needsUpdate = true;
|
|
16907
|
+
}
|
|
16908
|
+
else {
|
|
16909
|
+
// Expand cache size for better hit rate
|
|
16910
|
+
adaptations.maxSize = Math.min(500, (currentConfig.maxSize || 100) * 1.5);
|
|
16911
|
+
reasoning.push('Increased cache size due to low hit rate');
|
|
16912
|
+
needsUpdate = true;
|
|
16913
|
+
}
|
|
16914
|
+
}
|
|
16915
|
+
// High hit rate but slow responses → might need cache warming
|
|
16916
|
+
if (performanceMetrics.hitRate > 0.8 && performanceMetrics.avgResponseTime > 100) {
|
|
16917
|
+
reasoning.push('High hit rate but slow responses - consider cache warming');
|
|
16918
|
+
}
|
|
16919
|
+
// Memory pressure → reduce cache size
|
|
16920
|
+
if (performanceMetrics.memoryUsage > 100 * 1024 * 1024) { // 100MB
|
|
16921
|
+
adaptations.maxSize = Math.max(20, (currentConfig.maxSize || 100) * 0.7);
|
|
16922
|
+
reasoning.push('Reduced cache size due to memory pressure');
|
|
16923
|
+
needsUpdate = true;
|
|
16924
|
+
}
|
|
16925
|
+
// Recent external changes → adaptive TTL
|
|
16926
|
+
if (performanceMetrics.timeSinceLastChange < 30000) { // 30 seconds
|
|
16927
|
+
adaptations.maxAge = Math.max(30000, currentConfig.maxAge * 0.8);
|
|
16928
|
+
reasoning.push('Shortened TTL due to recent external changes');
|
|
16929
|
+
needsUpdate = true;
|
|
16930
|
+
}
|
|
16931
|
+
if (!needsUpdate) {
|
|
16932
|
+
return null;
|
|
16933
|
+
}
|
|
16934
|
+
const newCacheConfig = {
|
|
16935
|
+
...currentConfig,
|
|
16936
|
+
...adaptations
|
|
16937
|
+
};
|
|
16938
|
+
const newRealtimeConfig = this.calculateRealtimeConfig();
|
|
16939
|
+
return {
|
|
16940
|
+
cacheConfig: newCacheConfig,
|
|
16941
|
+
realtimeConfig: newRealtimeConfig,
|
|
16942
|
+
reasoning
|
|
16943
|
+
};
|
|
16944
|
+
}
|
|
16945
|
+
/**
|
|
16946
|
+
* Get recommended configuration for specific use case
|
|
16947
|
+
*/
|
|
16948
|
+
getRecommendedConfig(useCase) {
|
|
16949
|
+
const configs = {
|
|
16950
|
+
'high-consistency': {
|
|
16951
|
+
cache: { maxAge: 120000, maxSize: 50 },
|
|
16952
|
+
realtime: { interval: 15000, enabled: true },
|
|
16953
|
+
reasoning: ['Optimized for data consistency and real-time updates']
|
|
16954
|
+
},
|
|
16955
|
+
'balanced': {
|
|
16956
|
+
cache: { maxAge: 300000, maxSize: 100 },
|
|
16957
|
+
realtime: { interval: 30000, enabled: true },
|
|
16958
|
+
reasoning: ['Balanced performance and consistency']
|
|
16959
|
+
},
|
|
16960
|
+
'performance-first': {
|
|
16961
|
+
cache: { maxAge: 600000, maxSize: 200 },
|
|
16962
|
+
realtime: { interval: 60000, enabled: true },
|
|
16963
|
+
reasoning: ['Optimized for maximum cache performance']
|
|
16964
|
+
}
|
|
16965
|
+
};
|
|
16966
|
+
const config = configs[useCase];
|
|
16967
|
+
return {
|
|
16968
|
+
cacheConfig: {
|
|
16969
|
+
enabled: true,
|
|
16970
|
+
...config.cache
|
|
16971
|
+
},
|
|
16972
|
+
realtimeConfig: {
|
|
16973
|
+
updateIndex: true,
|
|
16974
|
+
updateStatistics: true,
|
|
16975
|
+
...config.realtime
|
|
16976
|
+
},
|
|
16977
|
+
reasoning: config.reasoning
|
|
16978
|
+
};
|
|
16979
|
+
}
|
|
16980
|
+
/**
|
|
16981
|
+
* Learn from usage patterns and improve recommendations
|
|
16982
|
+
*/
|
|
16983
|
+
learnFromUsage(usageData) {
|
|
16984
|
+
// Update internal stats for better future recommendations
|
|
16985
|
+
this.stats.totalQueries += usageData.totalQueries;
|
|
16986
|
+
this.stats.repeatQueries += usageData.cacheHits;
|
|
16987
|
+
this.stats.avgQueryTime = (this.stats.avgQueryTime + usageData.responseTime) / 2;
|
|
16988
|
+
this.stats.changeFrequency = usageData.dataChanges / (usageData.timeWindow / 60000);
|
|
16989
|
+
// Calculate read/write ratio
|
|
16990
|
+
const writes = usageData.dataChanges;
|
|
16991
|
+
const reads = usageData.totalQueries;
|
|
16992
|
+
this.stats.readWriteRatio = reads > 0 ? reads / Math.max(writes, 1) : 10;
|
|
16993
|
+
}
|
|
16994
|
+
detectEnvironment(storageConfig) {
|
|
16995
|
+
// Detect storage type
|
|
16996
|
+
if (storageConfig?.s3Storage || storageConfig?.customS3Storage) {
|
|
16997
|
+
this.stats.storageType = 's3';
|
|
16998
|
+
this.stats.isDistributed = true;
|
|
16999
|
+
}
|
|
17000
|
+
else if (storageConfig?.forceFileSystemStorage) {
|
|
17001
|
+
this.stats.storageType = 'filesystem';
|
|
17002
|
+
}
|
|
17003
|
+
else if (storageConfig?.forceMemoryStorage) {
|
|
17004
|
+
this.stats.storageType = 'memory';
|
|
17005
|
+
}
|
|
17006
|
+
else {
|
|
17007
|
+
// Auto-detect browser vs Node.js
|
|
17008
|
+
this.stats.storageType = typeof window !== 'undefined' ? 'opfs' : 'filesystem';
|
|
17009
|
+
}
|
|
17010
|
+
// Detect distributed mode indicators
|
|
17011
|
+
this.stats.isDistributed = this.stats.isDistributed ||
|
|
17012
|
+
Boolean(storageConfig?.s3Storage || storageConfig?.customS3Storage);
|
|
17013
|
+
}
|
|
17014
|
+
generateOptimalConfig() {
|
|
17015
|
+
const reasoning = [];
|
|
17016
|
+
// Base configuration
|
|
17017
|
+
let cacheConfig = {
|
|
17018
|
+
enabled: true,
|
|
17019
|
+
maxSize: 100,
|
|
17020
|
+
maxAge: 300000, // 5 minutes
|
|
17021
|
+
hitCountWeight: 0.3
|
|
17022
|
+
};
|
|
17023
|
+
let realtimeConfig = {
|
|
17024
|
+
enabled: false,
|
|
17025
|
+
interval: 60000,
|
|
17026
|
+
updateIndex: true,
|
|
17027
|
+
updateStatistics: true
|
|
17028
|
+
};
|
|
17029
|
+
// Adjust for storage type
|
|
17030
|
+
if (this.stats.storageType === 's3' || this.stats.isDistributed) {
|
|
17031
|
+
cacheConfig.maxAge = 180000; // 3 minutes for distributed
|
|
17032
|
+
realtimeConfig.enabled = true;
|
|
17033
|
+
realtimeConfig.interval = 30000; // 30 seconds
|
|
17034
|
+
reasoning.push('Distributed storage detected - enabled real-time updates');
|
|
17035
|
+
reasoning.push('Reduced cache TTL for distributed consistency');
|
|
17036
|
+
}
|
|
17037
|
+
// Adjust for read/write patterns
|
|
17038
|
+
if (this.stats.readWriteRatio > 20) {
|
|
17039
|
+
// Read-heavy workload
|
|
17040
|
+
cacheConfig.maxSize = Math.min(300, (cacheConfig.maxSize || 100) * 2);
|
|
17041
|
+
cacheConfig.maxAge = Math.min(900000, (cacheConfig.maxAge || 300000) * 1.5); // Up to 15 minutes
|
|
17042
|
+
reasoning.push('Read-heavy workload detected - increased cache size and TTL');
|
|
17043
|
+
}
|
|
17044
|
+
else if (this.stats.readWriteRatio < 5) {
|
|
17045
|
+
// Write-heavy workload
|
|
17046
|
+
cacheConfig.maxSize = Math.max(50, (cacheConfig.maxSize || 100) * 0.7);
|
|
17047
|
+
cacheConfig.maxAge = Math.max(60000, (cacheConfig.maxAge || 300000) * 0.6);
|
|
17048
|
+
reasoning.push('Write-heavy workload detected - reduced cache size and TTL');
|
|
17049
|
+
}
|
|
17050
|
+
// Adjust for change frequency
|
|
17051
|
+
if (this.stats.changeFrequency > 10) { // More than 10 changes per minute
|
|
17052
|
+
realtimeConfig.interval = Math.max(10000, realtimeConfig.interval * 0.5);
|
|
17053
|
+
cacheConfig.maxAge = Math.max(30000, (cacheConfig.maxAge || 300000) * 0.5);
|
|
17054
|
+
reasoning.push('High change frequency detected - increased update frequency');
|
|
17055
|
+
}
|
|
17056
|
+
// Memory constraints
|
|
17057
|
+
if (this.detectMemoryConstraints()) {
|
|
17058
|
+
cacheConfig.maxSize = Math.max(20, (cacheConfig.maxSize || 100) * 0.6);
|
|
17059
|
+
reasoning.push('Memory constraints detected - reduced cache size');
|
|
17060
|
+
}
|
|
17061
|
+
// Performance optimization
|
|
17062
|
+
if (this.stats.avgQueryTime > 200) {
|
|
17063
|
+
cacheConfig.maxSize = Math.min(500, (cacheConfig.maxSize || 100) * 1.5);
|
|
17064
|
+
reasoning.push('Slow queries detected - increased cache size');
|
|
17065
|
+
}
|
|
17066
|
+
return {
|
|
17067
|
+
cacheConfig,
|
|
17068
|
+
realtimeConfig,
|
|
17069
|
+
reasoning
|
|
17070
|
+
};
|
|
17071
|
+
}
|
|
17072
|
+
calculateRealtimeConfig() {
|
|
17073
|
+
return {
|
|
17074
|
+
enabled: this.stats.isDistributed || this.stats.changeFrequency > 1,
|
|
17075
|
+
interval: this.stats.isDistributed ? 30000 : 60000,
|
|
17076
|
+
updateIndex: true,
|
|
17077
|
+
updateStatistics: true
|
|
17078
|
+
};
|
|
17079
|
+
}
|
|
17080
|
+
detectMemoryConstraints() {
|
|
17081
|
+
// Simple heuristic for memory constraints
|
|
17082
|
+
try {
|
|
17083
|
+
if (typeof performance !== 'undefined' && 'memory' in performance) {
|
|
17084
|
+
const memInfo = performance.memory;
|
|
17085
|
+
return memInfo.usedJSHeapSize > memInfo.jsHeapSizeLimit * 0.8;
|
|
17086
|
+
}
|
|
17087
|
+
}
|
|
17088
|
+
catch (e) {
|
|
17089
|
+
// Ignore errors
|
|
17090
|
+
}
|
|
17091
|
+
// Default assumption for constrained environments
|
|
17092
|
+
return false;
|
|
17093
|
+
}
|
|
17094
|
+
/**
|
|
17095
|
+
* Get human-readable explanation of current configuration
|
|
17096
|
+
*/
|
|
17097
|
+
getConfigExplanation(config) {
|
|
17098
|
+
const lines = [
|
|
17099
|
+
'🤖 Brainy Auto-Configuration:',
|
|
17100
|
+
'',
|
|
17101
|
+
`📊 Cache: ${config.cacheConfig.maxSize} queries, ${config.cacheConfig.maxAge / 1000}s TTL`,
|
|
17102
|
+
`🔄 Updates: ${config.realtimeConfig.enabled ? `Every ${(config.realtimeConfig.interval || 30000) / 1000}s` : 'Disabled'}`,
|
|
17103
|
+
'',
|
|
17104
|
+
'🎯 Optimizations applied:'
|
|
17105
|
+
];
|
|
17106
|
+
config.reasoning.forEach(reason => {
|
|
17107
|
+
lines.push(` • ${reason}`);
|
|
17108
|
+
});
|
|
17109
|
+
return lines.join('\n');
|
|
17110
|
+
}
|
|
17111
|
+
}
|
|
17112
|
+
|
|
16604
17113
|
/**
|
|
16605
17114
|
* BrainyData
|
|
16606
17115
|
* Main class that provides the vector database functionality
|
|
@@ -16758,6 +17267,26 @@ class BrainyData {
|
|
|
16758
17267
|
this.distributedConfig = config.distributed;
|
|
16759
17268
|
}
|
|
16760
17269
|
}
|
|
17270
|
+
// Initialize cache auto-configurator first
|
|
17271
|
+
this.cacheAutoConfigurator = new CacheAutoConfigurator();
|
|
17272
|
+
// Auto-detect optimal cache configuration if not explicitly provided
|
|
17273
|
+
let finalSearchCacheConfig = config.searchCache;
|
|
17274
|
+
if (!config.searchCache || Object.keys(config.searchCache).length === 0) {
|
|
17275
|
+
const autoConfig = this.cacheAutoConfigurator.autoDetectOptimalConfig(config.storage);
|
|
17276
|
+
finalSearchCacheConfig = autoConfig.cacheConfig;
|
|
17277
|
+
// Apply auto-detected real-time update configuration if not explicitly set
|
|
17278
|
+
if (!config.realtimeUpdates && autoConfig.realtimeConfig.enabled) {
|
|
17279
|
+
this.realtimeUpdateConfig = {
|
|
17280
|
+
...this.realtimeUpdateConfig,
|
|
17281
|
+
...autoConfig.realtimeConfig
|
|
17282
|
+
};
|
|
17283
|
+
}
|
|
17284
|
+
if (this.loggingConfig?.verbose) {
|
|
17285
|
+
console.log(this.cacheAutoConfigurator.getConfigExplanation(autoConfig));
|
|
17286
|
+
}
|
|
17287
|
+
}
|
|
17288
|
+
// Initialize search cache with final configuration
|
|
17289
|
+
this.searchCache = new SearchCache(finalSearchCacheConfig);
|
|
16761
17290
|
}
|
|
16762
17291
|
/**
|
|
16763
17292
|
* Check if the database is in read-only mode and throw an error if it is
|
|
@@ -16897,6 +17426,17 @@ class BrainyData {
|
|
|
16897
17426
|
await this.applyChangesFromFullScan();
|
|
16898
17427
|
}
|
|
16899
17428
|
}
|
|
17429
|
+
// Cleanup expired cache entries (defensive mechanism for distributed scenarios)
|
|
17430
|
+
const expiredCount = this.searchCache.cleanupExpiredEntries();
|
|
17431
|
+
if (expiredCount > 0 && this.loggingConfig?.verbose) {
|
|
17432
|
+
console.log(`Cleaned up ${expiredCount} expired cache entries`);
|
|
17433
|
+
}
|
|
17434
|
+
// Adapt cache configuration based on performance (every few updates)
|
|
17435
|
+
// Only adapt every 5th update to avoid over-optimization
|
|
17436
|
+
const updateCount = Math.floor((Date.now() - (this.lastUpdateTime || 0)) / this.realtimeUpdateConfig.interval);
|
|
17437
|
+
if (updateCount % 5 === 0) {
|
|
17438
|
+
this.adaptCacheConfiguration();
|
|
17439
|
+
}
|
|
16900
17440
|
// Update the last update time
|
|
16901
17441
|
this.lastUpdateTime = Date.now();
|
|
16902
17442
|
if (this.loggingConfig?.verbose) {
|
|
@@ -16971,6 +17511,13 @@ class BrainyData {
|
|
|
16971
17511
|
(addedCount > 0 || updatedCount > 0 || deletedCount > 0)) {
|
|
16972
17512
|
console.log(`Real-time update: Added ${addedCount}, updated ${updatedCount}, deleted ${deletedCount} nouns using change log`);
|
|
16973
17513
|
}
|
|
17514
|
+
// Invalidate search cache if any external changes were detected
|
|
17515
|
+
if (addedCount > 0 || updatedCount > 0 || deletedCount > 0) {
|
|
17516
|
+
this.searchCache.invalidateOnDataChange('update');
|
|
17517
|
+
if (this.loggingConfig?.verbose) {
|
|
17518
|
+
console.log('Search cache invalidated due to external data changes');
|
|
17519
|
+
}
|
|
17520
|
+
}
|
|
16974
17521
|
// Update the last known noun count
|
|
16975
17522
|
this.lastKnownNounCount = await this.getNounCount();
|
|
16976
17523
|
}
|
|
@@ -17014,6 +17561,13 @@ class BrainyData {
|
|
|
17014
17561
|
}
|
|
17015
17562
|
// Update the last known noun count
|
|
17016
17563
|
this.lastKnownNounCount = currentCount;
|
|
17564
|
+
// Invalidate search cache if new nouns were detected
|
|
17565
|
+
if (newNouns.length > 0) {
|
|
17566
|
+
this.searchCache.invalidateOnDataChange('add');
|
|
17567
|
+
if (this.loggingConfig?.verbose) {
|
|
17568
|
+
console.log('Search cache invalidated due to external data changes');
|
|
17569
|
+
}
|
|
17570
|
+
}
|
|
17017
17571
|
if (this.loggingConfig?.verbose && newNouns.length > 0) {
|
|
17018
17572
|
console.log(`Real-time update: Added ${newNouns.length} new nouns to index using full scan`);
|
|
17019
17573
|
}
|
|
@@ -17565,6 +18119,8 @@ class BrainyData {
|
|
|
17565
18119
|
console.warn(`Failed to add to remote server: ${remoteError}. Continuing with local add.`);
|
|
17566
18120
|
}
|
|
17567
18121
|
}
|
|
18122
|
+
// Invalidate search cache since data has changed
|
|
18123
|
+
this.searchCache.invalidateOnDataChange('add');
|
|
17568
18124
|
return id;
|
|
17569
18125
|
}
|
|
17570
18126
|
catch (error) {
|
|
@@ -17822,11 +18378,16 @@ class BrainyData {
|
|
|
17822
18378
|
console.log(`Lazy loading mode: Added ${limitedNouns.length} nodes to index for search`);
|
|
17823
18379
|
}
|
|
17824
18380
|
}
|
|
17825
|
-
//
|
|
17826
|
-
const
|
|
18381
|
+
// When using offset, we need to fetch more results and then slice
|
|
18382
|
+
const offset = options.offset || 0;
|
|
18383
|
+
const totalNeeded = k + offset;
|
|
18384
|
+
// Search in the index for totalNeeded results
|
|
18385
|
+
const results = await this.index.search(queryVector, totalNeeded);
|
|
18386
|
+
// Skip the offset number of results
|
|
18387
|
+
const paginatedResults = results.slice(offset, offset + k);
|
|
17827
18388
|
// Get metadata for each result
|
|
17828
18389
|
const searchResults = [];
|
|
17829
|
-
for (const [id, score] of
|
|
18390
|
+
for (const [id, score] of paginatedResults) {
|
|
17830
18391
|
const noun = this.index.getNouns().get(id);
|
|
17831
18392
|
if (!noun) {
|
|
17832
18393
|
continue;
|
|
@@ -17867,8 +18428,9 @@ class BrainyData {
|
|
|
17867
18428
|
}
|
|
17868
18429
|
// Sort by distance (ascending)
|
|
17869
18430
|
results.sort((a, b) => a[1] - b[1]);
|
|
17870
|
-
//
|
|
17871
|
-
const
|
|
18431
|
+
// Apply offset and take k results
|
|
18432
|
+
const offset = options.offset || 0;
|
|
18433
|
+
const topResults = results.slice(offset, offset + k);
|
|
17872
18434
|
// Get metadata for each result
|
|
17873
18435
|
const searchResults = [];
|
|
17874
18436
|
for (const [id, score] of topResults) {
|
|
@@ -17962,12 +18524,29 @@ class BrainyData {
|
|
|
17962
18524
|
}
|
|
17963
18525
|
// Default behavior (backward compatible): search locally
|
|
17964
18526
|
try {
|
|
18527
|
+
// Check cache first (transparent to user)
|
|
18528
|
+
const cacheKey = this.searchCache.getCacheKey(queryVectorOrData, k, options);
|
|
18529
|
+
const cachedResults = this.searchCache.get(cacheKey);
|
|
18530
|
+
if (cachedResults) {
|
|
18531
|
+
// Track cache hit in health monitor
|
|
18532
|
+
if (this.healthMonitor) {
|
|
18533
|
+
const latency = Date.now() - startTime;
|
|
18534
|
+
this.healthMonitor.recordRequest(latency, false);
|
|
18535
|
+
this.healthMonitor.recordCacheAccess(true);
|
|
18536
|
+
}
|
|
18537
|
+
return cachedResults;
|
|
18538
|
+
}
|
|
18539
|
+
// Cache miss - perform actual search
|
|
17965
18540
|
const results = await this.searchLocal(queryVectorOrData, k, options);
|
|
18541
|
+
// Cache results for future queries (unless explicitly disabled)
|
|
18542
|
+
if (!options.skipCache) {
|
|
18543
|
+
this.searchCache.set(cacheKey, results);
|
|
18544
|
+
}
|
|
17966
18545
|
// Track successful search in health monitor
|
|
17967
18546
|
if (this.healthMonitor) {
|
|
17968
18547
|
const latency = Date.now() - startTime;
|
|
17969
18548
|
this.healthMonitor.recordRequest(latency, false);
|
|
17970
|
-
this.healthMonitor.recordCacheAccess(
|
|
18549
|
+
this.healthMonitor.recordCacheAccess(false);
|
|
17971
18550
|
}
|
|
17972
18551
|
return results;
|
|
17973
18552
|
}
|
|
@@ -17980,6 +18559,58 @@ class BrainyData {
|
|
|
17980
18559
|
throw error;
|
|
17981
18560
|
}
|
|
17982
18561
|
}
|
|
18562
|
+
/**
|
|
18563
|
+
* Search with cursor-based pagination for better performance on large datasets
|
|
18564
|
+
* @param queryVectorOrData Query vector or data to search for
|
|
18565
|
+
* @param k Number of results to return
|
|
18566
|
+
* @param options Additional options including cursor for pagination
|
|
18567
|
+
* @returns Paginated search results with cursor for next page
|
|
18568
|
+
*/
|
|
18569
|
+
async searchWithCursor(queryVectorOrData, k = 10, options = {}) {
|
|
18570
|
+
// For cursor-based search, we need to fetch more results and filter
|
|
18571
|
+
const searchK = options.cursor ? k + 20 : k; // Get extra results for filtering
|
|
18572
|
+
// Perform regular search
|
|
18573
|
+
const allResults = await this.search(queryVectorOrData, searchK, {
|
|
18574
|
+
...options,
|
|
18575
|
+
skipCache: options.skipCache
|
|
18576
|
+
});
|
|
18577
|
+
let results = allResults;
|
|
18578
|
+
let startIndex = 0;
|
|
18579
|
+
// If cursor provided, find starting position
|
|
18580
|
+
if (options.cursor) {
|
|
18581
|
+
startIndex = allResults.findIndex(r => r.id === options.cursor.lastId &&
|
|
18582
|
+
Math.abs(r.score - options.cursor.lastScore) < 0.0001);
|
|
18583
|
+
if (startIndex >= 0) {
|
|
18584
|
+
startIndex += 1; // Start after the cursor position
|
|
18585
|
+
results = allResults.slice(startIndex, startIndex + k);
|
|
18586
|
+
}
|
|
18587
|
+
else {
|
|
18588
|
+
// Cursor not found, might be stale - return from beginning
|
|
18589
|
+
results = allResults.slice(0, k);
|
|
18590
|
+
startIndex = 0;
|
|
18591
|
+
}
|
|
18592
|
+
}
|
|
18593
|
+
else {
|
|
18594
|
+
results = allResults.slice(0, k);
|
|
18595
|
+
}
|
|
18596
|
+
// Create cursor for next page
|
|
18597
|
+
let nextCursor;
|
|
18598
|
+
const hasMoreResults = (startIndex + results.length) < allResults.length || allResults.length >= searchK;
|
|
18599
|
+
if (results.length > 0 && hasMoreResults) {
|
|
18600
|
+
const lastResult = results[results.length - 1];
|
|
18601
|
+
nextCursor = {
|
|
18602
|
+
lastId: lastResult.id,
|
|
18603
|
+
lastScore: lastResult.score,
|
|
18604
|
+
position: startIndex + results.length
|
|
18605
|
+
};
|
|
18606
|
+
}
|
|
18607
|
+
return {
|
|
18608
|
+
results,
|
|
18609
|
+
cursor: nextCursor,
|
|
18610
|
+
hasMore: !!nextCursor,
|
|
18611
|
+
totalEstimate: allResults.length > searchK ? undefined : allResults.length
|
|
18612
|
+
};
|
|
18613
|
+
}
|
|
17983
18614
|
/**
|
|
17984
18615
|
* Search the local database for similar vectors
|
|
17985
18616
|
* @param queryVectorOrData Query vector or data to search for
|
|
@@ -18035,14 +18666,16 @@ class BrainyData {
|
|
|
18035
18666
|
if (options.nounTypes && options.nounTypes.length > 0) {
|
|
18036
18667
|
searchResults = await this.searchByNounTypes(queryToUse, k, options.nounTypes, {
|
|
18037
18668
|
forceEmbed: options.forceEmbed,
|
|
18038
|
-
service: options.service
|
|
18669
|
+
service: options.service,
|
|
18670
|
+
offset: options.offset
|
|
18039
18671
|
});
|
|
18040
18672
|
}
|
|
18041
18673
|
else {
|
|
18042
18674
|
// Otherwise, search all GraphNouns
|
|
18043
18675
|
searchResults = await this.searchByNounTypes(queryToUse, k, null, {
|
|
18044
18676
|
forceEmbed: options.forceEmbed,
|
|
18045
|
-
service: options.service
|
|
18677
|
+
service: options.service,
|
|
18678
|
+
offset: options.offset
|
|
18046
18679
|
});
|
|
18047
18680
|
}
|
|
18048
18681
|
// Filter out placeholder nouns from search results
|
|
@@ -18376,6 +19009,8 @@ class BrainyData {
|
|
|
18376
19009
|
catch (error) {
|
|
18377
19010
|
// Ignore
|
|
18378
19011
|
}
|
|
19012
|
+
// Invalidate search cache since data has changed
|
|
19013
|
+
this.searchCache.invalidateOnDataChange('delete');
|
|
18379
19014
|
return true;
|
|
18380
19015
|
}
|
|
18381
19016
|
catch (error) {
|
|
@@ -18459,6 +19094,8 @@ class BrainyData {
|
|
|
18459
19094
|
// Track metadata statistics
|
|
18460
19095
|
const service = this.getServiceName(options);
|
|
18461
19096
|
await this.storage.incrementStatistic('metadata', service);
|
|
19097
|
+
// Invalidate search cache since metadata has changed
|
|
19098
|
+
this.searchCache.invalidateOnDataChange('update');
|
|
18462
19099
|
return true;
|
|
18463
19100
|
}
|
|
18464
19101
|
catch (error) {
|
|
@@ -18813,6 +19450,8 @@ class BrainyData {
|
|
|
18813
19450
|
await this.storage.incrementStatistic('verb', serviceForStats);
|
|
18814
19451
|
// Update HNSW index size (excluding verbs)
|
|
18815
19452
|
await this.storage.updateHnswIndexSize(await this.getNounCount());
|
|
19453
|
+
// Invalidate search cache since verb data has changed
|
|
19454
|
+
this.searchCache.invalidateOnDataChange('add');
|
|
18816
19455
|
return id;
|
|
18817
19456
|
}
|
|
18818
19457
|
catch (error) {
|
|
@@ -19035,6 +19674,8 @@ class BrainyData {
|
|
|
19035
19674
|
await this.index.clear();
|
|
19036
19675
|
// Clear storage
|
|
19037
19676
|
await this.storage.clear();
|
|
19677
|
+
// Clear search cache since all data has been removed
|
|
19678
|
+
this.searchCache.invalidateOnDataChange('delete');
|
|
19038
19679
|
}
|
|
19039
19680
|
catch (error) {
|
|
19040
19681
|
console.error('Failed to clear vector database:', error);
|
|
@@ -19047,6 +19688,66 @@ class BrainyData {
|
|
|
19047
19688
|
size() {
|
|
19048
19689
|
return this.index.size();
|
|
19049
19690
|
}
|
|
19691
|
+
/**
|
|
19692
|
+
* Get search cache statistics for performance monitoring
|
|
19693
|
+
* @returns Cache statistics including hit rate and memory usage
|
|
19694
|
+
*/
|
|
19695
|
+
getCacheStats() {
|
|
19696
|
+
return {
|
|
19697
|
+
search: this.searchCache.getStats(),
|
|
19698
|
+
searchMemoryUsage: this.searchCache.getMemoryUsage()
|
|
19699
|
+
};
|
|
19700
|
+
}
|
|
19701
|
+
/**
|
|
19702
|
+
* Clear search cache manually (useful for testing or memory management)
|
|
19703
|
+
*/
|
|
19704
|
+
clearCache() {
|
|
19705
|
+
this.searchCache.clear();
|
|
19706
|
+
}
|
|
19707
|
+
/**
|
|
19708
|
+
* Adapt cache configuration based on current performance metrics
|
|
19709
|
+
* This method analyzes usage patterns and automatically optimizes cache settings
|
|
19710
|
+
* @private
|
|
19711
|
+
*/
|
|
19712
|
+
adaptCacheConfiguration() {
|
|
19713
|
+
const stats = this.searchCache.getStats();
|
|
19714
|
+
const memoryUsage = this.searchCache.getMemoryUsage();
|
|
19715
|
+
const currentConfig = this.searchCache.getConfig();
|
|
19716
|
+
// Prepare performance metrics for adaptation
|
|
19717
|
+
const performanceMetrics = {
|
|
19718
|
+
hitRate: stats.hitRate,
|
|
19719
|
+
avgResponseTime: 50, // Would be measured in real implementation
|
|
19720
|
+
memoryUsage: memoryUsage,
|
|
19721
|
+
externalChangesDetected: 0, // Would be tracked from real-time updates
|
|
19722
|
+
timeSinceLastChange: Date.now() - this.lastUpdateTime
|
|
19723
|
+
};
|
|
19724
|
+
// Try to adapt configuration
|
|
19725
|
+
const newConfig = this.cacheAutoConfigurator.adaptConfiguration(currentConfig, performanceMetrics);
|
|
19726
|
+
if (newConfig) {
|
|
19727
|
+
// Apply new cache configuration
|
|
19728
|
+
this.searchCache.updateConfig(newConfig.cacheConfig);
|
|
19729
|
+
// Apply new real-time update configuration if needed
|
|
19730
|
+
if (newConfig.realtimeConfig.enabled !== this.realtimeUpdateConfig.enabled ||
|
|
19731
|
+
newConfig.realtimeConfig.interval !== this.realtimeUpdateConfig.interval) {
|
|
19732
|
+
const wasEnabled = this.realtimeUpdateConfig.enabled;
|
|
19733
|
+
this.realtimeUpdateConfig = {
|
|
19734
|
+
...this.realtimeUpdateConfig,
|
|
19735
|
+
...newConfig.realtimeConfig
|
|
19736
|
+
};
|
|
19737
|
+
// Restart real-time updates with new configuration
|
|
19738
|
+
if (wasEnabled) {
|
|
19739
|
+
this.stopRealtimeUpdates();
|
|
19740
|
+
}
|
|
19741
|
+
if (this.realtimeUpdateConfig.enabled && this.isInitialized) {
|
|
19742
|
+
this.startRealtimeUpdates();
|
|
19743
|
+
}
|
|
19744
|
+
}
|
|
19745
|
+
if (this.loggingConfig?.verbose) {
|
|
19746
|
+
console.log('🔧 Auto-adapted cache configuration:');
|
|
19747
|
+
console.log(this.cacheAutoConfigurator.getConfigExplanation(newConfig));
|
|
19748
|
+
}
|
|
19749
|
+
}
|
|
19750
|
+
}
|
|
19050
19751
|
/**
|
|
19051
19752
|
* Get the number of nouns in the database (excluding verbs)
|
|
19052
19753
|
* This is used for statistics reporting to match the expected behavior in tests
|
|
@@ -19593,12 +20294,17 @@ class BrainyData {
|
|
|
19593
20294
|
if (!this.serverSearchConduit || !this.serverConnection) {
|
|
19594
20295
|
throw new Error('Server search conduit or connection is not initialized');
|
|
19595
20296
|
}
|
|
19596
|
-
//
|
|
19597
|
-
const
|
|
20297
|
+
// When using offset, fetch more results and slice
|
|
20298
|
+
const offset = options.offset || 0;
|
|
20299
|
+
const totalNeeded = k + offset;
|
|
20300
|
+
// Search the remote server for totalNeeded results
|
|
20301
|
+
const searchResult = await this.serverSearchConduit.searchServer(this.serverConnection.connectionId, query, totalNeeded);
|
|
19598
20302
|
if (!searchResult.success) {
|
|
19599
20303
|
throw new Error(`Remote search failed: ${searchResult.error}`);
|
|
19600
20304
|
}
|
|
19601
|
-
|
|
20305
|
+
// Apply offset to remote results
|
|
20306
|
+
const allResults = searchResult.data;
|
|
20307
|
+
return allResults.slice(offset, offset + k);
|
|
19602
20308
|
}
|
|
19603
20309
|
catch (error) {
|
|
19604
20310
|
console.error('Failed to search remote server:', error);
|