@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +271 -0
  2. package/README.md +38 -1
  3. package/dist/augmentations/brainyAugmentation.d.ts +76 -0
  4. package/dist/augmentations/brainyAugmentation.js +126 -0
  5. package/dist/augmentations/cacheAugmentation.js +9 -4
  6. package/dist/brainy.d.ts +248 -15
  7. package/dist/brainy.js +707 -17
  8. package/dist/cli/commands/cow.d.ts +60 -0
  9. package/dist/cli/commands/cow.js +444 -0
  10. package/dist/cli/commands/import.js +1 -1
  11. package/dist/cli/commands/vfs.js +24 -40
  12. package/dist/cli/index.js +50 -0
  13. package/dist/hnsw/hnswIndex.d.ts +41 -0
  14. package/dist/hnsw/hnswIndex.js +96 -1
  15. package/dist/hnsw/typeAwareHNSWIndex.d.ts +9 -0
  16. package/dist/hnsw/typeAwareHNSWIndex.js +22 -0
  17. package/dist/import/ImportHistory.js +3 -3
  18. package/dist/importers/VFSStructureGenerator.d.ts +1 -1
  19. package/dist/importers/VFSStructureGenerator.js +3 -3
  20. package/dist/index.d.ts +6 -0
  21. package/dist/index.js +10 -0
  22. package/dist/storage/adapters/memoryStorage.d.ts +6 -0
  23. package/dist/storage/adapters/memoryStorage.js +39 -14
  24. package/dist/storage/adapters/typeAwareStorageAdapter.d.ts +31 -1
  25. package/dist/storage/adapters/typeAwareStorageAdapter.js +272 -43
  26. package/dist/storage/baseStorage.d.ts +64 -0
  27. package/dist/storage/baseStorage.js +252 -12
  28. package/dist/storage/cow/BlobStorage.d.ts +232 -0
  29. package/dist/storage/cow/BlobStorage.js +437 -0
  30. package/dist/storage/cow/CommitLog.d.ts +199 -0
  31. package/dist/storage/cow/CommitLog.js +363 -0
  32. package/dist/storage/cow/CommitObject.d.ts +276 -0
  33. package/dist/storage/cow/CommitObject.js +431 -0
  34. package/dist/storage/cow/RefManager.d.ts +213 -0
  35. package/dist/storage/cow/RefManager.js +409 -0
  36. package/dist/storage/cow/TreeObject.d.ts +177 -0
  37. package/dist/storage/cow/TreeObject.js +293 -0
  38. package/dist/storage/storageFactory.d.ts +6 -0
  39. package/dist/storage/storageFactory.js +92 -74
  40. package/dist/types/brainy.types.d.ts +1 -0
  41. package/dist/vfs/FSCompat.d.ts +1 -1
  42. package/dist/vfs/FSCompat.js +1 -1
  43. package/dist/vfs/VirtualFileSystem.js +5 -6
  44. package/package.json +1 -1
