@soulcraft/brainy 3.50.2 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +201 -0
  2. package/README.md +358 -658
  3. package/dist/api/ConfigAPI.js +56 -19
  4. package/dist/api/DataAPI.js +24 -18
  5. package/dist/augmentations/storageAugmentations.d.ts +24 -0
  6. package/dist/augmentations/storageAugmentations.js +22 -0
  7. package/dist/brainy.js +32 -9
  8. package/dist/cli/commands/core.d.ts +20 -10
  9. package/dist/cli/commands/core.js +384 -82
  10. package/dist/cli/commands/import.d.ts +41 -0
  11. package/dist/cli/commands/import.js +456 -0
  12. package/dist/cli/commands/insights.d.ts +34 -0
  13. package/dist/cli/commands/insights.js +300 -0
  14. package/dist/cli/commands/neural.d.ts +6 -12
  15. package/dist/cli/commands/neural.js +113 -10
  16. package/dist/cli/commands/nlp.d.ts +28 -0
  17. package/dist/cli/commands/nlp.js +246 -0
  18. package/dist/cli/commands/storage.d.ts +64 -0
  19. package/dist/cli/commands/storage.js +730 -0
  20. package/dist/cli/index.js +210 -24
  21. package/dist/coreTypes.d.ts +206 -34
  22. package/dist/distributed/configManager.js +8 -6
  23. package/dist/distributed/shardMigration.js +2 -0
  24. package/dist/distributed/storageDiscovery.js +6 -4
  25. package/dist/embeddings/EmbeddingManager.d.ts +2 -2
  26. package/dist/embeddings/EmbeddingManager.js +5 -1
  27. package/dist/graph/lsm/LSMTree.js +32 -20
  28. package/dist/hnsw/typeAwareHNSWIndex.js +6 -2
  29. package/dist/storage/adapters/azureBlobStorage.d.ts +545 -0
  30. package/dist/storage/adapters/azureBlobStorage.js +1809 -0
  31. package/dist/storage/adapters/baseStorageAdapter.d.ts +16 -13
  32. package/dist/storage/adapters/fileSystemStorage.d.ts +21 -9
  33. package/dist/storage/adapters/fileSystemStorage.js +204 -127
  34. package/dist/storage/adapters/gcsStorage.d.ts +119 -9
  35. package/dist/storage/adapters/gcsStorage.js +317 -62
  36. package/dist/storage/adapters/memoryStorage.d.ts +30 -18
  37. package/dist/storage/adapters/memoryStorage.js +99 -94
  38. package/dist/storage/adapters/opfsStorage.d.ts +48 -10
  39. package/dist/storage/adapters/opfsStorage.js +201 -80
  40. package/dist/storage/adapters/r2Storage.d.ts +12 -5
  41. package/dist/storage/adapters/r2Storage.js +63 -15
  42. package/dist/storage/adapters/s3CompatibleStorage.d.ts +164 -17
  43. package/dist/storage/adapters/s3CompatibleStorage.js +472 -80
  44. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +38 -6
  45. package/dist/storage/adapters/typeAwareStorageAdapter.js +218 -39
  46. package/dist/storage/baseStorage.d.ts +41 -38
  47. package/dist/storage/baseStorage.js +110 -134
  48. package/dist/storage/storageFactory.d.ts +29 -2
  49. package/dist/storage/storageFactory.js +30 -1
  50. package/dist/utils/entityIdMapper.js +5 -2
  51. package/dist/utils/fieldTypeInference.js +8 -1
  52. package/dist/utils/metadataFilter.d.ts +3 -2
  53. package/dist/utils/metadataFilter.js +1 -0
  54. package/dist/utils/metadataIndex.js +2 -0
  55. package/dist/utils/metadataIndexChunking.js +9 -4
  56. package/dist/utils/periodicCleanup.js +1 -0
  57. package/package.json +3 -1
