@soulcraft/brainy 4.1.1 β 4.1.3
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 +40 -0
- package/dist/brainy.d.ts +31 -2
- package/dist/brainy.js +62 -18
- package/dist/neural/improvedNeuralAPI.js +5 -5
- package/dist/storage/adapters/azureBlobStorage.js +2 -5
- package/dist/storage/adapters/baseStorageAdapter.d.ts +16 -4
- package/dist/storage/adapters/baseStorageAdapter.js +42 -26
- package/dist/storage/adapters/fileSystemStorage.js +4 -23
- package/dist/storage/adapters/gcsStorage.js +4 -10
- package/dist/storage/adapters/memoryStorage.js +2 -1
- package/dist/storage/adapters/r2Storage.js +2 -4
- package/dist/storage/baseStorage.d.ts +12 -0
- package/dist/storage/baseStorage.js +50 -5
- package/dist/types/brainy.types.d.ts +69 -0
- package/dist/utils/rebuildCounts.d.ts +40 -0
- package/dist/utils/rebuildCounts.js +112 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
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
|
+
### [4.1.3](https://github.com/soulcraftlabs/brainy/compare/v4.1.2...v4.1.3) (2025-10-21)
|
|
6
|
+
|
|
7
|
+
- perf: make getRelations() pagination consistent and efficient (54d819c)
|
|
8
|
+
- fix: resolve getRelations() empty array bug and add string ID shorthand (8d217f3)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### [4.1.3](https://github.com/soulcraftlabs/brainy/compare/v4.1.2...v4.1.3) (2025-10-21)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### π Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **api**: fix getRelations() returning empty array when called without parameters
|
|
17
|
+
- Fixed critical bug where `brain.getRelations()` returned `[]` instead of all relationships
|
|
18
|
+
- Added support for retrieving all relationships with pagination (default limit: 100)
|
|
19
|
+
- Added string ID shorthand syntax: `brain.getRelations(entityId)` as alias for `brain.getRelations({ from: entityId })`
|
|
20
|
+
- **Performance**: Made pagination consistent - now ALL query patterns paginate at storage layer
|
|
21
|
+
- **Efficiency**: `getRelations({ from: id, limit: 10 })` now fetches only 10 instead of fetching ALL then slicing
|
|
22
|
+
- Fixed storage.getVerbs() offset handling - now properly converts offset to cursor for adapters
|
|
23
|
+
- Production safety: Warns when fetching >10k relationships without filters
|
|
24
|
+
- Fixed broken method calls in improvedNeuralAPI.ts (replaced non-existent `getVerbsForNoun` with `getRelations`)
|
|
25
|
+
- Fixed property access bugs: `verb.target` β `verb.to`, `verb.verb` β `verb.type`
|
|
26
|
+
- Added comprehensive integration tests (14 tests covering all query patterns)
|
|
27
|
+
- Updated JSDoc documentation with usage examples
|
|
28
|
+
- **Impact**: Resolves Workshop team bug where 524 imported relationships were inaccessible
|
|
29
|
+
- **Breaking**: None - fully backward compatible
|
|
30
|
+
|
|
31
|
+
### [4.1.2](https://github.com/soulcraftlabs/brainy/compare/v4.1.1...v4.1.2) (2025-10-21)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### π Bug Fixes
|
|
35
|
+
|
|
36
|
+
* **storage**: resolve count synchronization race condition across all storage adapters ([798a694](https://github.com/soulcraftlabs/brainy/commit/798a694))
|
|
37
|
+
- Fixed critical bug where entity and relationship counts were not tracked correctly during add(), relate(), and import()
|
|
38
|
+
- Root cause: Race condition where count increment tried to read metadata before it was saved
|
|
39
|
+
- Fixed in baseStorage for all storage adapters (FileSystem, GCS, R2, Azure, Memory, OPFS, S3, TypeAware)
|
|
40
|
+
- Added verb type to VerbMetadata for proper count tracking
|
|
41
|
+
- Refactored verb count methods to prevent mutex deadlocks
|
|
42
|
+
- Added rebuildCounts utility to repair corrupted counts from actual storage data
|
|
43
|
+
- Added comprehensive integration tests (11 tests covering all operations)
|
|
44
|
+
|
|
5
45
|
### [4.1.1](https://github.com/soulcraftlabs/brainy/compare/v4.1.0...v4.1.1) (2025-10-20)
|
|
6
46
|
|
|
7
47
|
|
package/dist/brainy.d.ts
CHANGED
|
@@ -314,9 +314,38 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
|
|
|
314
314
|
*/
|
|
315
315
|
unrelate(id: string): Promise<void>;
|
|
316
316
|
/**
|
|
317
|
-
* Get relationships
|
|
317
|
+
* Get relationships between entities
|
|
318
|
+
*
|
|
319
|
+
* Supports multiple query patterns:
|
|
320
|
+
* - No parameters: Returns all relationships (paginated, default limit: 100)
|
|
321
|
+
* - String ID: Returns relationships from that entity (shorthand for { from: id })
|
|
322
|
+
* - Parameters object: Fine-grained filtering and pagination
|
|
323
|
+
*
|
|
324
|
+
* @param paramsOrId - Optional string ID or parameters object
|
|
325
|
+
* @returns Promise resolving to array of relationships
|
|
326
|
+
*
|
|
327
|
+
* @example
|
|
328
|
+
* ```typescript
|
|
329
|
+
* // Get all relationships (first 100)
|
|
330
|
+
* const all = await brain.getRelations()
|
|
331
|
+
*
|
|
332
|
+
* // Get relationships from specific entity (shorthand syntax)
|
|
333
|
+
* const fromEntity = await brain.getRelations(entityId)
|
|
334
|
+
*
|
|
335
|
+
* // Get relationships with filters
|
|
336
|
+
* const filtered = await brain.getRelations({
|
|
337
|
+
* type: VerbType.FriendOf,
|
|
338
|
+
* limit: 50
|
|
339
|
+
* })
|
|
340
|
+
*
|
|
341
|
+
* // Pagination
|
|
342
|
+
* const page2 = await brain.getRelations({ offset: 100, limit: 100 })
|
|
343
|
+
* ```
|
|
344
|
+
*
|
|
345
|
+
* @since v4.1.3 - Fixed bug where calling without parameters returned empty array
|
|
346
|
+
* @since v4.1.3 - Added string ID shorthand syntax: getRelations(id)
|
|
318
347
|
*/
|
|
319
|
-
getRelations(
|
|
348
|
+
getRelations(paramsOrId?: string | GetRelationsParams): Promise<Relation<T>[]>;
|
|
320
349
|
/**
|
|
321
350
|
* Unified find method - supports natural language and structured queries
|
|
322
351
|
* Implements Triple Intelligence with parallel search optimization
|
package/dist/brainy.js
CHANGED
|
@@ -648,7 +648,9 @@ export class Brainy {
|
|
|
648
648
|
const relationVector = fromEntity.vector.map((v, i) => (v + toEntity.vector[i]) / 2);
|
|
649
649
|
return this.augmentationRegistry.execute('relate', params, async () => {
|
|
650
650
|
// v4.0.0: Prepare verb metadata
|
|
651
|
+
// CRITICAL (v4.1.2): Include verb type in metadata for count tracking
|
|
651
652
|
const verbMetadata = {
|
|
653
|
+
verb: params.type, // Store verb type for count synchronization
|
|
652
654
|
weight: params.weight ?? 1.0,
|
|
653
655
|
...(params.metadata || {}),
|
|
654
656
|
createdAt: Date.now()
|
|
@@ -717,33 +719,75 @@ export class Brainy {
|
|
|
717
719
|
});
|
|
718
720
|
}
|
|
719
721
|
/**
|
|
720
|
-
* Get relationships
|
|
722
|
+
* Get relationships between entities
|
|
723
|
+
*
|
|
724
|
+
* Supports multiple query patterns:
|
|
725
|
+
* - No parameters: Returns all relationships (paginated, default limit: 100)
|
|
726
|
+
* - String ID: Returns relationships from that entity (shorthand for { from: id })
|
|
727
|
+
* - Parameters object: Fine-grained filtering and pagination
|
|
728
|
+
*
|
|
729
|
+
* @param paramsOrId - Optional string ID or parameters object
|
|
730
|
+
* @returns Promise resolving to array of relationships
|
|
731
|
+
*
|
|
732
|
+
* @example
|
|
733
|
+
* ```typescript
|
|
734
|
+
* // Get all relationships (first 100)
|
|
735
|
+
* const all = await brain.getRelations()
|
|
736
|
+
*
|
|
737
|
+
* // Get relationships from specific entity (shorthand syntax)
|
|
738
|
+
* const fromEntity = await brain.getRelations(entityId)
|
|
739
|
+
*
|
|
740
|
+
* // Get relationships with filters
|
|
741
|
+
* const filtered = await brain.getRelations({
|
|
742
|
+
* type: VerbType.FriendOf,
|
|
743
|
+
* limit: 50
|
|
744
|
+
* })
|
|
745
|
+
*
|
|
746
|
+
* // Pagination
|
|
747
|
+
* const page2 = await brain.getRelations({ offset: 100, limit: 100 })
|
|
748
|
+
* ```
|
|
749
|
+
*
|
|
750
|
+
* @since v4.1.3 - Fixed bug where calling without parameters returned empty array
|
|
751
|
+
* @since v4.1.3 - Added string ID shorthand syntax: getRelations(id)
|
|
721
752
|
*/
|
|
722
|
-
async getRelations(
|
|
753
|
+
async getRelations(paramsOrId) {
|
|
723
754
|
await this.ensureInitialized();
|
|
724
|
-
|
|
755
|
+
// Handle string ID shorthand: getRelations(id) -> getRelations({ from: id })
|
|
756
|
+
const params = typeof paramsOrId === 'string'
|
|
757
|
+
? { from: paramsOrId }
|
|
758
|
+
: (paramsOrId || {});
|
|
759
|
+
const limit = params.limit || 100;
|
|
760
|
+
const offset = params.offset || 0;
|
|
761
|
+
// Production safety: warn for large unfiltered queries
|
|
762
|
+
if (!params.from && !params.to && !params.type && limit > 10000) {
|
|
763
|
+
console.warn(`[Brainy] getRelations(): Fetching ${limit} relationships without filters. ` +
|
|
764
|
+
`Consider adding 'from', 'to', or 'type' filter for better performance.`);
|
|
765
|
+
}
|
|
766
|
+
// Build filter for storage query
|
|
767
|
+
const filter = {};
|
|
725
768
|
if (params.from) {
|
|
726
|
-
|
|
727
|
-
relations.push(...this.verbsToRelations(verbs));
|
|
769
|
+
filter.sourceId = params.from;
|
|
728
770
|
}
|
|
729
771
|
if (params.to) {
|
|
730
|
-
|
|
731
|
-
relations.push(...this.verbsToRelations(verbs));
|
|
772
|
+
filter.targetId = params.to;
|
|
732
773
|
}
|
|
733
|
-
// Filter by type
|
|
734
|
-
let filtered = relations;
|
|
735
774
|
if (params.type) {
|
|
736
|
-
|
|
737
|
-
filtered = relations.filter((r) => types.includes(r.type));
|
|
775
|
+
filter.verbType = Array.isArray(params.type) ? params.type : [params.type];
|
|
738
776
|
}
|
|
739
|
-
// Filter by service
|
|
740
777
|
if (params.service) {
|
|
741
|
-
|
|
742
|
-
}
|
|
743
|
-
//
|
|
744
|
-
const
|
|
745
|
-
|
|
746
|
-
|
|
778
|
+
filter.service = params.service;
|
|
779
|
+
}
|
|
780
|
+
// Fetch from storage with pagination at storage layer (efficient!)
|
|
781
|
+
const result = await this.storage.getVerbs({
|
|
782
|
+
pagination: {
|
|
783
|
+
limit,
|
|
784
|
+
offset,
|
|
785
|
+
cursor: params.cursor
|
|
786
|
+
},
|
|
787
|
+
filter: Object.keys(filter).length > 0 ? filter : undefined
|
|
788
|
+
});
|
|
789
|
+
// Convert to Relation format
|
|
790
|
+
return this.verbsToRelations(result.items);
|
|
747
791
|
}
|
|
748
792
|
// ============= SEARCH & DISCOVERY =============
|
|
749
793
|
/**
|
|
@@ -1310,14 +1310,14 @@ export class ImprovedNeuralAPI {
|
|
|
1310
1310
|
for (const sourceId of itemIds) {
|
|
1311
1311
|
const sourceVerbs = await this.brain.getRelations(sourceId);
|
|
1312
1312
|
for (const verb of sourceVerbs) {
|
|
1313
|
-
const targetId = verb.
|
|
1313
|
+
const targetId = verb.to;
|
|
1314
1314
|
if (nodes.has(targetId) && sourceId !== targetId) {
|
|
1315
1315
|
// Initialize edge map if needed
|
|
1316
1316
|
if (!edges.has(sourceId)) {
|
|
1317
1317
|
edges.set(sourceId, new Map());
|
|
1318
1318
|
}
|
|
1319
1319
|
// Calculate edge weight from verb type and metadata
|
|
1320
|
-
const verbType = verb.
|
|
1320
|
+
const verbType = verb.type;
|
|
1321
1321
|
const baseWeight = relationshipWeights[verbType] || 0.5;
|
|
1322
1322
|
const confidenceWeight = verb.confidence || 1.0;
|
|
1323
1323
|
const weight = baseWeight * confidenceWeight;
|
|
@@ -2743,7 +2743,7 @@ export class ImprovedNeuralAPI {
|
|
|
2743
2743
|
const sampleSize = Math.min(50, itemIds.length);
|
|
2744
2744
|
for (let i = 0; i < sampleSize; i++) {
|
|
2745
2745
|
try {
|
|
2746
|
-
const verbs = await this.brain.
|
|
2746
|
+
const verbs = await this.brain.getRelations({ from: itemIds[i] });
|
|
2747
2747
|
connectionCount += verbs.length;
|
|
2748
2748
|
}
|
|
2749
2749
|
catch (error) {
|
|
@@ -2797,9 +2797,9 @@ export class ImprovedNeuralAPI {
|
|
|
2797
2797
|
if (fromType !== toType) {
|
|
2798
2798
|
for (const fromItem of fromItems.slice(0, 10)) { // Sample to avoid N^2
|
|
2799
2799
|
try {
|
|
2800
|
-
const verbs = await this.brain.
|
|
2800
|
+
const verbs = await this.brain.getRelations({ from: fromItem.id });
|
|
2801
2801
|
for (const verb of verbs) {
|
|
2802
|
-
const toItem = toItems.find(item => item.id === verb.
|
|
2802
|
+
const toItem = toItems.find(item => item.id === verb.to);
|
|
2803
2803
|
if (toItem) {
|
|
2804
2804
|
connections.push({
|
|
2805
2805
|
from: fromItem.id,
|
|
@@ -791,11 +791,8 @@ export class AzureBlobStorage extends BaseStorage {
|
|
|
791
791
|
});
|
|
792
792
|
// Update cache
|
|
793
793
|
this.verbCacheManager.set(edge.id, edge);
|
|
794
|
-
//
|
|
795
|
-
|
|
796
|
-
if (metadata && metadata.type) {
|
|
797
|
-
await this.incrementVerbCount(metadata.type);
|
|
798
|
-
}
|
|
794
|
+
// Count tracking happens in baseStorage.saveVerbMetadata_internal (v4.1.2)
|
|
795
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
799
796
|
this.logger.trace(`Edge ${edge.id} saved successfully`);
|
|
800
797
|
this.releaseBackpressure(true, requestId);
|
|
801
798
|
}
|
|
@@ -320,15 +320,27 @@ export declare abstract class BaseStorageAdapter implements StorageAdapter {
|
|
|
320
320
|
*/
|
|
321
321
|
protected decrementEntityCountSafe(type: string): Promise<void>;
|
|
322
322
|
/**
|
|
323
|
-
* Increment verb count - O(1) operation
|
|
323
|
+
* Increment verb count - O(1) operation (v4.1.2: now synchronous)
|
|
324
|
+
* Protected by storage-specific mechanisms (mutex, distributed consensus, etc.)
|
|
325
|
+
* @param type The verb type
|
|
326
|
+
*/
|
|
327
|
+
protected incrementVerbCount(type: string): void;
|
|
328
|
+
/**
|
|
329
|
+
* Thread-safe increment for verb counts (v4.1.2)
|
|
330
|
+
* Uses mutex for single-node, distributed consensus for multi-node
|
|
331
|
+
* @param type The verb type
|
|
332
|
+
*/
|
|
333
|
+
protected incrementVerbCountSafe(type: string): Promise<void>;
|
|
334
|
+
/**
|
|
335
|
+
* Decrement verb count - O(1) operation (v4.1.2: now synchronous)
|
|
324
336
|
* @param type The verb type
|
|
325
337
|
*/
|
|
326
|
-
protected
|
|
338
|
+
protected decrementVerbCount(type: string): void;
|
|
327
339
|
/**
|
|
328
|
-
*
|
|
340
|
+
* Thread-safe decrement for verb counts (v4.1.2)
|
|
329
341
|
* @param type The verb type
|
|
330
342
|
*/
|
|
331
|
-
protected
|
|
343
|
+
protected decrementVerbCountSafe(type: string): Promise<void>;
|
|
332
344
|
/**
|
|
333
345
|
* Detect if this storage adapter uses cloud storage (network I/O)
|
|
334
346
|
* Cloud storage benefits from batching; local storage does not.
|
|
@@ -709,45 +709,61 @@ export class BaseStorageAdapter {
|
|
|
709
709
|
});
|
|
710
710
|
}
|
|
711
711
|
/**
|
|
712
|
-
* Increment verb count - O(1) operation
|
|
712
|
+
* Increment verb count - O(1) operation (v4.1.2: now synchronous)
|
|
713
|
+
* Protected by storage-specific mechanisms (mutex, distributed consensus, etc.)
|
|
714
|
+
* @param type The verb type
|
|
715
|
+
*/
|
|
716
|
+
incrementVerbCount(type) {
|
|
717
|
+
this.verbCounts.set(type, (this.verbCounts.get(type) || 0) + 1);
|
|
718
|
+
this.totalVerbCount++;
|
|
719
|
+
// Update cache
|
|
720
|
+
this.countCache.set('verbs_count', {
|
|
721
|
+
count: this.totalVerbCount,
|
|
722
|
+
timestamp: Date.now()
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Thread-safe increment for verb counts (v4.1.2)
|
|
727
|
+
* Uses mutex for single-node, distributed consensus for multi-node
|
|
713
728
|
* @param type The verb type
|
|
714
729
|
*/
|
|
715
|
-
async
|
|
730
|
+
async incrementVerbCountSafe(type) {
|
|
716
731
|
const mutex = getGlobalMutex();
|
|
717
732
|
await mutex.runExclusive(`count-verb-${type}`, async () => {
|
|
718
|
-
this.
|
|
719
|
-
this.totalVerbCount++;
|
|
720
|
-
// Update cache
|
|
721
|
-
this.countCache.set('verbs_count', {
|
|
722
|
-
count: this.totalVerbCount,
|
|
723
|
-
timestamp: Date.now()
|
|
724
|
-
});
|
|
733
|
+
this.incrementVerbCount(type);
|
|
725
734
|
// Smart batching (v3.32.3+): Adapts to storage type
|
|
726
735
|
await this.scheduleCountPersist();
|
|
727
736
|
});
|
|
728
737
|
}
|
|
729
738
|
/**
|
|
730
|
-
* Decrement verb count - O(1) operation
|
|
739
|
+
* Decrement verb count - O(1) operation (v4.1.2: now synchronous)
|
|
740
|
+
* @param type The verb type
|
|
741
|
+
*/
|
|
742
|
+
decrementVerbCount(type) {
|
|
743
|
+
const current = this.verbCounts.get(type) || 0;
|
|
744
|
+
if (current > 1) {
|
|
745
|
+
this.verbCounts.set(type, current - 1);
|
|
746
|
+
}
|
|
747
|
+
else {
|
|
748
|
+
this.verbCounts.delete(type);
|
|
749
|
+
}
|
|
750
|
+
if (this.totalVerbCount > 0) {
|
|
751
|
+
this.totalVerbCount--;
|
|
752
|
+
}
|
|
753
|
+
// Update cache
|
|
754
|
+
this.countCache.set('verbs_count', {
|
|
755
|
+
count: this.totalVerbCount,
|
|
756
|
+
timestamp: Date.now()
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Thread-safe decrement for verb counts (v4.1.2)
|
|
731
761
|
* @param type The verb type
|
|
732
762
|
*/
|
|
733
|
-
async
|
|
763
|
+
async decrementVerbCountSafe(type) {
|
|
734
764
|
const mutex = getGlobalMutex();
|
|
735
765
|
await mutex.runExclusive(`count-verb-${type}`, async () => {
|
|
736
|
-
|
|
737
|
-
if (current > 1) {
|
|
738
|
-
this.verbCounts.set(type, current - 1);
|
|
739
|
-
}
|
|
740
|
-
else {
|
|
741
|
-
this.verbCounts.delete(type);
|
|
742
|
-
}
|
|
743
|
-
if (this.totalVerbCount > 0) {
|
|
744
|
-
this.totalVerbCount--;
|
|
745
|
-
}
|
|
746
|
-
// Update cache
|
|
747
|
-
this.countCache.set('verbs_count', {
|
|
748
|
-
count: this.totalVerbCount,
|
|
749
|
-
timestamp: Date.now()
|
|
750
|
-
});
|
|
766
|
+
this.decrementVerbCount(type);
|
|
751
767
|
// Smart batching (v3.32.3+): Adapts to storage type
|
|
752
768
|
await this.scheduleCountPersist();
|
|
753
769
|
});
|
|
@@ -179,8 +179,6 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
179
179
|
*/
|
|
180
180
|
async saveNode(node) {
|
|
181
181
|
await this.ensureInitialized();
|
|
182
|
-
// Check if this is a new node to update counts
|
|
183
|
-
const isNew = !(await this.fileExists(this.getNodePath(node.id)));
|
|
184
182
|
// Convert connections Map to a serializable format
|
|
185
183
|
// CRITICAL: Only save lightweight vector data (no metadata)
|
|
186
184
|
// Metadata is saved separately via saveNounMetadata() (2-file system)
|
|
@@ -194,17 +192,8 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
194
192
|
const filePath = this.getNodePath(node.id);
|
|
195
193
|
await this.ensureDirectoryExists(path.dirname(filePath));
|
|
196
194
|
await fs.promises.writeFile(filePath, JSON.stringify(serializableNode, null, 2));
|
|
197
|
-
//
|
|
198
|
-
|
|
199
|
-
// v4.0.0: Get type from separate metadata storage
|
|
200
|
-
const metadata = await this.getNounMetadata(node.id);
|
|
201
|
-
const type = metadata?.noun || 'default';
|
|
202
|
-
this.incrementEntityCount(type);
|
|
203
|
-
// Persist counts periodically (every 10 operations for efficiency)
|
|
204
|
-
if (this.totalNounCount % 10 === 0) {
|
|
205
|
-
await this.persistCounts();
|
|
206
|
-
}
|
|
207
|
-
}
|
|
195
|
+
// Count tracking happens in baseStorage.saveNounMetadata_internal (v4.1.2)
|
|
196
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
208
197
|
}
|
|
209
198
|
/**
|
|
210
199
|
* Get a node from storage
|
|
@@ -354,8 +343,6 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
354
343
|
*/
|
|
355
344
|
async saveEdge(edge) {
|
|
356
345
|
await this.ensureInitialized();
|
|
357
|
-
// Check if this is a new edge to update counts
|
|
358
|
-
const isNew = !(await this.fileExists(this.getVerbPath(edge.id)));
|
|
359
346
|
// Convert connections Map to a serializable format
|
|
360
347
|
// ARCHITECTURAL FIX (v3.50.1): Include core relational fields in verb vector file
|
|
361
348
|
// These fields are essential for 90% of operations - no metadata lookup needed
|
|
@@ -373,14 +360,8 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
373
360
|
const filePath = this.getVerbPath(edge.id);
|
|
374
361
|
await this.ensureDirectoryExists(path.dirname(filePath));
|
|
375
362
|
await fs.promises.writeFile(filePath, JSON.stringify(serializableEdge, null, 2));
|
|
376
|
-
//
|
|
377
|
-
|
|
378
|
-
this.totalVerbCount++;
|
|
379
|
-
// Persist counts periodically (every 10 operations for efficiency)
|
|
380
|
-
if (this.totalVerbCount % 10 === 0) {
|
|
381
|
-
this.persistCounts(); // Async persist, don't await
|
|
382
|
-
}
|
|
383
|
-
}
|
|
363
|
+
// Count tracking happens in baseStorage.saveVerbMetadata_internal (v4.1.2)
|
|
364
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
384
365
|
}
|
|
385
366
|
/**
|
|
386
367
|
* Get an edge from storage
|
|
@@ -353,11 +353,8 @@ export class GcsStorage extends BaseStorage {
|
|
|
353
353
|
this.nounCacheManager.set(node.id, node);
|
|
354
354
|
}
|
|
355
355
|
// Note: Empty vectors are intentional during HNSW lazy mode - not logged
|
|
356
|
-
//
|
|
357
|
-
|
|
358
|
-
if (metadata && metadata.type) {
|
|
359
|
-
await this.incrementEntityCountSafe(metadata.type);
|
|
360
|
-
}
|
|
356
|
+
// Count tracking happens in baseStorage.saveNounMetadata_internal (v4.1.2)
|
|
357
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
361
358
|
this.logger.trace(`Node ${node.id} saved successfully`);
|
|
362
359
|
this.releaseBackpressure(true, requestId);
|
|
363
360
|
}
|
|
@@ -663,11 +660,8 @@ export class GcsStorage extends BaseStorage {
|
|
|
663
660
|
});
|
|
664
661
|
// Update cache
|
|
665
662
|
this.verbCacheManager.set(edge.id, edge);
|
|
666
|
-
//
|
|
667
|
-
|
|
668
|
-
if (metadata && metadata.type) {
|
|
669
|
-
await this.incrementVerbCount(metadata.type);
|
|
670
|
-
}
|
|
663
|
+
// Count tracking happens in baseStorage.saveVerbMetadata_internal (v4.1.2)
|
|
664
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
671
665
|
this.logger.trace(`Edge ${edge.id} saved successfully`);
|
|
672
666
|
this.releaseBackpressure(true, requestId);
|
|
673
667
|
}
|
|
@@ -56,7 +56,8 @@ export class MemoryStorage extends BaseStorage {
|
|
|
56
56
|
}
|
|
57
57
|
// Save the noun directly in the nouns map
|
|
58
58
|
this.nouns.set(noun.id, nounCopy);
|
|
59
|
-
//
|
|
59
|
+
// Count tracking happens in baseStorage.saveNounMetadata_internal (v4.1.2)
|
|
60
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
60
61
|
}
|
|
61
62
|
/**
|
|
62
63
|
* Get a noun from storage (v4.0.0: returns pure vector only)
|
|
@@ -574,10 +574,8 @@ export class R2Storage extends BaseStorage {
|
|
|
574
574
|
ContentType: 'application/json'
|
|
575
575
|
}));
|
|
576
576
|
this.verbCacheManager.set(edge.id, edge);
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
await this.incrementVerbCount(metadata.type);
|
|
580
|
-
}
|
|
577
|
+
// Count tracking happens in baseStorage.saveVerbMetadata_internal (v4.1.2)
|
|
578
|
+
// This fixes the race condition where metadata didn't exist yet
|
|
581
579
|
this.releaseBackpressure(true, requestId);
|
|
582
580
|
}
|
|
583
581
|
catch (error) {
|
|
@@ -231,6 +231,11 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
231
231
|
/**
|
|
232
232
|
* Internal method for saving noun metadata (v4.0.0: now typed)
|
|
233
233
|
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
234
|
+
*
|
|
235
|
+
* CRITICAL (v4.1.2): Count synchronization happens here
|
|
236
|
+
* This ensures counts are updated AFTER metadata exists, fixing the race condition
|
|
237
|
+
* where storage adapters tried to read metadata before it was saved.
|
|
238
|
+
*
|
|
234
239
|
* @protected
|
|
235
240
|
*/
|
|
236
241
|
protected saveNounMetadata_internal(id: string, metadata: NounMetadata): Promise<void>;
|
|
@@ -252,6 +257,13 @@ export declare abstract class BaseStorage extends BaseStorageAdapter {
|
|
|
252
257
|
/**
|
|
253
258
|
* Internal method for saving verb metadata (v4.0.0: now typed)
|
|
254
259
|
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
260
|
+
*
|
|
261
|
+
* CRITICAL (v4.1.2): Count synchronization happens here
|
|
262
|
+
* This ensures verb counts are updated AFTER metadata exists, fixing the race condition
|
|
263
|
+
* where storage adapters tried to read metadata before it was saved.
|
|
264
|
+
*
|
|
265
|
+
* Note: Verb type is now stored in both HNSWVerb (vector file) and VerbMetadata for count tracking
|
|
266
|
+
*
|
|
255
267
|
* @protected
|
|
256
268
|
*/
|
|
257
269
|
protected saveVerbMetadata_internal(id: string, metadata: VerbMetadata): Promise<void>;
|
|
@@ -600,13 +600,15 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
600
600
|
// Check if the adapter has a paginated method for getting verbs
|
|
601
601
|
if (typeof this.getVerbsWithPagination === 'function') {
|
|
602
602
|
// Use the adapter's paginated method
|
|
603
|
+
// Convert offset to cursor if no cursor provided (adapters use cursor for offset)
|
|
604
|
+
const effectiveCursor = cursor || (offset > 0 ? offset.toString() : undefined);
|
|
603
605
|
const result = await this.getVerbsWithPagination({
|
|
604
606
|
limit,
|
|
605
|
-
cursor,
|
|
607
|
+
cursor: effectiveCursor,
|
|
606
608
|
filter: options?.filter
|
|
607
609
|
});
|
|
608
|
-
//
|
|
609
|
-
const items = result.items
|
|
610
|
+
// Items are already offset by the adapter via cursor, no need to slice
|
|
611
|
+
const items = result.items;
|
|
610
612
|
// CRITICAL SAFETY CHECK: Prevent infinite loops
|
|
611
613
|
// If we have no items but hasMore is true, force hasMore to false
|
|
612
614
|
// This prevents pagination bugs from causing infinite loops
|
|
@@ -706,12 +708,32 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
706
708
|
/**
|
|
707
709
|
* Internal method for saving noun metadata (v4.0.0: now typed)
|
|
708
710
|
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
711
|
+
*
|
|
712
|
+
* CRITICAL (v4.1.2): Count synchronization happens here
|
|
713
|
+
* This ensures counts are updated AFTER metadata exists, fixing the race condition
|
|
714
|
+
* where storage adapters tried to read metadata before it was saved.
|
|
715
|
+
*
|
|
709
716
|
* @protected
|
|
710
717
|
*/
|
|
711
718
|
async saveNounMetadata_internal(id, metadata) {
|
|
712
719
|
await this.ensureInitialized();
|
|
720
|
+
// Determine if this is a new entity by checking if metadata already exists
|
|
713
721
|
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
714
|
-
|
|
722
|
+
const existingMetadata = await this.readObjectFromPath(keyInfo.fullPath);
|
|
723
|
+
const isNew = !existingMetadata;
|
|
724
|
+
// Save the metadata
|
|
725
|
+
await this.writeObjectToPath(keyInfo.fullPath, metadata);
|
|
726
|
+
// CRITICAL FIX (v4.1.2): Increment count for new entities
|
|
727
|
+
// This runs AFTER metadata is saved, guaranteeing type information is available
|
|
728
|
+
// Uses synchronous increment since storage operations are already serialized
|
|
729
|
+
// Fixes Bug #1: Count synchronization failure during add() and import()
|
|
730
|
+
if (isNew && metadata.noun) {
|
|
731
|
+
this.incrementEntityCount(metadata.noun);
|
|
732
|
+
// Persist counts asynchronously (fire and forget)
|
|
733
|
+
this.scheduleCountPersist().catch(() => {
|
|
734
|
+
// Ignore persist errors - will retry on next operation
|
|
735
|
+
});
|
|
736
|
+
}
|
|
715
737
|
}
|
|
716
738
|
/**
|
|
717
739
|
* Get noun metadata from storage (v4.0.0: now typed)
|
|
@@ -742,12 +764,35 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
742
764
|
/**
|
|
743
765
|
* Internal method for saving verb metadata (v4.0.0: now typed)
|
|
744
766
|
* Uses routing logic to handle both UUIDs (sharded) and system keys (unsharded)
|
|
767
|
+
*
|
|
768
|
+
* CRITICAL (v4.1.2): Count synchronization happens here
|
|
769
|
+
* This ensures verb counts are updated AFTER metadata exists, fixing the race condition
|
|
770
|
+
* where storage adapters tried to read metadata before it was saved.
|
|
771
|
+
*
|
|
772
|
+
* Note: Verb type is now stored in both HNSWVerb (vector file) and VerbMetadata for count tracking
|
|
773
|
+
*
|
|
745
774
|
* @protected
|
|
746
775
|
*/
|
|
747
776
|
async saveVerbMetadata_internal(id, metadata) {
|
|
748
777
|
await this.ensureInitialized();
|
|
778
|
+
// Determine if this is a new verb by checking if metadata already exists
|
|
749
779
|
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
750
|
-
|
|
780
|
+
const existingMetadata = await this.readObjectFromPath(keyInfo.fullPath);
|
|
781
|
+
const isNew = !existingMetadata;
|
|
782
|
+
// Save the metadata
|
|
783
|
+
await this.writeObjectToPath(keyInfo.fullPath, metadata);
|
|
784
|
+
// CRITICAL FIX (v4.1.2): Increment verb count for new relationships
|
|
785
|
+
// This runs AFTER metadata is saved
|
|
786
|
+
// Verb type is now stored in metadata (as of v4.1.2) to avoid loading HNSWVerb
|
|
787
|
+
// Uses synchronous increment since storage operations are already serialized
|
|
788
|
+
// Fixes Bug #2: Count synchronization failure during relate() and import()
|
|
789
|
+
if (isNew && metadata.verb) {
|
|
790
|
+
this.incrementVerbCount(metadata.verb);
|
|
791
|
+
// Persist counts asynchronously (fire and forget)
|
|
792
|
+
this.scheduleCountPersist().catch(() => {
|
|
793
|
+
// Ignore persist errors - will retry on next operation
|
|
794
|
+
});
|
|
795
|
+
}
|
|
751
796
|
}
|
|
752
797
|
/**
|
|
753
798
|
* Get verb metadata from storage (v4.0.0: now typed)
|
|
@@ -172,14 +172,83 @@ export interface SimilarParams<T = any> {
|
|
|
172
172
|
}
|
|
173
173
|
/**
|
|
174
174
|
* Parameters for getting relationships
|
|
175
|
+
*
|
|
176
|
+
* All parameters are optional. When called without parameters, returns all relationships
|
|
177
|
+
* with pagination (default limit: 100).
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```typescript
|
|
181
|
+
* // Get all relationships (default limit: 100)
|
|
182
|
+
* const all = await brain.getRelations()
|
|
183
|
+
*
|
|
184
|
+
* // Get relationships from a specific entity (string shorthand)
|
|
185
|
+
* const fromEntity = await brain.getRelations(entityId)
|
|
186
|
+
*
|
|
187
|
+
* // Equivalent to:
|
|
188
|
+
* const fromEntity2 = await brain.getRelations({ from: entityId })
|
|
189
|
+
*
|
|
190
|
+
* // Get relationships to a specific entity
|
|
191
|
+
* const toEntity = await brain.getRelations({ to: entityId })
|
|
192
|
+
*
|
|
193
|
+
* // Filter by relationship type
|
|
194
|
+
* const friends = await brain.getRelations({ type: VerbType.FriendOf })
|
|
195
|
+
*
|
|
196
|
+
* // Pagination
|
|
197
|
+
* const page2 = await brain.getRelations({ offset: 100, limit: 50 })
|
|
198
|
+
*
|
|
199
|
+
* // Combined filters
|
|
200
|
+
* const filtered = await brain.getRelations({
|
|
201
|
+
* from: entityId,
|
|
202
|
+
* type: VerbType.WorksWith,
|
|
203
|
+
* limit: 20
|
|
204
|
+
* })
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @since v4.1.3 - Fixed bug where calling without parameters returned empty array
|
|
208
|
+
* @since v4.1.3 - Added string ID shorthand syntax
|
|
175
209
|
*/
|
|
176
210
|
export interface GetRelationsParams {
|
|
211
|
+
/**
|
|
212
|
+
* Filter by source entity ID
|
|
213
|
+
*
|
|
214
|
+
* Returns all relationships originating from this entity.
|
|
215
|
+
*/
|
|
177
216
|
from?: string;
|
|
217
|
+
/**
|
|
218
|
+
* Filter by target entity ID
|
|
219
|
+
*
|
|
220
|
+
* Returns all relationships pointing to this entity.
|
|
221
|
+
*/
|
|
178
222
|
to?: string;
|
|
223
|
+
/**
|
|
224
|
+
* Filter by relationship type(s)
|
|
225
|
+
*
|
|
226
|
+
* Can be a single VerbType or array of VerbTypes.
|
|
227
|
+
*/
|
|
179
228
|
type?: VerbType | VerbType[];
|
|
229
|
+
/**
|
|
230
|
+
* Maximum number of results to return
|
|
231
|
+
*
|
|
232
|
+
* @default 100
|
|
233
|
+
*/
|
|
180
234
|
limit?: number;
|
|
235
|
+
/**
|
|
236
|
+
* Number of results to skip (offset-based pagination)
|
|
237
|
+
*
|
|
238
|
+
* @default 0
|
|
239
|
+
*/
|
|
181
240
|
offset?: number;
|
|
241
|
+
/**
|
|
242
|
+
* Cursor for cursor-based pagination
|
|
243
|
+
*
|
|
244
|
+
* More efficient than offset for large result sets.
|
|
245
|
+
*/
|
|
182
246
|
cursor?: string;
|
|
247
|
+
/**
|
|
248
|
+
* Filter by service (multi-tenancy)
|
|
249
|
+
*
|
|
250
|
+
* Only return relationships belonging to this service.
|
|
251
|
+
*/
|
|
183
252
|
service?: string;
|
|
184
253
|
}
|
|
185
254
|
/**
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuild Counts Utility
|
|
3
|
+
*
|
|
4
|
+
* Scans storage and rebuilds counts.json from actual data
|
|
5
|
+
* Use this to fix databases affected by the v4.1.1 count synchronization bug
|
|
6
|
+
*
|
|
7
|
+
* NO MOCKS - Production-ready implementation
|
|
8
|
+
*/
|
|
9
|
+
import type { BaseStorage } from '../storage/baseStorage.js';
|
|
10
|
+
export interface RebuildCountsResult {
|
|
11
|
+
/** Total number of entities (nouns) found */
|
|
12
|
+
nounCount: number;
|
|
13
|
+
/** Total number of relationships (verbs) found */
|
|
14
|
+
verbCount: number;
|
|
15
|
+
/** Entity counts by type */
|
|
16
|
+
entityCounts: Map<string, number>;
|
|
17
|
+
/** Verb counts by type */
|
|
18
|
+
verbCounts: Map<string, number>;
|
|
19
|
+
/** Processing time in milliseconds */
|
|
20
|
+
duration: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Rebuild counts.json from actual storage data
|
|
24
|
+
*
|
|
25
|
+
* This scans all entities and relationships in storage and reconstructs
|
|
26
|
+
* the counts index from scratch. Use this to fix count desynchronization.
|
|
27
|
+
*
|
|
28
|
+
* @param storage - The storage adapter to rebuild counts for
|
|
29
|
+
* @returns Promise that resolves to rebuild statistics
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* const brain = new Brainy({ storage: { type: 'filesystem', path: './brainy-data' } })
|
|
34
|
+
* await brain.init()
|
|
35
|
+
*
|
|
36
|
+
* const result = await rebuildCounts(brain.storage)
|
|
37
|
+
* console.log(`Rebuilt counts: ${result.nounCount} nouns, ${result.verbCount} verbs`)
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export declare function rebuildCounts(storage: BaseStorage): Promise<RebuildCountsResult>;
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuild Counts Utility
|
|
3
|
+
*
|
|
4
|
+
* Scans storage and rebuilds counts.json from actual data
|
|
5
|
+
* Use this to fix databases affected by the v4.1.1 count synchronization bug
|
|
6
|
+
*
|
|
7
|
+
* NO MOCKS - Production-ready implementation
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Rebuild counts.json from actual storage data
|
|
11
|
+
*
|
|
12
|
+
* This scans all entities and relationships in storage and reconstructs
|
|
13
|
+
* the counts index from scratch. Use this to fix count desynchronization.
|
|
14
|
+
*
|
|
15
|
+
* @param storage - The storage adapter to rebuild counts for
|
|
16
|
+
* @returns Promise that resolves to rebuild statistics
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```typescript
|
|
20
|
+
* const brain = new Brainy({ storage: { type: 'filesystem', path: './brainy-data' } })
|
|
21
|
+
* await brain.init()
|
|
22
|
+
*
|
|
23
|
+
* const result = await rebuildCounts(brain.storage)
|
|
24
|
+
* console.log(`Rebuilt counts: ${result.nounCount} nouns, ${result.verbCount} verbs`)
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export async function rebuildCounts(storage) {
|
|
28
|
+
const startTime = Date.now();
|
|
29
|
+
console.log('π§ Rebuilding counts from storage...');
|
|
30
|
+
const entityCounts = new Map();
|
|
31
|
+
const verbCounts = new Map();
|
|
32
|
+
let totalNouns = 0;
|
|
33
|
+
let totalVerbs = 0;
|
|
34
|
+
// Scan all nouns using pagination
|
|
35
|
+
console.log('π Scanning entities...');
|
|
36
|
+
// Check if pagination method exists
|
|
37
|
+
const storageWithPagination = storage;
|
|
38
|
+
if (typeof storageWithPagination.getNounsWithPagination !== 'function') {
|
|
39
|
+
throw new Error('Storage adapter does not support getNounsWithPagination');
|
|
40
|
+
}
|
|
41
|
+
let hasMore = true;
|
|
42
|
+
let cursor;
|
|
43
|
+
while (hasMore) {
|
|
44
|
+
const result = await storageWithPagination.getNounsWithPagination({ limit: 100, cursor });
|
|
45
|
+
for (const noun of result.items) {
|
|
46
|
+
const metadata = await storage.getNounMetadata(noun.id);
|
|
47
|
+
if (metadata?.noun) {
|
|
48
|
+
const entityType = metadata.noun;
|
|
49
|
+
entityCounts.set(entityType, (entityCounts.get(entityType) || 0) + 1);
|
|
50
|
+
totalNouns++;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
hasMore = result.hasMore;
|
|
54
|
+
cursor = result.nextCursor;
|
|
55
|
+
}
|
|
56
|
+
console.log(` Found ${totalNouns} entities across ${entityCounts.size} types`);
|
|
57
|
+
// Scan all verbs using pagination
|
|
58
|
+
console.log('π Scanning relationships...');
|
|
59
|
+
if (typeof storageWithPagination.getVerbsWithPagination !== 'function') {
|
|
60
|
+
throw new Error('Storage adapter does not support getVerbsWithPagination');
|
|
61
|
+
}
|
|
62
|
+
hasMore = true;
|
|
63
|
+
cursor = undefined;
|
|
64
|
+
while (hasMore) {
|
|
65
|
+
const result = await storageWithPagination.getVerbsWithPagination({ limit: 100, cursor });
|
|
66
|
+
for (const verb of result.items) {
|
|
67
|
+
if (verb.verb) {
|
|
68
|
+
const verbType = verb.verb;
|
|
69
|
+
verbCounts.set(verbType, (verbCounts.get(verbType) || 0) + 1);
|
|
70
|
+
totalVerbs++;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
hasMore = result.hasMore;
|
|
74
|
+
cursor = result.nextCursor;
|
|
75
|
+
}
|
|
76
|
+
console.log(` Found ${totalVerbs} relationships across ${verbCounts.size} types`);
|
|
77
|
+
// Update storage adapter's in-memory counts FIRST
|
|
78
|
+
storageWithPagination.totalNounCount = totalNouns;
|
|
79
|
+
storageWithPagination.totalVerbCount = totalVerbs;
|
|
80
|
+
storageWithPagination.entityCounts = entityCounts;
|
|
81
|
+
storageWithPagination.verbCounts = verbCounts;
|
|
82
|
+
// Mark counts as pending persist (required for flushCounts to actually persist)
|
|
83
|
+
storageWithPagination.pendingCountPersist = true;
|
|
84
|
+
storageWithPagination.pendingCountOperations = 1;
|
|
85
|
+
// Persist counts using storage adapter's own persist method
|
|
86
|
+
// This ensures counts.json is written correctly (compressed or uncompressed)
|
|
87
|
+
await storageWithPagination.flushCounts();
|
|
88
|
+
const duration = Date.now() - startTime;
|
|
89
|
+
console.log(`β
Counts rebuilt successfully in ${duration}ms`);
|
|
90
|
+
console.log(` Entities: ${totalNouns}`);
|
|
91
|
+
console.log(` Relationships: ${totalVerbs}`);
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log('Entity breakdown:');
|
|
94
|
+
entityCounts.forEach((count, entityType) => {
|
|
95
|
+
console.log(` ${entityType}: ${count}`);
|
|
96
|
+
});
|
|
97
|
+
if (verbCounts.size > 0) {
|
|
98
|
+
console.log('');
|
|
99
|
+
console.log('Relationship breakdown:');
|
|
100
|
+
verbCounts.forEach((count, verbType) => {
|
|
101
|
+
console.log(` ${verbType}: ${count}`);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
return {
|
|
105
|
+
nounCount: totalNouns,
|
|
106
|
+
verbCount: totalVerbs,
|
|
107
|
+
entityCounts,
|
|
108
|
+
verbCounts,
|
|
109
|
+
duration
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//# sourceMappingURL=rebuildCounts.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "4.1.
|
|
3
|
+
"version": "4.1.3",
|
|
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",
|