@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 CHANGED
@@ -2,6 +2,58 @@
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
+ ### [3.30.2](https://github.com/soulcraftlabs/brainy/compare/v3.30.1...v3.30.2) (2025-10-09)
6
+
7
+ - chore: update dependencies to latest safe versions (053f292)
8
+
9
+
10
+ ### [3.30.1](https://github.com/soulcraftlabs/brainy/compare/v3.30.0...v3.30.1) (2025-10-09)
11
+
12
+ - fix: move metadata routing to base class, fix GCS/S3 system key crashes (1966c39)
13
+
14
+
15
+ ### [3.30.1] - Critical Storage Architecture Fix (2025-10-09)
16
+
17
+ #### 🐛 Critical Bug Fixes
18
+
19
+ **Fixed: GCS/S3 Storage Crash on System Metadata Keys**
20
+ - GCS and S3 native adapters were crashing with "Invalid UUID format" errors when saving metadata index keys
21
+ - Root cause: Storage adapters incorrectly assumed ALL metadata keys are UUIDs
22
+ - System keys like `__metadata_field_index__status` and `statistics_` are NOT UUIDs and should not be sharded
23
+
24
+ **Architecture Improvement: Base Class Enforcement Pattern**
25
+ - Moved sharding/routing logic from individual adapters to BaseStorage class
26
+ - All adapters now implement 4 primitive operations instead of metadata-specific methods:
27
+ - `writeObjectToPath(path, data)` - Write any object to storage
28
+ - `readObjectFromPath(path)` - Read any object from storage
29
+ - `deleteObjectFromPath(path)` - Delete object from storage
30
+ - `listObjectsUnderPath(prefix)` - List objects under path prefix
31
+ - BaseStorage.analyzeKey() now routes ALL metadata operations through primitive layer
32
+ - System keys automatically routed to `_system/` directory (no sharding)
33
+ - Entity UUIDs automatically sharded to `entities/{type}/metadata/{shard}/` directories
34
+
35
+ **Benefits:**
36
+ - Impossible for future adapters to make the same mistake
37
+ - Cleaner separation of concerns (routing vs. storage primitives)
38
+ - Zero breaking changes for users
39
+ - No data migration required
40
+ - Full backward compatibility maintained
41
+
42
+ **Updated Adapters:**
43
+ - GcsStorage: Implements primitive operations using GCS bucket.file() API
44
+ - S3CompatibleStorage: Implements primitive operations using AWS SDK
45
+ - OPFSStorage: Implements primitive operations using browser FileSystem API
46
+ - FileSystemStorage: Implements primitive operations using Node.js fs.promises
47
+ - MemoryStorage: Implements primitive operations using Map data structures
48
+
49
+ **Documentation:**
50
+ - Added comprehensive storage architecture documentation: `docs/architecture/data-storage-architecture.md`
51
+ - Linked from README for easy discovery
52
+
53
+ **Impact:** CRITICAL FIX - GCS/S3 native storage now fully functional for metadata indexing
54
+
55
+ ---
56
+
5
57
  ### [3.30.0](https://github.com/soulcraftlabs/brainy/compare/v3.29.1...v3.30.0) (2025-10-09)
6
58
 
7
59
  - feat: remove legacy ImportManager, standardize getStats() API (58daf09)
package/README.md CHANGED
@@ -888,6 +888,7 @@ We welcome contributions! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
888
888
  - [Getting Started Guide](docs/guides/getting-started.md)
889
889
  - [API Reference](docs/api/README.md)
890
890
  - [Architecture Overview](docs/architecture/overview.md)
891
+ - [Data Storage Architecture](docs/architecture/data-storage-architecture.md) - Deep dive into storage, indexing, and sharding
891
892
  - [Natural Language Guide](docs/guides/natural-language.md)
892
893
  - [Triple Intelligence](docs/architecture/triple-intelligence.md)
893
894
  - [Noun-Verb Taxonomy](docs/architecture/noun-verb-taxonomy.md)