@@ -36,6 +36,11 @@ export class OPFSStorage extends BaseStorage {
36
36
  this.statistics = null;
37
37
  this.activeLocks = new Set();
38
38
  this.lockPrefix = 'opfs-lock-';
39
+ // Quota monitoring configuration (v4.0.0)
40
+ this.quotaWarningThreshold = 0.8; // Warn at 80% usage
41
+ this.quotaCriticalThreshold = 0.95; // Critical at 95% usage
42
+ this.lastQuotaCheck = 0;
43
+ this.quotaCheckInterval = 60000; // Check every 60 seconds
39
44
  // Check if OPFS is available
40
45
  this.isAvailable =
41
46
  typeof navigator !== 'undefined' &&
@@ -194,19 +199,15 @@ export class OPFSStorage extends BaseStorage {
194
199
  for (const [level, nounIds] of Object.entries(data.connections)) {
195
200
  connections.set(Number(level), new Set(nounIds));
196
201
  }
202
+ // v4.0.0: Return ONLY vector data (no metadata field)
197
203
  const node = {
198
204
  id: data.id,
199
205
  vector: data.vector,
200
206
  connections,
201
207
  level: data.level || 0
202
208
  };
203
- // Get metadata (entity data in 2-file system)
204
- const metadata = await this.getNounMetadata(id);
205
- // Combine into complete noun object
206
- return {
207
- ...node,
208
- metadata: metadata || {}
209
- };
209
+ // Return pure vector structure
210
+ return node;
210
211
  }
211
212
  catch (error) {
212
213
  // Noun not found or other error
@@ -346,21 +347,17 @@ export class OPFSStorage extends BaseStorage {
346
347
  }
347
348
  /**
348
349
  * Get a verb from storage (internal implementation)
349
- * Combines vector data from getEdge() with metadata from getVerbMetadata()
350
+ * v4.0.0: Returns ONLY vector + core relational fields (no metadata field)
351
+ * Base class combines with metadata via getVerb() -> HNSWVerbWithMetadata
350
352
  */
351
353
  async getVerb_internal(id) {
352
- // Get vector data (lightweight)
354
+ // v4.0.0: Return ONLY vector + core relational data (no metadata field)
353
355
  const edge = await this.getEdge(id);
354
356
  if (!edge) {
355
357
  return null;
356
358
  }
357
- // Get metadata (relationship data in 2-file system)
358
- const metadata = await this.getVerbMetadata(id);
359
- // Combine into complete verb object
360
- return {
361
- ...edge,
362
- metadata: metadata || {}
363
- };
359
+ // Return pure vector + core fields structure
360
+ return edge;
364
361
  }
365
362
  /**
366
363
  * Get an edge from storage
@@ -393,7 +390,7 @@ export class OPFSStorage extends BaseStorage {
393
390
  augmentation: 'unknown',
394
391
  version: '1.0'
395
392
  };
396
- // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
393
+ // v4.0.0: Return HNSWVerb with core relational fields (NO metadata field)
397
394
  return {
398
395
  id: data.id,
399
396
  vector: data.vector,
@@ -401,9 +398,9 @@ export class OPFSStorage extends BaseStorage {
401
398
  // CORE RELATIONAL DATA (read from vector file)
402
399
  verb: data.verb,
403
400
  sourceId: data.sourceId,
404
- targetId: data.targetId,
405
- // User metadata (retrieved separately via getVerbMetadata())
406
- metadata: data.metadata
401
+ targetId: data.targetId
402
+ // NO metadata field in v4.0.0
403
+ // User metadata retrieved separately via getVerbMetadata()
407
404
  };
408
405
  }
409
406
  catch (error) {
@@ -445,7 +442,7 @@ export class OPFSStorage extends BaseStorage {
445
442
  augmentation: 'unknown',
446
443
  version: '1.0'
447
444
  };
448
- // ARCHITECTURAL FIX (v3.50.1): Include core relational fields
445
+ // v4.0.0: Include core relational fields (NO metadata field)
449
446
  allEdges.push({
450
447
  id: data.id,
451
448
  vector: data.vector,
@@ -453,9 +450,9 @@ export class OPFSStorage extends BaseStorage {
453
450
  // CORE RELATIONAL DATA
454
451
  verb: data.verb,
455
452
  sourceId: data.sourceId,
456
- targetId: data.targetId,
457
- // User metadata
458
- metadata: data.metadata
453
+ targetId: data.targetId
454
+ // NO metadata field in v4.0.0
455
+ // User metadata retrieved separately via getVerbMetadata()
459
456
  });
460
457
  }
461
458
  catch (error) {
@@ -884,6 +881,108 @@ export class OPFSStorage extends BaseStorage {
884
881
  };
885
882
  }
886
883
  }
884
+ /**
885
+ * Get detailed quota status with warnings (v4.0.0)
886
+ * Monitors storage usage and warns when approaching quota limits
887
+ *
888
+ * @returns Promise that resolves to quota status with warning levels
889
+ *
890
+ * @example
891
+ * const status = await storage.getQuotaStatus()
892
+ * if (status.warning) {
893
+ * console.warn(`Storage ${status.usagePercent}% full: ${status.warningMessage}`)
894
+ * }
895
+ */
896
+ async getQuotaStatus() {
897
+ this.lastQuotaCheck = Date.now();
898
+ try {
899
+ if (!navigator.storage || !navigator.storage.estimate) {
900
+ return {
901
+ usage: 0,
902
+ quota: null,
903
+ usagePercent: 0,
904
+ remaining: null,
905
+ status: 'ok',
906
+ warning: false
907
+ };
908
+ }
909
+ const estimate = await navigator.storage.estimate();
910
+ const usage = estimate.usage || 0;
911
+ const quota = estimate.quota || null;
912
+ if (!quota) {
913
+ return {
914
+ usage,
915
+ quota: null,
916
+ usagePercent: 0,
917
+ remaining: null,
918
+ status: 'ok',
919
+ warning: false
920
+ };
921
+ }
922
+ const usagePercent = (usage / quota) * 100;
923
+ const remaining = quota - usage;
924
+ // Determine status
925
+ let status = 'ok';
926
+ let warning = false;
927
+ let warningMessage;
928
+ if (usagePercent >= this.quotaCriticalThreshold * 100) {
929
+ status = 'critical';
930
+ warning = true;
931
+ warningMessage = `Critical: Storage ${usagePercent.toFixed(1)}% full. Only ${(remaining / 1024 / 1024).toFixed(1)}MB remaining. Please delete old data.`;
932
+ }
933
+ else if (usagePercent >= this.quotaWarningThreshold * 100) {
934
+ status = 'warning';
935
+ warning = true;
936
+ warningMessage = `Warning: Storage ${usagePercent.toFixed(1)}% full. ${(remaining / 1024 / 1024).toFixed(1)}MB remaining.`;
937
+ }
938
+ if (warning) {
939
+ console.warn(`[OPFS Quota] ${warningMessage}`);
940
+ }
941
+ return {
942
+ usage,
943
+ quota,
944
+ usagePercent,
945
+ remaining,
946
+ status,
947
+ warning,
948
+ warningMessage
949
+ };
950
+ }
951
+ catch (error) {
952
+ console.error('Failed to get quota status:', error);
953
+ return {
954
+ usage: 0,
955
+ quota: null,
956
+ usagePercent: 0,
957
+ remaining: null,
958
+ status: 'ok',
959
+ warning: false
960
+ };
961
+ }
962
+ }
963
+ /**
964
+ * Monitor quota during operations (v4.0.0)
965
+ * Automatically checks quota at regular intervals and warns if approaching limits
966
+ * Call this before write operations to ensure quota is available
967
+ *
968
+ * @returns Promise that resolves when quota check is complete
969
+ *
970
+ * @example
971
+ * await storage.monitorQuota() // Checks quota if interval has passed
972
+ * await storage.saveNoun(noun) // Proceed with write operation
973
+ */
974
+ async monitorQuota() {
975
+ const now = Date.now();
976
+ // Only check if interval has passed
977
+ if (now - this.lastQuotaCheck < this.quotaCheckInterval) {
978
+ return;
979
+ }
980
+ const status = await this.getQuotaStatus();
981
+ // If critical, throw error to prevent data loss
982
+ if (status.status === 'critical' && status.warningMessage) {
983
+ throw new Error(`Storage quota critical: ${status.warningMessage}`);
984
+ }
985
+ }
887
986
  /**
888
987
  * Get the statistics key for a specific date
889
988
  * @param date The date to get the key for
@@ -1304,22 +1403,25 @@ export class OPFSStorage extends BaseStorage {
1304
1403
  }
1305
1404
  // Get the subset of files for this page
1306
1405
  const pageFiles = nounFiles.slice(startIndex, startIndex + limit);
1307
- // Load nouns from files
1406
+ // v4.0.0: Load nouns from files and combine with metadata
1308
1407
  const items = [];
1309
1408
  for (const fileName of pageFiles) {
1310
1409
  // fileName is in format "shard/uuid.json", extract just the UUID
1311
1410
  const id = fileName.split('/')[1].replace('.json', '');
1312
1411
  const noun = await this.getNoun_internal(id);
1313
1412
  if (noun) {
1413
+ // Load metadata for filtering and combining
1414
+ const metadata = await this.getNounMetadata(id);
1415
+ if (!metadata)
1416
+ continue;
1314
1417
  // Apply filters if provided
1315
1418
  if (options.filter) {
1316
- const metadata = await this.getNounMetadata(id);
1317
1419
  // Filter by noun type
1318
1420
  if (options.filter.nounType) {
1319
1421
  const nounTypes = Array.isArray(options.filter.nounType)
1320
1422
  ? options.filter.nounType
1321
1423
  : [options.filter.nounType];
1322
- if (metadata && !nounTypes.includes(metadata.type || metadata.noun)) {
1424
+ if (!nounTypes.includes((metadata.type || metadata.noun))) {
1323
1425
  continue;
1324
1426
  }
1325
1427
  }
@@ -1328,14 +1430,12 @@ export class OPFSStorage extends BaseStorage {
1328
1430
  const services = Array.isArray(options.filter.service)
1329
1431
  ? options.filter.service
1330
1432
  : [options.filter.service];
1331
- if (metadata && !services.includes(metadata.createdBy?.augmentation)) {
1433
+ if (!metadata.createdBy?.augmentation || !services.includes(metadata.createdBy.augmentation)) {
1332
1434
  continue;
1333
1435
  }
1334
1436
  }
1335
1437
  // Filter by metadata
1336
1438
  if (options.filter.metadata) {
1337
- if (!metadata)
1338
- continue;
1339
1439
  let matches = true;
1340
1440
  for (const [key, value] of Object.entries(options.filter.metadata)) {
1341
1441
  if (metadata[key] !== value) {
@@ -1347,7 +1447,15 @@ export class OPFSStorage extends BaseStorage {
1347
1447
  continue;
1348
1448
  }
1349
1449
  }
1350
- items.push(noun);
1450
+ // v4.0.0: Create HNSWNounWithMetadata by combining noun with metadata
1451
+ const nounWithMetadata = {
1452
+ id: noun.id,
1453
+ vector: [...noun.vector],
1454
+ connections: new Map(noun.connections),
1455
+ level: noun.level || 0,
1456
+ metadata: metadata
1457
+ };
1458
+ items.push(nounWithMetadata);
1351
1459
  }
1352
1460
  }
1353
1461
  // Determine if there are more items
@@ -1400,69 +1508,82 @@ export class OPFSStorage extends BaseStorage {
1400
1508
  }
1401
1509
  // Get the subset of files for this page
1402
1510
  const pageFiles = verbFiles.slice(startIndex, startIndex + limit);
1403
- // Load verbs from files and convert to GraphVerb
1511
+ // v4.0.0: Load verbs from files and combine with metadata
1404
1512
  const items = [];
1405
1513
  for (const fileName of pageFiles) {
1406
1514
  // fileName is in format "shard/uuid.json", extract just the UUID
1407
1515
  const id = fileName.split('/')[1].replace('.json', '');
1408
1516
  const hnswVerb = await this.getVerb_internal(id);
1409
1517
  if (hnswVerb) {
1410
- // Convert HNSWVerb to GraphVerb
1411
- const graphVerb = await this.convertHNSWVerbToGraphVerb(hnswVerb);
1412
- if (graphVerb) {
1413
- // Apply filters if provided
1414
- if (options.filter) {
1415
- // Filter by verb type
1416
- if (options.filter.verbType) {
1417
- const verbTypes = Array.isArray(options.filter.verbType)
1418
- ? options.filter.verbType
1419
- : [options.filter.verbType];
1420
- if (graphVerb.verb && !verbTypes.includes(graphVerb.verb)) {
1421
- continue;
1422
- }
1518
+ // Load metadata for filtering and combining
1519
+ const metadata = await this.getVerbMetadata(id);
1520
+ if (!metadata)
1521
+ continue;
1522
+ // Apply filters if provided
1523
+ if (options.filter) {
1524
+ // Filter by verb type
1525
+ // v4.0.0: verb field is in HNSWVerb structure (NOT in metadata)
1526
+ if (options.filter.verbType) {
1527
+ const verbTypes = Array.isArray(options.filter.verbType)
1528
+ ? options.filter.verbType
1529
+ : [options.filter.verbType];
1530
+ if (!hnswVerb.verb || !verbTypes.includes(hnswVerb.verb)) {
1531
+ continue;
1423
1532
  }
1424
- // Filter by source ID
1425
- if (options.filter.sourceId) {
1426
- const sourceIds = Array.isArray(options.filter.sourceId)
1427
- ? options.filter.sourceId
1428
- : [options.filter.sourceId];
1429
- if (graphVerb.source && !sourceIds.includes(graphVerb.source)) {
1430
- continue;
1431
- }
1533
+ }
1534
+ // Filter by source ID
1535
+ // v4.0.0: sourceId field is in HNSWVerb structure (NOT in metadata)
1536
+ if (options.filter.sourceId) {
1537
+ const sourceIds = Array.isArray(options.filter.sourceId)
1538
+ ? options.filter.sourceId
1539
+ : [options.filter.sourceId];
1540
+ if (!hnswVerb.sourceId || !sourceIds.includes(hnswVerb.sourceId)) {
1541
+ continue;
1432
1542
  }
1433
- // Filter by target ID
1434
- if (options.filter.targetId) {
1435
- const targetIds = Array.isArray(options.filter.targetId)
1436
- ? options.filter.targetId
1437
- : [options.filter.targetId];
1438
- if (graphVerb.target && !targetIds.includes(graphVerb.target)) {
1439
- continue;
1440
- }
1543
+ }
1544
+ // Filter by target ID
1545
+ // v4.0.0: targetId field is in HNSWVerb structure (NOT in metadata)
1546
+ if (options.filter.targetId) {
1547
+ const targetIds = Array.isArray(options.filter.targetId)
1548
+ ? options.filter.targetId
1549
+ : [options.filter.targetId];
1550
+ if (!hnswVerb.targetId || !targetIds.includes(hnswVerb.targetId)) {
1551
+ continue;
1441
1552
  }
1442
- // Filter by service
1443
- if (options.filter.service) {
1444
- const services = Array.isArray(options.filter.service)
1445
- ? options.filter.service
1446
- : [options.filter.service];
1447
- if (graphVerb.createdBy?.augmentation && !services.includes(graphVerb.createdBy.augmentation)) {
1448
- continue;
1449
- }
1553
+ }
1554
+ // Filter by service
1555
+ if (options.filter.service) {
1556
+ const services = Array.isArray(options.filter.service)
1557
+ ? options.filter.service
1558
+ : [options.filter.service];
1559
+ if (!metadata.createdBy?.augmentation || !services.includes(metadata.createdBy.augmentation)) {
1560
+ continue;
1450
1561
  }
1451
- // Filter by metadata
1452
- if (options.filter.metadata && graphVerb.metadata) {
1453
- let matches = true;
1454
- for (const [key, value] of Object.entries(options.filter.metadata)) {
1455
- if (graphVerb.metadata[key] !== value) {
1456
- matches = false;
1457
- break;
1458
- }
1562
+ }
1563
+ // Filter by metadata
1564
+ if (options.filter.metadata) {
1565
+ let matches = true;
1566
+ for (const [key, value] of Object.entries(options.filter.metadata)) {
1567
+ if (metadata[key] !== value) {
1568
+ matches = false;
1569
+ break;
1459
1570
  }
1460
- if (!matches)
1461
- continue;
1462
1571
  }
1572
+ if (!matches)
1573
+ continue;
1463
1574
  }
1464
- items.push(graphVerb);
1465
1575
  }
1576
+ // v4.0.0: Create HNSWVerbWithMetadata by combining verb with metadata
1577
+ const verbWithMetadata = {
1578
+ id: hnswVerb.id,
1579
+ vector: [...hnswVerb.vector],
1580
+ connections: new Map(hnswVerb.connections),
1581
+ verb: hnswVerb.verb,
1582
+ sourceId: hnswVerb.sourceId,
1583
+ targetId: hnswVerb.targetId,
1584
+ metadata: metadata
1585
+ };
1586
+ items.push(verbWithMetadata);
1466
1587
  }
1467
1588
  }
1468
1589
  // Determine if there are more items
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * Based on latest GCS and S3 implementations with R2-specific enhancements
13
13
  */
14
- import { GraphVerb, HNSWNoun, HNSWVerb, StatisticsData } from '../../coreTypes.js';
14
+ import { HNSWNoun, HNSWVerb, HNSWNounWithMetadata, HNSWVerbWithMetadata, StatisticsData } from '../../coreTypes.js';
15
15
  import { BaseStorage } from '../baseStorage.js';
16
16
  type HNSWNode = HNSWNoun;
17
17
  type Edge = HNSWVerb;
@@ -130,6 +130,8 @@ export declare class R2Storage extends BaseStorage {
130
130
  private saveNodeDirect;
131
131
  /**
132
132
  * Get a noun from storage (internal implementation)
133
+ * v4.0.0: Returns ONLY vector data (no metadata field)
134
+ * Base class combines with metadata via getNoun() -> HNSWNounWithMetadata
133
135
  */
134
136
  protected getNoun_internal(id: string): Promise<HNSWNoun | null>;
135
137
  /**
@@ -159,6 +161,11 @@ export declare class R2Storage extends BaseStorage {
159
161
  protected saveVerb_internal(verb: HNSWVerb): Promise<void>;
160
162
  protected saveEdge(edge: Edge): Promise<void>;
161
163
  private saveEdgeDirect;
164
+ /**
165
+ * Get a verb from storage (internal implementation)
166
+ * v4.0.0: Returns ONLY vector + core relational fields (no metadata field)
167
+ * Base class combines with metadata via getVerb() -> HNSWVerbWithMetadata
168
+ */
162
169
  protected getVerb_internal(id: string): Promise<HNSWVerb | null>;
163
170
  protected getEdge(id: string): Promise<Edge | null>;
164
171
  protected deleteVerb_internal(id: string): Promise<void>;
@@ -200,14 +207,14 @@ export declare class R2Storage extends BaseStorage {
200
207
  metadata?: Record<string, any>;
201
208
  };
202
209
  }): Promise<{
203
- items: HNSWNoun[];
210
+ items: HNSWNounWithMetadata[];
204
211
  totalCount?: number;
205
212
  hasMore: boolean;
206
213
  nextCursor?: string;
207
214
  }>;
208
215
  protected getNounsByNounType_internal(nounType: string): Promise<HNSWNoun[]>;
209
- protected getVerbsBySource_internal(sourceId: string): Promise<GraphVerb[]>;
210
- protected getVerbsByTarget_internal(targetId: string): Promise<GraphVerb[]>;
211
- protected getVerbsByType_internal(type: string): Promise<GraphVerb[]>;
216
+ protected getVerbsBySource_internal(sourceId: string): Promise<HNSWVerbWithMetadata[]>;
217
+ protected getVerbsByTarget_internal(targetId: string): Promise<HNSWVerbWithMetadata[]>;
218
+ protected getVerbsByType_internal(type: string): Promise<HNSWVerbWithMetadata[]>;
212
219
  }
