@soulcraft/brainy 4.7.4 → 4.8.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.
@@ -8,6 +8,7 @@
8
8
  * 3. Service Account Credentials Object
9
9
  * 4. HMAC Keys (fallback for backward compatibility)
10
10
  */
11
+ import { NounType } from '../../coreTypes.js';
11
12
  import { BaseStorage, SYSTEM_DIR, STATISTICS_KEY, getDirectoryPath } from '../baseStorage.js';
12
13
  import { BrainyError } from '../../errors/brainyError.js';
13
14
  import { CacheManager } from '../cacheManager.js';
@@ -832,13 +833,23 @@ export class GcsStorage extends BaseStorage {
832
833
  continue;
833
834
  }
834
835
  }
835
- // Combine node with metadata
836
+ // v4.8.0: Extract standard fields from metadata to top-level
837
+ const metadataObj = (metadata || {});
838
+ const { noun: nounType, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
836
839
  const nounWithMetadata = {
837
840
  id: node.id,
838
841
  vector: [...node.vector],
839
842
  connections: new Map(node.connections),
840
843
  level: node.level || 0,
841
- metadata: (metadata || {}) // Empty if none
844
+ type: nounType || NounType.Thing,
845
+ createdAt: createdAt || Date.now(),
846
+ updatedAt: updatedAt || Date.now(),
847
+ confidence: confidence,
848
+ weight: weight,
849
+ service: service,
850
+ data: data,
851
+ createdBy,
852
+ metadata: customMetadata
842
853
  };
843
854
  items.push(nounWithMetadata);
844
855
  }
@@ -1076,7 +1087,9 @@ export class GcsStorage extends BaseStorage {
1076
1087
  continue;
1077
1088
  }
1078
1089
  }
1079
- // Combine verb with metadata
1090
+ // v4.8.0: Extract standard fields from metadata to top-level
1091
+ const metadataObj = (metadata || {});
1092
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
1080
1093
  const verbWithMetadata = {
1081
1094
  id: hnswVerb.id,
1082
1095
  vector: [...hnswVerb.vector],
@@ -1084,7 +1097,14 @@ export class GcsStorage extends BaseStorage {
1084
1097
  verb: hnswVerb.verb,
1085
1098
  sourceId: hnswVerb.sourceId,
1086
1099
  targetId: hnswVerb.targetId,
1087
- metadata: metadata || {}
1100
+ createdAt: createdAt || Date.now(),
1101
+ updatedAt: updatedAt || Date.now(),
1102
+ confidence: confidence,
1103
+ weight: weight,
1104
+ service: service,
1105
+ data: data,
1106
+ createdBy,
1107
+ metadata: customMetadata
1088
1108
  };
1089
1109
  items.push(verbWithMetadata);
1090
1110
  }
@@ -2,6 +2,7 @@
2
2
  * Memory Storage Adapter
3
3
  * In-memory storage adapter for environments where persistent storage is not available or needed
4
4
  */
5
+ import { NounType } from '../../coreTypes.js';
5
6
  import { BaseStorage } from '../baseStorage.js';
6
7
  // No type aliases needed - using the original types directly