@@ -96,34 +96,30 @@ export declare class FileSystemStorage extends BaseStorage {
96
96
  */
97
97
  protected deleteEdge(id: string): Promise<void>;
98
98
  /**
99
- * Save metadata to storage
99
+ * Primitive operation: Write object to path
100
+ * All metadata operations use this internally via base class routing
100
101
  */
101
- saveMetadata(id: string, metadata: any): Promise<void>;
102
+ protected writeObjectToPath(pathStr: string, data: any): Promise<void>;
102
103
  /**
103
- * Get metadata from storage
104
+ * Primitive operation: Read object from path
105
+ * All metadata operations use this internally via base class routing
104
106
  */
105
- getMetadata(id: string): Promise<any | null>;
107
+ protected readObjectFromPath(pathStr: string): Promise<any | null>;
106
108
  /**
107
- * Get multiple metadata objects in batches (CRITICAL: Prevents socket exhaustion)
108
- * FileSystem implementation uses controlled concurrency to prevent too many file reads
109
- */
110
- getMetadataBatch(ids: string[]): Promise<Map<string, any>>;
111
- /**
112
- * Save noun metadata to storage
109
+ * Primitive operation: Delete object from path
110
+ * All metadata operations use this internally via base class routing
113
111
  */
114
- protected saveNounMetadata_internal(id: string, metadata: any): Promise<void>;
112
+ protected deleteObjectFromPath(pathStr: string): Promise<void>;
115
113
  /**
116
- * Get noun metadata from storage
114
+ * Primitive operation: List objects under path prefix
115
+ * All metadata operations use this internally via base class routing
117
116
  */
118
- getNounMetadata(id: string): Promise<any | null>;
117
+ protected listObjectsUnderPath(prefix: string): Promise<string[]>;
119
118
  /**
120
- * Save verb metadata to storage
121
- */
122
- protected saveVerbMetadata_internal(id: string, metadata: any): Promise<void>;
123
- /**
124
- * Get verb metadata from storage
119
+ * Get multiple metadata objects in batches (CRITICAL: Prevents socket exhaustion)
120
+ * FileSystem implementation uses controlled concurrency to prevent too many file reads
125
121
  */
126
- getVerbMetadata(id: string): Promise<any | null>;
122
+ getMetadataBatch(ids: string[]): Promise<Map<string, any>>;
127
123
  /**
128
124
  * Get nouns with pagination support
129
125
  * @param options Pagination options
@@ -449,30 +449,79 @@ export class FileSystemStorage extends BaseStorage {
449
449
  }
450
450
  }
451
451
  /**
452
- * Save metadata to storage
452
+ * Primitive operation: Write object to path
453
+ * All metadata operations use this internally via base class routing
453
454
  */
454
- async saveMetadata(id, metadata) {
455
+ async writeObjectToPath(pathStr, data) {
455
456
  await this.ensureInitialized();
456
- const filePath = path.join(this.metadataDir, `${id}.json`);
457
- await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2));
457
+ const fullPath = path.join(this.rootDir, pathStr);
458
+ await this.ensureDirectoryExists(path.dirname(fullPath));
459
+ await fs.promises.writeFile(fullPath, JSON.stringify(data, null, 2));
458
460
  }
459
461
  /**
460
- * Get metadata from storage
462
+ * Primitive operation: Read object from path
463
+ * All metadata operations use this internally via base class routing
461
464
  */