213
220
  export {};
@@ -326,17 +326,17 @@ export class R2Storage extends BaseStorage {
326
326
  }
327
327
  /**
328
328
  * Get a noun from storage (internal implementation)
329
+ * v4.0.0: Returns ONLY vector data (no metadata field)
330
+ * Base class combines with metadata via getNoun() -> HNSWNounWithMetadata
329
331
  */
330
332
  async getNoun_internal(id) {
333
+ // v4.0.0: Return ONLY vector data (no metadata field)
331
334
  const node = await this.getNode(id);
332
335
  if (!node) {
333
336
  return null;
334
337
  }
335
- const metadata = await this.getNounMetadata(id);
336
- return {
337
- ...node,
338
- metadata: metadata || {}
339
- };
338
+ // Return pure vector structure
339
+ return node;
340
340
  }
341
341
  /**
342
342
  * Get a node from storage
@@ -589,16 +589,19 @@ export class R2Storage extends BaseStorage {
589
589
  throw new Error(`Failed to save edge ${edge.id}: ${error}`);
590
590
  }
591
591
  }
592
+ /**
593
+ * Get a verb from storage (internal implementation)
594
+ * v4.0.0: Returns ONLY vector + core relational fields (no metadata field)
595
+ * Base class combines with metadata via getVerb() -> HNSWVerbWithMetadata
596
+ */
592
597
  async getVerb_internal(id) {
598
+ // v4.0.0: Return ONLY vector + core relational data (no metadata field)
593
599
  const edge = await this.getEdge(id);
594
600
  if (!edge) {
595
601
  return null;
596
602
  }
597
- const metadata = await this.getVerbMetadata(id);
598
- return {
599
- ...edge,
600
- metadata: metadata || {}
601
- };
603
+ // Return pure vector + core fields structure
604
+ return edge;
602
605
  }
