@soulcraft/brainy 4.2.2 β 4.2.3
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 +23 -0
- package/dist/utils/metadataIndex.js +219 -135
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
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
|
+
### [4.2.3](https://github.com/soulcraftlabs/brainy/compare/v4.2.2...v4.2.3) (2025-10-23)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### π Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **metadata-index**: fix rebuild stalling after first batch on FileSystemStorage
|
|
11
|
+
- **Critical Fix**: v4.2.2 rebuild stalled after processing first batch (500/1,157 entities)
|
|
12
|
+
- **Root Cause**: `getAllShardedFiles()` was called on EVERY batch, re-reading all 256 shard directories each time
|
|
13
|
+
- **Performance Impact**: Second batch call to `getAllShardedFiles()` took 3+ minutes, appearing to hang
|
|
14
|
+
- **Solution**: Load all entities at once for local storage (FileSystem/Memory/OPFS)
|
|
15
|
+
- FileSystem/Memory/OPFS: Load all nouns/verbs in single batch (no pagination overhead)
|
|
16
|
+
- Cloud (GCS/S3/R2): Keep conservative pagination (25 items/batch for socket safety)
|
|
17
|
+
- **Benefits**:
|
|
18
|
+
- FileSystem: 1,157 entities load in **2-3 seconds** (one `getAllShardedFiles()` call)
|
|
19
|
+
- Cloud: Unchanged behavior (still uses safe batching)
|
|
20
|
+
- Zero config: Auto-detects storage type via `constructor.name`
|
|
21
|
+
- **Technical Details**:
|
|
22
|
+
- Pagination was designed for cloud storage socket exhaustion
|
|
23
|
+
- FileSystem doesn't need pagination - can handle loading thousands of entities at once
|
|
24
|
+
- Eliminates repeated directory scans: 3 batches Γ 256 dirs β 1 batch Γ 256 dirs
|
|
25
|
+
- **Workshop Team**: This resolves the v4.2.2 stalling issue - rebuild will now complete in seconds
|
|
26
|
+
- **Files Changed**: `src/utils/metadataIndex.ts` (rebuilt() method with adaptive loading strategy)
|
|
27
|
+
|
|
5
28
|
### [4.2.2](https://github.com/soulcraftlabs/brainy/compare/v4.2.1...v4.2.2) (2025-10-23)
|
|
6
29
|
|
|
7
30
|
|
|
@@ -1738,188 +1738,272 @@ export class MetadataIndexManager {
|
|
|
1738
1738
|
// Clear all cached sparse indices in UnifiedCache
|
|
1739
1739
|
// This ensures rebuild starts fresh (v3.44.1)
|
|
1740
1740
|
this.unifiedCache.clear('metadata');
|
|
1741
|
-
// Adaptive
|
|
1742
|
-
// FileSystem/Memory/OPFS:
|
|
1743
|
-
// Cloud (GCS/S3/R2):
|
|
1741
|
+
// Adaptive rebuild strategy based on storage adapter (v4.2.3)
|
|
1742
|
+
// FileSystem/Memory/OPFS: Load all at once (avoids getAllShardedFiles() overhead on every batch)
|
|
1743
|
+
// Cloud (GCS/S3/R2): Use pagination with small batches (prevent socket exhaustion)
|
|
1744
1744
|
const storageType = this.storage.constructor.name;
|
|
1745
1745
|
const isLocalStorage = storageType === 'FileSystemStorage' ||
|
|
1746
1746
|
storageType === 'MemoryStorage' ||
|
|
1747
1747
|
storageType === 'OPFSStorage';
|
|
1748
|
-
|
|
1749
|
-
prodLog.info(`β‘ Using ${isLocalStorage ? 'optimized' : 'conservative'} batch size: ${nounLimit} items/batch`);
|
|
1750
|
-
// Rebuild noun metadata indexes using pagination
|
|
1751
|
-
let nounOffset = 0;
|
|
1752
|
-
let hasMoreNouns = true;
|
|
1748
|
+
let nounLimit;
|
|
1753
1749
|
let totalNounsProcessed = 0;
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
iterations++;
|
|
1750
|
+
if (isLocalStorage) {
|
|
1751
|
+
// Load all nouns at once for local storage
|
|
1752
|
+
// Avoids repeated directory scans in getAllShardedFiles()
|
|
1753
|
+
prodLog.info(`β‘ Using optimized strategy: load all nouns at once (local storage)`);
|
|
1759
1754
|
const result = await this.storage.getNouns({
|
|
1760
|
-
pagination: { offset:
|
|
1755
|
+
pagination: { offset: 0, limit: 1000000 } // Effectively unlimited
|
|
1761
1756
|
});
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
consecutiveEmptyBatches++;
|
|
1765
|
-
if (consecutiveEmptyBatches >= 3) {
|
|
1766
|
-
prodLog.warn('β οΈ Breaking metadata rebuild loop: received 3 consecutive empty batches');
|
|
1767
|
-
break;
|
|
1768
|
-
}
|
|
1769
|
-
// If hasMore is true but items are empty, it's likely a bug
|
|
1770
|
-
if (result.hasMore) {
|
|
1771
|
-
prodLog.warn(`β οΈ Storage returned empty items but hasMore=true at offset ${nounOffset}`);
|
|
1772
|
-
hasMoreNouns = false; // Force exit
|
|
1773
|
-
break;
|
|
1774
|
-
}
|
|
1775
|
-
}
|
|
1776
|
-
else {
|
|
1777
|
-
consecutiveEmptyBatches = 0; // Reset counter on non-empty batch
|
|
1778
|
-
}
|
|
1779
|
-
// CRITICAL FIX: Use batch metadata reading to prevent socket exhaustion
|
|
1757
|
+
prodLog.info(`π¦ Loading ${result.items.length} nouns with metadata...`);
|
|
1758
|
+
// Get all metadata in one batch if available
|
|
1780
1759
|
const nounIds = result.items.map(noun => noun.id);
|
|
1781
1760
|
let metadataBatch;
|
|
1782
1761
|
if (this.storage.getMetadataBatch) {
|
|
1783
|
-
// Use batch reading if available (prevents socket exhaustion)
|
|
1784
|
-
prodLog.info(`π¦ Processing metadata batch ${Math.floor(totalNounsProcessed / nounLimit) + 1} (${nounIds.length} items)...`);
|
|
1785
1762
|
metadataBatch = await this.storage.getMetadataBatch(nounIds);
|
|
1786
|
-
|
|
1787
|
-
prodLog.info(`β
Batch loaded ${metadataBatch.size}/${nounIds.length} metadata objects (${successRate}% success)`);
|
|
1763
|
+
prodLog.info(`β
Loaded ${metadataBatch.size}/${nounIds.length} metadata objects`);
|
|
1788
1764
|
}
|
|
1789
1765
|
else {
|
|
1790
|
-
// Fallback to individual calls
|
|
1791
|
-
prodLog.warn(`β οΈ FALLBACK: Storage adapter missing getMetadataBatch - using individual calls with concurrency limit`);
|
|
1766
|
+
// Fallback to individual calls
|
|
1792
1767
|
metadataBatch = new Map();
|
|
1793
|
-
const
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
|
|
1797
|
-
try {
|
|
1798
|
-
const metadata = await this.storage.getNounMetadata(id);
|
|
1799
|
-
return { id, metadata };
|
|
1800
|
-
}
|
|
1801
|
-
catch (error) {
|
|
1802
|
-
prodLog.debug(`Failed to read metadata for ${id}:`, error);
|
|
1803
|
-
return { id, metadata: null };
|
|
1804
|
-
}
|
|
1805
|
-
});
|
|
1806
|
-
const batchResults = await Promise.all(batchPromises);
|
|
1807
|
-
for (const { id, metadata } of batchResults) {
|
|
1808
|
-
if (metadata) {
|
|
1768
|
+
for (const id of nounIds) {
|
|
1769
|
+
try {
|
|
1770
|
+
const metadata = await this.storage.getNounMetadata(id);
|
|
1771
|
+
if (metadata)
|
|
1809
1772
|
metadataBatch.set(id, metadata);
|
|
1810
|
-
}
|
|
1811
1773
|
}
|
|
1812
|
-
|
|
1813
|
-
|
|
1774
|
+
catch (error) {
|
|
1775
|
+
prodLog.debug(`Failed to read metadata for ${id}:`, error);
|
|
1776
|
+
}
|
|
1814
1777
|
}
|
|
1815
1778
|
}
|
|
1816
|
-
// Process
|
|
1779
|
+
// Process all nouns
|
|
1817
1780
|
for (const noun of result.items) {
|
|
1818
1781
|
const metadata = metadataBatch.get(noun.id);
|
|
1819
1782
|
if (metadata) {
|
|
1820
|
-
// Skip flush during rebuild for performance
|
|
1821
1783
|
await this.addToIndex(noun.id, metadata, true);
|
|
1822
1784
|
}
|
|
1823
1785
|
}
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
totalNounsProcessed += result.items.length;
|
|
1827
|
-
hasMoreNouns = result.hasMore;
|
|
1828
|
-
nounOffset += nounLimit;
|
|
1829
|
-
// Progress logging and event loop yield after each batch
|
|
1830
|
-
if (totalNounsProcessed % 100 === 0 || !hasMoreNouns) {
|
|
1831
|
-
prodLog.debug(`π Indexed ${totalNounsProcessed} nouns...`);
|
|
1832
|
-
}
|
|
1833
|
-
await this.yieldToEventLoop();
|
|
1786
|
+
totalNounsProcessed = result.items.length;
|
|
1787
|
+
prodLog.info(`β
Indexed ${totalNounsProcessed} nouns`);
|
|
1834
1788
|
}
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
if (
|
|
1851
|
-
|
|
1852
|
-
|
|
1789
|
+
else {
|
|
1790
|
+
// Cloud storage: use conservative batching
|
|
1791
|
+
nounLimit = 25;
|
|
1792
|
+
prodLog.info(`β‘ Using conservative batch size: ${nounLimit} items/batch (cloud storage)`);
|
|
1793
|
+
let nounOffset = 0;
|
|
1794
|
+
let hasMoreNouns = true;
|
|
1795
|
+
let consecutiveEmptyBatches = 0;
|
|
1796
|
+
const MAX_ITERATIONS = 10000;
|
|
1797
|
+
let iterations = 0;
|
|
1798
|
+
while (hasMoreNouns && iterations < MAX_ITERATIONS) {
|
|
1799
|
+
iterations++;
|
|
1800
|
+
const result = await this.storage.getNouns({
|
|
1801
|
+
pagination: { offset: nounOffset, limit: nounLimit }
|
|
1802
|
+
});
|
|
1803
|
+
// CRITICAL SAFETY CHECK: Prevent infinite loop on empty results
|
|
1804
|
+
if (result.items.length === 0) {
|
|
1805
|
+
consecutiveEmptyBatches++;
|
|
1806
|
+
if (consecutiveEmptyBatches >= 3) {
|
|
1807
|
+
prodLog.warn('β οΈ Breaking metadata rebuild loop: received 3 consecutive empty batches');
|
|
1808
|
+
break;
|
|
1809
|
+
}
|
|
1810
|
+
// If hasMore is true but items are empty, it's likely a bug
|
|
1811
|
+
if (result.hasMore) {
|
|
1812
|
+
prodLog.warn(`β οΈ Storage returned empty items but hasMore=true at offset ${nounOffset}`);
|
|
1813
|
+
hasMoreNouns = false; // Force exit
|
|
1814
|
+
break;
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
else {
|
|
1818
|
+
consecutiveEmptyBatches = 0; // Reset counter on non-empty batch
|
|
1819
|
+
}
|
|
1820
|
+
// CRITICAL FIX: Use batch metadata reading to prevent socket exhaustion
|
|
1821
|
+
const nounIds = result.items.map(noun => noun.id);
|
|
1822
|
+
let metadataBatch;
|
|
1823
|
+
if (this.storage.getMetadataBatch) {
|
|
1824
|
+
// Use batch reading if available (prevents socket exhaustion)
|
|
1825
|
+
prodLog.info(`π¦ Processing metadata batch ${Math.floor(totalNounsProcessed / nounLimit) + 1} (${nounIds.length} items)...`);
|
|
1826
|
+
metadataBatch = await this.storage.getMetadataBatch(nounIds);
|
|
1827
|
+
const successRate = ((metadataBatch.size / nounIds.length) * 100).toFixed(1);
|
|
1828
|
+
prodLog.info(`β
Batch loaded ${metadataBatch.size}/${nounIds.length} metadata objects (${successRate}% success)`);
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
// Fallback to individual calls with strict concurrency control
|
|
1832
|
+
prodLog.warn(`β οΈ FALLBACK: Storage adapter missing getMetadataBatch - using individual calls with concurrency limit`);
|
|
1833
|
+
metadataBatch = new Map();
|
|
1834
|
+
const CONCURRENCY_LIMIT = 3; // Very conservative limit
|
|
1835
|
+
for (let i = 0; i < nounIds.length; i += CONCURRENCY_LIMIT) {
|
|
1836
|
+
const batch = nounIds.slice(i, i + CONCURRENCY_LIMIT);
|
|
1837
|
+
const batchPromises = batch.map(async (id) => {
|
|
1838
|
+
try {
|
|
1839
|
+
const metadata = await this.storage.getNounMetadata(id);
|
|
1840
|
+
return { id, metadata };
|
|
1841
|
+
}
|
|
1842
|
+
catch (error) {
|
|
1843
|
+
prodLog.debug(`Failed to read metadata for ${id}:`, error);
|
|
1844
|
+
return { id, metadata: null };
|
|
1845
|
+
}
|
|
1846
|
+
});
|
|
1847
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1848
|
+
for (const { id, metadata } of batchResults) {
|
|
1849
|
+
if (metadata) {
|
|
1850
|
+
metadataBatch.set(id, metadata);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
// Yield between batches to prevent socket exhaustion
|
|
1854
|
+
await this.yieldToEventLoop();
|
|
1855
|
+
}
|
|
1856
|
+
}
|
|
1857
|
+
// Process the metadata batch
|
|
1858
|
+
for (const noun of result.items) {
|
|
1859
|
+
const metadata = metadataBatch.get(noun.id);
|
|
1860
|
+
if (metadata) {
|
|
1861
|
+
// Skip flush during rebuild for performance
|
|
1862
|
+
await this.addToIndex(noun.id, metadata, true);
|
|
1863
|
+
}
|
|
1853
1864
|
}
|
|
1854
|
-
//
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1865
|
+
// Yield after processing the entire batch
|
|
1866
|
+
await this.yieldToEventLoop();
|
|
1867
|
+
totalNounsProcessed += result.items.length;
|
|
1868
|
+
hasMoreNouns = result.hasMore;
|
|
1869
|
+
nounOffset += nounLimit;
|
|
1870
|
+
// Progress logging and event loop yield after each batch
|
|
1871
|
+
if (totalNounsProcessed % 100 === 0 || !hasMoreNouns) {
|
|
1872
|
+
prodLog.debug(`π Indexed ${totalNounsProcessed} nouns...`);
|
|
1859
1873
|
}
|
|
1874
|
+
await this.yieldToEventLoop();
|
|
1860
1875
|
}
|
|
1861
|
-
|
|
1862
|
-
|
|
1876
|
+
// Check iteration limits for cloud storage
|
|
1877
|
+
if (iterations >= MAX_ITERATIONS) {
|
|
1878
|
+
prodLog.error(`β Metadata noun rebuild hit maximum iteration limit (${MAX_ITERATIONS}). This indicates a bug in storage pagination.`);
|
|
1863
1879
|
}
|
|
1864
|
-
|
|
1880
|
+
}
|
|
1881
|
+
// Rebuild verb metadata indexes - same strategy as nouns
|
|
1882
|
+
let totalVerbsProcessed = 0;
|
|
1883
|
+
if (isLocalStorage) {
|
|
1884
|
+
// Load all verbs at once for local storage
|
|
1885
|
+
prodLog.info(`β‘ Loading all verbs at once (local storage)`);
|
|
1886
|
+
const result = await this.storage.getVerbs({
|
|
1887
|
+
pagination: { offset: 0, limit: 1000000 } // Effectively unlimited
|
|
1888
|
+
});
|
|
1889
|
+
prodLog.info(`π¦ Loading ${result.items.length} verbs with metadata...`);
|
|
1890
|
+
// Get all verb metadata at once
|
|
1865
1891
|
const verbIds = result.items.map(verb => verb.id);
|
|
1866
1892
|
let verbMetadataBatch;
|
|
1867
1893
|
if (this.storage.getVerbMetadataBatch) {
|
|
1868
|
-
// Use batch reading if available (prevents socket exhaustion)
|
|
1869
1894
|
verbMetadataBatch = await this.storage.getVerbMetadataBatch(verbIds);
|
|
1870
|
-
prodLog.
|
|
1895
|
+
prodLog.info(`β
Loaded ${verbMetadataBatch.size}/${verbIds.length} verb metadata objects`);
|
|
1871
1896
|
}
|
|
1872
1897
|
else {
|
|
1873
|
-
// Fallback to individual calls with strict concurrency control
|
|
1874
1898
|
verbMetadataBatch = new Map();
|
|
1875
|
-
const
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
try {
|
|
1880
|
-
const metadata = await this.storage.getVerbMetadata(id);
|
|
1881
|
-
return { id, metadata };
|
|
1882
|
-
}
|
|
1883
|
-
catch (error) {
|
|
1884
|
-
prodLog.debug(`Failed to read verb metadata for ${id}:`, error);
|
|
1885
|
-
return { id, metadata: null };
|
|
1886
|
-
}
|
|
1887
|
-
});
|
|
1888
|
-
const batchResults = await Promise.all(batchPromises);
|
|
1889
|
-
for (const { id, metadata } of batchResults) {
|
|
1890
|
-
if (metadata) {
|
|
1899
|
+
for (const id of verbIds) {
|
|
1900
|
+
try {
|
|
1901
|
+
const metadata = await this.storage.getVerbMetadata(id);
|
|
1902
|
+
if (metadata)
|
|
1891
1903
|
verbMetadataBatch.set(id, metadata);
|
|
1892
|
-
}
|
|
1893
1904
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1905
|
+
catch (error) {
|
|
1906
|
+
prodLog.debug(`Failed to read verb metadata for ${id}:`, error);
|
|
1907
|
+
}
|
|
1896
1908
|
}
|
|
1897
1909
|
}
|
|
1898
|
-
// Process
|
|
1910
|
+
// Process all verbs
|
|
1899
1911
|
for (const verb of result.items) {
|
|
1900
1912
|
const metadata = verbMetadataBatch.get(verb.id);
|
|
1901
1913
|
if (metadata) {
|
|
1902
|
-
// Skip flush during rebuild for performance
|
|
1903
1914
|
await this.addToIndex(verb.id, metadata, true);
|
|
1904
1915
|
}
|
|
1905
1916
|
}
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
totalVerbsProcessed += result.items.length;
|
|
1909
|
-
hasMoreVerbs = result.hasMore;
|
|
1910
|
-
verbOffset += verbLimit;
|
|
1911
|
-
// Progress logging and event loop yield after each batch
|
|
1912
|
-
if (totalVerbsProcessed % 100 === 0 || !hasMoreVerbs) {
|
|
1913
|
-
prodLog.debug(`π Indexed ${totalVerbsProcessed} verbs...`);
|
|
1914
|
-
}
|
|
1915
|
-
await this.yieldToEventLoop();
|
|
1916
|
-
}
|
|
1917
|
-
// Check if we hit iteration limits
|
|
1918
|
-
if (iterations >= MAX_ITERATIONS) {
|
|
1919
|
-
prodLog.error(`β Metadata noun rebuild hit maximum iteration limit (${MAX_ITERATIONS}). This indicates a bug in storage pagination.`);
|
|
1917
|
+
totalVerbsProcessed = result.items.length;
|
|
1918
|
+
prodLog.info(`β
Indexed ${totalVerbsProcessed} verbs`);
|
|
1920
1919
|
}
|
|
1921
|
-
|
|
1922
|
-
|
|
1920
|
+
else {
|
|
1921
|
+
// Cloud storage: use conservative batching
|
|
1922
|
+
let verbOffset = 0;
|
|
1923
|
+
const verbLimit = 25;
|
|
1924
|
+
let hasMoreVerbs = true;
|
|
1925
|
+
let consecutiveEmptyVerbBatches = 0;
|
|
1926
|
+
let verbIterations = 0;
|
|
1927
|
+
const MAX_ITERATIONS = 10000;
|
|
1928
|
+
while (hasMoreVerbs && verbIterations < MAX_ITERATIONS) {
|
|
1929
|
+
verbIterations++;
|
|
1930
|
+
const result = await this.storage.getVerbs({
|
|
1931
|
+
pagination: { offset: verbOffset, limit: verbLimit }
|
|
1932
|
+
});
|
|
1933
|
+
// CRITICAL SAFETY CHECK: Prevent infinite loop on empty results
|
|
1934
|
+
if (result.items.length === 0) {
|
|
1935
|
+
consecutiveEmptyVerbBatches++;
|
|
1936
|
+
if (consecutiveEmptyVerbBatches >= 3) {
|
|
1937
|
+
prodLog.warn('β οΈ Breaking verb metadata rebuild loop: received 3 consecutive empty batches');
|
|
1938
|
+
break;
|
|
1939
|
+
}
|
|
1940
|
+
// If hasMore is true but items are empty, it's likely a bug
|
|
1941
|
+
if (result.hasMore) {
|
|
1942
|
+
prodLog.warn(`β οΈ Storage returned empty verb items but hasMore=true at offset ${verbOffset}`);
|
|
1943
|
+
hasMoreVerbs = false; // Force exit
|
|
1944
|
+
break;
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
else {
|
|
1948
|
+
consecutiveEmptyVerbBatches = 0; // Reset counter on non-empty batch
|
|
1949
|
+
}
|
|
1950
|
+
// CRITICAL FIX: Use batch verb metadata reading to prevent socket exhaustion
|
|
1951
|
+
const verbIds = result.items.map(verb => verb.id);
|
|
1952
|
+
let verbMetadataBatch;
|
|
1953
|
+
if (this.storage.getVerbMetadataBatch) {
|
|
1954
|
+
// Use batch reading if available (prevents socket exhaustion)
|
|
1955
|
+
verbMetadataBatch = await this.storage.getVerbMetadataBatch(verbIds);
|
|
1956
|
+
prodLog.debug(`π¦ Batch loaded ${verbMetadataBatch.size}/${verbIds.length} verb metadata objects`);
|
|
1957
|
+
}
|
|
1958
|
+
else {
|
|
1959
|
+
// Fallback to individual calls with strict concurrency control
|
|
1960
|
+
verbMetadataBatch = new Map();
|
|
1961
|
+
const CONCURRENCY_LIMIT = 3; // Very conservative limit to prevent socket exhaustion
|
|
1962
|
+
for (let i = 0; i < verbIds.length; i += CONCURRENCY_LIMIT) {
|
|
1963
|
+
const batch = verbIds.slice(i, i + CONCURRENCY_LIMIT);
|
|
1964
|
+
const batchPromises = batch.map(async (id) => {
|
|
1965
|
+
try {
|
|
1966
|
+
const metadata = await this.storage.getVerbMetadata(id);
|
|
1967
|
+
return { id, metadata };
|
|
1968
|
+
}
|
|
1969
|
+
catch (error) {
|
|
1970
|
+
prodLog.debug(`Failed to read verb metadata for ${id}:`, error);
|
|
1971
|
+
return { id, metadata: null };
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
const batchResults = await Promise.all(batchPromises);
|
|
1975
|
+
for (const { id, metadata } of batchResults) {
|
|
1976
|
+
if (metadata) {
|
|
1977
|
+
verbMetadataBatch.set(id, metadata);
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
// Yield between batches to prevent socket exhaustion
|
|
1981
|
+
await this.yieldToEventLoop();
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
// Process the verb metadata batch
|
|
1985
|
+
for (const verb of result.items) {
|
|
1986
|
+
const metadata = verbMetadataBatch.get(verb.id);
|
|
1987
|
+
if (metadata) {
|
|
1988
|
+
// Skip flush during rebuild for performance
|
|
1989
|
+
await this.addToIndex(verb.id, metadata, true);
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1992
|
+
// Yield after processing the entire batch
|
|
1993
|
+
await this.yieldToEventLoop();
|
|
1994
|
+
totalVerbsProcessed += result.items.length;
|
|
1995
|
+
hasMoreVerbs = result.hasMore;
|
|
1996
|
+
verbOffset += verbLimit;
|
|
1997
|
+
// Progress logging and event loop yield after each batch
|
|
1998
|
+
if (totalVerbsProcessed % 100 === 0 || !hasMoreVerbs) {
|
|
1999
|
+
prodLog.debug(`π Indexed ${totalVerbsProcessed} verbs...`);
|
|
2000
|
+
}
|
|
2001
|
+
await this.yieldToEventLoop();
|
|
2002
|
+
}
|
|
2003
|
+
// Check iteration limits for cloud storage
|
|
2004
|
+
if (verbIterations >= MAX_ITERATIONS) {
|
|
2005
|
+
prodLog.error(`β Metadata verb rebuild hit maximum iteration limit (${MAX_ITERATIONS}). This indicates a bug in storage pagination.`);
|
|
2006
|
+
}
|
|
1923
2007
|
}
|
|
1924
2008
|
// Flush to storage with final yield
|
|
1925
2009
|
prodLog.debug('πΎ Flushing metadata index to storage...');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.3",
|
|
4
4
|
"description": "Universal Knowledge Protocolβ’ - World's first Triple Intelligence database unifying vector, graph, and document search in one API. 31 nouns Γ 40 verbs for infinite expressiveness.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.js",
|