462
- async getMetadata(id) {
465
+ async readObjectFromPath(pathStr) {
463
466
  await this.ensureInitialized();
464
- const filePath = path.join(this.metadataDir, `${id}.json`);
467
+ const fullPath = path.join(this.rootDir, pathStr);
465
468
  try {
466
- const data = await fs.promises.readFile(filePath, 'utf-8');
469
+ const data = await fs.promises.readFile(fullPath, 'utf-8');
467
470
  return JSON.parse(data);
468
471
  }
469
472
  catch (error) {
470
- if (error.code !== 'ENOENT') {
471
- console.error(`Error reading metadata ${id}:`, error);
473
+ if (error.code === 'ENOENT') {
474
+ return null;
472
475
  }
476
+ console.error(`Error reading object from ${pathStr}:`, error);
473
477
  return null;
474
478
  }
475
479
  }
480
+ /**
481
+ * Primitive operation: Delete object from path
482
+ * All metadata operations use this internally via base class routing
483
+ */
484
+ async deleteObjectFromPath(pathStr) {
485
+ await this.ensureInitialized();
486
+ const fullPath = path.join(this.rootDir, pathStr);
487
+ try {
488
+ await fs.promises.unlink(fullPath);
489
+ }
490
+ catch (error) {
491
+ if (error.code !== 'ENOENT') {
492
+ console.error(`Error deleting object from ${pathStr}:`, error);
493
+ throw error;
494
+ }
495
+ }
496
+ }
497
+ /**
498
+ * Primitive operation: List objects under path prefix
499
+ * All metadata operations use this internally via base class routing
500
+ */
501
+ async listObjectsUnderPath(prefix) {
502
+ await this.ensureInitialized();
503
+ const fullPath = path.join(this.rootDir, prefix);
504
+ const paths = [];
505
+ try {
506
+ const entries = await fs.promises.readdir(fullPath, { withFileTypes: true });
507
+ for (const entry of entries) {
508
+ if (entry.isFile() && entry.name.endsWith('.json')) {
509
+ paths.push(path.join(prefix, entry.name));
510
+ }
511
+ else if (entry.isDirectory()) {
512
+ const subdirPaths = await this.listObjectsUnderPath(path.join(prefix, entry.name));
513
+ paths.push(...subdirPaths);
514
+ }
515
+ }
516
+ return paths.sort();
517
+ }
518
+ catch (error) {
519
+ if (error.code === 'ENOENT') {
520
+ return [];
521
+ }
522
+ throw error;
523
+ }
524
+ }
476
525
  /**
477
526
  * Get multiple metadata objects in batches (CRITICAL: Prevents socket exhaustion)
478
527
  * FileSystem implementation uses controlled concurrency to prevent too many file reads
@@ -486,7 +535,7 @@ export class FileSystemStorage extends BaseStorage {
486
535
  const batch = ids.slice(i, i + batchSize);
487
536
  const batchPromises = batch.map(async (id) => {
488
537
  try {
489
- const metadata = await this.getNounMetadata(id);
538
+ const metadata = await this.getMetadata(id);
490
539
  return { id, metadata };
491
540
  }
492
541
  catch (error) {
@@ -505,75 +554,6 @@ export class FileSystemStorage extends BaseStorage {
505
554
  }
506
555
  return results;
507
556
  }
508
- /**
509
- * Save noun metadata to storage
510
- */
511
- async saveNounMetadata_internal(id, metadata) {
512
- await this.ensureInitialized();
513
- // Use UUID-based sharding for metadata (consistent with noun vectors)
514
- const filePath = this.getShardedPath(this.nounMetadataDir, id);
515
- // Ensure shard directory exists
516
- const shardDir = path.dirname(filePath);
517
- await fs.promises.mkdir(shardDir, { recursive: true });
518
- await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2));
519
- }
520
- /**
521
- * Get noun metadata from storage
522
- */
523
- async getNounMetadata(id) {
524
- await this.ensureInitialized();
525
- // Use UUID-based sharding for metadata (consistent with noun vectors)
526
- const filePath = this.getShardedPath(this.nounMetadataDir, id);
527
- try {
528
- const data = await fs.promises.readFile(filePath, 'utf-8');
529
- return JSON.parse(data);
530
- }
531
- catch (error) {
532
- if (error.code !== 'ENOENT') {
533
- console.error(`Error reading noun metadata ${id}:`, error);
534
- }
535
- return null;
536
- }
537
- }
538
- /**
539
- * Save verb metadata to storage
540
- */
541
- async saveVerbMetadata_internal(id, metadata) {
542
- await this.ensureInitialized();
543
- console.log(`[DEBUG] Saving verb metadata for ${id} to: ${this.verbMetadataDir}`);
544
- const filePath = path.join(this.verbMetadataDir, `${id}.json`);
545
- console.log(`[DEBUG] Full file path: ${filePath}`);
546
- try {
547
- await this.ensureDirectoryExists(path.dirname(filePath));
548
- console.log(`[DEBUG] Directory ensured: ${path.dirname(filePath)}`);
549
- await fs.promises.writeFile(filePath, JSON.stringify(metadata, null, 2));
550
- console.log(`[DEBUG] File written successfully: ${filePath}`);
551
- // Verify the file was actually written
552
- const exists = await fs.promises.access(filePath).then(() => true).catch(() => false);
553
- console.log(`[DEBUG] File exists after write: ${exists}`);
554
- }
555
- catch (error) {
556
- console.error(`[DEBUG] Error saving verb metadata:`, error);
557
- throw error;
558
- }
559
- }
560
- /**
561
- * Get verb metadata from storage
562
- */
563
- async getVerbMetadata(id) {
564
- await this.ensureInitialized();
565
- const filePath = path.join(this.verbMetadataDir, `${id}.json`);
566
- try {
567
- const data = await fs.promises.readFile(filePath, 'utf-8');
568
- return JSON.parse(data);
569
- }
570
- catch (error) {
571
- if (error.code !== 'ENOENT') {
572
- console.error(`Error reading verb metadata ${id}:`, error);
573
- }
574
- return null;
575
- }
576
- }
577
557
  /**
578
558
  * Get nouns with pagination support
579
559
  * @param options Pagination options
@@ -150,29 +150,29 @@ export declare class GcsStorage extends BaseStorage {
150
150
  */