7
8
  /**
@@ -154,13 +155,26 @@ export class MemoryStorage extends BaseStorage {
154
155
  // Get metadata from separate storage
155
156
  // FIX v4.7.4: Don't skip nouns without metadata - metadata is optional in v4.0.0
156
157
  const metadata = await this.getNounMetadata(id);
157
- // v4.0.0: Create HNSWNounWithMetadata with metadata field
158
+ // v4.8.0: Extract standard fields from metadata to top-level
159
+ const metadataObj = (metadata || {});
160
+ const { noun: nounType, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
161
+ // v4.8.0: Create HNSWNounWithMetadata with standard fields at top-level
158
162
  const nounWithMetadata = {
159
163
  id: noun.id,
160
164
  vector: [...noun.vector],
161
165
  connections: new Map(),
162
166
  level: noun.level || 0,
163
- metadata: (metadata || {}) // Include metadata field (empty if none)
167
+ // v4.8.0: Standard fields at top-level
168
+ type: nounType || NounType.Thing,
169
+ createdAt: createdAt || Date.now(),
170
+ updatedAt: updatedAt || Date.now(),
171
+ confidence: confidence,
172
+ weight: weight,
173
+ service: service,
174
+ data: data,
175
+ createdBy,
176
+ // Only custom user fields in metadata
177
+ metadata: customMetadata
164
178
  };
165
179
  // Copy connections
166
180
  for (const [level, connections] of noun.connections.entries()) {
@@ -359,7 +373,10 @@ export class MemoryStorage extends BaseStorage {
359
373
  // FIX v4.7.4: Don't skip verbs without metadata - metadata is optional in v4.0.0
360
374
  // Core fields (verb, sourceId, targetId) are in HNSWVerb itself
361
375
  const metadata = await this.getVerbMetadata(id);
362
- // v4.0.0: Create HNSWVerbWithMetadata with metadata field
376
+ // v4.8.0: Extract standard fields from metadata to top-level
377
+ const metadataObj = metadata || {};
378
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
379
+ // v4.8.0: Create HNSWVerbWithMetadata with standard fields at top-level
363
380
  const verbWithMetadata = {
364
381
  id: hnswVerb.id,
365
382
  vector: [...hnswVerb.vector],
@@ -368,8 +385,16 @@ export class MemoryStorage extends BaseStorage {
368
385
  verb: hnswVerb.verb,
369
386
  sourceId: hnswVerb.sourceId,
370
387
  targetId: hnswVerb.targetId,
371
- // Metadata field (empty if none)
372
- metadata: metadata || {}
388
+ // v4.8.0: Standard fields at top-level
389
+ createdAt: createdAt || Date.now(),
390
+ updatedAt: updatedAt || Date.now(),
391
+ confidence: confidence,
392
+ weight: weight,
393
+ service: service,
394
+ data: data,
395
+ createdBy,
396
+ // Only custom user fields in metadata
397
+ metadata: customMetadata
373
398
  };
374
399
  // Copy connections
375
400
  for (const [level, connections] of hnswVerb.connections.entries()) {
@@ -2,6 +2,7 @@
2
2
  * OPFS (Origin Private File System) Storage Adapter
3
3
  * Provides persistent storage for the vector database using the Origin Private File System API
4
4
  */
5
+ import { NounType } from '../../coreTypes.js';
5
6
  import { BaseStorage, NOUNS_DIR, VERBS_DIR, METADATA_DIR, NOUN_METADATA_DIR, VERB_METADATA_DIR, INDEX_DIR } from '../baseStorage.js';
6
7
  import { getShardIdFromUuid } from '../sharding.js';
7
8
  import '../../types/fileSystemTypes.js';
@@ -1446,13 +1447,23 @@ export class OPFSStorage extends BaseStorage {
1446
1447
  continue;
1447
1448
  }
1448
1449
  }
1449
- // v4.0.0: Create HNSWNounWithMetadata by combining noun with metadata
1450
+ // v4.8.0: Extract standard fields from metadata to top-level
1451
+ const metadataObj = (metadata || {});
1452
+ const { noun: nounType, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
1450
1453
  const nounWithMetadata = {
1451
1454
  id: noun.id,
1452
1455
  vector: [...noun.vector],
1453
1456
  connections: new Map(noun.connections),
1454
1457
  level: noun.level || 0,
1455
- metadata: (metadata || {}) // Empty if none
1458
+ type: nounType || NounType.Thing,
1459
+ createdAt: createdAt || Date.now(),
1460
+ updatedAt: updatedAt || Date.now(),
1461
+ confidence: confidence,
1462
+ weight: weight,
1463
+ service: service,
1464
+ data: data,
1465
+ createdBy,
1466
+ metadata: customMetadata
1456
1467
  };
1457
1468
  items.push(nounWithMetadata);
1458
1469
  }
