@soulcraft/brainy 3.1.1 → 3.2.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.
- package/dist/augmentations/metricsAugmentation.d.ts +19 -4
- package/dist/augmentations/metricsAugmentation.js +19 -4
- package/dist/brainy.d.ts +76 -0
- package/dist/brainy.js +269 -23
- package/dist/graph/graphAdjacencyIndex.d.ts +24 -1
- package/dist/graph/graphAdjacencyIndex.js +54 -1
- package/dist/utils/metadataIndex.d.ts +18 -1
- package/dist/utils/metadataIndex.js +39 -1
- package/package.json +1 -1
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Metrics Augmentation - Optional Performance & Usage Metrics
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* IMPORTANT: This is SEPARATE from core counting (brain.counts.*) which is always enabled.
|
|
5
|
+
*
|
|
6
|
+
* Core counting provides O(1) entity/relationship counts for scalability.
|
|
7
|
+
* This augmentation provides performance analytics and observability.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Search performance tracking (latency, throughput)
|
|
11
|
+
* - Cache hit/miss rates
|
|
12
|
+
* - Operation timing analysis
|
|
13
|
+
* - Usage pattern insights
|
|
6
14
|
*
|
|
7
15
|
* Zero-config: Automatically enabled for observability
|
|
8
|
-
* Can be disabled
|
|
16
|
+
* Can be disabled via augmentation registry without affecting core performance
|
|
9
17
|
*/
|
|
10
18
|
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
11
19
|
export interface MetricsConfig {
|
|
@@ -71,10 +79,16 @@ export declare class MetricsAugmentation extends BaseAugmentation {
|
|
|
71
79
|
*/
|
|
72
80
|
private persistMetrics;
|
|
73
81
|
/**
|
|
74
|
-
* Get current metrics
|
|
82
|
+
* Get current metrics (performance analytics only)
|
|
83
|
+
*
|
|
84
|
+
* NOTE: For production counting (entities, relationships), use:
|
|
85
|
+
* - brain.counts.entities() - O(1) total count
|
|
86
|
+
* - brain.counts.byType() - O(1) type-specific counts
|
|
87
|
+
* - brain.counts.relationships() - O(1) relationship counts
|
|
75
88
|
*/
|
|
76
89
|
getStatistics(): {
|
|
77
90
|
enabled: boolean;
|
|
91
|
+
note: string;
|
|
78
92
|
totalSearches: number;
|
|
79
93
|
totalUpdates: number;
|
|
80
94
|
contentTypes: {};
|
|
@@ -166,6 +180,7 @@ export declare class MetricsAugmentation extends BaseAugmentation {
|
|
|
166
180
|
lastUpdated?: string | undefined;
|
|
167
181
|
distributedConfig?: import("../types/distributedTypes.js").SharedConfig | undefined;
|
|
168
182
|
enabled: boolean;
|
|
183
|
+
note: string;
|
|
169
184
|
totalSearches?: undefined;
|
|
170
185
|
totalUpdates?: undefined;
|
|
171
186
|
verbTypes?: undefined;
|
|
@@ -1,11 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Metrics Augmentation - Optional Performance & Usage Metrics
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* IMPORTANT: This is SEPARATE from core counting (brain.counts.*) which is always enabled.
|
|
5
|
+
*
|
|
6
|
+
* Core counting provides O(1) entity/relationship counts for scalability.
|
|
7
|
+
* This augmentation provides performance analytics and observability.
|
|
8
|
+
*
|
|
9
|
+
* Features:
|
|
10
|
+
* - Search performance tracking (latency, throughput)
|
|
11
|
+
* - Cache hit/miss rates
|
|
12
|
+
* - Operation timing analysis
|
|
13
|
+
* - Usage pattern insights
|
|
6
14
|
*
|
|
7
15
|
* Zero-config: Automatically enabled for observability
|
|
8
|
-
* Can be disabled
|
|
16
|
+
* Can be disabled via augmentation registry without affecting core performance
|
|
9
17
|
*/
|
|
10
18
|
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
11
19
|
import { StatisticsCollector } from '../utils/statisticsCollector.js';
|
|
@@ -224,12 +232,18 @@ export class MetricsAugmentation extends BaseAugmentation {
|
|
|
224
232
|
}
|
|
225
233
|
}
|
|
226
234
|
/**
|
|
227
|
-
* Get current metrics
|
|
235
|
+
* Get current metrics (performance analytics only)
|
|
236
|
+
*
|
|
237
|
+
* NOTE: For production counting (entities, relationships), use:
|
|
238
|
+
* - brain.counts.entities() - O(1) total count
|
|
239
|
+
* - brain.counts.byType() - O(1) type-specific counts
|
|
240
|
+
* - brain.counts.relationships() - O(1) relationship counts
|
|
228
241
|
*/
|
|
229
242
|
getStatistics() {
|
|
230
243
|
if (!this.statisticsCollector) {
|
|
231
244
|
return {
|
|
232
245
|
enabled: false,
|
|
246
|
+
note: 'For core counting, use brain.counts.* APIs which are always available',
|
|
233
247
|
totalSearches: 0,
|
|
234
248
|
totalUpdates: 0,
|
|
235
249
|
contentTypes: {},
|
|
@@ -243,6 +257,7 @@ export class MetricsAugmentation extends BaseAugmentation {
|
|
|
243
257
|
}
|
|
244
258
|
return {
|
|
245
259
|
enabled: true,
|
|
260
|
+
note: 'Performance analytics only. Core counting available via brain.counts.*',
|
|
246
261
|
...this.statisticsCollector.getStatistics()
|
|
247
262
|
};
|
|
248
263
|
}
|
package/dist/brainy.d.ts
CHANGED
|
@@ -195,6 +195,82 @@ export declare class Brainy<T = any> {
|
|
|
195
195
|
services: string[];
|
|
196
196
|
density: number;
|
|
197
197
|
}>;
|
|
198
|
+
/**
|
|
199
|
+
* Efficient Pagination API - Production-scale pagination using index-first approach
|
|
200
|
+
* Automatically optimizes based on query type and applies pagination at the index level
|
|
201
|
+
*/
|
|
202
|
+
get pagination(): {
|
|
203
|
+
find: (params: FindParams<T> & {
|
|
204
|
+
page?: number;
|
|
205
|
+
pageSize?: number;
|
|
206
|
+
}) => Promise<Result<T>[]>;
|
|
207
|
+
count: (params: Omit<FindParams<T>, "limit" | "offset">) => Promise<number>;
|
|
208
|
+
meta: (params: FindParams<T> & {
|
|
209
|
+
page?: number;
|
|
210
|
+
pageSize?: number;
|
|
211
|
+
}) => Promise<{
|
|
212
|
+
page: number;
|
|
213
|
+
pageSize: number;
|
|
214
|
+
totalCount: number;
|
|
215
|
+
totalPages: number;
|
|
216
|
+
hasNext: boolean;
|
|
217
|
+
hasPrev: boolean;
|
|
218
|
+
}>;
|
|
219
|
+
};
|
|
220
|
+
/**
|
|
221
|
+
* Streaming API - Process millions of entities with constant memory using existing Pipeline
|
|
222
|
+
* Integrates with index-based optimizations for maximum efficiency
|
|
223
|
+
*/
|
|
224
|
+
get streaming(): {
|
|
225
|
+
entities: (filter?: Partial<FindParams<T>>) => AsyncGenerator<Entity<T>>;
|
|
226
|
+
search: (params: FindParams<T>, batchSize?: number) => AsyncGenerator<{
|
|
227
|
+
id: string;
|
|
228
|
+
score: number;
|
|
229
|
+
entity: Entity<T>;
|
|
230
|
+
}>;
|
|
231
|
+
relationships: (filter?: {
|
|
232
|
+
type?: string;
|
|
233
|
+
sourceId?: string;
|
|
234
|
+
targetId?: string;
|
|
235
|
+
}) => AsyncGenerator<any>;
|
|
236
|
+
pipeline: (source: AsyncIterable<any>) => any;
|
|
237
|
+
process: (processor: (entity: Entity<T>) => Promise<Entity<T>>, filter?: Partial<FindParams<T>>, options?: {
|
|
238
|
+
batchSize: number;
|
|
239
|
+
parallel: number;
|
|
240
|
+
}) => Promise<void>;
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* O(1) Count API - Production-scale counting using existing indexes
|
|
244
|
+
* Works across all storage adapters (FileSystem, OPFS, S3, Memory)
|
|
245
|
+
*/
|
|
246
|
+
get counts(): {
|
|
247
|
+
entities: () => number;
|
|
248
|
+
relationships: () => number;
|
|
249
|
+
byType: (type?: string) => number | {
|
|
250
|
+
[k: string]: number;
|
|
251
|
+
};
|
|
252
|
+
byRelationshipType: (type?: string) => number | {
|
|
253
|
+
[k: string]: number;
|
|
254
|
+
};
|
|
255
|
+
byCriteria: (field: string, value: any) => Promise<number>;
|
|
256
|
+
getAllTypeCounts: () => Map<string, number>;
|
|
257
|
+
getStats: () => {
|
|
258
|
+
entities: {
|
|
259
|
+
total: number;
|
|
260
|
+
byType: {
|
|
261
|
+
[k: string]: number;
|
|
262
|
+
};
|
|
263
|
+
};
|
|
264
|
+
relationships: {
|
|
265
|
+
totalRelationships: number;
|
|
266
|
+
relationshipsByType: Record<string, number>;
|
|
267
|
+
uniqueSourceNodes: number;
|
|
268
|
+
uniqueTargetNodes: number;
|
|
269
|
+
totalNodes: number;
|
|
270
|
+
};
|
|
271
|
+
density: number;
|
|
272
|
+
};
|
|
273
|
+
};
|
|
198
274
|
/**
|
|
199
275
|
* Augmentations API - Clean and simple
|
|
200
276
|
*/
|
package/dist/brainy.js
CHANGED
|
@@ -16,6 +16,7 @@ import { NaturalLanguageProcessor } from './neural/naturalLanguageProcessor.js';
|
|
|
16
16
|
import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js';
|
|
17
17
|
import { MetadataIndexManager } from './utils/metadataIndex.js';
|
|
18
18
|
import { GraphAdjacencyIndex } from './graph/graphAdjacencyIndex.js';
|
|
19
|
+
import { createPipeline } from './streaming/pipeline.js';
|
|
19
20
|
import { configureLogger, LogLevel } from './utils/logger.js';
|
|
20
21
|
import { NounType } from './types/graphTypes.js';
|
|
21
22
|
/**
|
|
@@ -468,22 +469,51 @@ export class Brainy {
|
|
|
468
469
|
const filteredIds = await this.metadataIndex.getIdsForFilter(filter);
|
|
469
470
|
// CRITICAL FIX: Handle both cases properly
|
|
470
471
|
if (results.length > 0) {
|
|
471
|
-
// Filter existing results (from vector search)
|
|
472
|
+
// OPTIMIZED: Filter existing results (from vector search) efficiently
|
|
472
473
|
const filteredIdSet = new Set(filteredIds);
|
|
473
474
|
results = results.filter((r) => filteredIdSet.has(r.id));
|
|
475
|
+
// Apply early pagination for vector + metadata queries
|
|
476
|
+
const limit = params.limit || 10;
|
|
477
|
+
const offset = params.offset || 0;
|
|
478
|
+
// If we have enough filtered results, sort and paginate early
|
|
479
|
+
if (results.length >= offset + limit) {
|
|
480
|
+
results.sort((a, b) => b.score - a.score);
|
|
481
|
+
results = results.slice(offset, offset + limit);
|
|
482
|
+
// Load entities only for the paginated results
|
|
483
|
+
for (const result of results) {
|
|
484
|
+
if (!result.entity) {
|
|
485
|
+
const entity = await this.get(result.id);
|
|
486
|
+
if (entity) {
|
|
487
|
+
result.entity = entity;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
// Early return if no other processing needed
|
|
492
|
+
if (!params.connected && !params.fusion) {
|
|
493
|
+
return results;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
474
496
|
}
|
|
475
497
|
else {
|
|
476
|
-
//
|
|
477
|
-
|
|
498
|
+
// OPTIMIZED: Apply pagination to filtered IDs BEFORE loading entities
|
|
499
|
+
const limit = params.limit || 10;
|
|
500
|
+
const offset = params.offset || 0;
|
|
501
|
+
const pageIds = filteredIds.slice(offset, offset + limit);
|
|
502
|
+
// Load only entities for current page - O(page_size) instead of O(total_results)
|
|
503
|
+
for (const id of pageIds) {
|
|
478
504
|
const entity = await this.get(id);
|
|
479
505
|
if (entity) {
|
|
480
506
|
results.push({
|
|
481
507
|
id,
|
|
482
508
|
score: 1.0, // All metadata matches are equally relevant
|
|
483
|
-
entity
|
|
509
|
+
entity: entity
|
|
484
510
|
});
|
|
485
511
|
}
|
|
486
512
|
}
|
|
513
|
+
// Early return for metadata-only queries with pagination applied
|
|
514
|
+
if (!params.query && !params.connected) {
|
|
515
|
+
return results;
|
|
516
|
+
}
|
|
487
517
|
}
|
|
488
518
|
}
|
|
489
519
|
// Graph search component with O(1) traversal
|
|
@@ -494,10 +524,11 @@ export class Brainy {
|
|
|
494
524
|
if (params.fusion && results.length > 0) {
|
|
495
525
|
results = this.applyFusionScoring(results, params.fusion);
|
|
496
526
|
}
|
|
497
|
-
// Sort
|
|
527
|
+
// OPTIMIZED: Sort first, then apply efficient pagination
|
|
498
528
|
results.sort((a, b) => b.score - a.score);
|
|
499
529
|
const limit = params.limit || 10;
|
|
500
530
|
const offset = params.offset || 0;
|
|
531
|
+
// Efficient pagination - only slice what we need
|
|
501
532
|
return results.slice(offset, offset + limit);
|
|
502
533
|
});
|
|
503
534
|
// Record performance for auto-tuning
|
|
@@ -863,24 +894,16 @@ export class Brainy {
|
|
|
863
894
|
*/
|
|
864
895
|
async insights() {
|
|
865
896
|
await this.ensureInitialized();
|
|
866
|
-
//
|
|
867
|
-
const
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
const
|
|
871
|
-
//
|
|
872
|
-
const
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
const
|
|
876
|
-
// Count by type
|
|
877
|
-
const types = {};
|
|
878
|
-
for (const entity of entitiesResult.items) {
|
|
879
|
-
const type = entity.metadata?.noun || 'unknown';
|
|
880
|
-
types[type] = (types[type] || 0) + 1;
|
|
881
|
-
}
|
|
882
|
-
// Get unique services
|
|
883
|
-
const services = [...new Set(entitiesResult.items.map((e) => e.metadata?.service).filter(Boolean))];
|
|
897
|
+
// O(1) entity counting using existing MetadataIndexManager
|
|
898
|
+
const entities = this.metadataIndex.getTotalEntityCount();
|
|
899
|
+
// O(1) count by type using existing index tracking
|
|
900
|
+
const typeCountsMap = this.metadataIndex.getAllEntityCounts();
|
|
901
|
+
const types = Object.fromEntries(typeCountsMap);
|
|
902
|
+
// O(1) relationships count using GraphAdjacencyIndex
|
|
903
|
+
const relationships = this.graphIndex.getTotalRelationshipCount();
|
|
904
|
+
// Get unique services - O(log n) using index
|
|
905
|
+
const serviceValues = await this.metadataIndex.getFilterValues('service');
|
|
906
|
+
const services = serviceValues.filter(Boolean);
|
|
884
907
|
// Calculate density (relationships per entity)
|
|
885
908
|
const density = entities > 0 ? relationships / entities : 0;
|
|
886
909
|
return {
|
|
@@ -891,6 +914,229 @@ export class Brainy {
|
|
|
891
914
|
density
|
|
892
915
|
};
|
|
893
916
|
}
|
|
917
|
+
/**
|
|
918
|
+
* Efficient Pagination API - Production-scale pagination using index-first approach
|
|
919
|
+
* Automatically optimizes based on query type and applies pagination at the index level
|
|
920
|
+
*/
|
|
921
|
+
get pagination() {
|
|
922
|
+
return {
|
|
923
|
+
// Get paginated results with automatic optimization
|
|
924
|
+
find: async (params) => {
|
|
925
|
+
const page = params.page || 1;
|
|
926
|
+
const pageSize = params.pageSize || 10;
|
|
927
|
+
const offset = (page - 1) * pageSize;
|
|
928
|
+
return this.find({
|
|
929
|
+
...params,
|
|
930
|
+
limit: pageSize,
|
|
931
|
+
offset
|
|
932
|
+
});
|
|
933
|
+
},
|
|
934
|
+
// Get total count for pagination UI (O(1) when possible)
|
|
935
|
+
count: async (params) => {
|
|
936
|
+
// For simple type queries, use O(1) index counting
|
|
937
|
+
if (params.type && !params.query && !params.where && !params.connected) {
|
|
938
|
+
const types = Array.isArray(params.type) ? params.type : [params.type];
|
|
939
|
+
return types.reduce((sum, type) => sum + this.metadataIndex.getEntityCountByType(type), 0);
|
|
940
|
+
}
|
|
941
|
+
// For complex queries, use metadata index for efficient counting
|
|
942
|
+
if (params.where || params.service) {
|
|
943
|
+
let filter = {};
|
|
944
|
+
if (params.where)
|
|
945
|
+
Object.assign(filter, params.where);
|
|
946
|
+
if (params.service)
|
|
947
|
+
filter.service = params.service;
|
|
948
|
+
if (params.type) {
|
|
949
|
+
const types = Array.isArray(params.type) ? params.type : [params.type];
|
|
950
|
+
if (types.length === 1) {
|
|
951
|
+
filter.noun = types[0];
|
|
952
|
+
}
|
|
953
|
+
else {
|
|
954
|
+
const baseFilter = { ...filter };
|
|
955
|
+
filter = {
|
|
956
|
+
anyOf: types.map(type => ({ noun: type, ...baseFilter }))
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
}
|
|
960
|
+
const filteredIds = await this.metadataIndex.getIdsForFilter(filter);
|
|
961
|
+
return filteredIds.length;
|
|
962
|
+
}
|
|
963
|
+
// Fallback: total entity count
|
|
964
|
+
return this.metadataIndex.getTotalEntityCount();
|
|
965
|
+
},
|
|
966
|
+
// Get pagination metadata
|
|
967
|
+
meta: async (params) => {
|
|
968
|
+
const page = params.page || 1;
|
|
969
|
+
const pageSize = params.pageSize || 10;
|
|
970
|
+
const totalCount = await this.pagination.count(params);
|
|
971
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
972
|
+
return {
|
|
973
|
+
page,
|
|
974
|
+
pageSize,
|
|
975
|
+
totalCount,
|
|
976
|
+
totalPages,
|
|
977
|
+
hasNext: page < totalPages,
|
|
978
|
+
hasPrev: page > 1
|
|
979
|
+
};
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
}
|
|
983
|
+
/**
|
|
984
|
+
* Streaming API - Process millions of entities with constant memory using existing Pipeline
|
|
985
|
+
* Integrates with index-based optimizations for maximum efficiency
|
|
986
|
+
*/
|
|
987
|
+
get streaming() {
|
|
988
|
+
return {
|
|
989
|
+
// Stream all entities with optional filtering
|
|
990
|
+
entities: async function* (filter) {
|
|
991
|
+
if (filter?.type || filter?.where || filter?.service) {
|
|
992
|
+
// Use MetadataIndexManager for efficient filtered streaming
|
|
993
|
+
let filterObj = {};
|
|
994
|
+
if (filter.where)
|
|
995
|
+
Object.assign(filterObj, filter.where);
|
|
996
|
+
if (filter.service)
|
|
997
|
+
filterObj.service = filter.service;
|
|
998
|
+
if (filter.type) {
|
|
999
|
+
const types = Array.isArray(filter.type) ? filter.type : [filter.type];
|
|
1000
|
+
if (types.length === 1) {
|
|
1001
|
+
filterObj.noun = types[0];
|
|
1002
|
+
}
|
|
1003
|
+
else {
|
|
1004
|
+
const baseFilterObj = { ...filterObj };
|
|
1005
|
+
filterObj = {
|
|
1006
|
+
anyOf: types.map(type => ({ noun: type, ...baseFilterObj }))
|
|
1007
|
+
};
|
|
1008
|
+
}
|
|
1009
|
+
}
|
|
1010
|
+
const filteredIds = await this.metadataIndex.getIdsForFilter(filterObj);
|
|
1011
|
+
// Stream filtered entities in batches for memory efficiency
|
|
1012
|
+
const batchSize = 100;
|
|
1013
|
+
for (let i = 0; i < filteredIds.length; i += batchSize) {
|
|
1014
|
+
const batchIds = filteredIds.slice(i, i + batchSize);
|
|
1015
|
+
for (const id of batchIds) {
|
|
1016
|
+
const entity = await this.get(id);
|
|
1017
|
+
if (entity)
|
|
1018
|
+
yield entity;
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
else {
|
|
1023
|
+
// Stream all entities using storage adapter pagination
|
|
1024
|
+
let offset = 0;
|
|
1025
|
+
const batchSize = 100;
|
|
1026
|
+
let hasMore = true;
|
|
1027
|
+
while (hasMore) {
|
|
1028
|
+
const result = await this.storage.getNouns({
|
|
1029
|
+
pagination: { offset, limit: batchSize }
|
|
1030
|
+
});
|
|
1031
|
+
for (const noun of result.items) {
|
|
1032
|
+
// Convert HNSWNoun to Entity<T>
|
|
1033
|
+
yield noun;
|
|
1034
|
+
}
|
|
1035
|
+
hasMore = result.hasMore;
|
|
1036
|
+
offset += batchSize;
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
}.bind(this),
|
|
1040
|
+
// Stream search results efficiently
|
|
1041
|
+
search: async function* (params, batchSize = 50) {
|
|
1042
|
+
const originalLimit = params.limit;
|
|
1043
|
+
let offset = 0;
|
|
1044
|
+
let hasMore = true;
|
|
1045
|
+
while (hasMore) {
|
|
1046
|
+
const batchResults = await this.find({
|
|
1047
|
+
...params,
|
|
1048
|
+
limit: batchSize,
|
|
1049
|
+
offset
|
|
1050
|
+
});
|
|
1051
|
+
for (const result of batchResults) {
|
|
1052
|
+
yield result;
|
|
1053
|
+
}
|
|
1054
|
+
hasMore = batchResults.length === batchSize;
|
|
1055
|
+
offset += batchSize;
|
|
1056
|
+
// Respect original limit if specified
|
|
1057
|
+
if (originalLimit && offset >= originalLimit) {
|
|
1058
|
+
break;
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
}.bind(this),
|
|
1062
|
+
// Stream relationships efficiently
|
|
1063
|
+
relationships: async function* (filter) {
|
|
1064
|
+
let offset = 0;
|
|
1065
|
+
const batchSize = 100;
|
|
1066
|
+
let hasMore = true;
|
|
1067
|
+
while (hasMore) {
|
|
1068
|
+
const result = await this.storage.getVerbs({
|
|
1069
|
+
pagination: { offset, limit: batchSize },
|
|
1070
|
+
filter
|
|
1071
|
+
});
|
|
1072
|
+
for (const verb of result.items) {
|
|
1073
|
+
yield verb;
|
|
1074
|
+
}
|
|
1075
|
+
hasMore = result.hasMore;
|
|
1076
|
+
offset += batchSize;
|
|
1077
|
+
}
|
|
1078
|
+
}.bind(this),
|
|
1079
|
+
// Create processing pipeline from stream
|
|
1080
|
+
pipeline: (source) => {
|
|
1081
|
+
return createPipeline(this).source(source);
|
|
1082
|
+
},
|
|
1083
|
+
// Batch process entities with Pipeline system
|
|
1084
|
+
process: async function (processor, filter, options = { batchSize: 50, parallel: 4 }) {
|
|
1085
|
+
return createPipeline(this)
|
|
1086
|
+
.source(this.streaming.entities(filter))
|
|
1087
|
+
.batch(options.batchSize)
|
|
1088
|
+
.parallelSink(async (batch) => {
|
|
1089
|
+
await Promise.all(batch.map(processor));
|
|
1090
|
+
}, options.parallel)
|
|
1091
|
+
.run();
|
|
1092
|
+
}.bind(this)
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* O(1) Count API - Production-scale counting using existing indexes
|
|
1097
|
+
* Works across all storage adapters (FileSystem, OPFS, S3, Memory)
|
|
1098
|
+
*/
|
|
1099
|
+
get counts() {
|
|
1100
|
+
return {
|
|
1101
|
+
// O(1) total entity count
|
|
1102
|
+
entities: () => this.metadataIndex.getTotalEntityCount(),
|
|
1103
|
+
// O(1) total relationship count
|
|
1104
|
+
relationships: () => this.graphIndex.getTotalRelationshipCount(),
|
|
1105
|
+
// O(1) count by type
|
|
1106
|
+
byType: (type) => {
|
|
1107
|
+
if (type) {
|
|
1108
|
+
return this.metadataIndex.getEntityCountByType(type);
|
|
1109
|
+
}
|
|
1110
|
+
return Object.fromEntries(this.metadataIndex.getAllEntityCounts());
|
|
1111
|
+
},
|
|
1112
|
+
// O(1) count by relationship type
|
|
1113
|
+
byRelationshipType: (type) => {
|
|
1114
|
+
if (type) {
|
|
1115
|
+
return this.graphIndex.getRelationshipCountByType(type);
|
|
1116
|
+
}
|
|
1117
|
+
return Object.fromEntries(this.graphIndex.getAllRelationshipCounts());
|
|
1118
|
+
},
|
|
1119
|
+
// O(1) count by field-value criteria
|
|
1120
|
+
byCriteria: async (field, value) => {
|
|
1121
|
+
return this.metadataIndex.getCountForCriteria(field, value);
|
|
1122
|
+
},
|
|
1123
|
+
// Get all type counts as Map for performance-critical operations
|
|
1124
|
+
getAllTypeCounts: () => this.metadataIndex.getAllEntityCounts(),
|
|
1125
|
+
// Get complete statistics
|
|
1126
|
+
getStats: () => {
|
|
1127
|
+
const entityStats = {
|
|
1128
|
+
total: this.metadataIndex.getTotalEntityCount(),
|
|
1129
|
+
byType: Object.fromEntries(this.metadataIndex.getAllEntityCounts())
|
|
1130
|
+
};
|
|
1131
|
+
const relationshipStats = this.graphIndex.getRelationshipStats();
|
|
1132
|
+
return {
|
|
1133
|
+
entities: entityStats,
|
|
1134
|
+
relationships: relationshipStats,
|
|
1135
|
+
density: entityStats.total > 0 ? relationshipStats.totalRelationships / entityStats.total : 0
|
|
1136
|
+
};
|
|
1137
|
+
}
|
|
1138
|
+
};
|
|
1139
|
+
}
|
|
894
1140
|
/**
|
|
895
1141
|
* Augmentations API - Clean and simple
|
|
896
1142
|
*/
|
|
@@ -42,6 +42,7 @@ export declare class GraphAdjacencyIndex {
|
|
|
42
42
|
private flushTimer?;
|
|
43
43
|
private rebuildStartTime;
|
|
44
44
|
private totalRelationshipsIndexed;
|
|
45
|
+
private relationshipCountsByType;
|
|
45
46
|
constructor(storage: StorageAdapter, config?: GraphIndexConfig);
|
|
46
47
|
/**
|
|
47
48
|
* Core API - O(1) neighbor lookup
|
|
@@ -49,9 +50,31 @@ export declare class GraphAdjacencyIndex {
|
|
|
49
50
|
*/
|
|
50
51
|
getNeighbors(id: string, direction?: 'in' | 'out' | 'both'): Promise<string[]>;
|
|
51
52
|
/**
|
|
52
|
-
* Get relationship count
|
|
53
|
+
* Get total relationship count - O(1) operation
|
|
53
54
|
*/
|
|
54
55
|
size(): number;
|
|
56
|
+
/**
|
|
57
|
+
* Get relationship count by type - O(1) operation using existing tracking
|
|
58
|
+
*/
|
|
59
|
+
getRelationshipCountByType(type: string): number;
|
|
60
|
+
/**
|
|
61
|
+
* Get total relationship count - O(1) operation
|
|
62
|
+
*/
|
|
63
|
+
getTotalRelationshipCount(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Get all relationship types and their counts - O(1) operation
|
|
66
|
+
*/
|
|
67
|
+
getAllRelationshipCounts(): Map<string, number>;
|
|
68
|
+
/**
|
|
69
|
+
* Get relationship statistics with enhanced counting information
|
|
70
|
+
*/
|
|
71
|
+
getRelationshipStats(): {
|
|
72
|
+
totalRelationships: number;
|
|
73
|
+
relationshipsByType: Record<string, number>;
|
|
74
|
+
uniqueSourceNodes: number;
|
|
75
|
+
uniqueTargetNodes: number;
|
|
76
|
+
totalNodes: number;
|
|
77
|
+
};
|
|
55
78
|
/**
|
|
56
79
|
* Add relationship to index - O(1) amortized
|
|
57
80
|
*/
|
|
@@ -28,6 +28,8 @@ export class GraphAdjacencyIndex {
|
|
|
28
28
|
this.isRebuilding = false;
|
|
29
29
|
this.rebuildStartTime = 0;
|
|
30
30
|
this.totalRelationshipsIndexed = 0;
|
|
31
|
+
// Production-scale relationship counting by type
|
|
32
|
+
this.relationshipCountsByType = new Map();
|
|
31
33
|
this.storage = storage;
|
|
32
34
|
this.config = {
|
|
33
35
|
maxIndexSize: config.maxIndexSize ?? 100000,
|
|
@@ -70,11 +72,50 @@ export class GraphAdjacencyIndex {
|
|
|
70
72
|
return result;
|
|
71
73
|
}
|
|
72
74
|
/**
|
|
73
|
-
* Get relationship count
|
|
75
|
+
* Get total relationship count - O(1) operation
|
|
74
76
|
*/
|
|
75
77
|
size() {
|
|
76
78
|
return this.verbIndex.size;
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Get relationship count by type - O(1) operation using existing tracking
|
|
82
|
+
*/
|
|
83
|
+
getRelationshipCountByType(type) {
|
|
84
|
+
return this.relationshipCountsByType.get(type) || 0;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get total relationship count - O(1) operation
|
|
88
|
+
*/
|
|
89
|
+
getTotalRelationshipCount() {
|
|
90
|
+
return this.verbIndex.size;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get all relationship types and their counts - O(1) operation
|
|
94
|
+
*/
|
|
95
|
+
getAllRelationshipCounts() {
|
|
96
|
+
return new Map(this.relationshipCountsByType);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Get relationship statistics with enhanced counting information
|
|
100
|
+
*/
|
|
101
|
+
getRelationshipStats() {
|
|
102
|
+
const totalRelationships = this.verbIndex.size;
|
|
103
|
+
const relationshipsByType = Object.fromEntries(this.relationshipCountsByType);
|
|
104
|
+
const uniqueSourceNodes = this.sourceIndex.size;
|
|
105
|
+
const uniqueTargetNodes = this.targetIndex.size;
|
|
106
|
+
// Calculate total unique nodes (source ∪ target)
|
|
107
|
+
const allNodes = new Set();
|
|
108
|
+
this.sourceIndex.keys().forEach(id => allNodes.add(id));
|
|
109
|
+
this.targetIndex.keys().forEach(id => allNodes.add(id));
|
|
110
|
+
const totalNodes = allNodes.size;
|
|
111
|
+
return {
|
|
112
|
+
totalRelationships,
|
|
113
|
+
relationshipsByType,
|
|
114
|
+
uniqueSourceNodes,
|
|
115
|
+
uniqueTargetNodes,
|
|
116
|
+
totalNodes
|
|
117
|
+
};
|
|
118
|
+
}
|
|
78
119
|
/**
|
|
79
120
|
* Add relationship to index - O(1) amortized
|
|
80
121
|
*/
|
|
@@ -98,6 +139,9 @@ export class GraphAdjacencyIndex {
|
|
|
98
139
|
// Cache immediately for hot data
|
|
99
140
|
await this.cacheIndexEntry(verb.sourceId, 'source');
|
|
100
141
|
await this.cacheIndexEntry(verb.targetId, 'target');
|
|
142
|
+
// Update type-specific counts atomically
|
|
143
|
+
const verbType = verb.type || 'unknown';
|
|
144
|
+
this.relationshipCountsByType.set(verbType, (this.relationshipCountsByType.get(verbType) || 0) + 1);
|
|
101
145
|
const elapsed = performance.now() - startTime;
|
|
102
146
|
this.totalRelationshipsIndexed++;
|
|
103
147
|
// Performance assertion
|
|
@@ -115,6 +159,15 @@ export class GraphAdjacencyIndex {
|
|
|
115
159
|
const startTime = performance.now();
|
|
116
160
|
// Remove from verb cache
|
|
117
161
|
this.verbIndex.delete(verbId);
|
|
162
|
+
// Update type-specific counts atomically
|
|
163
|
+
const verbType = verb.type || 'unknown';
|
|
164
|
+
const currentCount = this.relationshipCountsByType.get(verbType) || 0;
|
|
165
|
+
if (currentCount > 1) {
|
|
166
|
+
this.relationshipCountsByType.set(verbType, currentCount - 1);
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
this.relationshipCountsByType.delete(verbType);
|
|
170
|
+
}
|
|
118
171
|
// Remove from source index
|
|
119
172
|
const sourceNeighbors = this.sourceIndex.get(verb.sourceId);
|
|
120
173
|
if (sourceNeighbors) {
|
|
@@ -224,7 +224,24 @@ export declare class MetadataIndexManager {
|
|
|
224
224
|
*/
|
|
225
225
|
private loadSortedIndex;
|
|
226
226
|
/**
|
|
227
|
-
* Get
|
|
227
|
+
* Get count of entities by type - O(1) operation using existing tracking
|
|
228
|
+
* This exposes the production-ready counting that's already maintained
|
|
229
|
+
*/
|
|
230
|
+
getEntityCountByType(type: string): number;
|
|
231
|
+
/**
|
|
232
|
+
* Get total count of all entities - O(1) operation
|
|
233
|
+
*/
|
|
234
|
+
getTotalEntityCount(): number;
|
|
235
|
+
/**
|
|
236
|
+
* Get all entity types and their counts - O(1) operation
|
|
237
|
+
*/
|
|
238
|
+
getAllEntityCounts(): Map<string, number>;
|
|
239
|
+
/**
|
|
240
|
+
* Get count of entities matching field-value criteria - O(1) lookup from existing indexes
|
|
241
|
+
*/
|
|
242
|
+
getCountForCriteria(field: string, value: any): Promise<number>;
|
|
243
|
+
/**
|
|
244
|
+
* Get index statistics with enhanced counting information
|
|
228
245
|
*/
|
|
229
246
|
getStats(): Promise<MetadataIndexStats>;
|
|
230
247
|
/**
|
|
@@ -1209,7 +1209,45 @@ export class MetadataIndexManager {
|
|
|
1209
1209
|
return cached;
|
|
1210
1210
|
}
|
|
1211
1211
|
/**
|
|
1212
|
-
* Get
|
|
1212
|
+
* Get count of entities by type - O(1) operation using existing tracking
|
|
1213
|
+
* This exposes the production-ready counting that's already maintained
|
|
1214
|
+
*/
|
|
1215
|
+
getEntityCountByType(type) {
|
|
1216
|
+
return this.totalEntitiesByType.get(type) || 0;
|
|
1217
|
+
}
|
|
1218
|
+
/**
|
|
1219
|
+
* Get total count of all entities - O(1) operation
|
|
1220
|
+
*/
|
|
1221
|
+
getTotalEntityCount() {
|
|
1222
|
+
let total = 0;
|
|
1223
|
+
for (const count of this.totalEntitiesByType.values()) {
|
|
1224
|
+
total += count;
|
|
1225
|
+
}
|
|
1226
|
+
return total;
|
|
1227
|
+
}
|
|
1228
|
+
/**
|
|
1229
|
+
* Get all entity types and their counts - O(1) operation
|
|
1230
|
+
*/
|
|
1231
|
+
getAllEntityCounts() {
|
|
1232
|
+
return new Map(this.totalEntitiesByType);
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Get count of entities matching field-value criteria - O(1) lookup from existing indexes
|
|
1236
|
+
*/
|
|
1237
|
+
async getCountForCriteria(field, value) {
|
|
1238
|
+
const key = this.getIndexKey(field, value);
|
|
1239
|
+
let entry = this.indexCache.get(key);
|
|
1240
|
+
if (!entry) {
|
|
1241
|
+
const loadedEntry = await this.loadIndexEntry(key);
|
|
1242
|
+
if (loadedEntry) {
|
|
1243
|
+
entry = loadedEntry;
|
|
1244
|
+
this.indexCache.set(key, entry);
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
return entry ? entry.ids.size : 0;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Get index statistics with enhanced counting information
|
|
1213
1251
|
*/
|
|
1214
1252
|
async getStats() {
|
|
1215
1253
|
const fields = new Set();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.2.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",
|