@soulcraft/brainy 3.42.0 → 3.43.1
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 +15 -0
- package/dist/augmentations/KnowledgeAugmentation.d.ts +40 -0
- package/dist/augmentations/KnowledgeAugmentation.js +251 -0
- package/dist/brainy.js +1 -0
- package/dist/types/brainyDataInterface.d.ts +52 -0
- package/dist/types/brainyDataInterface.js +10 -0
- package/dist/utils/entityIdMapper.d.ts +93 -0
- package/dist/utils/entityIdMapper.js +169 -0
- package/dist/utils/metadataIndex.d.ts +33 -2
- package/dist/utils/metadataIndex.js +157 -27
- package/dist/utils/metadataIndexChunking.d.ts +26 -17
- package/dist/utils/metadataIndexChunking.js +65 -36
- package/dist/vfs/ConceptSystem.d.ts +203 -0
- package/dist/vfs/ConceptSystem.js +545 -0
- package/dist/vfs/EntityManager.d.ts +75 -0
- package/dist/vfs/EntityManager.js +216 -0
- package/dist/vfs/EventRecorder.d.ts +84 -0
- package/dist/vfs/EventRecorder.js +269 -0
- package/dist/vfs/GitBridge.d.ts +167 -0
- package/dist/vfs/GitBridge.js +537 -0
- package/dist/vfs/KnowledgeLayer.d.ts +35 -0
- package/dist/vfs/KnowledgeLayer.js +443 -0
- package/dist/vfs/PersistentEntitySystem.d.ts +165 -0
- package/dist/vfs/PersistentEntitySystem.js +503 -0
- package/dist/vfs/SemanticVersioning.d.ts +105 -0
- package/dist/vfs/SemanticVersioning.js +309 -0
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
+
### [3.43.1](https://github.com/soulcraftlabs/brainy/compare/v3.43.0...v3.43.1) (2025-10-14)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
* **dependencies**: migrate from roaring (native C++) to roaring-wasm for universal compatibility ([b2afcad](https://github.com/soulcraftlabs/brainy/commit/b2afcad))
|
|
11
|
+
- Eliminates native compilation requirements (no python, make, gcc/g++ needed)
|
|
12
|
+
- Works in all environments (Node.js, browsers, serverless, Docker, Lambda, Cloud Run)
|
|
13
|
+
- Same API and performance (100% compatible RoaringBitmap32 interface)
|
|
14
|
+
- 90% memory savings maintained vs JavaScript Sets
|
|
15
|
+
- Hardware-accelerated bitmap operations unchanged
|
|
16
|
+
- WebAssembly-based for cross-platform compatibility
|
|
17
|
+
|
|
18
|
+
**Impact**: Fixes installation failures on systems without native build tools. Users can now `npm install @soulcraft/brainy` without any prerequisites.
|
|
19
|
+
|
|
5
20
|
### [3.41.1](https://github.com/soulcraftlabs/brainy/compare/v3.41.0...v3.41.1) (2025-10-13)
|
|
6
21
|
|
|
7
22
|
- test: skip failing delete test temporarily (7c47de8)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Layer Augmentation for VFS
|
|
3
|
+
*
|
|
4
|
+
* Adds intelligent features to VFS without modifying core functionality:
|
|
5
|
+
* - Event recording for all operations
|
|
6
|
+
* - Semantic versioning based on content changes
|
|
7
|
+
* - Entity and concept extraction
|
|
8
|
+
* - Git bridge for import/export
|
|
9
|
+
*
|
|
10
|
+
* This is a TRUE augmentation - VFS works perfectly without it
|
|
11
|
+
*/
|
|
12
|
+
import { Brainy } from '../brainy.js';
|
|
13
|
+
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
14
|
+
export declare class KnowledgeAugmentation extends BaseAugmentation {
|
|
15
|
+
name: string;
|
|
16
|
+
timing: 'after';
|
|
17
|
+
metadata: 'none';
|
|
18
|
+
operations: any;
|
|
19
|
+
priority: number;
|
|
20
|
+
constructor(config?: any);
|
|
21
|
+
execute<T = any>(operation: string, params: any, next: () => Promise<T>): Promise<T>;
|
|
22
|
+
private eventRecorder?;
|
|
23
|
+
private semanticVersioning?;
|
|
24
|
+
private entitySystem?;
|
|
25
|
+
private conceptSystem?;
|
|
26
|
+
private gitBridge?;
|
|
27
|
+
private originalMethods;
|
|
28
|
+
initialize(context: any): Promise<void>;
|
|
29
|
+
augment(brain: Brainy): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Wrap a VFS method to add Knowledge Layer functionality
|
|
32
|
+
*/
|
|
33
|
+
private wrapMethod;
|
|
34
|
+
/**
|
|
35
|
+
* Add Knowledge Layer methods to VFS
|
|
36
|
+
*/
|
|
37
|
+
private addKnowledgeMethods;
|
|
38
|
+
private isSemanticChange;
|
|
39
|
+
cleanup(brain: Brainy): Promise<void>;
|
|
40
|
+
}
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Layer Augmentation for VFS
|
|
3
|
+
*
|
|
4
|
+
* Adds intelligent features to VFS without modifying core functionality:
|
|
5
|
+
* - Event recording for all operations
|
|
6
|
+
* - Semantic versioning based on content changes
|
|
7
|
+
* - Entity and concept extraction
|
|
8
|
+
* - Git bridge for import/export
|
|
9
|
+
*
|
|
10
|
+
* This is a TRUE augmentation - VFS works perfectly without it
|
|
11
|
+
*/
|
|
12
|
+
import { BaseAugmentation } from './brainyAugmentation.js';
|
|
13
|
+
import { EventRecorder } from '../vfs/EventRecorder.js';
|
|
14
|
+
import { SemanticVersioning } from '../vfs/SemanticVersioning.js';
|
|
15
|
+
import { PersistentEntitySystem } from '../vfs/PersistentEntitySystem.js';
|
|
16
|
+
import { ConceptSystem } from '../vfs/ConceptSystem.js';
|
|
17
|
+
import { GitBridge } from '../vfs/GitBridge.js';
|
|
18
|
+
export class KnowledgeAugmentation extends BaseAugmentation {
|
|
19
|
+
constructor(config = {}) {
|
|
20
|
+
super(config);
|
|
21
|
+
this.name = 'knowledge';
|
|
22
|
+
this.timing = 'after'; // Process after VFS operations
|
|
23
|
+
this.metadata = 'none'; // No metadata access needed
|
|
24
|
+
this.operations = []; // VFS-specific augmentation, no operation interception
|
|
25
|
+
this.priority = 100; // Run last
|
|
26
|
+
this.originalMethods = new Map();
|
|
27
|
+
}
|
|
28
|
+
async execute(operation, params, next) {
|
|
29
|
+
// Pass through - this augmentation works at VFS level, not operation level
|
|
30
|
+
return await next();
|
|
31
|
+
}
|
|
32
|
+
async initialize(context) {
|
|
33
|
+
await this.augment(context.brain);
|
|
34
|
+
}
|
|
35
|
+
async augment(brain) {
|
|
36
|
+
// Only augment if VFS exists
|
|
37
|
+
const vfs = brain.vfs?.();
|
|
38
|
+
if (!vfs) {
|
|
39
|
+
console.warn('KnowledgeAugmentation: VFS not found, skipping');
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
// Initialize Knowledge Layer components
|
|
43
|
+
this.eventRecorder = new EventRecorder(brain);
|
|
44
|
+
this.semanticVersioning = new SemanticVersioning(brain);
|
|
45
|
+
this.entitySystem = new PersistentEntitySystem(brain);
|
|
46
|
+
this.conceptSystem = new ConceptSystem(brain);
|
|
47
|
+
this.gitBridge = new GitBridge(vfs, brain);
|
|
48
|
+
// Wrap VFS methods to add intelligence WITHOUT slowing them down
|
|
49
|
+
this.wrapMethod(vfs, 'writeFile', async (original, path, data, options) => {
|
|
50
|
+
// Call original first (stays fast)
|
|
51
|
+
const result = await original.call(vfs, path, data, options);
|
|
52
|
+
// Knowledge processing in background (non-blocking)
|
|
53
|
+
setImmediate(async () => {
|
|
54
|
+
try {
|
|
55
|
+
// Record event
|
|
56
|
+
if (this.eventRecorder) {
|
|
57
|
+
await this.eventRecorder.recordEvent({
|
|
58
|
+
type: 'write',
|
|
59
|
+
path,
|
|
60
|
+
content: data,
|
|
61
|
+
size: data.length,
|
|
62
|
+
author: options?.author || 'system'
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
// Check for semantic versioning
|
|
66
|
+
if (this.semanticVersioning) {
|
|
67
|
+
const existingContent = await vfs.readFile(path).catch(() => null);
|
|
68
|
+
const shouldVersion = existingContent && this.isSemanticChange(existingContent, data);
|
|
69
|
+
if (shouldVersion) {
|
|
70
|
+
await this.semanticVersioning.createVersion(path, data, {
|
|
71
|
+
message: 'Automatic semantic version'
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// Extract concepts
|
|
76
|
+
if (this.conceptSystem && options?.extractConcepts !== false) {
|
|
77
|
+
await this.conceptSystem.extractAndLinkConcepts(path, data);
|
|
78
|
+
}
|
|
79
|
+
// Extract entities
|
|
80
|
+
if (this.entitySystem && options?.extractEntities !== false) {
|
|
81
|
+
await this.entitySystem.extractEntities(data.toString('utf8'), data);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Knowledge Layer errors should not affect VFS operations
|
|
86
|
+
console.debug('KnowledgeLayer background processing error:', error);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
return result;
|
|
90
|
+
});
|
|
91
|
+
this.wrapMethod(vfs, 'unlink', async (original, path) => {
|
|
92
|
+
const result = await original.call(vfs, path);
|
|
93
|
+
// Record deletion event
|
|
94
|
+
setImmediate(async () => {
|
|
95
|
+
if (this.eventRecorder) {
|
|
96
|
+
await this.eventRecorder.recordEvent({
|
|
97
|
+
type: 'delete',
|
|
98
|
+
path,
|
|
99
|
+
author: 'system'
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
return result;
|
|
104
|
+
});
|
|
105
|
+
this.wrapMethod(vfs, 'rename', async (original, oldPath, newPath) => {
|
|
106
|
+
const result = await original.call(vfs, oldPath, newPath);
|
|
107
|
+
// Record rename event
|
|
108
|
+
setImmediate(async () => {
|
|
109
|
+
if (this.eventRecorder) {
|
|
110
|
+
await this.eventRecorder.recordEvent({
|
|
111
|
+
type: 'rename',
|
|
112
|
+
path: oldPath,
|
|
113
|
+
metadata: { newPath },
|
|
114
|
+
author: 'system'
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
return result;
|
|
119
|
+
});
|
|
120
|
+
// Add Knowledge Layer methods to VFS
|
|
121
|
+
this.addKnowledgeMethods(vfs);
|
|
122
|
+
console.log('✨ Knowledge Layer augmentation enabled');
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Wrap a VFS method to add Knowledge Layer functionality
|
|
126
|
+
*/
|
|
127
|
+
wrapMethod(vfs, methodName, wrapper) {
|
|
128
|
+
const original = vfs[methodName];
|
|
129
|
+
if (!original)
|
|
130
|
+
return;
|
|
131
|
+
// Store original for cleanup
|
|
132
|
+
this.originalMethods.set(methodName, original);
|
|
133
|
+
// Replace with wrapped version
|
|
134
|
+
vfs[methodName] = async (...args) => {
|
|
135
|
+
return await wrapper(original, ...args);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Add Knowledge Layer methods to VFS
|
|
140
|
+
*/
|
|
141
|
+
addKnowledgeMethods(vfs) {
|
|
142
|
+
// Event history
|
|
143
|
+
vfs.getHistory = async (path, options) => {
|
|
144
|
+
if (!this.eventRecorder)
|
|
145
|
+
throw new Error('Knowledge Layer not initialized');
|
|
146
|
+
return await this.eventRecorder.getHistory(path, options);
|
|
147
|
+
};
|
|
148
|
+
vfs.reconstructAtTime = async (path, timestamp) => {
|
|
149
|
+
if (!this.eventRecorder)
|
|
150
|
+
throw new Error('Knowledge Layer not initialized');
|
|
151
|
+
return await this.eventRecorder.reconstructFileAtTime(path, timestamp);
|
|
152
|
+
};
|
|
153
|
+
// Semantic versioning
|
|
154
|
+
vfs.getVersions = async (path) => {
|
|
155
|
+
if (!this.semanticVersioning)
|
|
156
|
+
throw new Error('Knowledge Layer not initialized');
|
|
157
|
+
return await this.semanticVersioning.getVersions(path);
|
|
158
|
+
};
|
|
159
|
+
vfs.restoreVersion = async (path, versionId) => {
|
|
160
|
+
if (!this.semanticVersioning)
|
|
161
|
+
throw new Error('Knowledge Layer not initialized');
|
|
162
|
+
const version = await this.semanticVersioning.getVersion(path, versionId);
|
|
163
|
+
if (version) {
|
|
164
|
+
await vfs.writeFile(path, version);
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
// Entities
|
|
168
|
+
vfs.findEntity = async (query) => {
|
|
169
|
+
if (!this.entitySystem)
|
|
170
|
+
throw new Error('Knowledge Layer not initialized');
|
|
171
|
+
return await this.entitySystem.findEntity(query);
|
|
172
|
+
};
|
|
173
|
+
vfs.getEntityAppearances = async (entityId) => {
|
|
174
|
+
if (!this.entitySystem)
|
|
175
|
+
throw new Error('Knowledge Layer not initialized');
|
|
176
|
+
return await this.entitySystem.getEvolution(entityId);
|
|
177
|
+
};
|
|
178
|
+
// Concepts
|
|
179
|
+
vfs.getConcepts = async (path) => {
|
|
180
|
+
if (!this.conceptSystem)
|
|
181
|
+
throw new Error('Knowledge Layer not initialized');
|
|
182
|
+
const concepts = await this.conceptSystem.findConcepts({ manifestedIn: path });
|
|
183
|
+
return concepts;
|
|
184
|
+
};
|
|
185
|
+
vfs.getConceptGraph = async (options) => {
|
|
186
|
+
if (!this.conceptSystem)
|
|
187
|
+
throw new Error('Knowledge Layer not initialized');
|
|
188
|
+
return await this.conceptSystem.getConceptGraph(options);
|
|
189
|
+
};
|
|
190
|
+
// Git bridge
|
|
191
|
+
vfs.exportToGit = async (vfsPath, gitPath) => {
|
|
192
|
+
if (!this.gitBridge)
|
|
193
|
+
throw new Error('Knowledge Layer not initialized');
|
|
194
|
+
return await this.gitBridge.exportToGit(vfsPath, gitPath);
|
|
195
|
+
};
|
|
196
|
+
vfs.importFromGit = async (gitPath, vfsPath) => {
|
|
197
|
+
if (!this.gitBridge)
|
|
198
|
+
throw new Error('Knowledge Layer not initialized');
|
|
199
|
+
return await this.gitBridge.importFromGit(gitPath, vfsPath);
|
|
200
|
+
};
|
|
201
|
+
// Temporal coupling
|
|
202
|
+
vfs.findTemporalCoupling = async (path, windowMs) => {
|
|
203
|
+
if (!this.eventRecorder)
|
|
204
|
+
throw new Error('Knowledge Layer not initialized');
|
|
205
|
+
return await this.eventRecorder.findTemporalCoupling(path, windowMs);
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
isSemanticChange(oldContent, newContent) {
|
|
209
|
+
// Simple heuristic - significant size change or different content
|
|
210
|
+
const oldStr = oldContent.toString('utf8');
|
|
211
|
+
const newStr = newContent.toString('utf8');
|
|
212
|
+
// Check for significant size change (>10%)
|
|
213
|
+
const sizeDiff = Math.abs(oldStr.length - newStr.length) / oldStr.length;
|
|
214
|
+
if (sizeDiff > 0.1)
|
|
215
|
+
return true;
|
|
216
|
+
// Check for structural changes (simplified)
|
|
217
|
+
const oldLines = oldStr.split('\n').filter(l => l.trim());
|
|
218
|
+
const newLines = newStr.split('\n').filter(l => l.trim());
|
|
219
|
+
// Different number of non-empty lines
|
|
220
|
+
return Math.abs(oldLines.length - newLines.length) > 5;
|
|
221
|
+
}
|
|
222
|
+
async cleanup(brain) {
|
|
223
|
+
const vfs = brain.vfs?.();
|
|
224
|
+
if (!vfs)
|
|
225
|
+
return;
|
|
226
|
+
// Restore original methods
|
|
227
|
+
for (const [methodName, original] of this.originalMethods) {
|
|
228
|
+
vfs[methodName] = original;
|
|
229
|
+
}
|
|
230
|
+
// Remove added methods
|
|
231
|
+
delete vfs.getHistory;
|
|
232
|
+
delete vfs.reconstructAtTime;
|
|
233
|
+
delete vfs.getVersions;
|
|
234
|
+
delete vfs.restoreVersion;
|
|
235
|
+
delete vfs.findEntity;
|
|
236
|
+
delete vfs.getEntityAppearances;
|
|
237
|
+
delete vfs.getConcepts;
|
|
238
|
+
delete vfs.getConceptGraph;
|
|
239
|
+
delete vfs.exportToGit;
|
|
240
|
+
delete vfs.importFromGit;
|
|
241
|
+
delete vfs.findTemporalCoupling;
|
|
242
|
+
// Clean up components
|
|
243
|
+
this.eventRecorder = undefined;
|
|
244
|
+
this.semanticVersioning = undefined;
|
|
245
|
+
this.entitySystem = undefined;
|
|
246
|
+
this.conceptSystem = undefined;
|
|
247
|
+
this.gitBridge = undefined;
|
|
248
|
+
console.log('Knowledge Layer augmentation removed');
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
//# sourceMappingURL=KnowledgeAugmentation.js.map
|
package/dist/brainy.js
CHANGED
|
@@ -100,6 +100,7 @@ export class Brainy {
|
|
|
100
100
|
this.index = this.setupIndex();
|
|
101
101
|
// Initialize core metadata index
|
|
102
102
|
this.metadataIndex = new MetadataIndexManager(this.storage);
|
|
103
|
+
await this.metadataIndex.init();
|
|
103
104
|
// Initialize core graph index
|
|
104
105
|
this.graphIndex = new GraphAdjacencyIndex(this.storage);
|
|
105
106
|
// Rebuild indexes if needed for existing data
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainyInterface - Modern API Only
|
|
3
|
+
*
|
|
4
|
+
* This interface defines the MODERN methods from Brainy 3.0.
|
|
5
|
+
* Used to break circular dependencies while enforcing modern API usage.
|
|
6
|
+
*
|
|
7
|
+
* NO DEPRECATED METHODS - Only clean, modern API patterns.
|
|
8
|
+
*/
|
|
9
|
+
import { Vector } from '../coreTypes.js';
|
|
10
|
+
import { AddParams, RelateParams, Result, Entity, FindParams, SimilarParams } from './brainy.types.js';
|
|
11
|
+
export interface BrainyInterface<T = unknown> {
|
|
12
|
+
/**
|
|
13
|
+
* Initialize the database
|
|
14
|
+
*/
|
|
15
|
+
init(): Promise<void>;
|
|
16
|
+
/**
|
|
17
|
+
* Modern add method - unified entity creation
|
|
18
|
+
* @param params Parameters for adding entities
|
|
19
|
+
* @returns The ID of the created entity
|
|
20
|
+
*/
|
|
21
|
+
add(params: AddParams<T>): Promise<string>;
|
|
22
|
+
/**
|
|
23
|
+
* Modern relate method - unified relationship creation
|
|
24
|
+
* @param params Parameters for creating relationships
|
|
25
|
+
* @returns The ID of the created relationship
|
|
26
|
+
*/
|
|
27
|
+
relate(params: RelateParams<T>): Promise<string>;
|
|
28
|
+
/**
|
|
29
|
+
* Modern find method - unified search and discovery
|
|
30
|
+
* @param query Search query or parameters object
|
|
31
|
+
* @returns Array of search results
|
|
32
|
+
*/
|
|
33
|
+
find(query: string | FindParams<T>): Promise<Result<T>[]>;
|
|
34
|
+
/**
|
|
35
|
+
* Modern get method - retrieve entities by ID
|
|
36
|
+
* @param id The entity ID to retrieve
|
|
37
|
+
* @returns Entity or null if not found
|
|
38
|
+
*/
|
|
39
|
+
get(id: string): Promise<Entity<T> | null>;
|
|
40
|
+
/**
|
|
41
|
+
* Modern similar method - find similar entities
|
|
42
|
+
* @param params Parameters for similarity search
|
|
43
|
+
* @returns Array of similar entities with scores
|
|
44
|
+
*/
|
|
45
|
+
similar(params: SimilarParams<T>): Promise<Result<T>[]>;
|
|
46
|
+
/**
|
|
47
|
+
* Generate embedding vector from text
|
|
48
|
+
* @param text The text to embed
|
|
49
|
+
* @returns Vector representation of the text
|
|
50
|
+
*/
|
|
51
|
+
embed(text: string): Promise<Vector>;
|
|
52
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BrainyInterface - Modern API Only
|
|
3
|
+
*
|
|
4
|
+
* This interface defines the MODERN methods from Brainy 3.0.
|
|
5
|
+
* Used to break circular dependencies while enforcing modern API usage.
|
|
6
|
+
*
|
|
7
|
+
* NO DEPRECATED METHODS - Only clean, modern API patterns.
|
|
8
|
+
*/
|
|
9
|
+
export {};
|
|
10
|
+
//# sourceMappingURL=brainyDataInterface.js.map
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntityIdMapper - Bidirectional mapping between UUID strings and integer IDs for roaring bitmaps
|
|
3
|
+
*
|
|
4
|
+
* Roaring bitmaps require 32-bit unsigned integers, but Brainy uses UUID strings as entity IDs.
|
|
5
|
+
* This class provides efficient bidirectional mapping with persistence support.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - O(1) lookup in both directions
|
|
9
|
+
* - Persistent storage via storage adapter
|
|
10
|
+
* - Atomic counter for next ID
|
|
11
|
+
* - Serialization/deserialization support
|
|
12
|
+
*
|
|
13
|
+
* @module utils/entityIdMapper
|
|
14
|
+
*/
|
|
15
|
+
import type { StorageAdapter } from '../coreTypes.js';
|
|
16
|
+
export interface EntityIdMapperOptions {
|
|
17
|
+
storage: StorageAdapter;
|
|
18
|
+
storageKey?: string;
|
|
19
|
+
}
|
|
20
|
+
export interface EntityIdMapperData {
|
|
21
|
+
nextId: number;
|
|
22
|
+
uuidToInt: Record<string, number>;
|
|
23
|
+
intToUuid: Record<number, string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Maps entity UUIDs to integer IDs for use with Roaring Bitmaps
|
|
27
|
+
*/
|
|
28
|
+
export declare class EntityIdMapper {
|
|
29
|
+
private storage;
|
|
30
|
+
private storageKey;
|
|
31
|
+
private uuidToInt;
|
|
32
|
+
private intToUuid;
|
|
33
|
+
private nextId;
|
|
34
|
+
private dirty;
|
|
35
|
+
constructor(options: EntityIdMapperOptions);
|
|
36
|
+
/**
|
|
37
|
+
* Initialize the mapper by loading from storage
|
|
38
|
+
*/
|
|
39
|
+
init(): Promise<void>;
|
|
40
|
+
/**
|
|
41
|
+
* Get integer ID for UUID, assigning a new ID if not exists
|
|
42
|
+
*/
|
|
43
|
+
getOrAssign(uuid: string): number;
|
|
44
|
+
/**
|
|
45
|
+
* Get UUID for integer ID
|
|
46
|
+
*/
|
|
47
|
+
getUuid(intId: number): string | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Get integer ID for UUID (without assigning if not exists)
|
|
50
|
+
*/
|
|
51
|
+
getInt(uuid: string): number | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* Check if UUID has been assigned an integer ID
|
|
54
|
+
*/
|
|
55
|
+
has(uuid: string): boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Remove mapping for UUID
|
|
58
|
+
*/
|
|
59
|
+
remove(uuid: string): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Get total number of mappings
|
|
62
|
+
*/
|
|
63
|
+
get size(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Convert array of UUIDs to array of integers
|
|
66
|
+
*/
|
|
67
|
+
uuidsToInts(uuids: string[]): number[];
|
|
68
|
+
/**
|
|
69
|
+
* Convert array of integers to array of UUIDs
|
|
70
|
+
*/
|
|
71
|
+
intsToUuids(ints: number[]): string[];
|
|
72
|
+
/**
|
|
73
|
+
* Convert iterable of integers to array of UUIDs (for roaring bitmap iteration)
|
|
74
|
+
*/
|
|
75
|
+
intsIterableToUuids(ints: Iterable<number>): string[];
|
|
76
|
+
/**
|
|
77
|
+
* Flush mappings to storage
|
|
78
|
+
*/
|
|
79
|
+
flush(): Promise<void>;
|
|
80
|
+
/**
|
|
81
|
+
* Clear all mappings
|
|
82
|
+
*/
|
|
83
|
+
clear(): Promise<void>;
|
|
84
|
+
/**
|
|
85
|
+
* Get statistics about the mapper
|
|
86
|
+
*/
|
|
87
|
+
getStats(): {
|
|
88
|
+
mappings: number;
|
|
89
|
+
nextId: number;
|
|
90
|
+
dirty: boolean;
|
|
91
|
+
memoryEstimate: number;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EntityIdMapper - Bidirectional mapping between UUID strings and integer IDs for roaring bitmaps
|
|
3
|
+
*
|
|
4
|
+
* Roaring bitmaps require 32-bit unsigned integers, but Brainy uses UUID strings as entity IDs.
|
|
5
|
+
* This class provides efficient bidirectional mapping with persistence support.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - O(1) lookup in both directions
|
|
9
|
+
* - Persistent storage via storage adapter
|
|
10
|
+
* - Atomic counter for next ID
|
|
11
|
+
* - Serialization/deserialization support
|
|
12
|
+
*
|
|
13
|
+
* @module utils/entityIdMapper
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* Maps entity UUIDs to integer IDs for use with Roaring Bitmaps
|
|
17
|
+
*/
|
|
18
|
+
export class EntityIdMapper {
|
|
19
|
+
constructor(options) {
|
|
20
|
+
// Bidirectional maps
|
|
21
|
+
this.uuidToInt = new Map();
|
|
22
|
+
this.intToUuid = new Map();
|
|
23
|
+
// Atomic counter for next ID
|
|
24
|
+
this.nextId = 1;
|
|
25
|
+
// Dirty flag for persistence
|
|
26
|
+
this.dirty = false;
|
|
27
|
+
this.storage = options.storage;
|
|
28
|
+
this.storageKey = options.storageKey || 'brainy:entityIdMapper';
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Initialize the mapper by loading from storage
|
|
32
|
+
*/
|
|
33
|
+
async init() {
|
|
34
|
+
try {
|
|
35
|
+
const data = await this.storage.getMetadata(this.storageKey);
|
|
36
|
+
if (data) {
|
|
37
|
+
this.nextId = data.nextId;
|
|
38
|
+
// Rebuild maps from serialized data
|
|
39
|
+
this.uuidToInt = new Map(Object.entries(data.uuidToInt).map(([k, v]) => [k, Number(v)]));
|
|
40
|
+
this.intToUuid = new Map(Object.entries(data.intToUuid).map(([k, v]) => [Number(k), v]));
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
// First time initialization - maps are empty, nextId = 1
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get integer ID for UUID, assigning a new ID if not exists
|
|
49
|
+
*/
|
|
50
|
+
getOrAssign(uuid) {
|
|
51
|
+
const existing = this.uuidToInt.get(uuid);
|
|
52
|
+
if (existing !== undefined) {
|
|
53
|
+
return existing;
|
|
54
|
+
}
|
|
55
|
+
// Assign new ID
|
|
56
|
+
const newId = this.nextId++;
|
|
57
|
+
this.uuidToInt.set(uuid, newId);
|
|
58
|
+
this.intToUuid.set(newId, uuid);
|
|
59
|
+
this.dirty = true;
|
|
60
|
+
return newId;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get UUID for integer ID
|
|
64
|
+
*/
|
|
65
|
+
getUuid(intId) {
|
|
66
|
+
return this.intToUuid.get(intId);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Get integer ID for UUID (without assigning if not exists)
|
|
70
|
+
*/
|
|
71
|
+
getInt(uuid) {
|
|
72
|
+
return this.uuidToInt.get(uuid);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Check if UUID has been assigned an integer ID
|
|
76
|
+
*/
|
|
77
|
+
has(uuid) {
|
|
78
|
+
return this.uuidToInt.has(uuid);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Remove mapping for UUID
|
|
82
|
+
*/
|
|
83
|
+
remove(uuid) {
|
|
84
|
+
const intId = this.uuidToInt.get(uuid);
|
|
85
|
+
if (intId === undefined) {
|
|
86
|
+
return false;
|
|
87
|
+
}
|
|
88
|
+
this.uuidToInt.delete(uuid);
|
|
89
|
+
this.intToUuid.delete(intId);
|
|
90
|
+
this.dirty = true;
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get total number of mappings
|
|
95
|
+
*/
|
|
96
|
+
get size() {
|
|
97
|
+
return this.uuidToInt.size;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Convert array of UUIDs to array of integers
|
|
101
|
+
*/
|
|
102
|
+
uuidsToInts(uuids) {
|
|
103
|
+
return uuids.map(uuid => this.getOrAssign(uuid));
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Convert array of integers to array of UUIDs
|
|
107
|
+
*/
|
|
108
|
+
intsToUuids(ints) {
|
|
109
|
+
const result = [];
|
|
110
|
+
for (const intId of ints) {
|
|
111
|
+
const uuid = this.intToUuid.get(intId);
|
|
112
|
+
if (uuid) {
|
|
113
|
+
result.push(uuid);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return result;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Convert iterable of integers to array of UUIDs (for roaring bitmap iteration)
|
|
120
|
+
*/
|
|
121
|
+
intsIterableToUuids(ints) {
|
|
122
|
+
const result = [];
|
|
123
|
+
for (const intId of ints) {
|
|
124
|
+
const uuid = this.intToUuid.get(intId);
|
|
125
|
+
if (uuid) {
|
|
126
|
+
result.push(uuid);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Flush mappings to storage
|
|
133
|
+
*/
|
|
134
|
+
async flush() {
|
|
135
|
+
if (!this.dirty) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
// Convert maps to plain objects for serialization
|
|
139
|
+
const data = {
|
|
140
|
+
nextId: this.nextId,
|
|
141
|
+
uuidToInt: Object.fromEntries(this.uuidToInt),
|
|
142
|
+
intToUuid: Object.fromEntries(this.intToUuid)
|
|
143
|
+
};
|
|
144
|
+
await this.storage.saveMetadata(this.storageKey, data);
|
|
145
|
+
this.dirty = false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Clear all mappings
|
|
149
|
+
*/
|
|
150
|
+
async clear() {
|
|
151
|
+
this.uuidToInt.clear();
|
|
152
|
+
this.intToUuid.clear();
|
|
153
|
+
this.nextId = 1;
|
|
154
|
+
this.dirty = true;
|
|
155
|
+
await this.flush();
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get statistics about the mapper
|
|
159
|
+
*/
|
|
160
|
+
getStats() {
|
|
161
|
+
return {
|
|
162
|
+
mappings: this.uuidToInt.size,
|
|
163
|
+
nextId: this.nextId,
|
|
164
|
+
dirty: this.dirty,
|
|
165
|
+
memoryEstimate: this.uuidToInt.size * (36 + 8 + 4 + 8) // uuid string + map overhead + int + map overhead
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=entityIdMapper.js.map
|