@@ -1572,7 +1583,9 @@ export class OPFSStorage extends BaseStorage {
1572
1583
  continue;
1573
1584
  }
1574
1585
  }
1575
- // v4.0.0: Create HNSWVerbWithMetadata by combining verb with metadata
1586
+ // v4.8.0: Extract standard fields from metadata to top-level
1587
+ const metadataObj = (metadata || {});
1588
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
1576
1589
  const verbWithMetadata = {
1577
1590
  id: hnswVerb.id,
1578
1591
  vector: [...hnswVerb.vector],
@@ -1580,7 +1593,14 @@ export class OPFSStorage extends BaseStorage {
1580
1593
  verb: hnswVerb.verb,
1581
1594
  sourceId: hnswVerb.sourceId,
1582
1595
  targetId: hnswVerb.targetId,
1583
- metadata: (metadata || {}) // Empty if none
1596
+ createdAt: createdAt || Date.now(),
1597
+ updatedAt: updatedAt || Date.now(),
1598
+ confidence: confidence,
1599
+ weight: weight,
1600
+ service: service,
1601
+ data: data,
1602
+ createdBy,
1603
+ metadata: customMetadata
1584
1604
  };
1585
1605
  items.push(verbWithMetadata);
1586
1606
  }
@@ -11,6 +11,7 @@
11
11
  *
12
12
  * Based on latest GCS and S3 implementations with R2-specific enhancements
13
13
  */
14
+ import { NounType } from '../../coreTypes.js';
14
15
  import { BaseStorage, SYSTEM_DIR, STATISTICS_KEY, getDirectoryPath } from '../baseStorage.js';
15
16
  import { BrainyError } from '../../errors/brainyError.js';
16
17
  import { CacheManager } from '../cacheManager.js';
@@ -918,13 +919,23 @@ export class R2Storage extends BaseStorage {
918
919
  continue;
919
920
  }
920
921
  }
921
- // v4.0.0: Create HNSWNounWithMetadata by combining noun with metadata
922
+ // v4.8.0: Extract standard fields from metadata to top-level
923
+ const metadataObj = (metadata || {});
924
+ const { noun: nounType, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
922
925
  const nounWithMetadata = {
923
926
  id: noun.id,
924
927
  vector: [...noun.vector],
925
928
  connections: new Map(noun.connections),
926
929
  level: noun.level || 0,
927
- metadata: (metadata || {}) // Empty if none
930
+ type: nounType || NounType.Thing,
931
+ createdAt: createdAt || Date.now(),
932
+ updatedAt: updatedAt || Date.now(),
933
+ confidence: confidence,
934
+ weight: weight,
935
+ service: service,
936
+ data: data,
937
+ createdBy,
938
+ metadata: customMetadata
928
939
  };
929
940
  items.push(nounWithMetadata);
930
941
  }
@@ -3,6 +3,7 @@
3
3
  * Uses the AWS S3 client to interact with S3-compatible storage services
4
4
  * including Amazon S3, Cloudflare R2, and Google Cloud Storage
5
5
  */
6
+ import { NounType } from '../../coreTypes.js';
6
7
  import { BaseStorage, INDEX_DIR, SYSTEM_DIR, STATISTICS_KEY, getDirectoryPath } from '../baseStorage.js';
7
8
  import { StorageCompatibilityLayer } from '../backwardCompatibility.js';
8
9
  import { StorageOperationExecutors } from '../../utils/operationUtils.js';
