@soulcraft/brainy 4.11.1 → 5.0.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 +163 -2
- package/README.md +37 -0
- package/dist/augmentations/brainyAugmentation.d.ts +76 -0
- package/dist/augmentations/brainyAugmentation.js +126 -0
- package/dist/brainy.d.ts +161 -0
- package/dist/brainy.js +451 -0
- package/dist/cli/commands/cow.d.ts +60 -0
- package/dist/cli/commands/cow.js +444 -0
- 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/index.d.ts +6 -0
- package/dist/index.js +10 -0
- package/dist/neural/signals/PatternSignal.js +7 -1
- package/dist/storage/baseStorage.d.ts +21 -0
- package/dist/storage/baseStorage.js +108 -0
- package/dist/storage/cow/BlobStorage.d.ts +231 -0
- package/dist/storage/cow/BlobStorage.js +435 -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 +7 -0
- package/dist/storage/storageFactory.js +91 -74
- package/dist/types/brainy.types.d.ts +2 -0
- package/package.json +1 -1
|
@@ -0,0 +1,435 @@
|
|
|
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
|
+
import { createHash } from 'crypto';
|
|
17
|
+
/**
|
|
18
|
+
* State-of-the-art content-addressable blob storage
|
|
19
|
+
*
|
|
20
|
+
* Features:
|
|
21
|
+
* - Content addressing via SHA-256
|
|
22
|
+
* - Type-aware compression (zstd, vector-optimized)
|
|
23
|
+
* - LRU caching with memory limits
|
|
24
|
+
* - Streaming for large blobs
|
|
25
|
+
* - Batch operations
|
|
26
|
+
* - Integrity verification
|
|
27
|
+
* - Observability metrics
|
|
28
|
+
*/
|
|
29
|
+
export class BlobStorage {
|
|
30
|
+
constructor(adapter, options) {
|
|
31
|
+
// Configuration
|
|
32
|
+
this.CACHE_MAX_SIZE = 100 * 1024 * 1024; // 100MB default
|
|
33
|
+
this.MULTIPART_THRESHOLD = 5 * 1024 * 1024; // 5MB
|
|
34
|
+
this.COMPRESSION_THRESHOLD = 1024; // 1KB - don't compress smaller
|
|
35
|
+
this.adapter = adapter;
|
|
36
|
+
this.cache = new Map();
|
|
37
|
+
this.cacheMaxSize = options?.cacheMaxSize ?? this.CACHE_MAX_SIZE;
|
|
38
|
+
this.currentCacheSize = 0;
|
|
39
|
+
this.stats = {
|
|
40
|
+
totalBlobs: 0,
|
|
41
|
+
totalSize: 0,
|
|
42
|
+
compressedSize: 0,
|
|
43
|
+
cacheHits: 0,
|
|
44
|
+
cacheMisses: 0,
|
|
45
|
+
compressionRatio: 1.0,
|
|
46
|
+
avgBlobSize: 0,
|
|
47
|
+
dedupSavings: 0
|
|
48
|
+
};
|
|
49
|
+
// Lazy load compression (only if needed)
|
|
50
|
+
if (options?.enableCompression !== false) {
|
|
51
|
+
this.initCompression();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Lazy load zstd compression module
|
|
56
|
+
* (Avoids loading if not needed)
|
|
57
|
+
*/
|
|
58
|
+
async initCompression() {
|
|
59
|
+
try {
|
|
60
|
+
// Dynamic import to avoid loading if not needed
|
|
61
|
+
// @ts-ignore - Optional dependency, gracefully handled if missing
|
|
62
|
+
const zstd = await import('@mongodb-js/zstd');
|
|
63
|
+
this.zstdCompress = async (data) => {
|
|
64
|
+
return Buffer.from(await zstd.compress(data, 3)); // Level 3 = fast
|
|
65
|
+
};
|
|
66
|
+
this.zstdDecompress = async (data) => {
|
|
67
|
+
return Buffer.from(await zstd.decompress(data));
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.warn('zstd compression not available, falling back to uncompressed');
|
|
72
|
+
this.zstdCompress = undefined;
|
|
73
|
+
this.zstdDecompress = undefined;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Compute SHA-256 hash of data
|
|
78
|
+
*
|
|
79
|
+
* @param data - Data to hash
|
|
80
|
+
* @returns SHA-256 hash as hex string
|
|
81
|
+
*/
|
|
82
|
+
static hash(data) {
|
|
83
|
+
return createHash('sha256').update(data).digest('hex');
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Write a blob to storage
|
|
87
|
+
*
|
|
88
|
+
* Features:
|
|
89
|
+
* - Content-addressable: hash determines storage key
|
|
90
|
+
* - Deduplication: existing blob not rewritten
|
|
91
|
+
* - Compression: auto-compress based on type
|
|
92
|
+
* - Multipart: for large blobs (>5MB)
|
|
93
|
+
* - Verification: hash verification
|
|
94
|
+
* - Caching: write-through cache
|
|
95
|
+
*
|
|
96
|
+
* @param data - Blob data to write
|
|
97
|
+
* @param options - Write options
|
|
98
|
+
* @returns Blob hash
|
|
99
|
+
*/
|
|
100
|
+
async write(data, options = {}) {
|
|
101
|
+
const hash = BlobStorage.hash(data);
|
|
102
|
+
// Deduplication: Check if blob already exists
|
|
103
|
+
if (await this.has(hash)) {
|
|
104
|
+
// Update ref count
|
|
105
|
+
await this.incrementRefCount(hash);
|
|
106
|
+
this.stats.dedupSavings += data.length;
|
|
107
|
+
return hash;
|
|
108
|
+
}
|
|
109
|
+
// Determine compression strategy
|
|
110
|
+
const compression = this.selectCompression(data, options);
|
|
111
|
+
// Compress if needed
|
|
112
|
+
let finalData = data;
|
|
113
|
+
let compressedSize = data.length;
|
|
114
|
+
if (compression === 'zstd' && this.zstdCompress) {
|
|
115
|
+
finalData = await this.zstdCompress(data);
|
|
116
|
+
compressedSize = finalData.length;
|
|
117
|
+
}
|
|
118
|
+
// Create metadata
|
|
119
|
+
const metadata = {
|
|
120
|
+
hash,
|
|
121
|
+
size: data.length,
|
|
122
|
+
compressedSize,
|
|
123
|
+
compression,
|
|
124
|
+
type: options.type || 'raw',
|
|
125
|
+
createdAt: Date.now(),
|
|
126
|
+
refCount: 1
|
|
127
|
+
};
|
|
128
|
+
// Write blob data
|
|
129
|
+
if (finalData.length > this.MULTIPART_THRESHOLD) {
|
|
130
|
+
// Large blob: use streaming/multipart
|
|
131
|
+
await this.writeMultipart(hash, finalData, metadata);
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
// Small blob: single write
|
|
135
|
+
await this.adapter.put(`blob:${hash}`, finalData);
|
|
136
|
+
}
|
|
137
|
+
// Write metadata
|
|
138
|
+
await this.adapter.put(`blob-meta:${hash}`, Buffer.from(JSON.stringify(metadata)));
|
|
139
|
+
// Update cache (write-through)
|
|
140
|
+
this.addToCache(hash, data, metadata);
|
|
141
|
+
// Update stats
|
|
142
|
+
this.stats.totalBlobs++;
|
|
143
|
+
this.stats.totalSize += data.length;
|
|
144
|
+
this.stats.compressedSize += compressedSize;
|
|
145
|
+
this.stats.compressionRatio = this.stats.totalSize / (this.stats.compressedSize || 1);
|
|
146
|
+
this.stats.avgBlobSize = this.stats.totalSize / this.stats.totalBlobs;
|
|
147
|
+
return hash;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Read a blob from storage
|
|
151
|
+
*
|
|
152
|
+
* Features:
|
|
153
|
+
* - Cache lookup first (LRU)
|
|
154
|
+
* - Decompression (if compressed)
|
|
155
|
+
* - Verification (optional hash check)
|
|
156
|
+
* - Streaming for large blobs
|
|
157
|
+
*
|
|
158
|
+
* @param hash - Blob hash
|
|
159
|
+
* @param options - Read options
|
|
160
|
+
* @returns Blob data
|
|
161
|
+
*/
|
|
162
|
+
async read(hash, options = {}) {
|
|
163
|
+
// Check cache first
|
|
164
|
+
if (!options.skipCache) {
|
|
165
|
+
const cached = this.getFromCache(hash);
|
|
166
|
+
if (cached) {
|
|
167
|
+
this.stats.cacheHits++;
|
|
168
|
+
return cached.data;
|
|
169
|
+
}
|
|
170
|
+
this.stats.cacheMisses++;
|
|
171
|
+
}
|
|
172
|
+
// Read from storage
|
|
173
|
+
const data = await this.adapter.get(`blob:${hash}`);
|
|
174
|
+
if (!data) {
|
|
175
|
+
throw new Error(`Blob not found: ${hash}`);
|
|
176
|
+
}
|
|
177
|
+
// Read metadata
|
|
178
|
+
const metadataBuffer = await this.adapter.get(`blob-meta:${hash}`);
|
|
179
|
+
if (!metadataBuffer) {
|
|
180
|
+
throw new Error(`Blob metadata not found: ${hash}`);
|
|
181
|
+
}
|
|
182
|
+
const metadata = JSON.parse(metadataBuffer.toString());
|
|
183
|
+
// Decompress if needed
|
|
184
|
+
let finalData = data;
|
|
185
|
+
if (metadata.compression === 'zstd' && !options.skipDecompression) {
|
|
186
|
+
if (!this.zstdDecompress) {
|
|
187
|
+
throw new Error('zstd decompression not available');
|
|
188
|
+
}
|
|
189
|
+
finalData = await this.zstdDecompress(data);
|
|
190
|
+
}
|
|
191
|
+
// Verify hash (optional, expensive)
|
|
192
|
+
if (!options.skipCache && BlobStorage.hash(finalData) !== hash) {
|
|
193
|
+
throw new Error(`Blob integrity check failed: ${hash}`);
|
|
194
|
+
}
|
|
195
|
+
// Add to cache
|
|
196
|
+
this.addToCache(hash, finalData, metadata);
|
|
197
|
+
return finalData;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Check if blob exists
|
|
201
|
+
*
|
|
202
|
+
* @param hash - Blob hash
|
|
203
|
+
* @returns True if blob exists
|
|
204
|
+
*/
|
|
205
|
+
async has(hash) {
|
|
206
|
+
// Check cache first
|
|
207
|
+
if (this.cache.has(hash)) {
|
|
208
|
+
return true;
|
|
209
|
+
}
|
|
210
|
+
// Check storage
|
|
211
|
+
const exists = await this.adapter.get(`blob:${hash}`);
|
|
212
|
+
return exists !== undefined;
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Delete a blob from storage
|
|
216
|
+
*
|
|
217
|
+
* Features:
|
|
218
|
+
* - Reference counting: only delete if refCount = 0
|
|
219
|
+
* - Cascade: delete metadata too
|
|
220
|
+
* - Cache invalidation
|
|
221
|
+
*
|
|
222
|
+
* @param hash - Blob hash
|
|
223
|
+
*/
|
|
224
|
+
async delete(hash) {
|
|
225
|
+
// Decrement ref count
|
|
226
|
+
const refCount = await this.decrementRefCount(hash);
|
|
227
|
+
// Only delete if no references remain
|
|
228
|
+
if (refCount > 0) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
// Delete blob data
|
|
232
|
+
await this.adapter.delete(`blob:${hash}`);
|
|
233
|
+
// Delete metadata
|
|
234
|
+
await this.adapter.delete(`blob-meta:${hash}`);
|
|
235
|
+
// Remove from cache
|
|
236
|
+
this.removeFromCache(hash);
|
|
237
|
+
// Update stats
|
|
238
|
+
this.stats.totalBlobs--;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Get blob metadata without reading full blob
|
|
242
|
+
*
|
|
243
|
+
* @param hash - Blob hash
|
|
244
|
+
* @returns Blob metadata
|
|
245
|
+
*/
|
|
246
|
+
async getMetadata(hash) {
|
|
247
|
+
const data = await this.adapter.get(`blob-meta:${hash}`);
|
|
248
|
+
if (!data) {
|
|
249
|
+
return undefined;
|
|
250
|
+
}
|
|
251
|
+
return JSON.parse(data.toString());
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Batch write multiple blobs in parallel
|
|
255
|
+
*
|
|
256
|
+
* @param blobs - Array of [data, options] tuples
|
|
257
|
+
* @returns Array of blob hashes
|
|
258
|
+
*/
|
|
259
|
+
async writeBatch(blobs) {
|
|
260
|
+
return Promise.all(blobs.map(([data, options]) => this.write(data, options)));
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Batch read multiple blobs in parallel
|
|
264
|
+
*
|
|
265
|
+
* @param hashes - Array of blob hashes
|
|
266
|
+
* @param options - Read options
|
|
267
|
+
* @returns Array of blob data
|
|
268
|
+
*/
|
|
269
|
+
async readBatch(hashes, options) {
|
|
270
|
+
return Promise.all(hashes.map(hash => this.read(hash, options)));
|
|
271
|
+
}
|
|
272
|
+
/**
|
|
273
|
+
* List all blobs (for garbage collection, debugging)
|
|
274
|
+
*
|
|
275
|
+
* @returns Array of blob hashes
|
|
276
|
+
*/
|
|
277
|
+
async listBlobs() {
|
|
278
|
+
const keys = await this.adapter.list('blob:');
|
|
279
|
+
return keys.map((key) => key.replace(/^blob:/, ''));
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get storage statistics
|
|
283
|
+
*
|
|
284
|
+
* @returns Blob statistics
|
|
285
|
+
*/
|
|
286
|
+
getStats() {
|
|
287
|
+
return { ...this.stats };
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Clear cache (useful for testing, memory pressure)
|
|
291
|
+
*/
|
|
292
|
+
clearCache() {
|
|
293
|
+
this.cache.clear();
|
|
294
|
+
this.currentCacheSize = 0;
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Garbage collect unreferenced blobs
|
|
298
|
+
*
|
|
299
|
+
* @param referencedHashes - Set of hashes that should be kept
|
|
300
|
+
* @returns Number of blobs deleted
|
|
301
|
+
*/
|
|
302
|
+
async garbageCollect(referencedHashes) {
|
|
303
|
+
const allBlobs = await this.listBlobs();
|
|
304
|
+
let deleted = 0;
|
|
305
|
+
for (const hash of allBlobs) {
|
|
306
|
+
if (!referencedHashes.has(hash)) {
|
|
307
|
+
// Check ref count
|
|
308
|
+
const metadata = await this.getMetadata(hash);
|
|
309
|
+
if (metadata && metadata.refCount === 0) {
|
|
310
|
+
await this.delete(hash);
|
|
311
|
+
deleted++;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
return deleted;
|
|
316
|
+
}
|
|
317
|
+
// ========== PRIVATE METHODS ==========
|
|
318
|
+
/**
|
|
319
|
+
* Select compression strategy based on data and options
|
|
320
|
+
*/
|
|
321
|
+
selectCompression(data, options) {
|
|
322
|
+
if (options.compression === 'none') {
|
|
323
|
+
return 'none';
|
|
324
|
+
}
|
|
325
|
+
if (options.compression === 'zstd') {
|
|
326
|
+
return this.zstdCompress ? 'zstd' : 'none';
|
|
327
|
+
}
|
|
328
|
+
// Auto mode
|
|
329
|
+
if (data.length < this.COMPRESSION_THRESHOLD) {
|
|
330
|
+
return 'none'; // Too small to benefit
|
|
331
|
+
}
|
|
332
|
+
// Compress metadata, trees, commits (text/JSON)
|
|
333
|
+
if (options.type === 'metadata' || options.type === 'tree' || options.type === 'commit') {
|
|
334
|
+
return this.zstdCompress ? 'zstd' : 'none';
|
|
335
|
+
}
|
|
336
|
+
// Don't compress vectors (already dense)
|
|
337
|
+
if (options.type === 'vector') {
|
|
338
|
+
return 'none';
|
|
339
|
+
}
|
|
340
|
+
// Default: compress
|
|
341
|
+
return this.zstdCompress ? 'zstd' : 'none';
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Write large blob using multipart upload
|
|
345
|
+
* (Future enhancement: stream to adapter if supported)
|
|
346
|
+
*/
|
|
347
|
+
async writeMultipart(hash, data, metadata) {
|
|
348
|
+
// For now, just write as single blob
|
|
349
|
+
// TODO: Implement actual multipart upload for S3/R2/GCS
|
|
350
|
+
await this.adapter.put(`blob:${hash}`, data);
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Increment reference count for a blob
|
|
354
|
+
*/
|
|
355
|
+
async incrementRefCount(hash) {
|
|
356
|
+
const metadata = await this.getMetadata(hash);
|
|
357
|
+
if (!metadata) {
|
|
358
|
+
throw new Error(`Cannot increment ref count, blob not found: ${hash}`);
|
|
359
|
+
}
|
|
360
|
+
metadata.refCount++;
|
|
361
|
+
await this.adapter.put(`blob-meta:${hash}`, Buffer.from(JSON.stringify(metadata)));
|
|
362
|
+
return metadata.refCount;
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Decrement reference count for a blob
|
|
366
|
+
*/
|
|
367
|
+
async decrementRefCount(hash) {
|
|
368
|
+
const metadata = await this.getMetadata(hash);
|
|
369
|
+
if (!metadata) {
|
|
370
|
+
return 0;
|
|
371
|
+
}
|
|
372
|
+
metadata.refCount = Math.max(0, metadata.refCount - 1);
|
|
373
|
+
await this.adapter.put(`blob-meta:${hash}`, Buffer.from(JSON.stringify(metadata)));
|
|
374
|
+
return metadata.refCount;
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Add blob to LRU cache
|
|
378
|
+
*/
|
|
379
|
+
addToCache(hash, data, metadata) {
|
|
380
|
+
// Check if adding would exceed cache size
|
|
381
|
+
if (data.length > this.cacheMaxSize) {
|
|
382
|
+
return; // Blob too large for cache
|
|
383
|
+
}
|
|
384
|
+
// Evict old entries if needed
|
|
385
|
+
while (this.currentCacheSize + data.length > this.cacheMaxSize &&
|
|
386
|
+
this.cache.size > 0) {
|
|
387
|
+
this.evictLRU();
|
|
388
|
+
}
|
|
389
|
+
// Add to cache
|
|
390
|
+
this.cache.set(hash, {
|
|
391
|
+
data,
|
|
392
|
+
metadata,
|
|
393
|
+
lastAccess: Date.now(),
|
|
394
|
+
size: data.length
|
|
395
|
+
});
|
|
396
|
+
this.currentCacheSize += data.length;
|
|
397
|
+
}
|
|
398
|
+
/**
|
|
399
|
+
* Get blob from cache
|
|
400
|
+
*/
|
|
401
|
+
getFromCache(hash) {
|
|
402
|
+
const entry = this.cache.get(hash);
|
|
403
|
+
if (entry) {
|
|
404
|
+
entry.lastAccess = Date.now(); // Update LRU
|
|
405
|
+
}
|
|
406
|
+
return entry;
|
|
407
|
+
}
|
|
408
|
+
/**
|
|
409
|
+
* Remove blob from cache
|
|
410
|
+
*/
|
|
411
|
+
removeFromCache(hash) {
|
|
412
|
+
const entry = this.cache.get(hash);
|
|
413
|
+
if (entry) {
|
|
414
|
+
this.cache.delete(hash);
|
|
415
|
+
this.currentCacheSize -= entry.size;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Evict least recently used entry from cache
|
|
420
|
+
*/
|
|
421
|
+
evictLRU() {
|
|
422
|
+
let oldestHash = null;
|
|
423
|
+
let oldestTime = Infinity;
|
|
424
|
+
for (const [hash, entry] of this.cache.entries()) {
|
|
425
|
+
if (entry.lastAccess < oldestTime) {
|
|
426
|
+
oldestTime = entry.lastAccess;
|
|
427
|
+
oldestHash = hash;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
if (oldestHash) {
|
|
431
|
+
this.removeFromCache(oldestHash);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
//# sourceMappingURL=BlobStorage.js.map
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CommitLog: Commit history traversal and querying for COW (Copy-on-Write)
|
|
3
|
+
*
|
|
4
|
+
* Provides efficient commit history operations:
|
|
5
|
+
* - Walk commit graph (DAG traversal)
|
|
6
|
+
* - Find commits by time, author, operation
|
|
7
|
+
* - Time-travel queries (asOf)
|
|
8
|
+
* - Commit statistics and analytics
|
|
9
|
+
*
|
|
10
|
+
* Optimizations:
|
|
11
|
+
* - Commit index for fast timestamp lookups
|
|
12
|
+
* - Parent cache for efficient traversal
|
|
13
|
+
* - Lazy loading (only read commits when needed)
|
|
14
|
+
*
|
|
15
|
+
* @module storage/cow/CommitLog
|
|
16
|
+
*/
|
|
17
|
+
import { BlobStorage } from './BlobStorage.js';
|
|
18
|
+
import { CommitObject } from './CommitObject.js';
|
|
19
|
+
import { RefManager } from './RefManager.js';
|
|
20
|
+
/**
|
|
21
|
+
* Commit log statistics
|
|
22
|
+
*/
|
|
23
|
+
export interface CommitLogStats {
|
|
24
|
+
totalCommits: number;
|
|
25
|
+
oldestCommit: number;
|
|
26
|
+
newestCommit: number;
|
|
27
|
+
authors: Set<string>;
|
|
28
|
+
operations: Set<string>;
|
|
29
|
+
avgCommitInterval: number;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* CommitLog: Efficient commit history traversal and querying
|
|
33
|
+
*
|
|
34
|
+
* Pure v5.0.0 implementation - modern, clean, fast
|
|
35
|
+
*/
|
|
36
|
+
export declare class CommitLog {
|
|
37
|
+
private blobStorage;
|
|
38
|
+
private refManager;
|
|
39
|
+
private index;
|
|
40
|
+
private indexValid;
|
|
41
|
+
constructor(blobStorage: BlobStorage, refManager: RefManager);
|
|
42
|
+
/**
|
|
43
|
+
* Walk commit history from a starting point
|
|
44
|
+
*
|
|
45
|
+
* Yields commits in reverse chronological order (newest first)
|
|
46
|
+
*
|
|
47
|
+
* @param startRef - Starting ref/commit (e.g., 'main', commit hash)
|
|
48
|
+
* @param options - Walk options
|
|
49
|
+
*/
|
|
50
|
+
walk(startRef?: string, options?: {
|
|
51
|
+
maxDepth?: number;
|
|
52
|
+
until?: number;
|
|
53
|
+
stopAt?: string;
|
|
54
|
+
filter?: (commit: CommitObject) => boolean;
|
|
55
|
+
}): AsyncIterableIterator<CommitObject>;
|
|
56
|
+
/**
|
|
57
|
+
* Find commit at or before a specific timestamp
|
|
58
|
+
*
|
|
59
|
+
* Uses index for fast O(log n) lookup
|
|
60
|
+
*
|
|
61
|
+
* @param ref - Starting ref (e.g., 'main')
|
|
62
|
+
* @param timestamp - Target timestamp
|
|
63
|
+
* @returns Commit at or before timestamp, or null
|
|
64
|
+
*/
|
|
65
|
+
findAtTime(ref: string, timestamp: number): Promise<CommitObject | null>;
|
|
66
|
+
/**
|
|
67
|
+
* Get commit by hash
|
|
68
|
+
*
|
|
69
|
+
* @param hash - Commit hash
|
|
70
|
+
* @returns Commit object
|
|
71
|
+
*/
|
|
72
|
+
getCommit(hash: string): Promise<CommitObject>;
|
|
73
|
+
/**
|
|
74
|
+
* Get commits in time range
|
|
75
|
+
*
|
|
76
|
+
* @param ref - Starting ref
|
|
77
|
+
* @param startTime - Start of time range
|
|
78
|
+
* @param endTime - End of time range
|
|
79
|
+
* @returns Array of commits in range (newest first)
|
|
80
|
+
*/
|
|
81
|
+
getInTimeRange(ref: string, startTime: number, endTime: number): Promise<CommitObject[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Get commits by author
|
|
84
|
+
*
|
|
85
|
+
* @param ref - Starting ref
|
|
86
|
+
* @param author - Author name
|
|
87
|
+
* @param options - Additional options
|
|
88
|
+
* @returns Array of commits by author
|
|
89
|
+
*/
|
|
90
|
+
getByAuthor(ref: string, author: string, options?: {
|
|
91
|
+
maxCount?: number;
|
|
92
|
+
since?: number;
|
|
93
|
+
}): Promise<CommitObject[]>;
|
|
94
|
+
/**
|
|
95
|
+
* Get commits by operation type
|
|
96
|
+
*
|
|
97
|
+
* @param ref - Starting ref
|
|
98
|
+
* @param operation - Operation type (e.g., 'add', 'update', 'delete')
|
|
99
|
+
* @param options - Additional options
|
|
100
|
+
* @returns Array of commits by operation
|
|
101
|
+
*/
|
|
102
|
+
getByOperation(ref: string, operation: string, options?: {
|
|
103
|
+
maxCount?: number;
|
|
104
|
+
since?: number;
|
|
105
|
+
}): Promise<CommitObject[]>;
|
|
106
|
+
/**
|
|
107
|
+
* Get commit history as array
|
|
108
|
+
*
|
|
109
|
+
* @param ref - Starting ref
|
|
110
|
+
* @param options - Walk options
|
|
111
|
+
* @returns Array of commits (newest first)
|
|
112
|
+
*/
|
|
113
|
+
getHistory(ref: string, options?: {
|
|
114
|
+
maxCount?: number;
|
|
115
|
+
since?: number;
|
|
116
|
+
until?: number;
|
|
117
|
+
}): Promise<CommitObject[]>;
|
|
118
|
+
/**
|
|
119
|
+
* Count commits between two commits
|
|
120
|
+
*
|
|
121
|
+
* @param fromRef - Starting ref/commit
|
|
122
|
+
* @param toRef - Ending ref/commit (optional, defaults to fromRef's parent)
|
|
123
|
+
* @returns Number of commits between
|
|
124
|
+
*/
|
|
125
|
+
countBetween(fromRef: string, toRef?: string): Promise<number>;
|
|
126
|
+
/**
|
|
127
|
+
* Find common ancestor of two commits (merge base)
|
|
128
|
+
*
|
|
129
|
+
* @param ref1 - First ref/commit
|
|
130
|
+
* @param ref2 - Second ref/commit
|
|
131
|
+
* @returns Common ancestor commit or null
|
|
132
|
+
*/
|
|
133
|
+
findCommonAncestor(ref1: string, ref2: string): Promise<CommitObject | null>;
|
|
134
|
+
/**
|
|
135
|
+
* Get commit log statistics
|
|
136
|
+
*
|
|
137
|
+
* @param ref - Starting ref
|
|
138
|
+
* @param options - Options
|
|
139
|
+
* @returns Commit log statistics
|
|
140
|
+
*/
|
|
141
|
+
getStats(ref?: string, options?: {
|
|
142
|
+
maxDepth?: number;
|
|
143
|
+
}): Promise<CommitLogStats>;
|
|
144
|
+
/**
|
|
145
|
+
* Check if commit is ancestor of another commit
|
|
146
|
+
*
|
|
147
|
+
* @param ancestorRef - Potential ancestor ref/commit
|
|
148
|
+
* @param descendantRef - Descendant ref/commit
|
|
149
|
+
* @returns True if ancestor is in descendant's history
|
|
150
|
+
*/
|
|
151
|
+
isAncestor(ancestorRef: string, descendantRef: string): Promise<boolean>;
|
|
152
|
+
/**
|
|
153
|
+
* Get recent commits (last N)
|
|
154
|
+
*
|
|
155
|
+
* @param ref - Starting ref
|
|
156
|
+
* @param count - Number of commits to retrieve
|
|
157
|
+
* @returns Array of recent commits
|
|
158
|
+
*/
|
|
159
|
+
getRecent(ref: string, count?: number): Promise<CommitObject[]>;
|
|
160
|
+
/**
|
|
161
|
+
* Find commits with tag
|
|
162
|
+
*
|
|
163
|
+
* @param ref - Starting ref
|
|
164
|
+
* @param tag - Tag to search for
|
|
165
|
+
* @returns Array of commits with tag
|
|
166
|
+
*/
|
|
167
|
+
findWithTag(ref: string, tag: string): Promise<CommitObject[]>;
|
|
168
|
+
/**
|
|
169
|
+
* Get first (oldest) commit
|
|
170
|
+
*
|
|
171
|
+
* @param ref - Starting ref
|
|
172
|
+
* @returns Oldest commit
|
|
173
|
+
*/
|
|
174
|
+
getFirstCommit(ref: string): Promise<CommitObject | null>;
|
|
175
|
+
/**
|
|
176
|
+
* Get latest commit
|
|
177
|
+
*
|
|
178
|
+
* @param ref - Starting ref
|
|
179
|
+
* @returns Latest commit
|
|
180
|
+
*/
|
|
181
|
+
getLatestCommit(ref: string): Promise<CommitObject | null>;
|
|
182
|
+
/**
|
|
183
|
+
* Clear index (useful for testing, after new commits)
|
|
184
|
+
*/
|
|
185
|
+
clearIndex(): void;
|
|
186
|
+
/**
|
|
187
|
+
* Build commit index for fast lookups
|
|
188
|
+
*
|
|
189
|
+
* @param ref - Starting ref
|
|
190
|
+
*/
|
|
191
|
+
private buildIndex;
|
|
192
|
+
/**
|
|
193
|
+
* Resolve ref or hash to commit hash
|
|
194
|
+
*
|
|
195
|
+
* @param refOrHash - Ref name or commit hash
|
|
196
|
+
* @returns Commit hash
|
|
197
|
+
*/
|
|
198
|
+
private resolveToHash;
|
|
199
|
+
}
|