@soulcraft/brainy 5.3.3 → 5.3.5
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 +7 -0
- package/dist/brainy.js +13 -6
- package/dist/storage/adapters/fileSystemStorage.js +16 -2
- package/dist/storage/baseStorage.js +15 -3
- package/dist/storage/cow/BlobStorage.js +10 -0
- package/dist/storage/cow/CommitObject.js +6 -2
- package/dist/storage/cow/constants.d.ts +48 -0
- package/dist/storage/cow/constants.js +57 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
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
|
+
### [5.3.5](https://github.com/soulcraftlabs/brainy/compare/v5.3.4...v5.3.5) (2025-11-05)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### 🐛 Bug Fixes
|
|
9
|
+
|
|
10
|
+
* resolve fork + checkout workflow with COW file listing and branch persistence ([189b1b0](https://github.com/soulcraftlabs/brainy/commit/189b1b05dec4daad28a9ce7e0840ffaaf675ecfa))
|
|
11
|
+
|
|
5
12
|
### [5.3.0](https://github.com/soulcraftlabs/brainy/compare/v5.2.1...v5.3.0) (2025-11-04)
|
|
6
13
|
|
|
7
14
|
- feat: add entity versioning system with critical bug fixes (v5.3.0) (c488fa8)
|
package/dist/brainy.js
CHANGED
|
@@ -1993,14 +1993,19 @@ export class Brainy {
|
|
|
1993
1993
|
}
|
|
1994
1994
|
// Update storage currentBranch
|
|
1995
1995
|
this.storage.currentBranch = branch;
|
|
1996
|
-
// Reload from new branch
|
|
1997
|
-
//
|
|
1996
|
+
// v5.3.5 fix: Reload indexes from new branch WITHOUT recreating storage
|
|
1997
|
+
// Previous implementation called init() which recreated storage, losing currentBranch
|
|
1998
1998
|
this.index = this.setupIndex();
|
|
1999
1999
|
this.metadataIndex = new MetadataIndexManager(this.storage);
|
|
2000
|
+
await this.metadataIndex.init();
|
|
2000
2001
|
this.graphIndex = new GraphAdjacencyIndex(this.storage);
|
|
2001
|
-
//
|
|
2002
|
-
this.
|
|
2003
|
-
|
|
2002
|
+
// Rebuild indexes from new branch data
|
|
2003
|
+
await this.rebuildIndexesIfNeeded();
|
|
2004
|
+
// Re-initialize VFS for new branch
|
|
2005
|
+
if (this._vfs) {
|
|
2006
|
+
this._vfs = new VirtualFileSystem(this);
|
|
2007
|
+
await this._vfs.init();
|
|
2008
|
+
}
|
|
2004
2009
|
});
|
|
2005
2010
|
}
|
|
2006
2011
|
/**
|
|
@@ -2031,9 +2036,11 @@ export class Brainy {
|
|
|
2031
2036
|
// Get current state statistics
|
|
2032
2037
|
const entityCount = await this.getNounCount();
|
|
2033
2038
|
const relationshipCount = await this.getVerbCount();
|
|
2039
|
+
// v5.3.4: Import NULL_HASH constant
|
|
2040
|
+
const { NULL_HASH } = await import('./storage/cow/constants.js');
|
|
2034
2041
|
// Build commit object using builder pattern
|
|
2035
2042
|
const builder = CommitBuilder.create(blobStorage)
|
|
2036
|
-
.tree(
|
|
2043
|
+
.tree(NULL_HASH) // Empty tree hash (sentinel value)
|
|
2037
2044
|
.message(options?.message || 'Snapshot commit')
|
|
2038
2045
|
.author(options?.author || 'unknown')
|
|
2039
2046
|
.timestamp(Date.now())
|
|
@@ -736,7 +736,10 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
736
736
|
const entries = await fs.promises.readdir(fullPath, { withFileTypes: true });
|
|
737
737
|
for (const entry of entries) {
|
|
738
738
|
if (entry.isFile()) {
|
|
739
|
-
// Handle
|
|
739
|
+
// v5.3.5: Handle multiple compression formats for broad compatibility
|
|
740
|
+
// - .json.gz: Standard entity/metadata files (JSON compressed)
|
|
741
|
+
// - .gz: COW files (refs, blobs, commits - raw compressed)
|
|
742
|
+
// - .json: Uncompressed JSON files
|
|
740
743
|
if (entry.name.endsWith('.json.gz')) {
|
|
741
744
|
// Strip .gz extension and add the .json path
|
|
742
745
|
const normalizedName = entry.name.slice(0, -3); // Remove .gz
|
|
@@ -746,6 +749,16 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
746
749
|
seen.add(normalizedPath);
|
|
747
750
|
}
|
|
748
751
|
}
|
|
752
|
+
else if (entry.name.endsWith('.gz')) {
|
|
753
|
+
// v5.3.5 fix: COW files stored as .gz (not .json.gz)
|
|
754
|
+
// Strip .gz extension and return path
|
|
755
|
+
const normalizedName = entry.name.slice(0, -3); // Remove .gz
|
|
756
|
+
const normalizedPath = path.join(prefix, normalizedName);
|
|
757
|
+
if (!seen.has(normalizedPath)) {
|
|
758
|
+
paths.push(normalizedPath);
|
|
759
|
+
seen.add(normalizedPath);
|
|
760
|
+
}
|
|
761
|
+
}
|
|
749
762
|
else if (entry.name.endsWith('.json')) {
|
|
750
763
|
const filePath = path.join(prefix, entry.name);
|
|
751
764
|
if (!seen.has(filePath)) {
|
|
@@ -755,7 +768,8 @@ export class FileSystemStorage extends BaseStorage {
|
|
|
755
768
|
}
|
|
756
769
|
}
|
|
757
770
|
else if (entry.isDirectory()) {
|
|
758
|
-
const
|
|
771
|
+
const subpath = path.join(prefix, entry.name);
|
|
772
|
+
const subdirPaths = await this.listObjectsUnderPath(subpath);
|
|
759
773
|
paths.push(...subdirPaths);
|
|
760
774
|
}
|
|
761
775
|
}
|
|
@@ -201,11 +201,21 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
201
201
|
},
|
|
202
202
|
list: async (prefix) => {
|
|
203
203
|
try {
|
|
204
|
-
|
|
204
|
+
// v5.3.5 fix: Handle file prefixes, not just directory paths
|
|
205
|
+
// Refs are stored as files like: _cow/ref:refs/heads/main
|
|
206
|
+
// So list('ref:') should find all files starting with '_cow/ref:'
|
|
207
|
+
// List the _cow directory and filter by prefix
|
|
208
|
+
const allPaths = await this.listObjectsUnderPath('_cow/');
|
|
209
|
+
const filteredPaths = allPaths.filter(p => {
|
|
210
|
+
// Remove _cow/ prefix to get the key
|
|
211
|
+
const key = p.replace(/^_cow\//, '');
|
|
212
|
+
return key.startsWith(prefix);
|
|
213
|
+
});
|
|
205
214
|
// Remove _cow/ prefix and return relative keys
|
|
206
|
-
return
|
|
215
|
+
return filteredPaths.map(p => p.replace(/^_cow\//, ''));
|
|
207
216
|
}
|
|
208
217
|
catch (error) {
|
|
218
|
+
// If _cow directory doesn't exist yet, return empty array
|
|
209
219
|
return [];
|
|
210
220
|
}
|
|
211
221
|
}
|
|
@@ -222,7 +232,9 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
222
232
|
const mainRef = await this.refManager.getRef('main');
|
|
223
233
|
if (!mainRef) {
|
|
224
234
|
// Create initial commit with empty tree
|
|
225
|
-
|
|
235
|
+
// v5.3.4: Use NULL_HASH constant instead of hardcoded string
|
|
236
|
+
const { NULL_HASH } = await import('./cow/constants.js');
|
|
237
|
+
const emptyTreeHash = NULL_HASH;
|
|
226
238
|
// Import CommitBuilder
|
|
227
239
|
const { CommitBuilder } = await import('./cow/CommitObject.js');
|
|
228
240
|
// Create initial commit object
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
* @module storage/cow/BlobStorage
|
|
15
15
|
*/
|
|
16
16
|
import { createHash } from 'crypto';
|
|
17
|
+
import { NULL_HASH, isNullHash } from './constants.js';
|
|
17
18
|
/**
|
|
18
19
|
* State-of-the-art content-addressable blob storage
|
|
19
20
|
*
|
|
@@ -162,6 +163,15 @@ export class BlobStorage {
|
|
|
162
163
|
* @returns Blob data
|
|
163
164
|
*/
|
|
164
165
|
async read(hash, options = {}) {
|
|
166
|
+
// v5.3.4 fix: Guard against NULL hash (sentinel value)
|
|
167
|
+
// NULL_HASH ('0000...0000') is used as a sentinel for "no parent" or "empty tree"
|
|
168
|
+
// It should NEVER be read as actual blob data
|
|
169
|
+
if (isNullHash(hash)) {
|
|
170
|
+
throw new Error(`Cannot read NULL hash (${NULL_HASH}): ` +
|
|
171
|
+
`This is a sentinel value indicating "no parent commit" or "empty tree". ` +
|
|
172
|
+
`If you're seeing this error from CommitObject.walk(), there's a bug in commit traversal logic. ` +
|
|
173
|
+
`If you're seeing this from TreeObject operations, there's a bug in tree handling.`);
|
|
174
|
+
}
|
|
165
175
|
// Check cache first
|
|
166
176
|
if (!options.skipCache) {
|
|
167
177
|
const cached = this.getFromCache(hash);
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
* @module storage/cow/CommitObject
|
|
16
16
|
*/
|
|
17
17
|
import { BlobStorage } from './BlobStorage.js';
|
|
18
|
+
import { isNullHash } from './constants.js';
|
|
18
19
|
/**
|
|
19
20
|
* CommitBuilder: Fluent API for building commit objects
|
|
20
21
|
*
|
|
@@ -269,7 +270,10 @@ export class CommitObject {
|
|
|
269
270
|
static async *walk(blobStorage, startHash, options) {
|
|
270
271
|
let currentHash = startHash;
|
|
271
272
|
let depth = 0;
|
|
272
|
-
|
|
273
|
+
// v5.3.4 fix: Guard against NULL hash (sentinel for "no parent")
|
|
274
|
+
// The initial commit has parent = null or NULL_HASH ('0000...0000')
|
|
275
|
+
// We must stop walking when we reach it, not try to read it
|
|
276
|
+
while (currentHash && !isNullHash(currentHash)) {
|
|
273
277
|
// Check max depth
|
|
274
278
|
if (options?.maxDepth && depth >= options.maxDepth) {
|
|
275
279
|
break;
|
|
@@ -291,7 +295,7 @@ export class CommitObject {
|
|
|
291
295
|
if (options?.stopAt && currentHash === options.stopAt) {
|
|
292
296
|
break;
|
|
293
297
|
}
|
|
294
|
-
// Move to parent
|
|
298
|
+
// Move to parent (can be null or NULL_HASH for initial commit)
|
|
295
299
|
currentHash = commit.parent;
|
|
296
300
|
depth++;
|
|
297
301
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COW Storage Constants
|
|
3
|
+
*
|
|
4
|
+
* Sentinel values and utilities for Copy-On-Write storage system.
|
|
5
|
+
*
|
|
6
|
+
* @module storage/cow/constants
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* NULL_HASH - Sentinel value for "no parent commit" or "empty tree"
|
|
10
|
+
*
|
|
11
|
+
* In Git-like COW systems, we need a way to represent:
|
|
12
|
+
* - Initial commit (has no parent)
|
|
13
|
+
* - Empty tree (contains no files)
|
|
14
|
+
*
|
|
15
|
+
* We use a 64-character zero hash as a sentinel value.
|
|
16
|
+
* This should NEVER be used as an actual content hash.
|
|
17
|
+
*
|
|
18
|
+
* @constant
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const builder = CommitBuilder.create(storage)
|
|
22
|
+
* .tree(NULL_HASH) // Empty tree
|
|
23
|
+
* .parent(null) // No parent (use null, not NULL_HASH)
|
|
24
|
+
* .build()
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const NULL_HASH = "0000000000000000000000000000000000000000000000000000000000000000";
|
|
28
|
+
/**
|
|
29
|
+
* Check if a hash is the NULL sentinel value
|
|
30
|
+
*
|
|
31
|
+
* @param hash - Hash to check (can be string or null)
|
|
32
|
+
* @returns true if hash is null or NULL_HASH
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* if (isNullHash(commit.parent)) {
|
|
37
|
+
* console.log('This is the initial commit')
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export declare function isNullHash(hash: string | null | undefined): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Check if a hash is valid (non-null, non-empty, proper format)
|
|
44
|
+
*
|
|
45
|
+
* @param hash - Hash to check
|
|
46
|
+
* @returns true if hash is a valid SHA-256 hash
|
|
47
|
+
*/
|
|
48
|
+
export declare function isValidHash(hash: string | null | undefined): boolean;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* COW Storage Constants
|
|
3
|
+
*
|
|
4
|
+
* Sentinel values and utilities for Copy-On-Write storage system.
|
|
5
|
+
*
|
|
6
|
+
* @module storage/cow/constants
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* NULL_HASH - Sentinel value for "no parent commit" or "empty tree"
|
|
10
|
+
*
|
|
11
|
+
* In Git-like COW systems, we need a way to represent:
|
|
12
|
+
* - Initial commit (has no parent)
|
|
13
|
+
* - Empty tree (contains no files)
|
|
14
|
+
*
|
|
15
|
+
* We use a 64-character zero hash as a sentinel value.
|
|
16
|
+
* This should NEVER be used as an actual content hash.
|
|
17
|
+
*
|
|
18
|
+
* @constant
|
|
19
|
+
* @example
|
|
20
|
+
* ```typescript
|
|
21
|
+
* const builder = CommitBuilder.create(storage)
|
|
22
|
+
* .tree(NULL_HASH) // Empty tree
|
|
23
|
+
* .parent(null) // No parent (use null, not NULL_HASH)
|
|
24
|
+
* .build()
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const NULL_HASH = '0000000000000000000000000000000000000000000000000000000000000000';
|
|
28
|
+
/**
|
|
29
|
+
* Check if a hash is the NULL sentinel value
|
|
30
|
+
*
|
|
31
|
+
* @param hash - Hash to check (can be string or null)
|
|
32
|
+
* @returns true if hash is null or NULL_HASH
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* if (isNullHash(commit.parent)) {
|
|
37
|
+
* console.log('This is the initial commit')
|
|
38
|
+
* }
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function isNullHash(hash) {
|
|
42
|
+
return hash === null || hash === undefined || hash === NULL_HASH;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Check if a hash is valid (non-null, non-empty, proper format)
|
|
46
|
+
*
|
|
47
|
+
* @param hash - Hash to check
|
|
48
|
+
* @returns true if hash is a valid SHA-256 hash
|
|
49
|
+
*/
|
|
50
|
+
export function isValidHash(hash) {
|
|
51
|
+
if (isNullHash(hash)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
// SHA-256 hash must be exactly 64 hexadecimal characters
|
|
55
|
+
return typeof hash === 'string' && /^[a-f0-9]{64}$/.test(hash);
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=constants.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soulcraft/brainy",
|
|
3
|
-
"version": "5.3.
|
|
3
|
+
"version": "5.3.5",
|
|
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",
|