@@ -1479,6 +1480,9 @@ export class S3CompatibleStorage extends BaseStorage {
1479
1480
  const verbsWithMetadata = [];
1480
1481
  for (const hnswVerb of result.edges) {
1481
1482
  const metadata = await this.getVerbMetadata(hnswVerb.id);
1483
+ // v4.8.0: Extract standard fields from metadata to top-level
1484
+ const metadataObj = (metadata || {});
1485
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
1482
1486
  const verbWithMetadata = {
1483
1487
  id: hnswVerb.id,
1484
1488
  vector: [...hnswVerb.vector],
@@ -1486,7 +1490,14 @@ export class S3CompatibleStorage extends BaseStorage {
1486
1490
  verb: hnswVerb.verb,
1487
1491
  sourceId: hnswVerb.sourceId,
1488
1492
  targetId: hnswVerb.targetId,
1489
- metadata: metadata || {}
1493
+ createdAt: createdAt || Date.now(),
1494
+ updatedAt: updatedAt || Date.now(),
1495
+ confidence: confidence,
1496
+ weight: weight,
1497
+ service: service,
1498
+ data: data,
1499
+ createdBy,
1500
+ metadata: customMetadata
1490
1501
  };
1491
1502
  verbsWithMetadata.push(verbWithMetadata);
1492
1503
  }
@@ -2899,13 +2910,23 @@ export class S3CompatibleStorage extends BaseStorage {
2899
2910
  }
2900
2911
  }
2901
2912
  }
2902
- // Create HNSWNounWithMetadata
2913
+ // v4.8.0: Extract standard fields from metadata to top-level
2914
+ const metadataObj = (metadata || {});
2915
+ const { noun: nounType, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
2903
2916
  const nounWithMetadata = {
2904
2917
  id: node.id,
2905
2918
  vector: [...node.vector],
2906
2919
  connections: new Map(node.connections),
2907
2920
  level: node.level || 0,
2908
- metadata: (metadata || {}) // Empty if none
2921
+ type: nounType || NounType.Thing,
2922
+ createdAt: createdAt || Date.now(),
2923
+ updatedAt: updatedAt || Date.now(),
2924
+ confidence: confidence,
2925
+ weight: weight,
2926
+ service: service,
2927
+ data: data,
2928
+ createdBy,
2929
+ metadata: customMetadata
2909
2930
  };
2910
2931
  nounsWithMetadata.push(nounWithMetadata);
2911
2932
  }
@@ -366,6 +366,9 @@ export class TypeAwareStorageAdapter extends BaseStorage {
366
366
  connectionsMap.set(Number(level), new Set(ids));
367
367
  }
368
368
  }
369
+ // v4.8.0: Extract standard fields from metadata to top-level
370
+ const metadataObj = (metadata || {});
371
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
369
372
  const verbWithMetadata = {
370
373
  id: hnswVerb.id,
371
374
  vector: [...hnswVerb.vector],
@@ -373,7 +376,14 @@ export class TypeAwareStorageAdapter extends BaseStorage {
373
376
  verb: hnswVerb.verb,
374
377
  sourceId: hnswVerb.sourceId,
375
378
  targetId: hnswVerb.targetId,
376
- metadata: metadata || {} // Empty metadata if none exists
379
+ createdAt: createdAt || Date.now(),
380
+ updatedAt: updatedAt || Date.now(),
381
+ confidence: confidence,
382
+ weight: weight,
383
+ service: service,
384
+ data: data,
385
+ createdBy,
386
+ metadata: customMetadata
377
387
  };
378
388
  verbs.push(verbWithMetadata);
379
389
  }
@@ -417,6 +427,9 @@ export class TypeAwareStorageAdapter extends BaseStorage {
417
427
  connectionsMap.set(Number(level), new Set(ids));
418
428
  }
419
429
  }
430
+ // v4.8.0: Extract standard fields from metadata to top-level
431
+ const metadataObj = (metadata || {});
432
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
420
433
  const verbWithMetadata = {
421
434
  id: hnswVerb.id,
422
435
  vector: [...hnswVerb.vector],
@@ -424,7 +437,14 @@ export class TypeAwareStorageAdapter extends BaseStorage {
424
437
  verb: hnswVerb.verb,
425
438
  sourceId: hnswVerb.sourceId,
426
439
  targetId: hnswVerb.targetId,
427
- metadata: metadata || {} // Empty metadata if none exists
440
+ createdAt: createdAt || Date.now(),
441
+ updatedAt: updatedAt || Date.now(),
442
+ confidence: confidence,
443
+ weight: weight,
444
+ service: service,
445
+ data: data,
446
+ createdBy,
447
+ metadata: customMetadata
428
448
  };
429
449
  verbs.push(verbWithMetadata);
430
450
  }