@@ -0,0 +1,409 @@
1
+ /**
2
+ * RefManager: Branch and reference management for COW (Copy-on-Write)
3
+ *
4
+ * Similar to Git refs, manages symbolic names (branches, tags) that point to commits.
5
+ *
6
+ * Structure:
7
+ * - refs/heads/main → commit hash (main branch)
8
+ * - refs/heads/experiment → commit hash (experiment branch)
9
+ * - refs/tags/v1.0.0 → commit hash (version tag)
10
+ * - HEAD → ref name (current branch)
11
+ *
12
+ * Features:
13
+ * - Branch management (create, delete, list)
14
+ * - Tag management
15
+ * - HEAD pointer (current branch)
16
+ * - Fast-forward and force updates
17
+ * - Atomic operations
18
+ *
19
+ * @module storage/cow/RefManager
20
+ */
21
+ /**
22
+ * RefManager: Manages branches, tags, and HEAD pointer
23
+ *
24
+ * Pure implementation for v5.0.0 - no backward compatibility
25
+ */
26
+ export class RefManager {
27
+ constructor(adapter) {
28
+ this.adapter = adapter;
29
+ this.cache = new Map();
30
+ this.cacheValid = false;
31
+ }
32
+ /**
33
+ * Get reference by name
34
+ *
35
+ * @param name - Reference name (e.g., 'main', 'refs/heads/main')
36
+ * @returns Reference object or undefined
37
+ */
38
+ async getRef(name) {
39
+ const fullName = this.normalizeRefName(name);
40
+ // Check cache
41
+ if (this.cacheValid && this.cache.has(fullName)) {
42
+ return this.cache.get(fullName);
43
+ }
44
+ // Read from storage
45
+ const data = await this.adapter.get(`ref:${fullName}`);
46
+ if (!data) {
47
+ return undefined;
48
+ }
49
+ const ref = JSON.parse(data.toString());
50
+ // Update cache
51
+ this.cache.set(fullName, ref);
52
+ return ref;
53
+ }
54
+ /**
55
+ * Set reference to point to commit
56
+ *
57
+ * @param name - Reference name
58
+ * @param commitHash - Commit hash to point to
59
+ * @param options - Update options
60
+ */
61
+ async setRef(name, commitHash, options = {}) {
62
+ const fullName = this.normalizeRefName(name);
63
+ // Validate commit hash format
64
+ if (!/^[a-f0-9]{64}$/.test(commitHash)) {
65
+ throw new Error(`Invalid commit hash: ${commitHash}`);
66
+ }
67
+ // Check if ref exists
68
+ const existing = await this.getRef(fullName);
69
+ // Handle createOnly
70
+ if (options.createOnly && existing) {
71
+ throw new Error(`Ref already exists: ${fullName}`);
72
+ }
73
+ // Handle updateOnly
74
+ if (options.updateOnly && !existing) {
75
+ throw new Error(`Ref does not exist: ${fullName}`);
76
+ }
77
+ // Handle CAS (Compare-And-Swap)
78
+ if (options.expectedOldValue !== undefined) {
79
+ if (!existing || existing.commitHash !== options.expectedOldValue) {
80
+ throw new Error(`Ref update failed: expected ${options.expectedOldValue}, ` +
81
+ `got ${existing?.commitHash ?? 'none'}`);
82
+ }
83
+ }
84
+ // Check for fast-forward (if not force)
85
+ if (!options.force && existing) {
86
+ // TODO: Verify this is a fast-forward update
87
+ // For now, allow all updates
88
+ }
89
+ // Create/update ref
90
+ const ref = {
91
+ name: fullName,
92
+ commitHash,
93
+ type: this.getRefType(fullName),
94
+ createdAt: existing?.createdAt ?? Date.now(),
95
+ updatedAt: Date.now(),
96
+ metadata: existing?.metadata
97
+ };
98
+ // Write to storage
99
+ await this.adapter.put(`ref:${fullName}`, Buffer.from(JSON.stringify(ref)));
100
+ // Update cache
101
+ this.cache.set(fullName, ref);
102
+ this.cacheValid = false; // Invalidate for listRefs
103
+ }
104
+ /**
105
+ * Delete reference
106
+ *
107
+ * @param name - Reference name
108
+ */
109
+ async deleteRef(name) {
110
+ const fullName = this.normalizeRefName(name);
111
+ // Don't allow deleting HEAD
112
+ if (fullName === 'HEAD') {
113
+ throw new Error('Cannot delete HEAD');
114
+ }
115
+ // Don't allow deleting main if it's the only branch
116
+ if (fullName === 'refs/heads/main') {
117
+ const branches = await this.listRefs('branch');
118
+ if (branches.length === 1) {
119
+ throw new Error('Cannot delete last branch');
120
+ }
121
+ }
122
+ // Delete from storage
123
+ await this.adapter.delete(`ref:${fullName}`);
124
+ // Update cache
125
+ this.cache.delete(fullName);
126
+ this.cacheValid = false;
127
+ }
128
+ /**
129
+ * List all references
130
+ *
131
+ * @param type - Filter by type (optional)
132
+ * @returns Array of references
133
+ */
134
+ async listRefs(type) {
135
+ // Get all ref keys
136
+ const keys = await this.adapter.list('ref:');
137
+ const refs = [];
138
+ for (const key of keys) {
139
+ const refName = key.replace(/^ref:/, '');
140
+ // Skip HEAD in listings (it's special)
141
+ if (refName === 'HEAD') {
142
+ continue;
143
+ }
144
+ const ref = await this.getRef(refName);
145
+ if (ref) {
146
+ // Filter by type if requested
147
+ if (!type || ref.type === type) {
148
+ refs.push(ref);
149
+ }
150
+ }
151
+ }
152
+ // Mark cache as valid
153
+ this.cacheValid = true;
154
+ return refs.sort((a, b) => a.name.localeCompare(b.name));
155
+ }
156
+ /**
157
+ * Copy reference (create branch from existing ref)
158
+ *
159
+ * @param sourceName - Source reference name
160
+ * @param targetName - Target reference name
161
+ * @param options - Update options
162
+ */
163
+ async copyRef(sourceName, targetName, options = {}) {
164
+ const sourceRef = await this.getRef(sourceName);
165
+ if (!sourceRef) {
166
+ throw new Error(`Source ref not found: ${sourceName}`);
167
+ }
168
+ // Set target ref to same commit as source
169
+ await this.setRef(targetName, sourceRef.commitHash, options);
170
+ }
171
+ /**
172
+ * Get current HEAD (current branch)
173
+ *
174
+ * @returns HEAD reference or undefined
175
+ */
176
+ async getHead() {
177
+ const data = await this.adapter.get('ref:HEAD');
178
+ if (!data) {
179
+ return undefined;
180
+ }
181
+ const head = JSON.parse(data.toString());
182
+ // Resolve symbolic ref
183
+ return this.getRef(head.ref);
184
+ }
185
+ /**
186
+ * Set HEAD to point to branch
187
+ *
188
+ * @param branchName - Branch name (e.g., 'main', 'refs/heads/experiment')
189
+ */
190
+ async setHead(branchName) {
191
+ const fullName = this.normalizeRefName(branchName);
192
+ // Verify branch exists
193
+ const branch = await this.getRef(fullName);
194
+ if (!branch) {
195
+ throw new Error(`Branch not found: ${fullName}`);
196
+ }
197
+ if (branch.type !== 'branch') {
198
+ throw new Error(`Cannot set HEAD to non-branch ref: ${fullName}`);
199
+ }
200
+ // Set HEAD (symbolic ref)
201
+ const head = { ref: fullName };
202
+ await this.adapter.put('ref:HEAD', Buffer.from(JSON.stringify(head)));
203
+ }
204
+ /**
205
+ * Get current commit hash (resolves HEAD)
206
+ *
207
+ * @returns Current commit hash or undefined
208
+ */
209
+ async getCurrentCommit() {
210
+ const head = await this.getHead();
211
+ return head?.commitHash;
212
+ }
213
+ /**
214
+ * Create branch
215
+ *
216
+ * @param name - Branch name (e.g., 'experiment')
217
+ * @param commitHash - Commit hash to point to
218
+ * @param options - Create options
219
+ */
220
+ async createBranch(name, commitHash, options) {
221
+ const fullName = this.normalizeRefName(name, 'branch');
222
+ await this.setRef(fullName, commitHash, { createOnly: true });
223
+ // Update metadata if provided
224
+ if (options?.description || options?.author) {
225
+ const ref = await this.getRef(fullName);
226
+ if (ref) {
227
+ ref.metadata = {
228
+ ...ref.metadata,
229
+ description: options.description,
230
+ author: options.author
231
+ };
232
+ await this.adapter.put(`ref:${fullName}`, Buffer.from(JSON.stringify(ref)));
233
+ }
234
+ }
235
+ }
236
+ /**
237
+ * Delete branch
238
+ *
239
+ * @param name - Branch name
240
+ */
241
+ async deleteBranch(name) {
242
+ const fullName = this.normalizeRefName(name, 'branch');
243
+ await this.deleteRef(fullName);
244
+ }
245
+ /**
246
+ * List all branches
247
+ *
248
+ * @returns Array of branch references
249
+ */
250
+ async listBranches() {
251
+ return this.listRefs('branch');
252
+ }
253
+ /**
254
+ * Create tag
255
+ *
256
+ * @param name - Tag name (e.g., 'v1.0.0')
257
+ * @param commitHash - Commit hash to point to
258
+ * @param options - Create options
259
+ */
260
+ async createTag(name, commitHash, options) {
261
+ const fullName = this.normalizeRefName(name, 'tag');
262
+ await this.setRef(fullName, commitHash, { createOnly: true });
263
+ // Update metadata if provided
264
+ if (options?.description || options?.author) {
265
+ const ref = await this.getRef(fullName);
266
+ if (ref) {
267
+ ref.metadata = {
268
+ ...ref.metadata,
269
+ description: options.description,
270
+ author: options.author
271
+ };
272
+ await this.adapter.put(`ref:${fullName}`, Buffer.from(JSON.stringify(ref)));
273
+ }
274
+ }
275
+ }
276
+ /**
277
+ * Delete tag
278
+ *
279
+ * @param name - Tag name
280
+ */
281
+ async deleteTag(name) {
282
+ const fullName = this.normalizeRefName(name, 'tag');
283
+ await this.deleteRef(fullName);
284
+ }
285
+ /**
286
+ * List all tags
287
+ *
288
+ * @returns Array of tag references
289
+ */
290
+ async listTags() {
291
+ return this.listRefs('tag');
292
+ }
293
+ /**
294
+ * Check if reference exists
295
+ *
296
+ * @param name - Reference name
297
+ * @returns True if reference exists
298
+ */
299
+ async hasRef(name) {
300
+ const ref = await this.getRef(name);
301
+ return ref !== undefined;
302
+ }
303
+ /**
304
+ * Update reference to new commit (with validation)
305
+ *
306
+ * @param name - Reference name
307
+ * @param newCommitHash - New commit hash
308
+ * @param oldCommitHash - Expected old commit hash (for safety)
309
+ */
310
+ async updateRef(name, newCommitHash, oldCommitHash) {
311
+ const options = {};
312
+ if (oldCommitHash) {
313
+ options.expectedOldValue = oldCommitHash;
314
+ }
315
+ await this.setRef(name, newCommitHash, options);
316
+ }
317
+ /**
318
+ * Get commit hash for reference
319
+ *
320
+ * @param name - Reference name
321
+ * @returns Commit hash or undefined
322
+ */
323
+ async resolveRef(name) {
324
+ const ref = await this.getRef(name);
325
+ return ref?.commitHash;
326
+ }
327
+ /**
328
+ * Find references pointing to commit
329
+ *
330
+ * @param commitHash - Commit hash
331
+ * @returns Array of references pointing to this commit
332
+ */
333
+ async findRefsPointingTo(commitHash) {
334
+ const allRefs = await this.listRefs();
335
+ return allRefs.filter(ref => ref.commitHash === commitHash);
336
+ }
337
+ /**
338
+ * Clear cache (useful for testing)
339
+ */
340
+ clearCache() {
341
+ this.cache.clear();
342
+ this.cacheValid = false;
343
+ }
344
+ // ========== PRIVATE METHODS ==========
345
+ /**
346
+ * Normalize reference name to full format
347
+ *
348
+ * Examples:
349
+ * - 'main' → 'refs/heads/main'
350
+ * - 'v1.0.0' (with type='tag') → 'refs/tags/v1.0.0'
351
+ * - 'refs/heads/experiment' → 'refs/heads/experiment'
352
+ *
353
+ * @param name - Reference name
354
+ * @param type - Reference type hint
355
+ * @returns Full reference name
356
+ */
357
+ normalizeRefName(name, type) {
358
+ // Already full format
359
+ if (name.startsWith('refs/')) {
360
+ return name;
361
+ }
362
+ // HEAD is special
363
+ if (name === 'HEAD') {
364
+ return 'HEAD';
365
+ }
366
+ // Infer type from name if not provided
367
+ if (!type) {
368
+ // Tags usually start with 'v' or contain dots
369
+ if (name.startsWith('v') && /\d/.test(name)) {
370
+ type = 'tag';
371
+ }
372
+ else {
373
+ type = 'branch'; // Default to branch
374
+ }
375
+ }
376
+ // Add prefix
377
+ switch (type) {
378
+ case 'branch':
379
+ return `refs/heads/${name}`;
380
+ case 'tag':
381
+ return `refs/tags/${name}`;
382
+ case 'remote':
383
+ return `refs/remotes/${name}`;
384
+ default:
385
+ return name;
386
+ }
387
+ }
388
+ /**
389
+ * Get reference type from full name
390
+ *
391
+ * @param fullName - Full reference name
392
+ * @returns Reference type
393
+ */
394
+ getRefType(fullName) {
395
+ if (fullName.startsWith('refs/heads/')) {
396
+ return 'branch';
397
+ }
398
+ else if (fullName.startsWith('refs/tags/')) {
399
+ return 'tag';
400
+ }
401
+ else if (fullName.startsWith('refs/remotes/')) {
402
+ return 'remote';
403
+ }
404
+ else {
405
+ return 'branch'; // Default
406
+ }
407
+ }
408
+ }
409
+ //# sourceMappingURL=RefManager.js.map
@@ -0,0 +1,177 @@
1
+ /**
2
+ * TreeObject: Directory structure for COW (Copy-on-Write)
3
+ *
4
+ * Similar to Git trees, represents the structure of a Brainy instance at a point in time.
5
+ * Trees contain entries mapping names to blob hashes.
6
+ *
7
+ * Structure:
8
+ * - entities/ → tree hash (entity blobs)
9
+ * - indexes/nouns → blob hash (HNSW noun index)
10
+ * - indexes/metadata → blob hash (metadata index)
11
+ * - indexes/graph → blob hash (graph adjacency index)
12
+ * - indexes/deleted → blob hash (deleted items index)
13
+ *
14
+ * @module storage/cow/TreeObject
15
+ */
16
+ import { BlobStorage } from './BlobStorage.js';
17
+ /**
18
+ * Tree entry: name → blob hash mapping
19
+ */
20
+ export interface TreeEntry {
21
+ name: string;
22
+ hash: string;
23
+ type: 'blob' | 'tree';
24
+ size: number;
25
+ }
26
+ /**
27
+ * Tree object structure
28
+ */
29
+ export interface TreeObject {
30
+ entries: TreeEntry[];
31
+ createdAt: number;
32
+ }
33
+ /**
34
+ * TreeBuilder: Fluent API for building tree objects
35
+ *
36
+ * Example:
37
+ * ```typescript
38
+ * const tree = await TreeBuilder.create(blobStorage)
39
+ * .addBlob('entities/abc123', entityHash, size)
40
+ * .addBlob('indexes/nouns', nounsHash, size)
41
+ * .build()
42
+ * ```
43
+ */
44
+ export declare class TreeBuilder {
45
+ private entries;
46
+ private blobStorage;
47
+ constructor(blobStorage: BlobStorage);
48
+ static create(blobStorage: BlobStorage): TreeBuilder;
49
+ /**
50
+ * Add a blob entry to the tree
51
+ *
52
+ * @param name - Entry name (e.g., 'entities/abc123')
53
+ * @param hash - Blob hash
54
+ * @param size - Original blob size
55
+ */
56
+ addBlob(name: string, hash: string, size: number): this;
57
+ /**
58
+ * Add a subtree entry to the tree
59
+ *
60
+ * @param name - Subtree name (e.g., 'entities/')
61
+ * @param treeHash - Tree hash
62
+ * @param size - Total size of subtree
63
+ */
64
+ addTree(name: string, treeHash: string, size: number): this;
65
+ /**
66
+ * Build and persist the tree object
67
+ *
68
+ * @returns Tree hash
69
+ */
70
+ build(): Promise<string>;
71
+ }
72
+ /**
73
+ * TreeObject: Represents directory structure in COW storage
74
+ */
75
+ export declare class TreeObject {
76
+ /**
77
+ * Serialize tree object to Buffer
78
+ *
79
+ * Format: JSON (simple, debuggable)
80
+ * Future: Could use protobuf for efficiency
81
+ *
82
+ * @param tree - Tree object
83
+ * @returns Serialized tree
84
+ */
85
+ static serialize(tree: TreeObject): Buffer;
86
+ /**
87
+ * Deserialize tree object from Buffer
88
+ *
89
+ * @param data - Serialized tree
90
+ * @returns Tree object
91
+ */
92
+ static deserialize(data: Buffer): TreeObject;
93
+ /**
94
+ * Compute hash of tree object
95
+ *
96
+ * @param tree - Tree object
97
+ * @returns SHA-256 hash
98
+ */
99
+ static hash(tree: TreeObject): string;
100
+ /**
101
+ * Write tree object to blob storage
102
+ *
103
+ * @param blobStorage - Blob storage instance
104
+ * @param tree - Tree object
105
+ * @returns Tree hash
106
+ */
107
+ static write(blobStorage: BlobStorage, tree: TreeObject): Promise<string>;
108
+ /**
109
+ * Read tree object from blob storage
110
+ *
111
+ * @param blobStorage - Blob storage instance
112
+ * @param hash - Tree hash
113
+ * @returns Tree object
114
+ */
115
+ static read(blobStorage: BlobStorage, hash: string): Promise<TreeObject>;
116
+ /**
117
+ * Get specific entry from tree
118
+ *
119
+ * @param tree - Tree object
120
+ * @param name - Entry name
121
+ * @returns Tree entry or undefined
122
+ */
123
+ static getEntry(tree: TreeObject, name: string): TreeEntry | undefined;
124
+ /**
125
+ * Get all blob entries from tree (non-recursive)
126
+ *
127
+ * @param tree - Tree object
128
+ * @returns Array of blob entries
129
+ */
130
+ static getBlobs(tree: TreeObject): TreeEntry[];
131
+ /**
132
+ * Get all subtree entries from tree (non-recursive)
133
+ *
134
+ * @param tree - Tree object
135
+ * @returns Array of tree entries
136
+ */
137
+ static getSubtrees(tree: TreeObject): TreeEntry[];
138
+ /**
139
+ * Walk tree recursively, yielding all blob entries
140
+ *
141
+ * @param blobStorage - Blob storage instance
142
+ * @param tree - Tree object
143
+ */
144
+ static walk(blobStorage: BlobStorage, tree: TreeObject): AsyncIterableIterator<TreeEntry>;
145
+ /**
146
+ * Compute total size of tree (recursive)
147
+ *
148
+ * @param blobStorage - Blob storage instance
149
+ * @param tree - Tree object
150
+ * @returns Total size in bytes
151
+ */
152
+ static getTotalSize(blobStorage: BlobStorage, tree: TreeObject): Promise<number>;
153
+ /**
154
+ * Create a new tree by updating a single entry
155
+ * (Copy-on-write: creates new tree, doesn't modify original)
156
+ *
157
+ * @param blobStorage - Blob storage instance
158
+ * @param tree - Original tree
159
+ * @param name - Entry name to update
160
+ * @param hash - New blob/tree hash
161
+ * @param size - New size
162
+ * @returns New tree hash
163
+ */
164
+ static updateEntry(blobStorage: BlobStorage, tree: TreeObject, name: string, hash: string, size: number): Promise<string>;
165
+ /**
166
+ * Diff two trees, return changed/added/deleted entries
167
+ *
168
+ * @param tree1 - First tree (base)
169
+ * @param tree2 - Second tree (comparison)
170
+ * @returns Diff result
171
+ */
172
+ static diff(tree1: TreeObject, tree2: TreeObject): {
173
+ added: TreeEntry[];
174
+ modified: TreeEntry[];
175
+ deleted: TreeEntry[];
176
+ };
177
+ }