@soulcraft/brainy 3.30.0 → 3.30.2
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 +52 -0
- package/README.md +1 -0
- package/dist/storage/adapters/fileSystemStorage.d.ts +15 -19
- package/dist/storage/adapters/fileSystemStorage.js +60 -80
- package/dist/storage/adapters/gcsStorage.d.ts +16 -16
- package/dist/storage/adapters/gcsStorage.js +48 -70
- package/dist/storage/adapters/memoryStorage.d.ts +19 -22
- package/dist/storage/adapters/memoryStorage.js +47 -56
- package/dist/storage/adapters/opfsStorage.d.ts +15 -19
- package/dist/storage/adapters/opfsStorage.js +105 -96
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +12 -16
- package/dist/storage/adapters/s3CompatibleStorage.js +77 -205
- package/dist/storage/baseStorage.d.ts +55 -14
- package/dist/storage/baseStorage.js +125 -2
- package/package.json +1 -1
|
@@ -1495,159 +1495,134 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1495
1495
|
}
|
|
1496
1496
|
}
|
|
1497
1497
|
/**
|
|
1498
|
-
*
|
|
1498
|
+
* Primitive operation: Write object to path
|
|
1499
|
+
* All metadata operations use this internally via base class routing
|
|
1499
1500
|
*/
|
|
1500
|
-
async
|
|
1501
|
+
async writeObjectToPath(path, data) {
|
|
1501
1502
|
await this.ensureInitialized();
|
|
1502
1503
|
// Apply backpressure before starting operation
|
|
1503
1504
|
const requestId = await this.applyBackpressure();
|
|
1504
1505
|
try {
|
|
1505
|
-
// Import the PutObjectCommand only when needed
|
|
1506
1506
|
const { PutObjectCommand } = await import('@aws-sdk/client-s3');
|
|
1507
|
-
const
|
|
1508
|
-
|
|
1509
|
-
this.
|
|
1510
|
-
// Save the metadata to S3-compatible storage
|
|
1511
|
-
const result = await this.s3Client.send(new PutObjectCommand({
|
|
1507
|
+
const body = JSON.stringify(data, null, 2);
|
|
1508
|
+
this.logger.trace(`Writing object to path: ${path}`);
|
|
1509
|
+
await this.s3Client.send(new PutObjectCommand({
|
|
1512
1510
|
Bucket: this.bucketName,
|
|
1513
|
-
Key:
|
|
1511
|
+
Key: path,
|
|
1514
1512
|
Body: body,
|
|
1515
1513
|
ContentType: 'application/json'
|
|
1516
1514
|
}));
|
|
1517
|
-
this.logger.debug(`
|
|
1515
|
+
this.logger.debug(`Object written successfully to ${path}`);
|
|
1518
1516
|
// Log the change for efficient synchronization
|
|
1519
1517
|
await this.appendToChangeLog({
|
|
1520
1518
|
timestamp: Date.now(),
|
|
1521
|
-
operation: 'add',
|
|
1519
|
+
operation: 'add',
|
|
1522
1520
|
entityType: 'metadata',
|
|
1523
|
-
entityId:
|
|
1524
|
-
data:
|
|
1521
|
+
entityId: path,
|
|
1522
|
+
data: data
|
|
1525
1523
|
});
|
|
1526
|
-
// Verify the metadata was saved by trying to retrieve it
|
|
1527
|
-
const { GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
1528
|
-
try {
|
|
1529
|
-
const verifyResponse = await this.s3Client.send(new GetObjectCommand({
|
|
1530
|
-
Bucket: this.bucketName,
|
|
1531
|
-
Key: key
|
|
1532
|
-
}));
|
|
1533
|
-
if (verifyResponse && verifyResponse.Body) {
|
|
1534
|
-
this.logger.trace(`Verified metadata for ${id} was saved correctly`);
|
|
1535
|
-
}
|
|
1536
|
-
else {
|
|
1537
|
-
this.logger.warn(`Failed to verify metadata for ${id} was saved correctly: no response or body`);
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
catch (verifyError) {
|
|
1541
|
-
this.logger.warn(`Failed to verify metadata for ${id} was saved correctly:`, verifyError);
|
|
1542
|
-
}
|
|
1543
1524
|
// Release backpressure on success
|
|
1544
1525
|
this.releaseBackpressure(true, requestId);
|
|
1545
1526
|
}
|
|
1546
1527
|
catch (error) {
|
|
1547
1528
|
// Release backpressure on error
|
|
1548
1529
|
this.releaseBackpressure(false, requestId);
|
|
1549
|
-
this.logger.error(`Failed to
|
|
1550
|
-
throw new Error(`Failed to
|
|
1530
|
+
this.logger.error(`Failed to write object to ${path}:`, error);
|
|
1531
|
+
throw new Error(`Failed to write object to ${path}: ${error}`);
|
|
1551
1532
|
}
|
|
1552
1533
|
}
|
|
1553
1534
|
/**
|
|
1554
|
-
*
|
|
1535
|
+
* Primitive operation: Read object from path
|
|
1536
|
+
* All metadata operations use this internally via base class routing
|
|
1555
1537
|
*/
|
|
1556
|
-
async
|
|
1538
|
+
async readObjectFromPath(path) {
|
|
1557
1539
|
await this.ensureInitialized();
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1540
|
+
return this.operationExecutors.executeGet(async () => {
|
|
1541
|
+
try {
|
|
1542
|
+
const { GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
1543
|
+
this.logger.trace(`Reading object from path: ${path}`);
|
|
1544
|
+
const response = await this.s3Client.send(new GetObjectCommand({
|
|
1545
|
+
Bucket: this.bucketName,
|
|
1546
|
+
Key: path
|
|
1547
|
+
}));
|
|
1548
|
+
if (!response || !response.Body) {
|
|
1549
|
+
this.logger.trace(`Object not found at ${path}`);
|
|
1550
|
+
return null;
|
|
1551
|
+
}
|
|
1552
|
+
const bodyContents = await response.Body.transformToString();
|
|
1553
|
+
const data = JSON.parse(bodyContents);
|
|
1554
|
+
this.logger.trace(`Object read successfully from ${path}`);
|
|
1555
|
+
return data;
|
|
1556
|
+
}
|
|
1557
|
+
catch (error) {
|
|
1558
|
+
// 404 errors return null (object doesn't exist)
|
|
1559
|
+
if (error.name === 'NoSuchKey' ||
|
|
1560
|
+
(error.message &&
|
|
1561
|
+
(error.message.includes('NoSuchKey') ||
|
|
1562
|
+
error.message.includes('not found') ||
|
|
1563
|
+
error.message.includes('does not exist')))) {
|
|
1564
|
+
this.logger.trace(`Object not found at ${path}`);
|
|
1565
|
+
return null;
|
|
1566
|
+
}
|
|
1567
|
+
throw BrainyError.fromError(error, `readObjectFromPath(${path})`);
|
|
1568
|
+
}
|
|
1569
|
+
}, `readObjectFromPath(${path})`);
|
|
1577
1570
|
}
|
|
1578
1571
|
/**
|
|
1579
|
-
*
|
|
1572
|
+
* Primitive operation: Delete object from path
|
|
1573
|
+
* All metadata operations use this internally via base class routing
|
|
1580
1574
|
*/
|
|
1581
|
-
async
|
|
1575
|
+
async deleteObjectFromPath(path) {
|
|
1582
1576
|
await this.ensureInitialized();
|
|
1583
1577
|
try {
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
this.logger.trace(`Getting verb metadata for ${id} from key: ${key}`);
|
|
1588
|
-
// Try to get the verb metadata
|
|
1589
|
-
const response = await this.s3Client.send(new GetObjectCommand({
|
|
1578
|
+
const { DeleteObjectCommand } = await import('@aws-sdk/client-s3');
|
|
1579
|
+
this.logger.trace(`Deleting object at path: ${path}`);
|
|
1580
|
+
await this.s3Client.send(new DeleteObjectCommand({
|
|
1590
1581
|
Bucket: this.bucketName,
|
|
1591
|
-
Key:
|
|
1582
|
+
Key: path
|
|
1592
1583
|
}));
|
|
1593
|
-
|
|
1594
|
-
if (!response || !response.Body) {
|
|
1595
|
-
this.logger.trace(`No verb metadata found for ${id}`);
|
|
1596
|
-
return null;
|
|
1597
|
-
}
|
|
1598
|
-
// Convert the response body to a string
|
|
1599
|
-
const bodyContents = await response.Body.transformToString();
|
|
1600
|
-
this.logger.trace(`Retrieved verb metadata body for ${id}`);
|
|
1601
|
-
// Parse the JSON string
|
|
1602
|
-
try {
|
|
1603
|
-
const parsedMetadata = JSON.parse(bodyContents);
|
|
1604
|
-
this.logger.trace(`Successfully retrieved verb metadata for ${id}`);
|
|
1605
|
-
return parsedMetadata;
|
|
1606
|
-
}
|
|
1607
|
-
catch (parseError) {
|
|
1608
|
-
this.logger.error(`Failed to parse verb metadata for ${id}:`, parseError);
|
|
1609
|
-
return null;
|
|
1610
|
-
}
|
|
1584
|
+
this.logger.trace(`Object deleted successfully from ${path}`);
|
|
1611
1585
|
}
|
|
1612
1586
|
catch (error) {
|
|
1613
|
-
//
|
|
1587
|
+
// 404 errors are ok (already deleted)
|
|
1614
1588
|
if (error.name === 'NoSuchKey' ||
|
|
1615
1589
|
(error.message &&
|
|
1616
1590
|
(error.message.includes('NoSuchKey') ||
|
|
1617
1591
|
error.message.includes('not found') ||
|
|
1618
1592
|
error.message.includes('does not exist')))) {
|
|
1619
|
-
this.logger.trace(`
|
|
1620
|
-
return
|
|
1593
|
+
this.logger.trace(`Object at ${path} not found (already deleted)`);
|
|
1594
|
+
return;
|
|
1621
1595
|
}
|
|
1622
|
-
|
|
1623
|
-
throw
|
|
1596
|
+
this.logger.error(`Failed to delete object from ${path}:`, error);
|
|
1597
|
+
throw new Error(`Failed to delete object from ${path}: ${error}`);
|
|
1624
1598
|
}
|
|
1625
1599
|
}
|
|
1626
1600
|
/**
|
|
1627
|
-
*
|
|
1601
|
+
* Primitive operation: List objects under path prefix
|
|
1602
|
+
* All metadata operations use this internally via base class routing
|
|
1628
1603
|
*/
|
|
1629
|
-
async
|
|
1604
|
+
async listObjectsUnderPath(prefix) {
|
|
1630
1605
|
await this.ensureInitialized();
|
|
1631
1606
|
try {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
const shardId = getShardIdFromUuid(id);
|
|
1636
|
-
const key = `${this.metadataPrefix}${shardId}/${id}.json`;
|
|
1637
|
-
const body = JSON.stringify(metadata, null, 2);
|
|
1638
|
-
this.logger.trace(`Saving noun metadata for ${id} to key: ${key}`);
|
|
1639
|
-
// Save the noun metadata to S3-compatible storage
|
|
1640
|
-
const result = await this.s3Client.send(new PutObjectCommand({
|
|
1607
|
+
const { ListObjectsV2Command } = await import('@aws-sdk/client-s3');
|
|
1608
|
+
this.logger.trace(`Listing objects under prefix: ${prefix}`);
|
|
1609
|
+
const response = await this.s3Client.send(new ListObjectsV2Command({
|
|
1641
1610
|
Bucket: this.bucketName,
|
|
1642
|
-
|
|
1643
|
-
Body: body,
|
|
1644
|
-
ContentType: 'application/json'
|
|
1611
|
+
Prefix: prefix
|
|
1645
1612
|
}));
|
|
1646
|
-
|
|
1613
|
+
if (!response || !response.Contents || response.Contents.length === 0) {
|
|
1614
|
+
this.logger.trace(`No objects found under ${prefix}`);
|
|
1615
|
+
return [];
|
|
1616
|
+
}
|
|
1617
|
+
const paths = response.Contents
|
|
1618
|
+
.map((object) => object.Key)
|
|
1619
|
+
.filter((key) => key && key.length > 0);
|
|
1620
|
+
this.logger.trace(`Found ${paths.length} objects under ${prefix}`);
|
|
1621
|
+
return paths;
|
|
1647
1622
|
}
|
|
1648
1623
|
catch (error) {
|
|
1649
|
-
this.logger.error(`Failed to
|
|
1650
|
-
throw new Error(`Failed to
|
|
1624
|
+
this.logger.error(`Failed to list objects under ${prefix}:`, error);
|
|
1625
|
+
throw new Error(`Failed to list objects under ${prefix}: ${error}`);
|
|
1651
1626
|
}
|
|
1652
1627
|
}
|
|
1653
1628
|
/**
|
|
@@ -1754,109 +1729,6 @@ export class S3CompatibleStorage extends BaseStorage {
|
|
|
1754
1729
|
}
|
|
1755
1730
|
return results;
|
|
1756
1731
|
}
|
|
1757
|
-
/**
|
|
1758
|
-
* Get noun metadata from storage
|
|
1759
|
-
*/
|
|
1760
|
-
async getNounMetadata(id) {
|
|
1761
|
-
await this.ensureInitialized();
|
|
1762
|
-
try {
|
|
1763
|
-
// Import the GetObjectCommand only when needed
|
|
1764
|
-
const { GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
1765
|
-
// Use UUID-based sharding for metadata (consistent with noun vectors)
|
|
1766
|
-
const shardId = getShardIdFromUuid(id);
|
|
1767
|
-
const key = `${this.metadataPrefix}${shardId}/${id}.json`;
|
|
1768
|
-
this.logger.trace(`Getting noun metadata for ${id} from key: ${key}`);
|
|
1769
|
-
// Try to get the noun metadata
|
|
1770
|
-
const response = await this.s3Client.send(new GetObjectCommand({
|
|
1771
|
-
Bucket: this.bucketName,
|
|
1772
|
-
Key: key
|
|
1773
|
-
}));
|
|
1774
|
-
// Check if response is null or undefined
|
|
1775
|
-
if (!response || !response.Body) {
|
|
1776
|
-
this.logger.trace(`No noun metadata found for ${id}`);
|
|
1777
|
-
return null;
|
|
1778
|
-
}
|
|
1779
|
-
// Convert the response body to a string
|
|
1780
|
-
const bodyContents = await response.Body.transformToString();
|
|
1781
|
-
this.logger.trace(`Retrieved noun metadata body for ${id}`);
|
|
1782
|
-
// Parse the JSON string
|
|
1783
|
-
try {
|
|
1784
|
-
const parsedMetadata = JSON.parse(bodyContents);
|
|
1785
|
-
this.logger.trace(`Successfully retrieved noun metadata for ${id}`);
|
|
1786
|
-
return parsedMetadata;
|
|
1787
|
-
}
|
|
1788
|
-
catch (parseError) {
|
|
1789
|
-
this.logger.error(`Failed to parse noun metadata for ${id}:`, parseError);
|
|
1790
|
-
return null;
|
|
1791
|
-
}
|
|
1792
|
-
}
|
|
1793
|
-
catch (error) {
|
|
1794
|
-
// Check if this is a "NoSuchKey" error (object doesn't exist)
|
|
1795
|
-
if (error.name === 'NoSuchKey' ||
|
|
1796
|
-
(error.message &&
|
|
1797
|
-
(error.message.includes('NoSuchKey') ||
|
|
1798
|
-
error.message.includes('not found') ||
|
|
1799
|
-
error.message.includes('does not exist')))) {
|
|
1800
|
-
this.logger.trace(`Noun metadata not found for ${id}`);
|
|
1801
|
-
return null;
|
|
1802
|
-
}
|
|
1803
|
-
// For other types of errors, convert to BrainyError for better classification
|
|
1804
|
-
throw BrainyError.fromError(error, `getNounMetadata(${id})`);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
/**
|
|
1808
|
-
* Get metadata from storage
|
|
1809
|
-
*/
|
|
1810
|
-
async getMetadata(id) {
|
|
1811
|
-
await this.ensureInitialized();
|
|
1812
|
-
return this.operationExecutors.executeGet(async () => {
|
|
1813
|
-
try {
|
|
1814
|
-
// Import the GetObjectCommand only when needed
|
|
1815
|
-
const { GetObjectCommand } = await import('@aws-sdk/client-s3');
|
|
1816
|
-
prodLog.debug(`Getting metadata for ${id} from bucket ${this.bucketName}`);
|
|
1817
|
-
const key = `${this.metadataPrefix}${id}.json`;
|
|
1818
|
-
prodLog.debug(`Looking for metadata at key: ${key}`);
|
|
1819
|
-
// Try to get the metadata from the metadata directory
|
|
1820
|
-
const response = await this.s3Client.send(new GetObjectCommand({
|
|
1821
|
-
Bucket: this.bucketName,
|
|
1822
|
-
Key: key
|
|
1823
|
-
}));
|
|
1824
|
-
// Check if response is null or undefined (can happen in mock implementations)
|
|
1825
|
-
if (!response || !response.Body) {
|
|
1826
|
-
prodLog.debug(`No metadata found for ${id}`);
|
|
1827
|
-
return null;
|
|
1828
|
-
}
|
|
1829
|
-
// Convert the response body to a string
|
|
1830
|
-
const bodyContents = await response.Body.transformToString();
|
|
1831
|
-
prodLog.debug(`Retrieved metadata body: ${bodyContents}`);
|
|
1832
|
-
// Parse the JSON string
|
|
1833
|
-
try {
|
|
1834
|
-
const parsedMetadata = JSON.parse(bodyContents);
|
|
1835
|
-
prodLog.debug(`Successfully retrieved metadata for ${id}:`, parsedMetadata);
|
|
1836
|
-
return parsedMetadata;
|
|
1837
|
-
}
|
|
1838
|
-
catch (parseError) {
|
|
1839
|
-
prodLog.error(`Failed to parse metadata for ${id}:`, parseError);
|
|
1840
|
-
return null;
|
|
1841
|
-
}
|
|
1842
|
-
}
|
|
1843
|
-
catch (error) {
|
|
1844
|
-
// Check if this is a "NoSuchKey" error (object doesn't exist)
|
|
1845
|
-
// In AWS SDK, this would be error.name === 'NoSuchKey'
|
|
1846
|
-
// In our mock, we might get different error types
|
|
1847
|
-
if (error.name === 'NoSuchKey' ||
|
|
1848
|
-
(error.message &&
|
|
1849
|
-
(error.message.includes('NoSuchKey') ||
|
|
1850
|
-
error.message.includes('not found') ||
|
|
1851
|
-
error.message.includes('does not exist')))) {
|
|
1852
|
-
prodLog.debug(`Metadata not found for ${id}`);
|
|
1853
|
-
return null;
|
|
1854
|
-
}
|
|
1855
|
-
// For other types of errors, convert to BrainyError for better classification
|
|
1856
|
-
throw BrainyError.fromError(error, `getMetadata(${id})`);
|
|
1857
|
-
}
|
|
1858
|
-
}, `getMetadata(${id})`);
|
|
1859
|
-
}
|
|
1860
1732
|
/**
|
|
1861
1733
|
* Clear all data from storage
|
|
1862
1734
|
*/
|
|
@@ -34,6 +34,14 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
34
34
|
protected isInitialized: boolean;
|
|
35
35
|
protected graphIndex?: GraphAdjacencyIndex;
|
|
36
36
|
protected readOnly: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Analyze a storage key to determine its routing and path
|
|
39
|
+
* @param id - The key to analyze (UUID or system key)
|
|
40
|
+
* @param context - The context for the key (noun-metadata, verb-metadata, or system)
|
|
41
|
+
* @returns Storage key information including path and shard ID
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
private analyzeKey;
|
|
37
45
|
/**
|
|
38
46
|
* Initialize the storage adapter
|
|
39
47
|
* This method should be implemented by each specific adapter
|
|
@@ -164,46 +172,79 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
164
172
|
quota: number | null;
|
|
165
173
|
details?: Record<string, any>;
|
|
166
174
|
}>;
|
|
175
|
+
/**
|
|
176
|
+
* Write a JSON object to a specific path in storage
|
|
177
|
+
* This is a primitive operation that all adapters must implement
|
|
178
|
+
* @param path - Full path including filename (e.g., "_system/statistics.json" or "entities/nouns/metadata/3f/3fa85f64-....json")
|
|
179
|
+
* @param data - Data to write (will be JSON.stringify'd)
|
|
180
|
+
* @protected
|
|
181
|
+
*/
|
|
182
|
+
protected abstract writeObjectToPath(path: string, data: any): Promise<void>;
|
|
183
|
+
/**
|
|
184
|
+
* Read a JSON object from a specific path in storage
|
|
185
|
+
* This is a primitive operation that all adapters must implement
|
|
186
|
+
* @param path - Full path including filename
|
|
187
|
+
* @returns The parsed JSON object, or null if not found
|
|
188
|
+
* @protected
|
|
189
|
+
*/
|
|
190
|
+
protected abstract readObjectFromPath(path: string): Promise<any | null>;
|
|
191
|
+
/**
|
|
192
|
+
* Delete an object from a specific path in storage
|
|
193
|
+
* This is a primitive operation that all adapters must implement
|
|
194
|
+
* @param path - Full path including filename
|
|
195
|
+
* @protected
|
|
196
|
+
*/
|
|
197
|
+
protected abstract deleteObjectFromPath(path: string): Promise<void>;
|
|
198
|
+
/**
|
|
199
|
+
* List all object paths under a given prefix
|
|
200
|
+
* This is a primitive operation that all adapters must implement
|
|
201
|
+
* @param prefix - Directory prefix to list (e.g., "entities/nouns/metadata/3f/")
|
|
202
|
+
* @returns Array of full paths
|
|
203
|
+
* @protected
|
|
204
|
+
*/
|
|
205
|
+
protected abstract listObjectsUnderPath(prefix: string): Promise<string[]>;
|
|
167
206
|
/**
|
|
168
207
|
* Save metadata to storage
|
|
169
|
-
*
|
|
208
|
+
* Routes to correct location (system or entity) based on key format
|
|
170
209
|
*/
|
|
171
|
-
|
|
210
|
+
saveMetadata(id: string, metadata: any): Promise<void>;
|
|
172
211
|
/**
|
|
173
212
|
* Get metadata from storage
|
|
174
|
-
*
|
|
213
|
+
* Routes to correct location (system or entity) based on key format
|
|
175
214
|
*/
|
|
176
|
-
|
|
215
|
+
getMetadata(id: string): Promise<any | null>;
|
|
177
216
|
/**
|
|
178
217
|
* Save noun metadata to storage
|
|
179
|
-
*
|
|
218
|
+
* Routes to correct sharded location based on UUID
|
|
180
219
|
*/
|
|
181
220
|
saveNounMetadata(id: string, metadata: any): Promise<void>;
|
|
182
221
|
/**
|
|
183
222
|
* Internal method for saving noun metadata
|
|
184
|
-
*
|
|
223
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
224
|
+
* @protected
|
|
185
225
|
*/
|
|
186
|
-
protected
|
|
226
|
+
protected saveNounMetadata_internal(id: string, metadata: any): Promise<void>;
|
|
187
227
|
/**
|
|
188
228
|
* Get noun metadata from storage
|
|
189
|
-
*
|
|
229
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
190
230
|
*/
|
|
191
|
-
|
|
231
|
+
getNounMetadata(id: string): Promise<any | null>;
|
|
192
232
|
/**
|
|
193
233
|
* Save verb metadata to storage
|
|
194
|
-
*
|
|
234
|
+
* Routes to correct sharded location based on UUID
|
|
195
235
|
*/
|
|
196
236
|
saveVerbMetadata(id: string, metadata: any): Promise<void>;
|
|
197
237
|
/**
|
|
198
238
|
* Internal method for saving verb metadata
|
|
199
|
-
*
|
|
239
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
240
|
+
* @protected
|
|
200
241
|
*/
|
|
201
|
-
protected
|
|
242
|
+
protected saveVerbMetadata_internal(id: string, metadata: any): Promise<void>;
|
|
202
243
|
/**
|
|
203
244
|
* Get verb metadata from storage
|
|
204
|
-
*
|
|
245
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
205
246
|
*/
|
|
206
|
-
|
|
247
|
+
getVerbMetadata(id: string): Promise<any | null>;
|
|
207
248
|
/**
|
|
208
249
|
* Save a noun to storage
|
|
209
250
|
* This method should be implemented by each specific adapter
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { GraphAdjacencyIndex } from '../graph/graphAdjacencyIndex.js';
|
|
6
6
|
import { BaseStorageAdapter } from './adapters/baseStorageAdapter.js';
|
|
7
7
|
import { validateNounType, validateVerbType } from '../utils/typeValidation.js';
|
|
8
|
+
import { getShardIdFromUuid } from './sharding.js';
|
|
8
9
|
// Common directory/prefix names
|
|
9
10
|
// Option A: Entity-Based Directory Structure
|
|
10
11
|
export const ENTITIES_DIR = 'entities';
|
|
@@ -60,6 +61,72 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
60
61
|
this.isInitialized = false;
|
|
61
62
|
this.readOnly = false;
|
|
62
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Analyze a storage key to determine its routing and path
|
|
66
|
+
* @param id - The key to analyze (UUID or system key)
|
|
67
|
+
* @param context - The context for the key (noun-metadata, verb-metadata, or system)
|
|
68
|
+
* @returns Storage key information including path and shard ID
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
analyzeKey(id, context) {
|
|
72
|
+
// System resource detection
|
|
73
|
+
const isSystemKey = id.startsWith('__metadata_') ||
|
|
74
|
+
id.startsWith('__index_') ||
|
|
75
|
+
id.startsWith('__system_') ||
|
|
76
|
+
id.startsWith('statistics_') ||
|
|
77
|
+
id === 'statistics';
|
|
78
|
+
if (isSystemKey) {
|
|
79
|
+
return {
|
|
80
|
+
original: id,
|
|
81
|
+
isEntity: false,
|
|
82
|
+
shardId: null,
|
|
83
|
+
directory: SYSTEM_DIR,
|
|
84
|
+
fullPath: `${SYSTEM_DIR}/${id}.json`
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// UUID validation for entity keys
|
|
88
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
89
|
+
if (!uuidRegex.test(id)) {
|
|
90
|
+
console.warn(`[Storage] Unknown key format: ${id} - treating as system resource`);
|
|
91
|
+
return {
|
|
92
|
+
original: id,
|
|
93
|
+
isEntity: false,
|
|
94
|
+
shardId: null,
|
|
95
|
+
directory: SYSTEM_DIR,
|
|
96
|
+
fullPath: `${SYSTEM_DIR}/${id}.json`
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
// Valid entity UUID - apply sharding
|
|
100
|
+
const shardId = getShardIdFromUuid(id);
|
|
101
|
+
if (context === 'noun-metadata') {
|
|
102
|
+
return {
|
|
103
|
+
original: id,
|
|
104
|
+
isEntity: true,
|
|
105
|
+
shardId,
|
|
106
|
+
directory: `${NOUNS_METADATA_DIR}/${shardId}`,
|
|
107
|
+
fullPath: `${NOUNS_METADATA_DIR}/${shardId}/${id}.json`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
else if (context === 'verb-metadata') {
|
|
111
|
+
return {
|
|
112
|
+
original: id,
|
|
113
|
+
isEntity: true,
|
|
114
|
+
shardId,
|
|
115
|
+
directory: `${VERBS_METADATA_DIR}/${shardId}`,
|
|
116
|
+
fullPath: `${VERBS_METADATA_DIR}/${shardId}/${id}.json`
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// system context - but UUID format
|
|
121
|
+
return {
|
|
122
|
+
original: id,
|
|
123
|
+
isEntity: false,
|
|
124
|
+
shardId: null,
|
|
125
|
+
directory: SYSTEM_DIR,
|
|
126
|
+
fullPath: `${SYSTEM_DIR}/${id}.json`
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
63
130
|
/**
|
|
64
131
|
* Ensure the storage adapter is initialized
|
|
65
132
|
*/
|
|
@@ -551,9 +618,27 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
551
618
|
}
|
|
552
619
|
return this.graphIndex;
|
|
553
620
|
}
|
|
621
|
+
/**
|
|
622
|
+
* Save metadata to storage
|
|
623
|
+
* Routes to correct location (system or entity) based on key format
|
|
624
|
+
*/
|
|
625
|
+
async saveMetadata(id, metadata) {
|
|
626
|
+
await this.ensureInitialized();
|
|
627
|
+
const keyInfo = this.analyzeKey(id, 'system');
|
|
628
|
+
return this.writeObjectToPath(keyInfo.fullPath, metadata);
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Get metadata from storage
|
|
632
|
+
* Routes to correct location (system or entity) based on key format
|
|
633
|
+
*/
|
|
634
|
+
async getMetadata(id) {
|
|
635
|
+
await this.ensureInitialized();
|
|
636
|
+
const keyInfo = this.analyzeKey(id, 'system');
|
|
637
|
+
return this.readObjectFromPath(keyInfo.fullPath);
|
|
638
|
+
}
|
|
554
639
|
/**
|
|
555
640
|
* Save noun metadata to storage
|
|
556
|
-
*
|
|
641
|
+
* Routes to correct sharded location based on UUID
|
|
557
642
|
*/
|
|
558
643
|
async saveNounMetadata(id, metadata) {
|
|
559
644
|
// Validate noun type in metadata - storage boundary protection
|
|
@@ -562,9 +647,28 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
562
647
|
}
|
|
563
648
|
return this.saveNounMetadata_internal(id, metadata);
|
|
564
649
|
}
|
|
650
|
+
/**
|
|
651
|
+
* Internal method for saving noun metadata
|
|
652
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
653
|
+
* @protected
|
|
654
|
+
*/
|
|
655
|
+
async saveNounMetadata_internal(id, metadata) {
|
|
656
|
+
await this.ensureInitialized();
|
|
657
|
+
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
658
|
+
return this.writeObjectToPath(keyInfo.fullPath, metadata);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Get noun metadata from storage
|
|
662
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
663
|
+
*/
|
|
664
|
+
async getNounMetadata(id) {
|
|
665
|
+
await this.ensureInitialized();
|
|
666
|
+
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
667
|
+
return this.readObjectFromPath(keyInfo.fullPath);
|
|
668
|
+
}
|
|
565
669
|
/**
|
|
566
670
|
* Save verb metadata to storage
|
|
567
|
-
*
|
|
671
|
+
* Routes to correct sharded location based on UUID
|
|
568
672
|
*/
|
|
569
673
|
async saveVerbMetadata(id, metadata) {
|
|
570
674
|
// Validate verb type in metadata - storage boundary protection
|
|
@@ -573,6 +677,25 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
573
677
|
}
|
|
574
678
|
return this.saveVerbMetadata_internal(id, metadata);
|
|
575
679
|
}
|
|
680
|
+
/**
|
|
681
|
+
* Internal method for saving verb metadata
|
|
682
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
683
|
+
* @protected
|
|
684
|
+
*/
|
|
685
|
+
async saveVerbMetadata_internal(id, metadata) {
|
|
686
|
+
await this.ensureInitialized();
|
|
687
|
+
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
688
|
+
return this.writeObjectToPath(keyInfo.fullPath, metadata);
|
|
689
|
+
}
|
|
690
|
+
/**
|
|
691
|
+
* Get verb metadata from storage
|
|
692
|
+
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
693
|
+
*/
|
|
694
|
+
async getVerbMetadata(id) {
|
|
695
|
+
await this.ensureInitialized();
|
|
696
|
+
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
697
|
+
return this.readObjectFromPath(keyInfo.fullPath);
|
|
698
|
+
}
|
|
576
699
|
/**
|
|
577
700
|
* Helper method to convert a Map to a plain object for serialization
|
|
578
701
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.30.
|
|
3
|
+
"version": "3.30.2",
|
|
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",
|