@@ -463,6 +483,9 @@ export class TypeAwareStorageAdapter extends BaseStorage {
463
483
  connectionsMap.set(Number(level), new Set(ids));
464
484
  }
465
485
  }
486
+ // v4.8.0: Extract standard fields from metadata to top-level
487
+ const metadataObj = (metadata || {});
488
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
466
489
  const verbWithMetadata = {
467
490
  id: hnswVerb.id,
468
491
  vector: [...hnswVerb.vector],
@@ -470,7 +493,14 @@ export class TypeAwareStorageAdapter extends BaseStorage {
470
493
  verb: hnswVerb.verb,
471
494
  sourceId: hnswVerb.sourceId,
472
495
  targetId: hnswVerb.targetId,
473
- metadata: metadata || {} // Empty metadata if none exists
496
+ createdAt: createdAt || Date.now(),
497
+ updatedAt: updatedAt || Date.now(),
498
+ confidence: confidence,
499
+ weight: weight,
500
+ service: service,
501
+ data: data,
502
+ createdBy,
503
+ metadata: customMetadata
474
504
  };
475
505
  verbs.push(verbWithMetadata);
476
506
  }
@@ -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 { NounType } from '../types/graphTypes.js';
8
9
  import { getShardIdFromUuid } from './sharding.js';
9
10
  // Clean directory structure (v4.7.2+)
10
11
  // All storage adapters use this consistent structure