151
151
  protected deleteNoun_internal(id: string): Promise<void>;
152
152
  /**
153
- * Save noun metadata to storage (internal implementation)
153
+ * Write an object to a specific path in GCS
154
+ * Primitive operation required by base class
155
+ * @protected
154
156
  */
155
- protected saveNounMetadata_internal(id: string, metadata: any): Promise<void>;
157
+ protected writeObjectToPath(path: string, data: any): Promise<void>;
156
158
  /**
157
- * Save metadata to storage (public API - delegates to saveNounMetadata_internal)
159
+ * Read an object from a specific path in GCS
160
+ * Primitive operation required by base class
161
+ * @protected
158
162
  */
159
- saveMetadata(id: string, metadata: any): Promise<void>;
163
+ protected readObjectFromPath(path: string): Promise<any | null>;
160
164
  /**
161
- * Get metadata from storage (public API - delegates to getNounMetadata)
165
+ * Delete an object from a specific path in GCS
166
+ * Primitive operation required by base class
167
+ * @protected
162
168
  */
163
- getMetadata(id: string): Promise<any | null>;
169
+ protected deleteObjectFromPath(path: string): Promise<void>;
164
170
  /**
165
- * Get noun metadata from storage
171
+ * List all objects under a specific prefix in GCS
172
+ * Primitive operation required by base class
173
+ * @protected
166
174
  */
167
- getNounMetadata(id: string): Promise<any | null>;
168
- /**
169
- * Save verb metadata to storage (internal implementation)
170
- */
171
- protected saveVerbMetadata_internal(id: string, metadata: any): Promise<void>;
172
- /**
173
- * Get verb metadata from storage
174
- */
175
- getVerbMetadata(id: string): Promise<any | null>;
175
+ protected listObjectsUnderPath(prefix: string): Promise<string[]>;
176
176
  /**
177
177
  * Save a verb to storage (internal implementation)
178
178
  */
@@ -438,113 +438,91 @@ export class GcsStorage extends BaseStorage {
438
438
  }
439
439
  }
440
440
  /**
441
- * Save noun metadata to storage (internal implementation)
441
+ * Write an object to a specific path in GCS
442
+ * Primitive operation required by base class
443
+ * @protected
442
444
  */
