@soulcraft/brainy 4.11.2 → 5.1.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/CHANGELOG.md +271 -0
- package/README.md +38 -1
- package/dist/augmentations/brainyAugmentation.d.ts +76 -0
- package/dist/augmentations/brainyAugmentation.js +126 -0
- package/dist/augmentations/cacheAugmentation.js +9 -4
- package/dist/brainy.d.ts +248 -15
- package/dist/brainy.js +707 -17
- package/dist/cli/commands/cow.d.ts +60 -0
- package/dist/cli/commands/cow.js +444 -0
- package/dist/cli/commands/import.js +1 -1
- package/dist/cli/commands/vfs.js +24 -40
- package/dist/cli/index.js +50 -0
- package/dist/hnsw/hnswIndex.d.ts +41 -0
- package/dist/hnsw/hnswIndex.js +96 -1
- package/dist/hnsw/typeAwareHNSWIndex.d.ts +9 -0
- package/dist/hnsw/typeAwareHNSWIndex.js +22 -0
- package/dist/import/ImportHistory.js +3 -3
- package/dist/importers/VFSStructureGenerator.d.ts +1 -1
- package/dist/importers/VFSStructureGenerator.js +3 -3
- package/dist/index.d.ts +6 -0
- package/dist/index.js +10 -0
- package/dist/storage/adapters/memoryStorage.d.ts +6 -0
- package/dist/storage/adapters/memoryStorage.js +39 -14
- package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +31 -1
- package/dist/storage/adapters/typeAwareStorageAdapter.js +272 -43
- package/dist/storage/baseStorage.d.ts +64 -0
- package/dist/storage/baseStorage.js +252 -12
- package/dist/storage/cow/BlobStorage.d.ts +232 -0
- package/dist/storage/cow/BlobStorage.js +437 -0
- package/dist/storage/cow/CommitLog.d.ts +199 -0
- package/dist/storage/cow/CommitLog.js +363 -0
- package/dist/storage/cow/CommitObject.d.ts +276 -0
- package/dist/storage/cow/CommitObject.js +431 -0
- package/dist/storage/cow/RefManager.d.ts +213 -0
- package/dist/storage/cow/RefManager.js +409 -0
- package/dist/storage/cow/TreeObject.d.ts +177 -0
- package/dist/storage/cow/TreeObject.js +293 -0
- package/dist/storage/storageFactory.d.ts +6 -0
- package/dist/storage/storageFactory.js +92 -74
- package/dist/types/brainy.types.d.ts +1 -0
- package/dist/vfs/FSCompat.d.ts +1 -1
- package/dist/vfs/FSCompat.js +1 -1
- package/dist/vfs/VirtualFileSystem.js +5 -6
- package/package.json +1 -1
|
@@ -7,6 +7,9 @@ import { BaseStorageAdapter } from './adapters/baseStorageAdapter.js';
|
|
|
7
7
|
import { validateNounType, validateVerbType } from '../utils/typeValidation.js';
|
|
8
8
|
import { NounType } from '../types/graphTypes.js';
|
|
9
9
|
import { getShardIdFromUuid } from './sharding.js';
|
|
10
|
+
import { RefManager } from './cow/RefManager.js';
|
|
11
|
+
import { BlobStorage } from './cow/BlobStorage.js';
|
|
12
|
+
import { CommitLog } from './cow/CommitLog.js';
|
|
10
13
|
// Clean directory structure (v4.7.2+)
|
|
11
14
|
// All storage adapters use this consistent structure
|
|
12
15
|
export const NOUNS_METADATA_DIR = 'entities/nouns/metadata';
|
|
@@ -38,6 +41,8 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
38
41
|
super(...arguments);
|
|
39
42
|
this.isInitialized = false;
|
|
40
43
|
this.readOnly = false;
|
|
44
|
+
this.currentBranch = 'main';
|
|
45
|
+
this.cowEnabled = false;
|
|
41
46
|
}
|
|
42
47
|
/**
|
|
43
48
|
* Analyze a storage key to determine its routing and path
|
|
@@ -119,6 +124,241 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
119
124
|
await this.init();
|
|
120
125
|
}
|
|
121
126
|
}
|
|
127
|
+
/**
|
|
128
|
+
* Lightweight COW enablement - just enables branch-scoped paths
|
|
129
|
+
* Called during init() to ensure all data is stored with branch prefixes from the start
|
|
130
|
+
* RefManager/BlobStorage/CommitLog are lazy-initialized on first fork()
|
|
131
|
+
* @param branch - Branch name to use (default: 'main')
|
|
132
|
+
*/
|
|
133
|
+
enableCOWLightweight(branch = 'main') {
|
|
134
|
+
if (this.cowEnabled) {
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
this.currentBranch = branch;
|
|
138
|
+
this.cowEnabled = true;
|
|
139
|
+
// RefManager/BlobStorage/CommitLog remain undefined until first fork()
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Initialize COW (Copy-on-Write) support
|
|
143
|
+
* Creates RefManager and BlobStorage for instant fork() capability
|
|
144
|
+
*
|
|
145
|
+
* v5.0.1: Now called automatically by storageFactory (zero-config)
|
|
146
|
+
*
|
|
147
|
+
* @param options - COW initialization options
|
|
148
|
+
* @param options.branch - Initial branch name (default: 'main')
|
|
149
|
+
* @param options.enableCompression - Enable zstd compression for blobs (default: true)
|
|
150
|
+
* @returns Promise that resolves when COW is initialized
|
|
151
|
+
*/
|
|
152
|
+
async initializeCOW(options) {
|
|
153
|
+
// Check if RefManager already initialized (full COW setup complete)
|
|
154
|
+
if (this.refManager) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// Enable lightweight COW if not already enabled
|
|
158
|
+
if (!this.cowEnabled) {
|
|
159
|
+
this.currentBranch = options?.branch || 'main';
|
|
160
|
+
this.cowEnabled = true;
|
|
161
|
+
}
|
|
162
|
+
// Create COWStorageAdapter bridge
|
|
163
|
+
// This adapts BaseStorage's methods to the simple key-value interface
|
|
164
|
+
const cowAdapter = {
|
|
165
|
+
get: async (key) => {
|
|
166
|
+
try {
|
|
167
|
+
const data = await this.readObjectFromPath(`_cow/${key}`);
|
|
168
|
+
if (data === null) {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
// Convert to Buffer
|
|
172
|
+
if (Buffer.isBuffer(data)) {
|
|
173
|
+
return data;
|
|
174
|
+
}
|
|
175
|
+
return Buffer.from(JSON.stringify(data));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
put: async (key, data) => {
|
|
182
|
+
// Store as Buffer (for blob data) or parse JSON (for metadata)
|
|
183
|
+
let obj;
|
|
184
|
+
try {
|
|
185
|
+
// Try to parse as JSON first (for metadata)
|
|
186
|
+
obj = JSON.parse(data.toString());
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// Not JSON, store as binary (base64 encoded for JSON storage)
|
|
190
|
+
obj = { _binary: true, data: data.toString('base64') };
|
|
191
|
+
}
|
|
192
|
+
await this.writeObjectToPath(`_cow/${key}`, obj);
|
|
193
|
+
},
|
|
194
|
+
delete: async (key) => {
|
|
195
|
+
try {
|
|
196
|
+
await this.deleteObjectFromPath(`_cow/${key}`);
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
// Ignore if doesn't exist
|
|
200
|
+
}
|
|
201
|
+
},
|
|
202
|
+
list: async (prefix) => {
|
|
203
|
+
try {
|
|
204
|
+
const paths = await this.listObjectsUnderPath(`_cow/${prefix}`);
|
|
205
|
+
// Remove _cow/ prefix and return relative keys
|
|
206
|
+
return paths.map(p => p.replace(/^_cow\//, ''));
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
return [];
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
// Initialize RefManager
|
|
214
|
+
this.refManager = new RefManager(cowAdapter);
|
|
215
|
+
// Initialize BlobStorage
|
|
216
|
+
this.blobStorage = new BlobStorage(cowAdapter, {
|
|
217
|
+
enableCompression: options?.enableCompression !== false
|
|
218
|
+
});
|
|
219
|
+
// Initialize CommitLog
|
|
220
|
+
this.commitLog = new CommitLog(this.blobStorage, this.refManager);
|
|
221
|
+
// Check if main branch exists, create if not
|
|
222
|
+
const mainRef = await this.refManager.getRef('main');
|
|
223
|
+
if (!mainRef) {
|
|
224
|
+
// Create initial commit (empty tree)
|
|
225
|
+
const emptyTreeHash = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
226
|
+
await this.refManager.createBranch('main', emptyTreeHash, {
|
|
227
|
+
description: 'Initial branch',
|
|
228
|
+
author: 'system'
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
// Set HEAD to current branch
|
|
232
|
+
const currentRef = await this.refManager.getRef(this.currentBranch);
|
|
233
|
+
if (currentRef) {
|
|
234
|
+
await this.refManager.setHead(this.currentBranch);
|
|
235
|
+
}
|
|
236
|
+
else {
|
|
237
|
+
// Branch doesn't exist, create it from main
|
|
238
|
+
const mainCommit = await this.refManager.resolveRef('main');
|
|
239
|
+
if (mainCommit) {
|
|
240
|
+
await this.refManager.createBranch(this.currentBranch, mainCommit, {
|
|
241
|
+
description: `Branch created from main`,
|
|
242
|
+
author: 'system'
|
|
243
|
+
});
|
|
244
|
+
await this.refManager.setHead(this.currentBranch);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
this.cowEnabled = true;
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Resolve branch-scoped path for COW isolation
|
|
251
|
+
* @protected - Available to subclasses for COW implementation
|
|
252
|
+
*/
|
|
253
|
+
resolveBranchPath(basePath, branch) {
|
|
254
|
+
if (!this.cowEnabled) {
|
|
255
|
+
return basePath; // COW disabled, use direct path
|
|
256
|
+
}
|
|
257
|
+
const targetBranch = branch || this.currentBranch || 'main';
|
|
258
|
+
// Branch-scoped path: branches/<branch>/<basePath>
|
|
259
|
+
return `branches/${targetBranch}/${basePath}`;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Write object to branch-specific path (COW layer)
|
|
263
|
+
* @protected - Available to subclasses for COW implementation
|
|
264
|
+
*/
|
|
265
|
+
async writeObjectToBranch(path, data, branch) {
|
|
266
|
+
const branchPath = this.resolveBranchPath(path, branch);
|
|
267
|
+
return this.writeObjectToPath(branchPath, data);
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Read object with inheritance from parent branches (COW layer)
|
|
271
|
+
* Tries current branch first, then walks commit history
|
|
272
|
+
* @protected - Available to subclasses for COW implementation
|
|
273
|
+
*/
|
|
274
|
+
async readWithInheritance(path, branch) {
|
|
275
|
+
if (!this.cowEnabled) {
|
|
276
|
+
// COW disabled, direct read
|
|
277
|
+
return this.readObjectFromPath(path);
|
|
278
|
+
}
|
|
279
|
+
const targetBranch = branch || this.currentBranch || 'main';
|
|
280
|
+
// Try current branch first
|
|
281
|
+
const branchPath = this.resolveBranchPath(path, targetBranch);
|
|
282
|
+
let data = await this.readObjectFromPath(branchPath);
|
|
283
|
+
if (data !== null) {
|
|
284
|
+
return data; // Found in current branch
|
|
285
|
+
}
|
|
286
|
+
// Not in branch, check if we're on main (no inheritance needed)
|
|
287
|
+
if (targetBranch === 'main') {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
// Not in branch, walk commit history to find in parent
|
|
291
|
+
if (this.refManager && this.commitLog) {
|
|
292
|
+
try {
|
|
293
|
+
const commitHash = await this.refManager.resolveRef(targetBranch);
|
|
294
|
+
if (commitHash) {
|
|
295
|
+
// Walk parent commits until we find the data
|
|
296
|
+
for await (const commit of this.commitLog.walk(commitHash)) {
|
|
297
|
+
// Try reading from parent's branch path
|
|
298
|
+
const parentBranch = commit.metadata?.branch || 'main';
|
|
299
|
+
if (parentBranch === targetBranch)
|
|
300
|
+
continue; // Skip self
|
|
301
|
+
const parentPath = this.resolveBranchPath(path, parentBranch);
|
|
302
|
+
data = await this.readObjectFromPath(parentPath);
|
|
303
|
+
if (data !== null) {
|
|
304
|
+
return data; // Found in ancestor
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
catch (error) {
|
|
310
|
+
// Commit walk failed, fall back to main
|
|
311
|
+
const mainPath = this.resolveBranchPath(path, 'main');
|
|
312
|
+
return this.readObjectFromPath(mainPath);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
// Last fallback: try main branch
|
|
316
|
+
const mainPath = this.resolveBranchPath(path, 'main');
|
|
317
|
+
return this.readObjectFromPath(mainPath);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Delete object from branch-specific path (COW layer)
|
|
321
|
+
* @protected - Available to subclasses for COW implementation
|
|
322
|
+
*/
|
|
323
|
+
async deleteObjectFromBranch(path, branch) {
|
|
324
|
+
const branchPath = this.resolveBranchPath(path, branch);
|
|
325
|
+
return this.deleteObjectFromPath(branchPath);
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* List objects under path in branch (COW layer)
|
|
329
|
+
* @protected - Available to subclasses for COW implementation
|
|
330
|
+
*/
|
|
331
|
+
async listObjectsInBranch(prefix, branch) {
|
|
332
|
+
const branchPrefix = this.resolveBranchPath(prefix, branch);
|
|
333
|
+
const paths = await this.listObjectsUnderPath(branchPrefix);
|
|
334
|
+
// Remove branch prefix from results
|
|
335
|
+
const targetBranch = branch || this.currentBranch || 'main';
|
|
336
|
+
const prefixToRemove = `branches/${targetBranch}/`;
|
|
337
|
+
return paths.map(p => p.startsWith(prefixToRemove) ? p.substring(prefixToRemove.length) : p);
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* List objects with inheritance (v5.0.1)
|
|
341
|
+
* Lists objects from current branch AND main branch, returns unique paths
|
|
342
|
+
* This enables fork to see parent's data in pagination operations
|
|
343
|
+
*
|
|
344
|
+
* Simplified approach: All branches inherit from main
|
|
345
|
+
*/
|
|
346
|
+
async listObjectsWithInheritance(prefix, branch) {
|
|
347
|
+
if (!this.cowEnabled) {
|
|
348
|
+
return this.listObjectsInBranch(prefix, branch);
|
|
349
|
+
}
|
|
350
|
+
const targetBranch = branch || this.currentBranch || 'main';
|
|
351
|
+
// Collect paths from current branch
|
|
352
|
+
const pathsSet = new Set();
|
|
353
|
+
const currentBranchPaths = await this.listObjectsInBranch(prefix, targetBranch);
|
|
354
|
+
currentBranchPaths.forEach(p => pathsSet.add(p));
|
|
355
|
+
// If not on main, also list from main (all branches inherit from main)
|
|
356
|
+
if (targetBranch !== 'main') {
|
|
357
|
+
const mainPaths = await this.listObjectsInBranch(prefix, 'main');
|
|
358
|
+
mainPaths.forEach(p => pathsSet.add(p));
|
|
359
|
+
}
|
|
360
|
+
return Array.from(pathsSet);
|
|
361
|
+
}
|
|
122
362
|
/**
|
|
123
363
|
* Save a noun to storage (v4.0.0: vector only, metadata saved separately)
|
|
124
364
|
* @param noun Pure HNSW vector data (no metadata)
|
|
@@ -731,7 +971,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
731
971
|
async saveMetadata(id, metadata) {
|
|
732
972
|
await this.ensureInitialized();
|
|
733
973
|
const keyInfo = this.analyzeKey(id, 'system');
|
|
734
|
-
return this.
|
|
974
|
+
return this.writeObjectToBranch(keyInfo.fullPath, metadata);
|
|
735
975
|
}
|
|
736
976
|
/**
|
|
737
977
|
* Get metadata from storage (v4.0.0: now typed)
|
|
@@ -740,7 +980,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
740
980
|
async getMetadata(id) {
|
|
741
981
|
await this.ensureInitialized();
|
|
742
982
|
const keyInfo = this.analyzeKey(id, 'system');
|
|
743
|
-
return this.
|
|
983
|
+
return this.readWithInheritance(keyInfo.fullPath);
|
|
744
984
|
}
|
|
745
985
|
/**
|
|
746
986
|
* Save noun metadata to storage (v4.0.0: now typed)
|
|
@@ -765,10 +1005,10 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
765
1005
|
await this.ensureInitialized();
|
|
766
1006
|
// Determine if this is a new entity by checking if metadata already exists
|
|
767
1007
|
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
768
|
-
const existingMetadata = await this.
|
|
1008
|
+
const existingMetadata = await this.readWithInheritance(keyInfo.fullPath);
|
|
769
1009
|
const isNew = !existingMetadata;
|
|
770
|
-
// Save the metadata
|
|
771
|
-
await this.
|
|
1010
|
+
// Save the metadata (COW-aware - writes to branch-specific path)
|
|
1011
|
+
await this.writeObjectToBranch(keyInfo.fullPath, metadata);
|
|
772
1012
|
// CRITICAL FIX (v4.1.2): Increment count for new entities
|
|
773
1013
|
// This runs AFTER metadata is saved, guaranteeing type information is available
|
|
774
1014
|
// Uses synchronous increment since storage operations are already serialized
|
|
@@ -788,7 +1028,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
788
1028
|
async getNounMetadata(id) {
|
|
789
1029
|
await this.ensureInitialized();
|
|
790
1030
|
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
791
|
-
return this.
|
|
1031
|
+
return this.readWithInheritance(keyInfo.fullPath);
|
|
792
1032
|
}
|
|
793
1033
|
/**
|
|
794
1034
|
* Delete noun metadata from storage
|
|
@@ -797,7 +1037,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
797
1037
|
async deleteNounMetadata(id) {
|
|
798
1038
|
await this.ensureInitialized();
|
|
799
1039
|
const keyInfo = this.analyzeKey(id, 'noun-metadata');
|
|
800
|
-
return this.
|
|
1040
|
+
return this.deleteObjectFromBranch(keyInfo.fullPath);
|
|
801
1041
|
}
|
|
802
1042
|
/**
|
|
803
1043
|
* Save verb metadata to storage (v4.0.0: now typed)
|
|
@@ -823,10 +1063,10 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
823
1063
|
await this.ensureInitialized();
|
|
824
1064
|
// Determine if this is a new verb by checking if metadata already exists
|
|
825
1065
|
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
826
|
-
const existingMetadata = await this.
|
|
1066
|
+
const existingMetadata = await this.readWithInheritance(keyInfo.fullPath);
|
|
827
1067
|
const isNew = !existingMetadata;
|
|
828
|
-
// Save the metadata
|
|
829
|
-
await this.
|
|
1068
|
+
// Save the metadata (COW-aware - writes to branch-specific path)
|
|
1069
|
+
await this.writeObjectToBranch(keyInfo.fullPath, metadata);
|
|
830
1070
|
// CRITICAL FIX (v4.1.2): Increment verb count for new relationships
|
|
831
1071
|
// This runs AFTER metadata is saved
|
|
832
1072
|
// Verb type is now stored in metadata (as of v4.1.2) to avoid loading HNSWVerb
|
|
@@ -847,7 +1087,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
847
1087
|
async getVerbMetadata(id) {
|
|
848
1088
|
await this.ensureInitialized();
|
|
849
1089
|
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
850
|
-
return this.
|
|
1090
|
+
return this.readWithInheritance(keyInfo.fullPath);
|
|
851
1091
|
}
|
|
852
1092
|
/**
|
|
853
1093
|
* Delete verb metadata from storage
|
|
@@ -856,7 +1096,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
856
1096
|
async deleteVerbMetadata(id) {
|
|
857
1097
|
await this.ensureInitialized();
|
|
858
1098
|
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
859
|
-
return this.
|
|
1099
|
+
return this.deleteObjectFromBranch(keyInfo.fullPath);
|
|
860
1100
|
}
|
|
861
1101
|
/**
|
|
862
1102
|
* Helper method to convert a Map to a plain object for serialization
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlobStorage: Content-Addressable Blob Storage for COW (Copy-on-Write)
|
|
3
|
+
*
|
|
4
|
+
* State-of-the-art implementation featuring:
|
|
5
|
+
* - Content-addressable: SHA-256 hashing
|
|
6
|
+
* - Type-aware chunking: Separate vectors, metadata, relationships
|
|
7
|
+
* - Compression: zstd for JSON, optimized for vectors
|
|
8
|
+
* - LRU caching: Hot blob performance
|
|
9
|
+
* - Streaming: Multipart upload for large blobs
|
|
10
|
+
* - Batch operations: Parallel I/O
|
|
11
|
+
* - Integrity: Cryptographic verification
|
|
12
|
+
* - Observability: Metrics and tracing
|
|
13
|
+
*
|
|
14
|
+
* @module storage/cow/BlobStorage
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Simple key-value storage interface for COW primitives
|
|
18
|
+
* This will be implemented by BaseStorage when COW is integrated
|
|
19
|
+
*/
|
|
20
|
+
export interface COWStorageAdapter {
|
|
21
|
+
get(key: string): Promise<Buffer | undefined>;
|
|
22
|
+
put(key: string, data: Buffer): Promise<void>;
|
|
23
|
+
delete(key: string): Promise<void>;
|
|
24
|
+
list(prefix: string): Promise<string[]>;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Blob metadata stored alongside blob data
|
|
28
|
+
*/
|
|
29
|
+
export interface BlobMetadata {
|
|
30
|
+
hash: string;
|
|
31
|
+
size: number;
|
|
32
|
+
compressedSize: number;
|
|
33
|
+
compression: 'none' | 'zstd';
|
|
34
|
+
type: 'vector' | 'metadata' | 'tree' | 'commit' | 'raw';
|
|
35
|
+
createdAt: number;
|
|
36
|
+
refCount: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Blob write options
|
|
40
|
+
*/
|
|
41
|
+
export interface BlobWriteOptions {
|
|
42
|
+
compression?: 'none' | 'zstd' | 'auto';
|
|
43
|
+
type?: 'vector' | 'metadata' | 'tree' | 'commit' | 'raw';
|
|
44
|
+
skipVerification?: boolean;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Blob read options
|
|
48
|
+
*/
|
|
49
|
+
export interface BlobReadOptions {
|
|
50
|
+
skipDecompression?: boolean;
|
|
51
|
+
skipCache?: boolean;
|
|
52
|
+
skipVerification?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Blob statistics for observability
|
|
56
|
+
*/
|
|
57
|
+
export interface BlobStats {
|
|
58
|
+
totalBlobs: number;
|
|
59
|
+
totalSize: number;
|
|
60
|
+
compressedSize: number;
|
|
61
|
+
cacheHits: number;
|
|
62
|
+
cacheMisses: number;
|
|
63
|
+
compressionRatio: number;
|
|
64
|
+
avgBlobSize: number;
|
|
65
|
+
dedupSavings: number;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* State-of-the-art content-addressable blob storage
|
|
69
|
+
*
|
|
70
|
+
* Features:
|
|
71
|
+
* - Content addressing via SHA-256
|
|
72
|
+
* - Type-aware compression (zstd, vector-optimized)
|
|
73
|
+
* - LRU caching with memory limits
|
|
74
|
+
* - Streaming for large blobs
|
|
75
|
+
* - Batch operations
|
|
76
|
+
* - Integrity verification
|
|
77
|
+
* - Observability metrics
|
|
78
|
+
*/
|
|
79
|
+
export declare class BlobStorage {
|
|
80
|
+
private adapter;
|
|
81
|
+
private cache;
|
|
82
|
+
private cacheMaxSize;
|
|
83
|
+
private currentCacheSize;
|
|
84
|
+
private stats;
|
|
85
|
+
private zstdCompress?;
|
|
86
|
+
private zstdDecompress?;
|
|
87
|
+
private readonly CACHE_MAX_SIZE;
|
|
88
|
+
private readonly MULTIPART_THRESHOLD;
|
|
89
|
+
private readonly COMPRESSION_THRESHOLD;
|
|
90
|
+
constructor(adapter: COWStorageAdapter, options?: {
|
|
91
|
+
cacheMaxSize?: number;
|
|
92
|
+
enableCompression?: boolean;
|
|
93
|
+
});
|
|
94
|
+
/**
|
|
95
|
+
* Lazy load zstd compression module
|
|
96
|
+
* (Avoids loading if not needed)
|
|
97
|
+
*/
|
|
98
|
+
private initCompression;
|
|
99
|
+
/**
|
|
100
|
+
* Compute SHA-256 hash of data
|
|
101
|
+
*
|
|
102
|
+
* @param data - Data to hash
|
|
103
|
+
* @returns SHA-256 hash as hex string
|
|
104
|
+
*/
|
|
105
|
+
static hash(data: Buffer): string;
|
|
106
|
+
/**
|
|
107
|
+
* Write a blob to storage
|
|
108
|
+
*
|
|
109
|
+
* Features:
|
|
110
|
+
* - Content-addressable: hash determines storage key
|
|
111
|
+
* - Deduplication: existing blob not rewritten
|
|
112
|
+
* - Compression: auto-compress based on type
|
|
113
|
+
* - Multipart: for large blobs (>5MB)
|
|
114
|
+
* - Verification: hash verification
|
|
115
|
+
* - Caching: write-through cache
|
|
116
|
+
*
|
|
117
|
+
* @param data - Blob data to write
|
|
118
|
+
* @param options - Write options
|
|
119
|
+
* @returns Blob hash
|
|
120
|
+
*/
|
|
121
|
+
write(data: Buffer, options?: BlobWriteOptions): Promise<string>;
|
|
122
|
+
/**
|
|
123
|
+
* Read a blob from storage
|
|
124
|
+
*
|
|
125
|
+
* Features:
|
|
126
|
+
* - Cache lookup first (LRU)
|
|
127
|
+
* - Decompression (if compressed)
|
|
128
|
+
* - Verification (optional hash check)
|
|
129
|
+
* - Streaming for large blobs
|
|
130
|
+
*
|
|
131
|
+
* @param hash - Blob hash
|
|
132
|
+
* @param options - Read options
|
|
133
|
+
* @returns Blob data
|
|
134
|
+
*/
|
|
135
|
+
read(hash: string, options?: BlobReadOptions): Promise<Buffer>;
|
|
136
|
+
/**
|
|
137
|
+
* Check if blob exists
|
|
138
|
+
*
|
|
139
|
+
* @param hash - Blob hash
|
|
140
|
+
* @returns True if blob exists
|
|
141
|
+
*/
|
|
142
|
+
has(hash: string): Promise<boolean>;
|
|
143
|
+
/**
|
|
144
|
+
* Delete a blob from storage
|
|
145
|
+
*
|
|
146
|
+
* Features:
|
|
147
|
+
* - Reference counting: only delete if refCount = 0
|
|
148
|
+
* - Cascade: delete metadata too
|
|
149
|
+
* - Cache invalidation
|
|
150
|
+
*
|
|
151
|
+
* @param hash - Blob hash
|
|
152
|
+
*/
|
|
153
|
+
delete(hash: string): Promise<void>;
|
|
154
|
+
/**
|
|
155
|
+
* Get blob metadata without reading full blob
|
|
156
|
+
*
|
|
157
|
+
* @param hash - Blob hash
|
|
158
|
+
* @returns Blob metadata
|
|
159
|
+
*/
|
|
160
|
+
getMetadata(hash: string): Promise<BlobMetadata | undefined>;
|
|
161
|
+
/**
|
|
162
|
+
* Batch write multiple blobs in parallel
|
|
163
|
+
*
|
|
164
|
+
* @param blobs - Array of [data, options] tuples
|
|
165
|
+
* @returns Array of blob hashes
|
|
166
|
+
*/
|
|
167
|
+
writeBatch(blobs: Array<[Buffer, BlobWriteOptions?]>): Promise<string[]>;
|
|
168
|
+
/**
|
|
169
|
+
* Batch read multiple blobs in parallel
|
|
170
|
+
*
|
|
171
|
+
* @param hashes - Array of blob hashes
|
|
172
|
+
* @param options - Read options
|
|
173
|
+
* @returns Array of blob data
|
|
174
|
+
*/
|
|
175
|
+
readBatch(hashes: string[], options?: BlobReadOptions): Promise<Buffer[]>;
|
|
176
|
+
/**
|
|
177
|
+
* List all blobs (for garbage collection, debugging)
|
|
178
|
+
*
|
|
179
|
+
* @returns Array of blob hashes
|
|
180
|
+
*/
|
|
181
|
+
listBlobs(): Promise<string[]>;
|
|
182
|
+
/**
|
|
183
|
+
* Get storage statistics
|
|
184
|
+
*
|
|
185
|
+
* @returns Blob statistics
|
|
186
|
+
*/
|
|
187
|
+
getStats(): BlobStats;
|
|
188
|
+
/**
|
|
189
|
+
* Clear cache (useful for testing, memory pressure)
|
|
190
|
+
*/
|
|
191
|
+
clearCache(): void;
|
|
192
|
+
/**
|
|
193
|
+
* Garbage collect unreferenced blobs
|
|
194
|
+
*
|
|
195
|
+
* @param referencedHashes - Set of hashes that should be kept
|
|
196
|
+
* @returns Number of blobs deleted
|
|
197
|
+
*/
|
|
198
|
+
garbageCollect(referencedHashes: Set<string>): Promise<number>;
|
|
199
|
+
/**
|
|
200
|
+
* Select compression strategy based on data and options
|
|
201
|
+
*/
|
|
202
|
+
private selectCompression;
|
|
203
|
+
/**
|
|
204
|
+
* Write large blob using multipart upload
|
|
205
|
+
* (Future enhancement: stream to adapter if supported)
|
|
206
|
+
*/
|
|
207
|
+
private writeMultipart;
|
|
208
|
+
/**
|
|
209
|
+
* Increment reference count for a blob
|
|
210
|
+
*/
|
|
211
|
+
private incrementRefCount;
|
|
212
|
+
/**
|
|
213
|
+
* Decrement reference count for a blob
|
|
214
|
+
*/
|
|
215
|
+
private decrementRefCount;
|
|
216
|
+
/**
|
|
217
|
+
* Add blob to LRU cache
|
|
218
|
+
*/
|
|
219
|
+
private addToCache;
|
|
220
|
+
/**
|
|
221
|
+
* Get blob from cache
|
|
222
|
+
*/
|
|
223
|
+
private getFromCache;
|
|
224
|
+
/**
|
|
225
|
+
* Remove blob from cache
|
|
226
|
+
*/
|
|
227
|
+
private removeFromCache;
|
|
228
|
+
/**
|
|
229
|
+
* Evict least recently used entry from cache
|
|
230
|
+
*/
|
|
231
|
+
private evictLRU;
|
|
232
|
+
}
|