@@ -46,6 +47,10 @@ export class BaseStorage extends BaseStorageAdapter {
46
47
  * @private
47
48
  */
48
49
  analyzeKey(id, context) {
50
+ // v4.8.0: Guard against undefined/null IDs
51
+ if (!id || typeof id !== 'string') {
52
+ throw new Error(`Invalid storage key: ${id} (must be a non-empty string)`);
53
+ }
49
54
  // System resource detection
50
55
  const isSystemKey = id.startsWith('__metadata_') ||
51
56
  id.startsWith('__index_') ||
@@ -142,13 +147,24 @@ export class BaseStorage extends BaseStorageAdapter {
142
147
  console.warn(`[Storage] Noun ${id} has vector but no metadata - this should not happen in v4.0.0`);
143
148
  return null;
144
149
  }
145
- // Combine into HNSWNounWithMetadata
150
+ // Combine into HNSWNounWithMetadata - v4.8.0: Extract standard fields to top-level
151
+ const { noun, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadata;
146
152
  return {
147
153
  id: vector.id,
148
154
  vector: vector.vector,
149
155
  connections: vector.connections,
150
156
  level: vector.level,
151
- metadata
157
+ // v4.8.0: Standard fields at top-level
158
+ type: noun || NounType.Thing,
159
+ createdAt: createdAt || Date.now(),
160
+ updatedAt: updatedAt || Date.now(),
161
+ confidence: confidence,
162
+ weight: weight,
163
+ service: service,
164
+ data: data,
165
+ createdBy,
166
+ // Only custom user fields remain in metadata
167
+ metadata: customMetadata
152
168
  };
153
169
  }
154
170
  /**
@@ -160,14 +176,25 @@ export class BaseStorage extends BaseStorageAdapter {
160
176
  await this.ensureInitialized();
161
177
  // Internal method returns HNSWNoun[], need to combine with metadata
162
178
  const nouns = await this.getNounsByNounType_internal(nounType);
163
- // Combine each noun with its metadata
179
+ // Combine each noun with its metadata - v4.8.0: Extract standard fields to top-level
164
180
  const nounsWithMetadata = [];
165
181
  for (const noun of nouns) {
166
182
  const metadata = await this.getNounMetadata(noun.id);
167
183
  if (metadata) {
184
+ const { noun: nounType, createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadata;
168
185
  nounsWithMetadata.push({
169
186
  ...noun,
170
- metadata
187
+ // v4.8.0: Standard fields at top-level
188
+ type: nounType || NounType.Thing,
189
+ createdAt: createdAt || Date.now(),
190
+ updatedAt: updatedAt || Date.now(),
191
+ confidence: confidence,
192
+ weight: weight,
193
+ service: service,
194
+ data: data,
195
+ createdBy,
196
+ // Only custom user fields in metadata
197
+ metadata: customMetadata
171
198
  });
172
199
  }
173
200
  }
@@ -220,7 +247,8 @@ export class BaseStorage extends BaseStorageAdapter {
220
247
  console.warn(`[Storage] Verb ${id} has vector but no metadata - this should not happen in v4.0.0`);
221
248
  return null;
222
249
  }
223
- // Combine into HNSWVerbWithMetadata
250
+ // Combine into HNSWVerbWithMetadata - v4.8.0: Extract standard fields to top-level
251
+ const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadata;
224
252
  return {
225
253
  id: verb.id,
226
254
  vector: verb.vector,
@@ -228,7 +256,16 @@ export class BaseStorage extends BaseStorageAdapter {
228
256
  verb: verb.verb,
229
257
  sourceId: verb.sourceId,
230
258
  targetId: verb.targetId,
231
- metadata
259
+ // v4.8.0: Standard fields at top-level
260
+ createdAt: createdAt || Date.now(),
261
+ updatedAt: updatedAt || Date.now(),
262
+ confidence: confidence,
263
+ weight: weight,
264
+ service: service,
265
+ data: data,
266
+ createdBy,
267
+ // Only custom user fields remain in metadata
268
+ metadata: customMetadata
232
269
  };
233
270
  }
234
271
  /**
@@ -89,6 +89,10 @@ export interface AddParams<T = any> {
89
89
  service?: string;
90
90
  confidence?: number;
91
91
  weight?: number;
92
+ createdBy?: {
93
+ augmentation: string;
94
+ version: string;
95
+ };
92
96
  }
93
97
  /**
94
98
  * Parameters for updating entities
@@ -206,6 +206,7 @@ export interface GraphVerb {
206
206
  createdAt: Timestamp;
207
207
  updatedAt: Timestamp;
208
208
  createdBy: CreatorMetadata;
209
+ service?: string;
209
210
  data?: Record<string, any>;
210
211
  embedding?: number[];
211
212
  confidence?: number;
@@ -33,8 +33,9 @@ export class EntityIdMapper {
33
33
  async init() {
34
34
  try {
35
35
  const metadata = await this.storage.getMetadata(this.storageKey);
36
- if (metadata && metadata.data) {
37
- const data = metadata.data;
36
+ // v4.8.0: metadata IS the data (no nested 'data' property)
37
+ if (metadata && metadata.nextId !== undefined) {
38
+ const data = metadata;
38
39
  this.nextId = data.nextId;
39
40
  // Rebuild maps from serialized data
40
41
  this.uuidToInt = new Map(Object.entries(data.uuidToInt).map(([k, v]) => [k, Number(v)]));
@@ -228,7 +228,13 @@ export declare class MetadataIndexManager {
228
228
  */
229
229
  private shouldIndexField;
230
230
  /**
231
- * Extract indexable field-value pairs from metadata
231
+ * Extract indexable field-value pairs from entity or metadata
232
+ *
233
+ * v4.8.0: Now handles BOTH entity structure (with top-level fields) AND plain metadata
234
+ * - Extracts from top-level fields (confidence, weight, timestamps, type, service, etc.)
235
+ * - Also extracts from nested metadata field (custom user fields)
236
+ * - Skips HNSW-specific fields (vector, connections, level, id)
237
+ * - Maps 'type' → 'noun' for backward compatibility with existing indexes
232
238
  *
233
239
  * BUG FIX (v3.50.1): Exclude vector embeddings and large arrays from indexing
234
240
  * BUG FIX (v3.50.2): Also exclude purely numeric field names (array indices)
@@ -238,14 +244,29 @@ export declare class MetadataIndexManager {
238
244
  private extractIndexableFields;
239
245
  /**
240
246
  * Add item to metadata indexes
247
+ *
248
+ * v4.8.0: Now accepts either entity structure or plain metadata
249
+ * - Entity structure: { id, type, confidence, weight, createdAt, metadata: {...} }
250
+ * - Plain metadata: { noun, confidence, weight, createdAt, ... }
251
+ *
252
+ * @param id - Entity ID
253
+ * @param entityOrMetadata - Either full entity structure (v4.8.0+) or plain metadata (backward compat)
254
+ * @param skipFlush - Skip automatic flush (used during batch operations)
241
255
  */
242
- addToIndex(id: string, metadata: any, skipFlush?: boolean): Promise<void>;
256
+ addToIndex(id: string, entityOrMetadata: any, skipFlush?: boolean): Promise<void>;
243
257
  /**
244
258
  * Update field index with value count
245
259
  */
246
260
  private updateFieldIndex;
247
261
  /**
248
262
  * Remove item from metadata indexes
263
+ *
264
+ * v4.8.0: Now accepts either entity structure or plain metadata (same as addToIndex)
265
+ * - Entity structure: { id, type, confidence, weight, createdAt, metadata: {...} }
266
+ * - Plain metadata: { noun, confidence, weight, createdAt, ... }
267
+ *
268
+ * @param id - Entity ID to remove
269
+ * @param metadata - Optional entity or metadata structure (if not provided, requires scanning all fields - slow!)
249
270
  */
250
271
  removeFromIndex(id: string, metadata?: any): Promise<void>;
251
272
  /**
@@ -856,22 +856,28 @@ export class MetadataIndexManager {
856
856
  return true;
857
857
  }
858
858
  /**
859
- * Extract indexable field-value pairs from metadata
859
+ * Extract indexable field-value pairs from entity or metadata
860
+ *
861
+ * v4.8.0: Now handles BOTH entity structure (with top-level fields) AND plain metadata
862
+ * - Extracts from top-level fields (confidence, weight, timestamps, type, service, etc.)
863
+ * - Also extracts from nested metadata field (custom user fields)
864
+ * - Skips HNSW-specific fields (vector, connections, level, id)
865
+ * - Maps 'type' → 'noun' for backward compatibility with existing indexes
860
866
  *
861
867
  * BUG FIX (v3.50.1): Exclude vector embeddings and large arrays from indexing
862
868
  * BUG FIX (v3.50.2): Also exclude purely numeric field names (array indices)
863
869
  * - Vector fields (384+ dimensions) were creating 825K chunk files for 1,144 entities
864
870
  * - Arrays converted to objects with numeric keys were still being indexed
865
871
  */
866
- extractIndexableFields(metadata) {
872
+ extractIndexableFields(data) {
867
873
  const fields = [];
868
- // Fields that should NEVER be indexed (vectors, embeddings, large arrays)
869
- const NEVER_INDEX = new Set(['vector', 'embedding', 'embeddings', 'connections']);
874
+ // Fields that should NEVER be indexed (vectors, embeddings, large arrays, HNSW internals)
875
+ const NEVER_INDEX = new Set(['vector', 'embedding', 'embeddings', 'connections', 'level', 'id']);
870
876
  const extract = (obj, prefix = '') => {
871
877
  for (const [key, value] of Object.entries(obj)) {
872
878
  const fullKey = prefix ? `${prefix}.${key}` : key;
873
- // Skip fields in never-index list (CRITICAL: prevents vector indexing bug)
874
- if (NEVER_INDEX.has(key))
879
+ // Skip fields in never-index list (CRITICAL: prevents vector indexing bug + HNSW fields)
880
+ if (!prefix && NEVER_INDEX.has(key))
875
881
  continue;
876
882
  // Skip purely numeric field names (array indices converted to object keys)
877
883
  // Legitimate field names should never be purely numeric
@@ -881,6 +887,14 @@ export class MetadataIndexManager {
881
887
  // Skip fields based on user configuration
882
888
  if (!this.shouldIndexField(fullKey))
883
889
  continue;
890
+ // Special handling for metadata field at top level
891
+ // v4.8.0: Flatten metadata fields to top-level (no prefix) for cleaner queries
892
+ // Standard fields are already at top-level, custom fields go in metadata
893
+ // By flattening here, queries can use { category: 'B' } instead of { 'metadata.category': 'B' }
894
+ if (key === 'metadata' && !prefix && typeof value === 'object' && !Array.isArray(value)) {
895
+ extract(value, ''); // Flatten to top-level, no prefix
896
+ continue;
897
+ }
884
898
  // Skip large arrays (> 10 elements) - likely vectors or bulk data
885
899
  if (Array.isArray(value) && value.length > 10)
886
900
  continue;
@@ -900,20 +914,30 @@ export class MetadataIndexManager {
900
914
  }
901
915
  else {
902
916
  // Primitive value: index it
903
- fields.push({ field: fullKey, value });
917
+ // v4.8.0: Map 'type' → 'noun' for backward compatibility
918
+ const indexField = (!prefix && key === 'type') ? 'noun' : fullKey;
919
+ fields.push({ field: indexField, value });
904
920
  }
905
921
  }
906
922
  };
907
- if (metadata && typeof metadata === 'object') {
908
- extract(metadata);
923
+ if (data && typeof data === 'object') {
924
+ extract(data);
909
925
  }
910
926
  return fields;
911
927
  }
912
928
  /**
913
929
  * Add item to metadata indexes
930
+ *
931
+ * v4.8.0: Now accepts either entity structure or plain metadata
932
+ * - Entity structure: { id, type, confidence, weight, createdAt, metadata: {...} }
933
+ * - Plain metadata: { noun, confidence, weight, createdAt, ... }
934
+ *
935
+ * @param id - Entity ID
936
+ * @param entityOrMetadata - Either full entity structure (v4.8.0+) or plain metadata (backward compat)
937
+ * @param skipFlush - Skip automatic flush (used during batch operations)
914
938
  */
915
- async addToIndex(id, metadata, skipFlush = false) {
916
- const fields = this.extractIndexableFields(metadata);
939
+ async addToIndex(id, entityOrMetadata, skipFlush = false) {
940
+ const fields = this.extractIndexableFields(entityOrMetadata);
917
941
  // Sort fields to process 'noun' field first for type-field affinity tracking
918
942
  fields.sort((a, b) => {
919
943
  if (a.field === 'noun')
@@ -930,7 +954,7 @@ export class MetadataIndexManager {
930
954
  await this.addToChunkedIndex(field, value, id);
931
955
  // Update statistics and tracking
932
956
  this.updateCardinalityStats(field, value, 'add');
933
- this.updateTypeFieldAffinity(id, field, value, 'add', metadata);
957
+ this.updateTypeFieldAffinity(id, field, value, 'add', entityOrMetadata);
934
958
  await this.updateFieldIndex(field, value, 1);
935
959
  // Yield to event loop every 5 fields to prevent blocking
936
960
  if (i % 5 === 4) {
@@ -988,6 +1012,13 @@ export class MetadataIndexManager {
988
1012
  }
989
1013
  /**
990
1014
  * Remove item from metadata indexes
1015
+ *
1016
+ * v4.8.0: Now accepts either entity structure or plain metadata (same as addToIndex)
1017
+ * - Entity structure: { id, type, confidence, weight, createdAt, metadata: {...} }
1018
+ * - Plain metadata: { noun, confidence, weight, createdAt, ... }
1019
+ *
1020
+ * @param id - Entity ID to remove
1021
+ * @param metadata - Optional entity or metadata structure (if not provided, requires scanning all fields - slow!)
991
1022
  */
992
1023
  async removeFromIndex(id, metadata) {
993
1024
  if (metadata) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "4.7.4",
3
+ "version": "4.8.0",
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",