443
- async saveNounMetadata_internal(id, metadata) {
445
+ async writeObjectToPath(path, data) {
444
446
  await this.ensureInitialized();
445
447
  try {
446
- // Use UUID-based sharding for metadata (consistent with noun vectors)
447
- const shardId = getShardIdFromUuid(id);
448
- const key = `${this.metadataPrefix}${shardId}/${id}.json`;
449
- this.logger.trace(`Saving noun metadata for ${id} to key: ${key}`);
450
- // Save to GCS
451
- const file = this.bucket.file(key);
452
- await file.save(JSON.stringify(metadata, null, 2), {
448
+ this.logger.trace(`Writing object to path: ${path}`);
449
+ const file = this.bucket.file(path);
450
+ await file.save(JSON.stringify(data, null, 2), {
453
451
  contentType: 'application/json',
454
452
  resumable: false
455
453
  });
456
- this.logger.debug(`Noun metadata for ${id} saved successfully`);
454
+ this.logger.trace(`Object written successfully to ${path}`);
457
455
  }
458
456
  catch (error) {
459
- this.logger.error(`Failed to save noun metadata for ${id}:`, error);
460
- throw new Error(`Failed to save noun metadata for ${id}: ${error}`);
457
+ this.logger.error(`Failed to write object to ${path}:`, error);
458
+ throw new Error(`Failed to write object to ${path}: ${error}`);
461
459
  }
462
460
  }
463
461
  /**
464
- * Save metadata to storage (public API - delegates to saveNounMetadata_internal)
465
- */
466
- async saveMetadata(id, metadata) {
467
- return this.saveNounMetadata_internal(id, metadata);
468
- }
469
- /**
470
- * Get metadata from storage (public API - delegates to getNounMetadata)
471
- */
472
- async getMetadata(id) {
473
- return this.getNounMetadata(id);
474
- }
475
- /**
476
- * Get noun metadata from storage
462
+ * Read an object from a specific path in GCS
463
+ * Primitive operation required by base class
464
+ * @protected
477
465
  */
478
- async getNounMetadata(id) {
466
+ async readObjectFromPath(path) {
479
467
  await this.ensureInitialized();
480
468
  try {
481
- // Use UUID-based sharding for metadata
482
- const shardId = getShardIdFromUuid(id);
483
- const key = `${this.metadataPrefix}${shardId}/${id}.json`;
484
- this.logger.trace(`Getting noun metadata for ${id} from key: ${key}`);
485
- // Download from GCS
486
- const file = this.bucket.file(key);
469
+ this.logger.trace(`Reading object from path: ${path}`);
470
+ const file = this.bucket.file(path);
487
471
  const [contents] = await file.download();
488
- // Parse JSON
489
- const metadata = JSON.parse(contents.toString());
490
- this.logger.trace(`Successfully retrieved noun metadata for ${id}`);
491
- return metadata;
472
+ const data = JSON.parse(contents.toString());
473
+ this.logger.trace(`Object read successfully from ${path}`);
474
+ return data;
492
475
  }
493
476
  catch (error) {
494
477
  // Check if this is a "not found" error
495
478
  if (error.code === 404) {
496
- this.logger.trace(`Noun metadata not found for ${id}`);
479
+ this.logger.trace(`Object not found at ${path}`);
497
480
  return null;
498
481
  }
499
- // For other types of errors, convert to BrainyError
500
- throw BrainyError.fromError(error, `getNounMetadata(${id})`);
482
+ this.logger.error(`Failed to read object from ${path}:`, error);
483
+ throw BrainyError.fromError(error, `readObjectFromPath(${path})`);
501
484
  }
502
485
  }
503
486
  /**
504
- * Save verb metadata to storage (internal implementation)
487
+ * Delete an object from a specific path in GCS
488
+ * Primitive operation required by base class
489
+ * @protected
505
490
  */
506
- async saveVerbMetadata_internal(id, metadata) {
491
+ async deleteObjectFromPath(path) {
507
492
  await this.ensureInitialized();
508
493
  try {
509
- const key = `${this.verbMetadataPrefix}${id}.json`;
510
- this.logger.trace(`Saving verb metadata for ${id} to key: ${key}`);
511
- // Save to GCS
512
- const file = this.bucket.file(key);
513
- await file.save(JSON.stringify(metadata, null, 2), {
514
- contentType: 'application/json',
515
- resumable: false
516
- });
517
- this.logger.debug(`Verb metadata for ${id} saved successfully`);
494
+ this.logger.trace(`Deleting object at path: ${path}`);
495
+ const file = this.bucket.file(path);
496
+ await file.delete();
497
+ this.logger.trace(`Object deleted successfully from ${path}`);
518
498
  }
519
499
  catch (error) {
520
- this.logger.error(`Failed to save verb metadata for ${id}:`, error);
521
- throw new Error(`Failed to save verb metadata for ${id}: ${error}`);
500
+ // If already deleted (404), treat as success
501
+ if (error.code === 404) {
502
+ this.logger.trace(`Object at ${path} not found (already deleted)`);
503
+ return;
504
+ }
505
+ this.logger.error(`Failed to delete object from ${path}:`, error);
506
+ throw new Error(`Failed to delete object from ${path}: ${error}`);
522
507
  }
523
508
  }
524
509
  /**
525
- * Get verb metadata from storage
510
+ * List all objects under a specific prefix in GCS
511
+ * Primitive operation required by base class
512
+ * @protected
526
513
  */
527
- async getVerbMetadata(id) {
514
+ async listObjectsUnderPath(prefix) {
528
515
  await this.ensureInitialized();
529
516
  try {
530
- const key = `${this.verbMetadataPrefix}${id}.json`;
531
- this.logger.trace(`Getting verb metadata for ${id} from key: ${key}`);
532
- // Download from GCS
533
- const file = this.bucket.file(key);
534
- const [contents] = await file.download();
535
- // Parse JSON
536
- const metadata = JSON.parse(contents.toString());
537
- this.logger.trace(`Successfully retrieved verb metadata for ${id}`);
538
- return metadata;
517
+ this.logger.trace(`Listing objects under prefix: ${prefix}`);
518
+ const [files] = await this.bucket.getFiles({ prefix });
519
+ const paths = files.map((file) => file.name).filter((name) => name && name.length > 0);
520
+ this.logger.trace(`Found ${paths.length} objects under ${prefix}`);
521
+ return paths;
539
522
  }
540
523
  catch (error) {
541
- // Check if this is a "not found" error
542
- if (error.code === 404) {
543
- this.logger.trace(`Verb metadata not found for ${id}`);
544
- return null;
545
- }
546
- // For other types of errors, convert to BrainyError
547
- throw BrainyError.fromError(error, `getVerbMetadata(${id})`);
524
+ this.logger.error(`Failed to list objects under ${prefix}:`, error);
525
+ throw new Error(`Failed to list objects under ${prefix}: ${error}`);
548
526
  }
549
527
  }
550
528
  /**
@@ -12,10 +12,11 @@ import { PaginatedResult } from '../../types/paginationTypes.js';
12
12
  export declare class MemoryStorage extends BaseStorage {
13
13
  private nouns;
14
14
  private verbs;
15
- private metadata;
16
- private nounMetadata;
17
- private verbMetadata;
18
15
  private statistics;
16
+ private objectStore;
17
+ private get metadata();
18
+ private get nounMetadata();
19
+ private get verbMetadata();
19
20
  constructor();
20
21
  /**
21
22
  * Initialize the storage adapter
@@ -118,34 +119,30 @@ export declare class MemoryStorage extends BaseStorage {
118
119
  */
119
120
  protected deleteVerb_internal(id: string): Promise<void>;
120
121
  /**
121
- * Save metadata to storage
122
+ * Primitive operation: Write object to path
123
+ * All metadata operations use this internally via base class routing
122
124
  */
123
- saveMetadata(id: string, metadata: any): Promise<void>;
125
+ protected writeObjectToPath(path: string, data: any): Promise<void>;
124
126
  /**
125
- * Get metadata from storage
127
+ * Primitive operation: Read object from path
128
+ * All metadata operations use this internally via base class routing
126
129
  */
127
- getMetadata(id: string): Promise<any | null>;
130
+ protected readObjectFromPath(path: string): Promise<any | null>;
128
131
  /**
129
- * Get multiple metadata objects in batches (CRITICAL: Prevents socket exhaustion)
130
- * Memory storage implementation is simple since all data is already in memory
131
- */
132
- getMetadataBatch(ids: string[]): Promise<Map<string, any>>;
133
- /**
134
- * Save noun metadata to storage (internal implementation)
132
+ * Primitive operation: Delete object from path
133
+ * All metadata operations use this internally via base class routing
135
134
  */
136
- protected saveNounMetadata_internal(id: string, metadata: any): Promise<void>;
135
+ protected deleteObjectFromPath(path: string): Promise<void>;
137
136
  /**
138
- * Get noun metadata from storage
137
+ * Primitive operation: List objects under path prefix
138
+ * All metadata operations use this internally via base class routing
139
139
  */
140
- getNounMetadata(id: string): Promise<any | null>;
140
+ protected listObjectsUnderPath(prefix: string): Promise<string[]>;
141
141
  /**
142
- * Save verb metadata to storage (internal implementation)
143
- */
144
- protected saveVerbMetadata_internal(id: string, metadata: any): Promise<void>;
145
- /**
146
- * Get verb metadata from storage
142
+ * Get multiple metadata objects in batches (CRITICAL: Prevents socket exhaustion)
143
+ * Memory storage implementation is simple since all data is already in memory
147
144
  */
148
- getVerbMetadata(id: string): Promise<any | null>;
145
+ getMetadataBatch(ids: string[]): Promise<Map<string, any>>;
149
146
  /**
150
147
  * Clear all data from storage
151
148
  */