@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.
@@ -1495,159 +1495,134 @@ export class S3CompatibleStorage extends BaseStorage {
1495
1495
  }
1496
1496
  }
1497
1497
  /**
1498
- * Save metadata to storage
1498
+ * Primitive operation: Write object to path
1499
+ * All metadata operations use this internally via base class routing
1499
1500
  */
1500
- async saveMetadata(id, metadata) {
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 key = `${this.metadataPrefix}${id}.json`;
1508
- const body = JSON.stringify(metadata, null, 2);
1509
- this.logger.trace(`Saving metadata for ${id} to key: ${key}`);
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: key,
1511
+ Key: path,
1514
1512
  Body: body,
1515
1513
  ContentType: 'application/json'
1516
1514
  }));
1517
- this.logger.debug(`Metadata for ${id} saved successfully`);
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', // Could be 'update' if we track existing metadata
1519
+ operation: 'add',
1522
1520
  entityType: 'metadata',
1523
- entityId: id,
1524
- data: metadata
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 save metadata for ${id}:`, error);
1550
- throw new Error(`Failed to save metadata for ${id}: ${error}`);
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
- * Save verb metadata to storage
1535
+ * Primitive operation: Read object from path
1536
+ * All metadata operations use this internally via base class routing
1555
1537
  */
1556
- async saveVerbMetadata_internal(id, metadata) {
1538
+ async readObjectFromPath(path) {
1557
1539
  await this.ensureInitialized();
1558
- try {
1559
- // Import the PutObjectCommand only when needed
1560
- const { PutObjectCommand } = await import('@aws-sdk/client-s3');
1561
- const key = `${this.verbMetadataPrefix}${id}.json`;
1562
- const body = JSON.stringify(metadata, null, 2);
1563
- this.logger.trace(`Saving verb metadata for ${id} to key: ${key}`);
1564
- // Save the verb metadata to S3-compatible storage
1565
- const result = await this.s3Client.send(new PutObjectCommand({
1566
- Bucket: this.bucketName,
1567
- Key: key,
1568
- Body: body,
1569
- ContentType: 'application/json'
1570
- }));
1571
- this.logger.debug(`Verb metadata for ${id} saved successfully`);
1572
- }
1573
- catch (error) {
1574
- this.logger.error(`Failed to save verb metadata for ${id}:`, error);
1575
- throw new Error(`Failed to save verb metadata for ${id}: ${error}`);
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
- * Get verb metadata from storage
1572
+ * Primitive operation: Delete object from path
1573
+ * All metadata operations use this internally via base class routing
1580
1574
  */
1581
- async getVerbMetadata(id) {
1575
+ async deleteObjectFromPath(path) {
1582
1576
  await this.ensureInitialized();
1583
1577
  try {
1584
- // Import the GetObjectCommand only when needed
1585
- const { GetObjectCommand } = await import('@aws-sdk/client-s3');
1586
- const key = `${this.verbMetadataPrefix}${id}.json`;
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: key
1582
+ Key: path
1592
1583
  }));
1593
- // Check if response is null or undefined
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
- // Check if this is a "NoSuchKey" error (object doesn't exist)
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(`Verb metadata not found for ${id}`);
1620
- return null;
1593
+ this.logger.trace(`Object at ${path} not found (already deleted)`);
1594
+ return;
1621
1595
  }
1622
- // For other types of errors, convert to BrainyError for better classification
1623
- throw BrainyError.fromError(error, `getVerbMetadata(${id})`);
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
- * Save noun metadata to storage
1601
+ * Primitive operation: List objects under path prefix
1602
+ * All metadata operations use this internally via base class routing
1628
1603
  */
1629
- async saveNounMetadata_internal(id, metadata) {
1604
+ async listObjectsUnderPath(prefix) {
1630
1605
  await this.ensureInitialized();
1631
1606
  try {
1632
- // Import the PutObjectCommand only when needed
1633
- const { PutObjectCommand } = await import('@aws-sdk/client-s3');
1634
- // Use UUID-based sharding for metadata (consistent with noun vectors)
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
- Key: key,
1643
- Body: body,
1644
- ContentType: 'application/json'
1611
+ Prefix: prefix
1645
1612
  }));
1646
- this.logger.debug(`Noun metadata for ${id} saved successfully`);
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 save noun metadata for ${id}:`, error);
1650
- throw new Error(`Failed to save noun metadata for ${id}: ${error}`);
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
- * This method should be implemented by each specific adapter
208
+ * Routes to correct location (system or entity) based on key format
170
209
  */
171
- abstract saveMetadata(id: string, metadata: any): Promise<void>;
210
+ saveMetadata(id: string, metadata: any): Promise<void>;
172
211
  /**
173
212
  * Get metadata from storage
174
- * This method should be implemented by each specific adapter
213
+ * Routes to correct location (system or entity) based on key format
175
214
  */
176
- abstract getMetadata(id: string): Promise<any | null>;
215
+ getMetadata(id: string): Promise<any | null>;
177
216
  /**
178
217
  * Save noun metadata to storage
179
- * This method should be implemented by each specific adapter
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
- * This method should be implemented by each specific adapter
223
+ * Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
224
+ * @protected
185
225
  */
186
- protected abstract saveNounMetadata_internal(id: string, metadata: any): Promise<void>;
226
+ protected saveNounMetadata_internal(id: string, metadata: any): Promise<void>;
187
227
  /**
188
228
  * Get noun metadata from storage
189
- * This method should be implemented by each specific adapter
229
+ * Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
190
230
  */
191
- abstract getNounMetadata(id: string): Promise<any | null>;
231
+ getNounMetadata(id: string): Promise<any | null>;
192
232
  /**
193
233
  * Save verb metadata to storage
194
- * This method should be implemented by each specific adapter
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
- * This method should be implemented by each specific adapter
239
+ * Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
240
+ * @protected
200
241
  */
201
- protected abstract saveVerbMetadata_internal(id: string, metadata: any): Promise<void>;
242
+ protected saveVerbMetadata_internal(id: string, metadata: any): Promise<void>;
202
243
  /**
203
244
  * Get verb metadata from storage
204
- * This method should be implemented by each specific adapter
245
+ * Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
205
246
  */
206
- abstract getVerbMetadata(id: string): Promise<any | null>;
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
- * This method should be implemented by each specific adapter
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
- * This method should be implemented by each specific adapter
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.0",
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",