@soulcraft/brainy 6.3.0 → 6.3.2
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 +28 -0
- package/dist/brainy.d.ts +55 -0
- package/dist/brainy.js +86 -0
- package/dist/versioning/VersionIndex.d.ts +42 -47
- package/dist/versioning/VersionIndex.js +141 -166
- package/dist/versioning/VersionManager.d.ts +26 -6
- package/dist/versioning/VersionManager.js +101 -8
- package/dist/versioning/VersionStorage.d.ts +25 -15
- package/dist/versioning/VersionStorage.js +49 -65
- package/package.json +1 -1
- package/dist/augmentations/KnowledgeAugmentation.d.ts +0 -40
- package/dist/augmentations/KnowledgeAugmentation.js +0 -251
- package/dist/importManager.d.ts +0 -78
- package/dist/importManager.js +0 -267
- package/dist/query/typeInference.d.ts +0 -158
- package/dist/query/typeInference.js +0 -760
- package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +0 -252
- package/dist/storage/adapters/typeAwareStorageAdapter.js +0 -814
- package/dist/types/brainyDataInterface.d.ts +0 -52
- package/dist/types/brainyDataInterface.js +0 -10
- package/dist/vfs/ConceptSystem.d.ts +0 -203
- package/dist/vfs/ConceptSystem.js +0 -545
- package/dist/vfs/EntityManager.d.ts +0 -75
- package/dist/vfs/EntityManager.js +0 -216
- package/dist/vfs/EventRecorder.d.ts +0 -84
- package/dist/vfs/EventRecorder.js +0 -269
- package/dist/vfs/GitBridge.d.ts +0 -167
- package/dist/vfs/GitBridge.js +0 -537
- package/dist/vfs/KnowledgeLayer.d.ts +0 -35
- package/dist/vfs/KnowledgeLayer.js +0 -443
- package/dist/vfs/PersistentEntitySystem.d.ts +0 -165
- package/dist/vfs/PersistentEntitySystem.js +0 -503
- package/dist/vfs/SemanticVersioning.d.ts +0 -105
- package/dist/vfs/SemanticVersioning.js +0 -309
|
@@ -1,31 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VersionIndex -
|
|
2
|
+
* VersionIndex - Pure Key-Value Version Storage (v6.3.0)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
7
|
-
* - Uses EntityIdMapper for efficient ID handling
|
|
8
|
-
* - Uses ChunkManager for adaptive chunking
|
|
9
|
-
* - Leverages Roaring Bitmaps for fast set operations
|
|
4
|
+
* Stores version metadata using simple key-value storage:
|
|
5
|
+
* - Version list per entity: __version_meta_{entityId}_{branch}
|
|
6
|
+
* - Content stored separately by VersionStorage
|
|
10
7
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
* -
|
|
16
|
-
* - versionBranch: Branch version was created on
|
|
17
|
-
* - versionNumber: Version number
|
|
18
|
-
* - versionTag: Optional user tag
|
|
19
|
-
* - versionTimestamp: Creation timestamp
|
|
20
|
-
* - versionCommitHash: Commit hash
|
|
8
|
+
* Key Design Decisions:
|
|
9
|
+
* - Versions are NOT entities (no brain.add())
|
|
10
|
+
* - Versions do NOT pollute find() results
|
|
11
|
+
* - Simple O(1) lookups per entity
|
|
12
|
+
* - Versions per entity is typically small (10-1000)
|
|
21
13
|
*
|
|
22
14
|
* NO MOCKS - Production implementation
|
|
23
15
|
*/
|
|
24
16
|
/**
|
|
25
|
-
* VersionIndex -
|
|
17
|
+
* VersionIndex - Pure key-value version metadata storage
|
|
26
18
|
*
|
|
27
|
-
*
|
|
28
|
-
* This
|
|
19
|
+
* Uses simple JSON storage instead of creating entities.
|
|
20
|
+
* This ensures versions never appear in find() results.
|
|
29
21
|
*/
|
|
30
22
|
export class VersionIndex {
|
|
31
23
|
constructor(brain) {
|
|
@@ -34,8 +26,6 @@ export class VersionIndex {
|
|
|
34
26
|
}
|
|
35
27
|
/**
|
|
36
28
|
* Initialize version index
|
|
37
|
-
*
|
|
38
|
-
* No special setup needed - we use existing entity storage and indexes!
|
|
39
29
|
*/
|
|
40
30
|
async initialize() {
|
|
41
31
|
if (this.initialized)
|
|
@@ -45,75 +35,85 @@ export class VersionIndex {
|
|
|
45
35
|
/**
|
|
46
36
|
* Add version to index
|
|
47
37
|
*
|
|
48
|
-
* Stores version
|
|
49
|
-
*
|
|
38
|
+
* Stores version entry in key-value storage.
|
|
39
|
+
* Handles deduplication by content hash.
|
|
50
40
|
*
|
|
51
|
-
* @param version Version metadata
|
|
41
|
+
* @param version Version metadata to store
|
|
52
42
|
*/
|
|
53
43
|
async addVersion(version) {
|
|
54
44
|
await this.initialize();
|
|
55
|
-
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
versionContentHash: version.contentHash, // Content hash
|
|
74
|
-
versionAuthor: version.author, // Author
|
|
75
|
-
versionDescription: version.description, // Description
|
|
76
|
-
versionMetadata: version.metadata // Additional metadata
|
|
45
|
+
const key = this.getMetaKey(version.entityId, version.branch);
|
|
46
|
+
const store = await this.loadStore(key) || {
|
|
47
|
+
entityId: version.entityId,
|
|
48
|
+
branch: version.branch,
|
|
49
|
+
versions: []
|
|
50
|
+
};
|
|
51
|
+
// Check for duplicate content hash (deduplication)
|
|
52
|
+
const existing = store.versions.find(v => v.contentHash === version.contentHash);
|
|
53
|
+
if (existing) {
|
|
54
|
+
// Update tag/description if provided on duplicate save
|
|
55
|
+
let updated = false;
|
|
56
|
+
if (version.tag && version.tag !== existing.tag) {
|
|
57
|
+
existing.tag = version.tag;
|
|
58
|
+
updated = true;
|
|
59
|
+
}
|
|
60
|
+
if (version.description && version.description !== existing.description) {
|
|
61
|
+
existing.description = version.description;
|
|
62
|
+
updated = true;
|
|
77
63
|
}
|
|
64
|
+
if (updated) {
|
|
65
|
+
await this.saveStore(key, store);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
// Add new version entry
|
|
70
|
+
store.versions.push({
|
|
71
|
+
version: version.version,
|
|
72
|
+
timestamp: version.timestamp,
|
|
73
|
+
contentHash: version.contentHash,
|
|
74
|
+
commitHash: version.commitHash,
|
|
75
|
+
tag: version.tag,
|
|
76
|
+
description: version.description,
|
|
77
|
+
author: version.author
|
|
78
78
|
});
|
|
79
|
+
await this.saveStore(key, store);
|
|
79
80
|
}
|
|
80
81
|
/**
|
|
81
82
|
* Get versions for an entity
|
|
82
83
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
* @param query Version query
|
|
84
|
+
* @param query Version query with filters
|
|
86
85
|
* @returns List of versions (newest first)
|
|
87
86
|
*/
|
|
88
87
|
async getVersions(query) {
|
|
89
88
|
await this.initialize();
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
const branch = query.branch || this.brain.currentBranch;
|
|
90
|
+
const key = this.getMetaKey(query.entityId, branch);
|
|
91
|
+
const store = await this.loadStore(key);
|
|
92
|
+
if (!store)
|
|
93
|
+
return [];
|
|
94
|
+
// Convert entries to EntityVersion format
|
|
95
|
+
let versions = store.versions.map(entry => ({
|
|
96
|
+
version: entry.version,
|
|
97
|
+
entityId: store.entityId,
|
|
98
|
+
branch: store.branch,
|
|
99
|
+
timestamp: entry.timestamp,
|
|
100
|
+
contentHash: entry.contentHash,
|
|
101
|
+
commitHash: entry.commitHash || '',
|
|
102
|
+
tag: entry.tag,
|
|
103
|
+
description: entry.description,
|
|
104
|
+
author: entry.author
|
|
105
|
+
}));
|
|
106
|
+
// Apply filters
|
|
98
107
|
if (query.tag) {
|
|
99
|
-
|
|
108
|
+
versions = versions.filter(v => v.tag === query.tag);
|
|
100
109
|
}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
// Convert entities back to EntityVersion format
|
|
104
|
-
const versions = [];
|
|
105
|
-
for (const entity of results) {
|
|
106
|
-
const version = this.entityToVersion(entity);
|
|
107
|
-
if (version) {
|
|
108
|
-
// Filter by date range if specified
|
|
109
|
-
if (query.startDate && version.timestamp < query.startDate)
|
|
110
|
-
continue;
|
|
111
|
-
if (query.endDate && version.timestamp > query.endDate)
|
|
112
|
-
continue;
|
|
113
|
-
versions.push(version);
|
|
114
|
-
}
|
|
110
|
+
if (query.startDate) {
|
|
111
|
+
versions = versions.filter(v => v.timestamp >= query.startDate);
|
|
115
112
|
}
|
|
116
|
-
|
|
113
|
+
if (query.endDate) {
|
|
114
|
+
versions = versions.filter(v => v.timestamp <= query.endDate);
|
|
115
|
+
}
|
|
116
|
+
// Sort newest first (highest version number first)
|
|
117
117
|
versions.sort((a, b) => b.version - a.version);
|
|
118
118
|
// Apply pagination
|
|
119
119
|
const start = query.offset || 0;
|
|
@@ -129,12 +129,8 @@ export class VersionIndex {
|
|
|
129
129
|
* @returns Version metadata or null
|
|
130
130
|
*/
|
|
131
131
|
async getVersion(entityId, version, branch) {
|
|
132
|
-
await this.
|
|
133
|
-
|
|
134
|
-
const entity = await this.brain.getNounMetadata(versionEntityId);
|
|
135
|
-
if (!entity)
|
|
136
|
-
return null;
|
|
137
|
-
return this.entityToVersion(entity);
|
|
132
|
+
const versions = await this.getVersions({ entityId, branch });
|
|
133
|
+
return versions.find(v => v.version === version) || null;
|
|
138
134
|
}
|
|
139
135
|
/**
|
|
140
136
|
* Get version by tag
|
|
@@ -145,19 +141,8 @@ export class VersionIndex {
|
|
|
145
141
|
* @returns Version metadata or null
|
|
146
142
|
*/
|
|
147
143
|
async getVersionByTag(entityId, tag, branch) {
|
|
148
|
-
await this.
|
|
149
|
-
|
|
150
|
-
const results = await this.brain.searchByMetadata({
|
|
151
|
-
type: 'state',
|
|
152
|
-
_isVersion: true,
|
|
153
|
-
versionEntityId: entityId,
|
|
154
|
-
versionBranch: branch,
|
|
155
|
-
versionTag: tag
|
|
156
|
-
});
|
|
157
|
-
if (results.length === 0)
|
|
158
|
-
return null;
|
|
159
|
-
// Return first match (tags should be unique per entity/branch)
|
|
160
|
-
return this.entityToVersion(results[0]);
|
|
144
|
+
const versions = await this.getVersions({ entityId, branch, tag });
|
|
145
|
+
return versions[0] || null;
|
|
161
146
|
}
|
|
162
147
|
/**
|
|
163
148
|
* Get version count for entity
|
|
@@ -167,15 +152,9 @@ export class VersionIndex {
|
|
|
167
152
|
* @returns Number of versions
|
|
168
153
|
*/
|
|
169
154
|
async getVersionCount(entityId, branch) {
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
type: 'state',
|
|
174
|
-
_isVersion: true,
|
|
175
|
-
versionEntityId: entityId,
|
|
176
|
-
versionBranch: branch
|
|
177
|
-
});
|
|
178
|
-
return results.length;
|
|
155
|
+
const key = this.getMetaKey(entityId, branch);
|
|
156
|
+
const store = await this.loadStore(key);
|
|
157
|
+
return store?.versions.length || 0;
|
|
179
158
|
}
|
|
180
159
|
/**
|
|
181
160
|
* Remove version from index
|
|
@@ -186,90 +165,86 @@ export class VersionIndex {
|
|
|
186
165
|
*/
|
|
187
166
|
async removeVersion(entityId, version, branch) {
|
|
188
167
|
await this.initialize();
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
*/
|
|
199
|
-
entityToVersion(entity) {
|
|
200
|
-
if (!entity || !entity.metadata)
|
|
201
|
-
return null;
|
|
202
|
-
const m = entity.metadata;
|
|
203
|
-
if (!m.versionEntityId ||
|
|
204
|
-
!m.versionBranch ||
|
|
205
|
-
m.versionNumber === undefined ||
|
|
206
|
-
!m.versionCommitHash ||
|
|
207
|
-
!m.versionContentHash ||
|
|
208
|
-
!m.versionTimestamp) {
|
|
209
|
-
return null;
|
|
168
|
+
const key = this.getMetaKey(entityId, branch);
|
|
169
|
+
const store = await this.loadStore(key);
|
|
170
|
+
if (!store)
|
|
171
|
+
return;
|
|
172
|
+
const initialLength = store.versions.length;
|
|
173
|
+
store.versions = store.versions.filter(v => v.version !== version);
|
|
174
|
+
// Only save if something was removed
|
|
175
|
+
if (store.versions.length < initialLength) {
|
|
176
|
+
await this.saveStore(key, store);
|
|
210
177
|
}
|
|
211
|
-
return {
|
|
212
|
-
version: m.versionNumber,
|
|
213
|
-
entityId: m.versionEntityId,
|
|
214
|
-
branch: m.versionBranch,
|
|
215
|
-
commitHash: m.versionCommitHash,
|
|
216
|
-
timestamp: m.versionTimestamp,
|
|
217
|
-
contentHash: m.versionContentHash,
|
|
218
|
-
tag: m.versionTag,
|
|
219
|
-
description: m.versionDescription,
|
|
220
|
-
author: m.versionAuthor,
|
|
221
|
-
metadata: m.versionMetadata
|
|
222
|
-
};
|
|
223
178
|
}
|
|
224
179
|
/**
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* Format: _version:{entityId}:{version}:{branch}
|
|
180
|
+
* Clear all versions for an entity
|
|
228
181
|
*
|
|
229
182
|
* @param entityId Entity ID
|
|
230
|
-
* @param version Version number
|
|
231
183
|
* @param branch Branch name
|
|
232
|
-
* @returns
|
|
184
|
+
* @returns Number of versions deleted
|
|
233
185
|
*/
|
|
234
|
-
|
|
235
|
-
|
|
186
|
+
async clearVersions(entityId, branch) {
|
|
187
|
+
const key = this.getMetaKey(entityId, branch);
|
|
188
|
+
const store = await this.loadStore(key);
|
|
189
|
+
if (!store)
|
|
190
|
+
return 0;
|
|
191
|
+
const count = store.versions.length;
|
|
192
|
+
// Delete the store by saving null/empty
|
|
193
|
+
await this.saveStore(key, { entityId, branch, versions: [] });
|
|
194
|
+
return count;
|
|
236
195
|
}
|
|
237
196
|
/**
|
|
238
197
|
* Get all versioned entities (for cleanup/debugging)
|
|
239
198
|
*
|
|
240
|
-
*
|
|
199
|
+
* Note: This is an expensive operation that requires scanning.
|
|
200
|
+
* In the simple key-value approach, we don't maintain a global index.
|
|
201
|
+
* This method returns an empty array - use storage-level scanning if needed.
|
|
202
|
+
*
|
|
203
|
+
* @returns Empty array (not supported in simple approach)
|
|
241
204
|
*/
|
|
242
205
|
async getVersionedEntities() {
|
|
243
|
-
|
|
244
|
-
//
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
_isVersion: true
|
|
248
|
-
});
|
|
249
|
-
// Extract unique entity IDs
|
|
250
|
-
const entityIds = new Set();
|
|
251
|
-
for (const entity of results) {
|
|
252
|
-
const version = this.entityToVersion(entity);
|
|
253
|
-
if (version) {
|
|
254
|
-
entityIds.add(version.entityId);
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
return Array.from(entityIds);
|
|
206
|
+
// In the simple key-value approach, we don't maintain a global index
|
|
207
|
+
// of all versioned entities. This would require scanning storage.
|
|
208
|
+
// For most use cases, you know which entities you've versioned.
|
|
209
|
+
return [];
|
|
258
210
|
}
|
|
211
|
+
// ============= Private Helpers =============
|
|
259
212
|
/**
|
|
260
|
-
*
|
|
213
|
+
* Generate storage key for version metadata
|
|
261
214
|
*
|
|
262
215
|
* @param entityId Entity ID
|
|
263
216
|
* @param branch Branch name
|
|
264
|
-
* @returns
|
|
217
|
+
* @returns Storage key
|
|
265
218
|
*/
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
219
|
+
getMetaKey(entityId, branch) {
|
|
220
|
+
return `__version_meta_${entityId}_${branch}`;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Load version store from storage
|
|
224
|
+
*
|
|
225
|
+
* @param key Storage key
|
|
226
|
+
* @returns Version store or null
|
|
227
|
+
*/
|
|
228
|
+
async loadStore(key) {
|
|
229
|
+
try {
|
|
230
|
+
const store = await this.brain.storageAdapter.getMetadata(key);
|
|
231
|
+
// Handle empty store
|
|
232
|
+
if (!store || !store.versions)
|
|
233
|
+
return null;
|
|
234
|
+
return store;
|
|
271
235
|
}
|
|
272
|
-
|
|
236
|
+
catch {
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Save version store to storage
|
|
242
|
+
*
|
|
243
|
+
* @param key Storage key
|
|
244
|
+
* @param store Version store
|
|
245
|
+
*/
|
|
246
|
+
async saveStore(key, store) {
|
|
247
|
+
await this.brain.storageAdapter.saveMetadata(key, store);
|
|
273
248
|
}
|
|
274
249
|
}
|
|
275
250
|
//# sourceMappingURL=VersionIndex.js.map
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VersionManager - Entity-Level Versioning Engine (v5.3.0)
|
|
2
|
+
* VersionManager - Entity-Level Versioning Engine (v5.3.0, v6.3.0 fix)
|
|
3
3
|
*
|
|
4
4
|
* Provides entity-level version control with:
|
|
5
5
|
* - save() - Create entity version
|
|
6
|
-
* - restore() - Restore entity to specific version
|
|
6
|
+
* - restore() - Restore entity to specific version (v6.3.0: now updates all indexes)
|
|
7
7
|
* - list() - List all versions of an entity
|
|
8
8
|
* - compare() - Deep diff between versions
|
|
9
9
|
* - prune() - Remove old versions (retention policies)
|
|
10
10
|
*
|
|
11
|
-
* Architecture:
|
|
12
|
-
* -
|
|
11
|
+
* Architecture (v6.3.0 - Clean Key-Value Storage):
|
|
12
|
+
* - Versions stored as key-value pairs, NOT as entities (no index pollution)
|
|
13
13
|
* - Content-addressable: SHA-256 hashing for deduplication
|
|
14
|
-
* - Space-efficient: Only stores
|
|
14
|
+
* - Space-efficient: Only stores unique content
|
|
15
15
|
* - Branch-aware: Versions tied to current branch
|
|
16
|
+
* - restore() uses brain.update() to refresh ALL indexes (HNSW, metadata, graph)
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
+
* Storage keys:
|
|
19
|
+
* - Version metadata: __version_meta_{entityId}_{branch}
|
|
20
|
+
* - Version content: __system_version_{entityId}_{contentHash}
|
|
21
|
+
*
|
|
22
|
+
* ZERO-CONFIG - Works automatically with existing storage infrastructure.
|
|
23
|
+
* NO MOCKS - Production implementation.
|
|
18
24
|
*/
|
|
19
25
|
import { VersionDiff } from './VersionDiff.js';
|
|
20
26
|
export interface EntityVersion {
|
|
@@ -93,6 +99,20 @@ export declare class VersionManager {
|
|
|
93
99
|
private versionIndex;
|
|
94
100
|
private initialized;
|
|
95
101
|
constructor(brain: any);
|
|
102
|
+
/**
|
|
103
|
+
* Check if an entity is a VFS file
|
|
104
|
+
* VFS files store content in BlobStorage, not in entity.data
|
|
105
|
+
*
|
|
106
|
+
* @param entity Entity metadata object
|
|
107
|
+
* @returns True if entity is a VFS file
|
|
108
|
+
*/
|
|
109
|
+
private isVFSFile;
|
|
110
|
+
/**
|
|
111
|
+
* Check if content is text-based for encoding decisions
|
|
112
|
+
* @param mimeType MIME type of the content
|
|
113
|
+
* @returns True if content should be stored as UTF-8 string
|
|
114
|
+
*/
|
|
115
|
+
private isTextContent;
|
|
96
116
|
/**
|
|
97
117
|
* Initialize versioning system (lazy)
|
|
98
118
|
*/
|
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* VersionManager - Entity-Level Versioning Engine (v5.3.0)
|
|
2
|
+
* VersionManager - Entity-Level Versioning Engine (v5.3.0, v6.3.0 fix)
|
|
3
3
|
*
|
|
4
4
|
* Provides entity-level version control with:
|
|
5
5
|
* - save() - Create entity version
|
|
6
|
-
* - restore() - Restore entity to specific version
|
|
6
|
+
* - restore() - Restore entity to specific version (v6.3.0: now updates all indexes)
|
|
7
7
|
* - list() - List all versions of an entity
|
|
8
8
|
* - compare() - Deep diff between versions
|
|
9
9
|
* - prune() - Remove old versions (retention policies)
|
|
10
10
|
*
|
|
11
|
-
* Architecture:
|
|
12
|
-
* -
|
|
11
|
+
* Architecture (v6.3.0 - Clean Key-Value Storage):
|
|
12
|
+
* - Versions stored as key-value pairs, NOT as entities (no index pollution)
|
|
13
13
|
* - Content-addressable: SHA-256 hashing for deduplication
|
|
14
|
-
* - Space-efficient: Only stores
|
|
14
|
+
* - Space-efficient: Only stores unique content
|
|
15
15
|
* - Branch-aware: Versions tied to current branch
|
|
16
|
+
* - restore() uses brain.update() to refresh ALL indexes (HNSW, metadata, graph)
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
+
* Storage keys:
|
|
19
|
+
* - Version metadata: __version_meta_{entityId}_{branch}
|
|
20
|
+
* - Version content: __system_version_{entityId}_{contentHash}
|
|
21
|
+
*
|
|
22
|
+
* ZERO-CONFIG - Works automatically with existing storage infrastructure.
|
|
23
|
+
* NO MOCKS - Production implementation.
|
|
18
24
|
*/
|
|
19
25
|
import { VersionStorage } from './VersionStorage.js';
|
|
20
26
|
import { VersionIndex } from './VersionIndex.js';
|
|
@@ -29,6 +35,34 @@ export class VersionManager {
|
|
|
29
35
|
this.versionStorage = new VersionStorage(brain);
|
|
30
36
|
this.versionIndex = new VersionIndex(brain);
|
|
31
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Check if an entity is a VFS file
|
|
40
|
+
* VFS files store content in BlobStorage, not in entity.data
|
|
41
|
+
*
|
|
42
|
+
* @param entity Entity metadata object
|
|
43
|
+
* @returns True if entity is a VFS file
|
|
44
|
+
*/
|
|
45
|
+
isVFSFile(entity) {
|
|
46
|
+
return (entity?.isVFS === true &&
|
|
47
|
+
entity?.vfsType === 'file' &&
|
|
48
|
+
typeof entity?.path === 'string');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Check if content is text-based for encoding decisions
|
|
52
|
+
* @param mimeType MIME type of the content
|
|
53
|
+
* @returns True if content should be stored as UTF-8 string
|
|
54
|
+
*/
|
|
55
|
+
isTextContent(mimeType) {
|
|
56
|
+
if (!mimeType)
|
|
57
|
+
return false;
|
|
58
|
+
return (mimeType.startsWith('text/') ||
|
|
59
|
+
mimeType === 'application/json' ||
|
|
60
|
+
mimeType === 'application/javascript' ||
|
|
61
|
+
mimeType === 'application/typescript' ||
|
|
62
|
+
mimeType === 'application/xml' ||
|
|
63
|
+
mimeType.includes('+xml') ||
|
|
64
|
+
mimeType.includes('+json'));
|
|
65
|
+
}
|
|
32
66
|
/**
|
|
33
67
|
* Initialize versioning system (lazy)
|
|
34
68
|
*/
|
|
@@ -56,6 +90,27 @@ export class VersionManager {
|
|
|
56
90
|
if (!entity) {
|
|
57
91
|
throw new Error(`Entity ${entityId} not found`);
|
|
58
92
|
}
|
|
93
|
+
// v6.3.2 FIX: For VFS file entities, fetch current content from blob storage
|
|
94
|
+
// The entity.data field contains stale embedding text, not actual file content
|
|
95
|
+
// VFS files store their real content in BlobStorage (content-addressable)
|
|
96
|
+
if (this.isVFSFile(entity)) {
|
|
97
|
+
if (!this.brain.vfs) {
|
|
98
|
+
throw new Error(`Cannot version VFS file ${entityId}: VFS not initialized. ` +
|
|
99
|
+
`Ensure brain.vfs is available before versioning VFS files.`);
|
|
100
|
+
}
|
|
101
|
+
// Read fresh content from blob storage via VFS
|
|
102
|
+
const freshContent = await this.brain.vfs.readFile(entity.path);
|
|
103
|
+
// Store content with appropriate encoding
|
|
104
|
+
// Text files as UTF-8 string (readable, smaller)
|
|
105
|
+
// Binary files as base64 (safe for JSON serialization)
|
|
106
|
+
if (this.isTextContent(entity.mimeType)) {
|
|
107
|
+
entity.data = freshContent.toString('utf-8');
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
entity.data = freshContent.toString('base64');
|
|
111
|
+
entity._vfsEncoding = 'base64'; // Flag for restore to decode
|
|
112
|
+
}
|
|
113
|
+
}
|
|
59
114
|
// Get current branch
|
|
60
115
|
const currentBranch = this.brain.currentBranch;
|
|
61
116
|
// Get next version number
|
|
@@ -201,8 +256,46 @@ export class VersionManager {
|
|
|
201
256
|
if (!versionedEntity) {
|
|
202
257
|
throw new Error(`Version data not found for entity ${entityId} version ${version}`);
|
|
203
258
|
}
|
|
204
|
-
//
|
|
205
|
-
|
|
259
|
+
// v6.3.2 FIX: For VFS file entities, write content back to blob storage
|
|
260
|
+
// The versioned data contains the actual file content (not stale embedding text)
|
|
261
|
+
// Using vfs.writeFile() ensures proper blob creation and metadata update
|
|
262
|
+
if (this.isVFSFile(versionedEntity)) {
|
|
263
|
+
if (!this.brain.vfs) {
|
|
264
|
+
throw new Error(`Cannot restore VFS file ${entityId}: VFS not initialized. ` +
|
|
265
|
+
`Ensure brain.vfs is available before restoring VFS files.`);
|
|
266
|
+
}
|
|
267
|
+
// Decode content based on how it was stored
|
|
268
|
+
let content;
|
|
269
|
+
if (versionedEntity._vfsEncoding === 'base64') {
|
|
270
|
+
// Binary file stored as base64
|
|
271
|
+
content = Buffer.from(versionedEntity.data, 'base64');
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Text file stored as UTF-8 string
|
|
275
|
+
content = Buffer.from(versionedEntity.data, 'utf-8');
|
|
276
|
+
}
|
|
277
|
+
// Write content back to VFS - this handles:
|
|
278
|
+
// - BlobStorage write (new hash)
|
|
279
|
+
// - Entity metadata update
|
|
280
|
+
// - Path resolver cache update
|
|
281
|
+
await this.brain.vfs.writeFile(versionedEntity.path, content);
|
|
282
|
+
return targetVersion;
|
|
283
|
+
}
|
|
284
|
+
// For non-VFS entities, use existing brain.update() logic
|
|
285
|
+
// Extract standard fields vs custom metadata
|
|
286
|
+
// NounMetadata has: noun, data, createdAt, updatedAt, createdBy, service, confidence, weight
|
|
287
|
+
const { noun, data, createdAt, updatedAt, createdBy, service, confidence, weight, ...customMetadata } = versionedEntity;
|
|
288
|
+
// Use brain.update() to restore - this updates ALL indexes (HNSW, metadata, graph)
|
|
289
|
+
// This is critical: saveNounMetadata() only saves to storage without updating indexes
|
|
290
|
+
await this.brain.update({
|
|
291
|
+
id: entityId,
|
|
292
|
+
data: data,
|
|
293
|
+
type: noun,
|
|
294
|
+
metadata: customMetadata,
|
|
295
|
+
confidence: confidence,
|
|
296
|
+
weight: weight,
|
|
297
|
+
merge: false // Replace entirely, don't merge with existing metadata
|
|
298
|
+
});
|
|
206
299
|
return targetVersion;
|
|
207
300
|
}
|
|
208
301
|
/**
|