@soulcraft/brainy 4.10.4 β 4.11.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
CHANGED
|
@@ -2,6 +2,131 @@
|
|
|
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.11.1](https://github.com/soulcraftlabs/brainy/compare/v4.11.0...v4.11.1) (2025-10-30)
|
|
6
|
+
|
|
7
|
+
- fix: prevent orphaned relationships in restore() and add VFS progress tracking (549d773)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
## [4.11.1](https://github.com/soulcraftlabs/brainy/compare/v4.11.0...v4.11.1) (2025-10-30)
|
|
11
|
+
|
|
12
|
+
### π Bug Fixes
|
|
13
|
+
|
|
14
|
+
* **fix(api)**: DataAPI.restore() now filters orphaned relationships (P0 Critical)
|
|
15
|
+
- **Issue**: restore() created relationships to entities that failed to restore, causing "Entity not found" errors
|
|
16
|
+
- **Root Cause**: Relationships were not filtered based on successfully restored entities
|
|
17
|
+
- **Fix**: Now builds Set of successful entity IDs and filters relationships accordingly
|
|
18
|
+
- **New Tracking**: Added `relationshipsSkipped` to return type for visibility
|
|
19
|
+
- **Impact**: Prevents complete data corruption when some entities fail to restore
|
|
20
|
+
|
|
21
|
+
* **fix(import)**: VFS creation now reports progress during import (P1 High)
|
|
22
|
+
- **Issue**: 3-5 minute VFS creation showed no progress (stuck at 0%), causing users to think import froze
|
|
23
|
+
- **Root Cause**: VFSStructureGenerator.generate() had no progress callback parameter
|
|
24
|
+
- **Fix**: Added onProgress callback to VFSStructureOptions interface
|
|
25
|
+
- **Progress Stages**: Reports 'directories', 'entities', 'metadata' with detailed messages
|
|
26
|
+
- **Frequency**: Reports every 10 entity files to avoid excessive updates
|
|
27
|
+
- **Integration**: Wired through ImportCoordinator to main progress callback
|
|
28
|
+
|
|
29
|
+
### π Files Modified
|
|
30
|
+
|
|
31
|
+
* `src/api/DataAPI.ts` (lines 173-350) - Added orphaned relationship filtering
|
|
32
|
+
* `src/importers/VFSStructureGenerator.ts` (lines 18-53, 110-347) - Added progress callback
|
|
33
|
+
* `src/import/ImportCoordinator.ts` (lines 438-459) - Wired progress callback
|
|
34
|
+
|
|
35
|
+
## [4.11.0](https://github.com/soulcraftlabs/brainy/compare/v4.10.4...v4.11.0) (2025-10-30)
|
|
36
|
+
|
|
37
|
+
### π¨ CRITICAL BUG FIX
|
|
38
|
+
|
|
39
|
+
**DataAPI.restore() Complete Data Loss Bug Fixed**
|
|
40
|
+
|
|
41
|
+
Previous versions (v4.10.4 and earlier) had a critical bug where `DataAPI.restore()` did NOT persist data to storage, causing complete data loss after instance restart or cache clear. **If you used backup/restore in v4.10.4 or earlier, your restored data was NOT saved.**
|
|
42
|
+
|
|
43
|
+
### π§ What Was Fixed
|
|
44
|
+
|
|
45
|
+
* **fix(api)**: DataAPI.restore() now properly persists data to all storage adapters
|
|
46
|
+
- **Root Cause**: restore() called `storage.saveNoun()` directly, bypassing all indexes and proper persistence
|
|
47
|
+
- **Fix**: Now uses `brain.addMany()` and `brain.relateMany()` (proper persistence path)
|
|
48
|
+
- **Result**: Data now survives instance restart and is fully indexed/searchable
|
|
49
|
+
|
|
50
|
+
### β¨ Improvements
|
|
51
|
+
|
|
52
|
+
* **feat(api)**: Enhanced restore() with progress reporting and error tracking
|
|
53
|
+
- **New Return Type**: Returns `{ entitiesRestored, relationshipsRestored, errors }` instead of `void`
|
|
54
|
+
- **Progress Callback**: Optional `onProgress(completed, total)` parameter for UI updates
|
|
55
|
+
- **Error Details**: Returns array of failed entities/relations with error messages
|
|
56
|
+
- **Verification**: Automatically verifies first entity is retrievable after restore
|
|
57
|
+
|
|
58
|
+
* **feat(api)**: Cross-storage restore support
|
|
59
|
+
- Backup from any storage adapter, restore to any other
|
|
60
|
+
- Example: Backup from GCS β Restore to Filesystem
|
|
61
|
+
- Automatically uses target storage's optimal batch configuration
|
|
62
|
+
|
|
63
|
+
* **perf(api)**: Storage-aware batching for restore operations
|
|
64
|
+
- Leverages v4.10.4's storage-aware batching (10-100x faster on cloud storage)
|
|
65
|
+
- Automatic backpressure management prevents circuit breaker activation
|
|
66
|
+
- Separate read/write circuit breakers (backup can run during restore throttling)
|
|
67
|
+
|
|
68
|
+
### π What's Now Guaranteed
|
|
69
|
+
|
|
70
|
+
| Feature | v4.10.4 | v4.11.0 |
|
|
71
|
+
|---------|---------|---------|
|
|
72
|
+
| Data Persists to Storage | β No | β
Yes |
|
|
73
|
+
| Data Survives Restart | β No | β
Yes |
|
|
74
|
+
| HNSW Index Updated | β No | β
Yes |
|
|
75
|
+
| Metadata Index Updated | β No | β
Yes |
|
|
76
|
+
| Searchable After Restore | β No | β
Yes |
|
|
77
|
+
| Progress Reporting | β No | β
Yes |
|
|
78
|
+
| Error Tracking | β Silent | β
Detailed |
|
|
79
|
+
| Cross-Storage Support | β No | β
Yes |
|
|
80
|
+
|
|
81
|
+
### π Migration Guide
|
|
82
|
+
|
|
83
|
+
**No code changes required!** The fix is backward compatible:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
// Old code (still works)
|
|
87
|
+
await brain.data().restore({ backup, overwrite: true })
|
|
88
|
+
|
|
89
|
+
// New code (with progress tracking)
|
|
90
|
+
const result = await brain.data().restore({
|
|
91
|
+
backup,
|
|
92
|
+
overwrite: true,
|
|
93
|
+
onProgress: (done, total) => {
|
|
94
|
+
console.log(`Restoring... ${done}/${total}`)
|
|
95
|
+
}
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
console.log(`β
Restored ${result.entitiesRestored} entities`)
|
|
99
|
+
if (result.errors.length > 0) {
|
|
100
|
+
console.warn(`β οΈ ${result.errors.length} failures`)
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### β οΈ Breaking Changes (Minor API Change)
|
|
105
|
+
|
|
106
|
+
* **DataAPI.restore()** return type changed from `Promise<void>` to `Promise<{ entitiesRestored, relationshipsRestored, errors }>`
|
|
107
|
+
- Impact: Minimal - most code doesn't use the return value
|
|
108
|
+
- Fix: Remove explicit `Promise<void>` type annotations if present
|
|
109
|
+
|
|
110
|
+
### π Files Modified
|
|
111
|
+
|
|
112
|
+
* `src/api/DataAPI.ts` - Complete rewrite of restore() method (lines 161-338)
|
|
113
|
+
|
|
114
|
+
### [4.10.4](https://github.com/soulcraftlabs/brainy/compare/v4.10.3...v4.10.4) (2025-10-30)
|
|
115
|
+
|
|
116
|
+
* fix: prevent circuit breaker activation and data loss during bulk imports
|
|
117
|
+
- Storage-aware batching system prevents rate limiting on cloud storage (GCS, S3, R2, Azure)
|
|
118
|
+
- Separate read/write circuit breakers prevent read lockouts during write throttling
|
|
119
|
+
- ImportCoordinator uses addMany()/relateMany() for 10-100x performance improvement
|
|
120
|
+
- Fixes silent data loss and 30+ second lockouts on 1000+ row imports
|
|
121
|
+
|
|
122
|
+
### [4.10.3](https://github.com/soulcraftlabs/brainy/compare/v4.10.2...v4.10.3) (2025-10-29)
|
|
123
|
+
|
|
124
|
+
* fix: add atomic writes to ALL file operations to prevent concurrent write corruption
|
|
125
|
+
|
|
126
|
+
### [4.10.2](https://github.com/soulcraftlabs/brainy/compare/v4.10.1...v4.10.2) (2025-10-29)
|
|
127
|
+
|
|
128
|
+
* fix: VFS not initialized during Excel import, causing 0 files accessible
|
|
129
|
+
|
|
5
130
|
### [4.10.1](https://github.com/soulcraftlabs/brainy/compare/v4.10.0...v4.10.1) (2025-10-29)
|
|
6
131
|
|
|
7
132
|
- fix: add mutex locks to FileSystemStorage for HNSW concurrency (CRITICAL) (ff86e88)
|
package/dist/api/DataAPI.d.ts
CHANGED
|
@@ -81,13 +81,32 @@ export declare class DataAPI {
|
|
|
81
81
|
}>;
|
|
82
82
|
/**
|
|
83
83
|
* Restore data from a backup
|
|
84
|
+
*
|
|
85
|
+
* v4.11.1: CRITICAL FIX - Now uses brain.addMany() and brain.relateMany()
|
|
86
|
+
* Previous implementation only wrote to storage cache without updating indexes,
|
|
87
|
+
* causing complete data loss on restart. This fix ensures:
|
|
88
|
+
* - All 5 indexes updated (HNSW, metadata, adjacency, sparse, type-aware)
|
|
89
|
+
* - Proper persistence to disk/cloud storage
|
|
90
|
+
* - Storage-aware batching for optimal performance
|
|
91
|
+
* - Atomic writes to prevent corruption
|
|
92
|
+
* - Data survives instance restart
|
|
84
93
|
*/
|
|
85
94
|
restore(params: {
|
|
86
95
|
backup: BackupData;
|
|
87
96
|
merge?: boolean;
|
|
88
97
|
overwrite?: boolean;
|
|
89
98
|
validate?: boolean;
|
|
90
|
-
|
|
99
|
+
onProgress?: (completed: number, total: number) => void;
|
|
100
|
+
}): Promise<{
|
|
101
|
+
entitiesRestored: number;
|
|
102
|
+
relationshipsRestored: number;
|
|
103
|
+
relationshipsSkipped: number;
|
|
104
|
+
errors: Array<{
|
|
105
|
+
type: 'entity' | 'relation';
|
|
106
|
+
id: string;
|
|
107
|
+
error: string;
|
|
108
|
+
}>;
|
|
109
|
+
}>;
|
|
91
110
|
/**
|
|
92
111
|
* Clear data
|
|
93
112
|
*/
|
package/dist/api/DataAPI.js
CHANGED
|
@@ -75,89 +75,171 @@ export class DataAPI {
|
|
|
75
75
|
}
|
|
76
76
|
/**
|
|
77
77
|
* Restore data from a backup
|
|
78
|
+
*
|
|
79
|
+
* v4.11.1: CRITICAL FIX - Now uses brain.addMany() and brain.relateMany()
|
|
80
|
+
* Previous implementation only wrote to storage cache without updating indexes,
|
|
81
|
+
* causing complete data loss on restart. This fix ensures:
|
|
82
|
+
* - All 5 indexes updated (HNSW, metadata, adjacency, sparse, type-aware)
|
|
83
|
+
* - Proper persistence to disk/cloud storage
|
|
84
|
+
* - Storage-aware batching for optimal performance
|
|
85
|
+
* - Atomic writes to prevent corruption
|
|
86
|
+
* - Data survives instance restart
|
|
78
87
|
*/
|
|
79
88
|
async restore(params) {
|
|
80
|
-
const { backup, merge = false, overwrite = false, validate = true } = params;
|
|
89
|
+
const { backup, merge = false, overwrite = false, validate = true, onProgress } = params;
|
|
90
|
+
const result = {
|
|
91
|
+
entitiesRestored: 0,
|
|
92
|
+
relationshipsRestored: 0,
|
|
93
|
+
relationshipsSkipped: 0,
|
|
94
|
+
errors: []
|
|
95
|
+
};
|
|
81
96
|
// Validate backup format
|
|
82
97
|
if (validate) {
|
|
83
98
|
if (!backup.version || !backup.entities || !backup.relations) {
|
|
84
|
-
throw new Error('Invalid backup format');
|
|
99
|
+
throw new Error('Invalid backup format: missing version, entities, or relations');
|
|
85
100
|
}
|
|
86
101
|
}
|
|
102
|
+
// Validate brain instance is available (required for v4.11.1+ restore)
|
|
103
|
+
if (!this.brain) {
|
|
104
|
+
throw new Error('Restore requires brain instance. DataAPI must be initialized with brain reference. ' +
|
|
105
|
+
'Use: await brain.data() instead of constructing DataAPI directly.');
|
|
106
|
+
}
|
|
87
107
|
// Clear existing data if not merging
|
|
88
108
|
if (!merge && overwrite) {
|
|
89
109
|
await this.clear({ entities: true, relations: true });
|
|
90
110
|
}
|
|
91
|
-
//
|
|
92
|
-
|
|
111
|
+
// ============================================
|
|
112
|
+
// Phase 1: Restore entities using addMany()
|
|
113
|
+
// v4.11.1: Uses proper persistence path through brain.addMany()
|
|
114
|
+
// ============================================
|
|
115
|
+
// Prepare entity parameters for addMany()
|
|
116
|
+
const entityParams = backup.entities
|
|
117
|
+
.filter(entity => {
|
|
118
|
+
// Skip existing entities when merging without overwrite
|
|
119
|
+
if (merge && !overwrite) {
|
|
120
|
+
// Note: We'll rely on addMany's internal duplicate handling
|
|
121
|
+
// rather than checking each entity individually (performance)
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
})
|
|
126
|
+
.map(entity => {
|
|
127
|
+
// Extract data field from metadata (backup format compatibility)
|
|
128
|
+
// Backup stores the original data in metadata.data
|
|
129
|
+
const data = entity.metadata?.data || entity.id;
|
|
130
|
+
return {
|
|
131
|
+
id: entity.id,
|
|
132
|
+
data, // Required field for brainy.add()
|
|
133
|
+
type: entity.type,
|
|
134
|
+
metadata: entity.metadata || {},
|
|
135
|
+
vector: entity.vector, // Preserve original vectors from backup
|
|
136
|
+
service: entity.service,
|
|
137
|
+
// Preserve confidence and weight if available
|
|
138
|
+
confidence: entity.metadata?.confidence,
|
|
139
|
+
weight: entity.metadata?.weight
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
// Restore entities in batches using storage-aware batching (v4.11.0)
|
|
143
|
+
// v4.11.1: Track successful entity IDs to prevent orphaned relationships
|
|
144
|
+
const successfulEntityIds = new Set();
|
|
145
|
+
if (entityParams.length > 0) {
|
|
93
146
|
try {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
level: 0
|
|
100
|
-
};
|
|
101
|
-
const metadata = {
|
|
102
|
-
...entity.metadata,
|
|
103
|
-
noun: entity.type,
|
|
104
|
-
service: entity.service,
|
|
105
|
-
createdAt: Date.now()
|
|
106
|
-
};
|
|
107
|
-
// Check if entity exists when merging
|
|
108
|
-
if (merge) {
|
|
109
|
-
const existing = await this.storage.getNoun(entity.id);
|
|
110
|
-
if (existing && !overwrite) {
|
|
111
|
-
continue; // Skip existing entities unless overwriting
|
|
147
|
+
const addResult = await this.brain.addMany({
|
|
148
|
+
items: entityParams,
|
|
149
|
+
continueOnError: true,
|
|
150
|
+
onProgress: (done, total) => {
|
|
151
|
+
onProgress?.(done, backup.entities.length + backup.relations.length);
|
|
112
152
|
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
153
|
+
});
|
|
154
|
+
result.entitiesRestored = addResult.successful.length;
|
|
155
|
+
// Build Set of successfully restored entity IDs (v4.11.1 Bug Fix)
|
|
156
|
+
addResult.successful.forEach((entityId) => {
|
|
157
|
+
successfulEntityIds.add(entityId);
|
|
158
|
+
});
|
|
159
|
+
// Track errors
|
|
160
|
+
addResult.failed.forEach((failure) => {
|
|
161
|
+
result.errors.push({
|
|
162
|
+
type: 'entity',
|
|
163
|
+
id: failure.item?.id || 'unknown',
|
|
164
|
+
error: failure.error || 'Unknown error'
|
|
165
|
+
});
|
|
166
|
+
});
|
|
116
167
|
}
|
|
117
168
|
catch (error) {
|
|
118
|
-
|
|
169
|
+
throw new Error(`Failed to restore entities: ${error.message}`);
|
|
119
170
|
}
|
|
120
171
|
}
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
...relation.metadata,
|
|
145
|
-
createdAt: Date.now()
|
|
146
|
-
};
|
|
147
|
-
// Check if relation exists when merging
|
|
148
|
-
if (merge) {
|
|
149
|
-
const existing = await this.storage.getVerb(relation.id);
|
|
150
|
-
if (existing && !overwrite) {
|
|
151
|
-
continue;
|
|
152
|
-
}
|
|
172
|
+
// ============================================
|
|
173
|
+
// Phase 2: Restore relationships using relateMany()
|
|
174
|
+
// v4.11.1: CRITICAL FIX - Filter orphaned relationships
|
|
175
|
+
// ============================================
|
|
176
|
+
// Prepare relationship parameters - filter out orphaned references
|
|
177
|
+
const relationParams = backup.relations
|
|
178
|
+
.filter(relation => {
|
|
179
|
+
// Skip existing relations when merging without overwrite
|
|
180
|
+
if (merge && !overwrite) {
|
|
181
|
+
// Note: We'll rely on relateMany's internal duplicate handling
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
// v4.11.1 CRITICAL BUG FIX: Skip relationships where source or target entity failed to restore
|
|
185
|
+
// This prevents creating orphaned references that cause "Entity not found" errors
|
|
186
|
+
const sourceExists = successfulEntityIds.has(relation.from);
|
|
187
|
+
const targetExists = successfulEntityIds.has(relation.to);
|
|
188
|
+
if (!sourceExists || !targetExists) {
|
|
189
|
+
// Track skipped relationship
|
|
190
|
+
result.relationshipsSkipped++;
|
|
191
|
+
// Optionally log for debugging (can be removed in production)
|
|
192
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
193
|
+
console.debug(`Skipping orphaned relationship: ${relation.from} -> ${relation.to} ` +
|
|
194
|
+
`(${!sourceExists ? 'missing source' : 'missing target'})`);
|
|
153
195
|
}
|
|
154
|
-
|
|
155
|
-
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
return true;
|
|
199
|
+
})
|
|
200
|
+
.map(relation => ({
|
|
201
|
+
from: relation.from,
|
|
202
|
+
to: relation.to,
|
|
203
|
+
type: relation.type,
|
|
204
|
+
metadata: relation.metadata || {},
|
|
205
|
+
weight: relation.weight || 1.0
|
|
206
|
+
// Note: relation.id is ignored - brain.relate() generates new IDs
|
|
207
|
+
// This is intentional to avoid ID conflicts
|
|
208
|
+
}));
|
|
209
|
+
// Restore relationships in batches using storage-aware batching (v4.11.0)
|
|
210
|
+
if (relationParams.length > 0) {
|
|
211
|
+
try {
|
|
212
|
+
const relateResult = await this.brain.relateMany({
|
|
213
|
+
items: relationParams,
|
|
214
|
+
continueOnError: true
|
|
215
|
+
});
|
|
216
|
+
result.relationshipsRestored = relateResult.successful.length;
|
|
217
|
+
// Track errors
|
|
218
|
+
relateResult.failed.forEach((failure) => {
|
|
219
|
+
result.errors.push({
|
|
220
|
+
type: 'relation',
|
|
221
|
+
id: failure.item?.from + '->' + failure.item?.to || 'unknown',
|
|
222
|
+
error: failure.error || 'Unknown error'
|
|
223
|
+
});
|
|
224
|
+
});
|
|
156
225
|
}
|
|
157
226
|
catch (error) {
|
|
158
|
-
|
|
227
|
+
throw new Error(`Failed to restore relationships: ${error.message}`);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ============================================
|
|
231
|
+
// Phase 3: Verify restoration succeeded
|
|
232
|
+
// ============================================
|
|
233
|
+
// Sample verification: Check that first entity is actually retrievable
|
|
234
|
+
if (backup.entities.length > 0 && result.entitiesRestored > 0) {
|
|
235
|
+
const firstEntityId = backup.entities[0].id;
|
|
236
|
+
const verified = await this.brain.get(firstEntityId);
|
|
237
|
+
if (!verified) {
|
|
238
|
+
console.warn(`β οΈ Restore completed but verification failed - entity ${firstEntityId} not retrievable. ` +
|
|
239
|
+
`This may indicate a persistence issue with the storage adapter.`);
|
|
159
240
|
}
|
|
160
241
|
}
|
|
242
|
+
return result;
|
|
161
243
|
}
|
|
162
244
|
/**
|
|
163
245
|
* Clear data
|
|
@@ -137,7 +137,16 @@ export class ImportCoordinator {
|
|
|
137
137
|
sourceFilename: normalizedSource.filename || `import.${detection.format}`,
|
|
138
138
|
createRelationshipFile: true,
|
|
139
139
|
createMetadataFile: true,
|
|
140
|
-
trackingContext // v4.10.0: Pass tracking metadata to VFS
|
|
140
|
+
trackingContext, // v4.10.0: Pass tracking metadata to VFS
|
|
141
|
+
// v4.11.1: Pass progress callback for VFS creation updates
|
|
142
|
+
onProgress: (vfsProgress) => {
|
|
143
|
+
options.onProgress?.({
|
|
144
|
+
stage: 'storing-vfs',
|
|
145
|
+
message: vfsProgress.message,
|
|
146
|
+
processed: vfsProgress.processed,
|
|
147
|
+
total: vfsProgress.total
|
|
148
|
+
});
|
|
149
|
+
}
|
|
141
150
|
});
|
|
142
151
|
// Report graph storage stage
|
|
143
152
|
options.onProgress?.({
|
|
@@ -30,6 +30,13 @@ export interface VFSStructureOptions {
|
|
|
30
30
|
createMetadataFile?: boolean;
|
|
31
31
|
/** Import tracking context (v4.10.0) */
|
|
32
32
|
trackingContext?: TrackingContext;
|
|
33
|
+
/** Progress callback (v4.11.1) - Reports VFS creation progress */
|
|
34
|
+
onProgress?: (progress: {
|
|
35
|
+
stage: 'directories' | 'entities' | 'metadata';
|
|
36
|
+
message: string;
|
|
37
|
+
processed: number;
|
|
38
|
+
total: number;
|
|
39
|
+
}) => void;
|
|
33
40
|
}
|
|
34
41
|
export interface VFSStructureResult {
|
|
35
42
|
/** Root path created */
|
|
@@ -48,6 +48,26 @@ export class VFSStructureGenerator {
|
|
|
48
48
|
};
|
|
49
49
|
// Ensure VFS is initialized
|
|
50
50
|
await this.init();
|
|
51
|
+
// v4.11.1: Calculate total operations for progress tracking
|
|
52
|
+
const groups = this.groupEntities(importResult, options);
|
|
53
|
+
const totalEntities = Array.from(groups.values()).reduce((sum, entities) => sum + entities.length, 0);
|
|
54
|
+
const totalOperations = 1 + // root directory
|
|
55
|
+
(options.preserveSource ? 1 : 0) + // source file
|
|
56
|
+
groups.size + // group directories
|
|
57
|
+
totalEntities + // entity files
|
|
58
|
+
(options.createRelationshipFile !== false ? 1 : 0) + // relationships file
|
|
59
|
+
(options.createMetadataFile !== false ? 1 : 0); // metadata file
|
|
60
|
+
let completedOperations = 0;
|
|
61
|
+
// Helper to report progress
|
|
62
|
+
const reportProgress = (stage, message) => {
|
|
63
|
+
completedOperations++;
|
|
64
|
+
options.onProgress?.({
|
|
65
|
+
stage,
|
|
66
|
+
message,
|
|
67
|
+
processed: completedOperations,
|
|
68
|
+
total: totalOperations
|
|
69
|
+
});
|
|
70
|
+
};
|
|
51
71
|
// Extract tracking metadata if provided
|
|
52
72
|
const trackingMetadata = options.trackingContext ? {
|
|
53
73
|
importIds: [options.trackingContext.importId],
|
|
@@ -65,6 +85,7 @@ export class VFSStructureGenerator {
|
|
|
65
85
|
});
|
|
66
86
|
result.directories.push(options.rootPath);
|
|
67
87
|
result.operations++;
|
|
88
|
+
reportProgress('directories', `Created root directory: ${options.rootPath}`);
|
|
68
89
|
}
|
|
69
90
|
catch (error) {
|
|
70
91
|
// Directory might already exist, that's fine
|
|
@@ -72,6 +93,7 @@ export class VFSStructureGenerator {
|
|
|
72
93
|
throw error;
|
|
73
94
|
}
|
|
74
95
|
result.directories.push(options.rootPath);
|
|
96
|
+
reportProgress('directories', `Root directory exists: ${options.rootPath}`);
|
|
75
97
|
}
|
|
76
98
|
// Preserve source file if requested
|
|
77
99
|
if (options.preserveSource && options.sourceBuffer && options.sourceFilename) {
|
|
@@ -84,9 +106,10 @@ export class VFSStructureGenerator {
|
|
|
84
106
|
type: 'source'
|
|
85
107
|
});
|
|
86
108
|
result.operations++;
|
|
109
|
+
reportProgress('metadata', `Preserved source file: ${options.sourceFilename}`);
|
|
87
110
|
}
|
|
88
|
-
//
|
|
89
|
-
const groups = this.groupEntities(importResult, options)
|
|
111
|
+
// Note: groups already calculated above for progress tracking
|
|
112
|
+
// const groups = this.groupEntities(importResult, options)
|
|
90
113
|
// Create directories and files for each group
|
|
91
114
|
for (const [groupName, entities] of groups.entries()) {
|
|
92
115
|
const groupPath = `${options.rootPath}/${groupName}`;
|
|
@@ -98,6 +121,7 @@ export class VFSStructureGenerator {
|
|
|
98
121
|
});
|
|
99
122
|
result.directories.push(groupPath);
|
|
100
123
|
result.operations++;
|
|
124
|
+
reportProgress('directories', `Created directory: ${groupName} (${entities.length} entities)`);
|
|
101
125
|
}
|
|
102
126
|
catch (error) {
|
|
103
127
|
// Directory might already exist
|
|
@@ -105,9 +129,12 @@ export class VFSStructureGenerator {
|
|
|
105
129
|
throw error;
|
|
106
130
|
}
|
|
107
131
|
result.directories.push(groupPath);
|
|
132
|
+
reportProgress('directories', `Directory exists: ${groupName}`);
|
|
108
133
|
}
|
|
109
134
|
// Create entity files
|
|
135
|
+
let entityCount = 0;
|
|
110
136
|
for (const extracted of entities) {
|
|
137
|
+
entityCount++;
|
|
111
138
|
const sanitizedName = this.sanitizeFilename(extracted.entity.name);
|
|
112
139
|
const entityPath = `${groupPath}/${sanitizedName}.json`;
|
|
113
140
|
// Create entity JSON
|
|
@@ -140,6 +167,10 @@ export class VFSStructureGenerator {
|
|
|
140
167
|
type: 'entity'
|
|
141
168
|
});
|
|
142
169
|
result.operations++;
|
|
170
|
+
// v4.11.1: Report progress every 10 entities (or on last entity)
|
|
171
|
+
if (entityCount % 10 === 0 || entityCount === entities.length) {
|
|
172
|
+
reportProgress('entities', `Created ${entityCount}/${entities.length} ${groupName} files`);
|
|
173
|
+
}
|
|
143
174
|
}
|
|
144
175
|
}
|
|
145
176
|
// Create relationships file
|
|
@@ -167,6 +198,7 @@ export class VFSStructureGenerator {
|
|
|
167
198
|
type: 'relationships'
|
|
168
199
|
});
|
|
169
200
|
result.operations++;
|
|
201
|
+
reportProgress('metadata', `Created relationships file (${allRelationships.length} relationships)`);
|
|
170
202
|
}
|
|
171
203
|
// Create metadata file
|
|
172
204
|
if (options.createMetadataFile !== false) {
|
|
@@ -206,6 +238,16 @@ export class VFSStructureGenerator {
|
|
|
206
238
|
type: 'metadata'
|
|
207
239
|
});
|
|
208
240
|
result.operations++;
|
|
241
|
+
reportProgress('metadata', 'Created metadata file');
|
|
242
|
+
}
|
|
243
|
+
// v4.11.1: Final progress update
|
|
244
|
+
if (options.onProgress) {
|
|
245
|
+
options.onProgress({
|
|
246
|
+
stage: 'metadata',
|
|
247
|
+
message: `VFS structure created successfully (${result.files.length} files, ${result.directories.length} directories)`,
|
|
248
|
+
processed: totalOperations,
|
|
249
|
+
total: totalOperations
|
|
250
|
+
});
|
|
209
251
|
}
|
|
210
252
|
result.duration = Date.now() - startTime;
|
|
211
253
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.11.1",
|
|
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",
|