603
606
  async getEdge(id) {
604
607
  await this.ensureInitialized();
@@ -620,7 +623,7 @@ export class R2Storage extends BaseStorage {
620
623
  for (const [level, verbIds] of Object.entries(data.connections || {})) {
621
624
  connections.set(Number(level), new Set(verbIds));
622
625
  }
623
- // ARCHITECTURAL FIX (v3.50.1): Return HNSWVerb with core relational fields
626
+ // v4.0.0: Return HNSWVerb with core relational fields (NO metadata field)
624
627
  const edge = {
625
628
  id: data.id,
626
629
  vector: data.vector,
@@ -628,9 +631,9 @@ export class R2Storage extends BaseStorage {
628
631
  // CORE RELATIONAL DATA (read from vector file)
629
632
  verb: data.verb,
630
633
  sourceId: data.sourceId,
631
- targetId: data.targetId,
632
- // User metadata (retrieved separately via getVerbMetadata())
633
- metadata: data.metadata
634
+ targetId: data.targetId
635
+ // NO metadata field in v4.0.0
636
+ // User metadata retrieved separately via getVerbMetadata()
634
637
  };
635
638
  this.verbCacheManager.set(id, edge);
636
639
  this.releaseBackpressure(true, requestId);
@@ -860,7 +863,52 @@ export class R2Storage extends BaseStorage {
860
863
  continue;
861
864
  const noun = await this.getNoun_internal(id);
862
865
  if (noun) {
863
- items.push(noun);
866
+ // v4.0.0: Load metadata and combine with noun to create HNSWNounWithMetadata
867
+ const metadata = await this.getNounMetadata(id);
868
+ if (!metadata)
869
+ continue;
870
+ // Apply filters if provided
871
+ if (options.filter) {
872
+ // Filter by noun type
873
+ if (options.filter.nounType) {
874
+ const nounTypes = Array.isArray(options.filter.nounType)
875
+ ? options.filter.nounType
876
+ : [options.filter.nounType];
877
+ if (!nounTypes.includes((metadata.type || metadata.noun))) {
878
+ continue;
879
+ }
880
+ }
881
+ // Filter by service
882
+ if (options.filter.service) {
883
+ const services = Array.isArray(options.filter.service)
884
+ ? options.filter.service
885
+ : [options.filter.service];
886
+ if (!metadata.createdBy?.augmentation || !services.includes(metadata.createdBy.augmentation)) {
887
+ continue;
888
+ }
889
+ }
890
+ // Filter by metadata
891
+ if (options.filter.metadata) {
892
+ let matches = true;
893
+ for (const [key, value] of Object.entries(options.filter.metadata)) {
894
+ if (metadata[key] !== value) {
895
+ matches = false;
896
+ break;
897
+ }
898
+ }
899
+ if (!matches)
900
+ continue;
901
+ }
902
+ }
903
+ // v4.0.0: Create HNSWNounWithMetadata by combining noun with metadata
904
+ const nounWithMetadata = {
905
+ id: noun.id,
906
+ vector: [...noun.vector],
907
+ connections: new Map(noun.connections),
908
+ level: noun.level || 0,
909
+ metadata: metadata
910
+ };
911
+ items.push(nounWithMetadata);
864
912
  }
865
913
  }
866
914
  return {