@soulcraft/brainy 3.20.3 → 3.20.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 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
+ ### [3.20.5](https://github.com/soulcraftlabs/brainy/compare/v3.20.4...v3.20.5) (2025-10-01)
6
+
7
+ - feat: add --skip-tests flag to release script (0614171)
8
+ - fix: resolve critical bugs in delete operations and fix flaky tests (8476047)
9
+ - feat: implement simpler, more reliable release workflow (386fd2c)
10
+
11
+
5
12
  ### [3.20.2](https://github.com/soulcraftlabs/brainy/compare/v3.20.1...v3.20.2) (2025-09-30)
6
13
 
7
14
  ### Bug Fixes
@@ -276,4 +283,4 @@ See [MIGRATION.md](MIGRATION.md) for detailed migration instructions including:
276
283
  - API changes and new patterns
277
284
  - Storage format updates
278
285
  - Configuration changes
279
- - New features and capabilities
286
+ - New features and capabilities
package/dist/brainy.js CHANGED
@@ -424,6 +424,15 @@ export class Brainy {
424
424
  await this.graphIndex.removeVerb(verb.id);
425
425
  // Then delete from storage
426
426
  await this.storage.deleteVerb(verb.id);
427
+ // Delete verb metadata if exists
428
+ try {
429
+ if (typeof this.storage.deleteVerbMetadata === 'function') {
430
+ await this.storage.deleteVerbMetadata(verb.id);
431
+ }
432
+ }
433
+ catch {
434
+ // Ignore if not supported
435
+ }
427
436
  }
428
437
  });
429
438
  }
@@ -897,26 +897,32 @@ export class FileSystemStorage extends BaseStorage {
897
897
  const limit = options.limit || 100;
898
898
  const startIndex = options.cursor ? parseInt(options.cursor, 10) : 0;
899
899
  try {
900
- // Production-scale optimization: Use persisted count for total instead of scanning
901
- const totalCount = this.totalVerbCount || 0;
900
+ // Get actual verb files first (critical for accuracy)
901
+ const verbFiles = await this.getAllShardedFiles(this.verbsDir);
902
+ verbFiles.sort(); // Consistent ordering for pagination
903
+ // Use actual file count - don't trust cached totalVerbCount
904
+ // This prevents accessing undefined array elements
905
+ const actualFileCount = verbFiles.length;
902
906
  // For large datasets, warn about performance
903
- if (totalCount > 1000000) {
904
- console.warn(`Very large verb dataset detected (${totalCount} verbs). Performance may be degraded. Consider database storage for optimal performance.`);
907
+ if (actualFileCount > 1000000) {
908
+ console.warn(`Very large verb dataset detected (${actualFileCount} verbs). Performance may be degraded. Consider database storage for optimal performance.`);
905
909
  }
906
- // Calculate pagination bounds
907
- const endIndex = Math.min(startIndex + limit, totalCount);
908
- const hasMore = endIndex < totalCount;
909
910
  // For production-scale datasets, use streaming approach
910
- if (totalCount > 50000) {
911
+ if (actualFileCount > 50000) {
911
912
  return await this.getVerbsWithPaginationStreaming(options, startIndex, limit);
912
913
  }
913
- // For smaller datasets, use the current approach (with optimizations)
914
- const verbFiles = await this.getAllShardedFiles(this.verbsDir);
915
- verbFiles.sort(); // This is still acceptable for <50k files
914
+ // Calculate pagination bounds using ACTUAL file count
915
+ const endIndex = Math.min(startIndex + limit, actualFileCount);
916
916
  // Load the requested page of verbs
917
917
  const verbs = [];
918
+ let successfullyLoaded = 0;
918
919
  for (let i = startIndex; i < endIndex; i++) {
919
920
  const file = verbFiles[i];
921
+ // CRITICAL: Null-safety check for undefined array elements
922
+ if (!file) {
923
+ console.warn(`Unexpected undefined file at index ${i}, skipping`);
924
+ continue;
925
+ }
920
926
  const id = file.replace('.json', '');
921
927
  try {
922
928
  // Read the verb data (HNSWVerb stored as edge) - use sharded path
@@ -1003,14 +1009,18 @@ export class FileSystemStorage extends BaseStorage {
1003
1009
  }
1004
1010
  }
1005
1011
  verbs.push(verb);
1012
+ successfullyLoaded++;
1006
1013
  }
1007
1014
  catch (error) {
1008
1015
  console.warn(`Failed to read verb ${id}:`, error);
1009
1016
  }
1010
1017
  }
1018
+ // CRITICAL FIX: hasMore based on actual file count, not cached totalVerbCount
1019
+ // Also verify we successfully loaded items (prevents infinite loops on corrupted storage)
1020
+ const hasMore = (endIndex < actualFileCount) && (successfullyLoaded > 0 || startIndex === 0);
1011
1021
  return {
1012
1022
  items: verbs,
1013
- totalCount,
1023
+ totalCount: actualFileCount, // Return actual count, not cached value
1014
1024
  hasMore,
1015
1025
  nextCursor: hasMore ? String(endIndex) : undefined
1016
1026
  };
@@ -1587,14 +1597,14 @@ export class FileSystemStorage extends BaseStorage {
1587
1597
  */
1588
1598
  async getVerbsWithPaginationStreaming(options, startIndex, limit) {
1589
1599
  const verbs = [];
1590
- const totalCount = this.totalVerbCount || 0;
1591
1600
  let processedCount = 0;
1592
1601
  let skippedCount = 0;
1593
1602
  let resultCount = 0;
1594
1603
  const depth = this.cachedShardingDepth ?? this.getOptimalShardingDepth();
1595
1604
  try {
1596
1605
  // Stream through sharded directories efficiently
1597
- const hasMore = await this.streamShardedFiles(this.verbsDir, depth, async (filename, filePath) => {
1606
+ // hasMore=false means we reached the end of files, hasMore=true means streaming stopped early
1607
+ const streamingHasMore = await this.streamShardedFiles(this.verbsDir, depth, async (filename, filePath) => {
1598
1608
  // Skip files until we reach start index
1599
1609
  if (skippedCount < startIndex) {
1600
1610
  skippedCount++;
@@ -1602,7 +1612,7 @@ export class FileSystemStorage extends BaseStorage {
1602
1612
  }
1603
1613
  // Stop if we have enough results
1604
1614
  if (resultCount >= limit) {
1605
- return false; // stop streaming
1615
+ return false; // stop streaming - more files exist
1606
1616
  }
1607
1617
  try {
1608
1618
  const id = filename.replace('.json', '');
@@ -1666,10 +1676,13 @@ export class FileSystemStorage extends BaseStorage {
1666
1676
  return true; // continue
1667
1677
  }
1668
1678
  });
1669
- const finalHasMore = (startIndex + resultCount) < totalCount;
1679
+ // CRITICAL FIX: Use streaming result for hasMore, not cached totalVerbCount
1680
+ // streamingHasMore=false means we exhausted all files
1681
+ // Also verify we loaded items to prevent infinite loops
1682
+ const finalHasMore = streamingHasMore && (resultCount > 0 || startIndex === 0);
1670
1683
  return {
1671
1684
  items: verbs,
1672
- totalCount,
1685
+ totalCount: this.totalVerbCount || undefined, // Return cached count as hint only
1673
1686
  hasMore: finalHasMore,
1674
1687
  nextCursor: finalHasMore ? String(startIndex + resultCount) : undefined
1675
1688
  };
@@ -240,9 +240,11 @@ export class BaseStorage extends BaseStorageAdapter {
240
240
  */
241
241
  async getVerbsBySource(sourceId) {
242
242
  await this.ensureInitialized();
243
- // Use the paginated getVerbs method with source filter
243
+ // CRITICAL: Fetch ALL verbs for this source, not just first page
244
+ // This is needed for delete operations to clean up all relationships
244
245
  const result = await this.getVerbs({
245
- filter: { sourceId }
246
+ filter: { sourceId },
247
+ pagination: { limit: Number.MAX_SAFE_INTEGER }
246
248
  });
247
249
  return result.items;
248
250
  }
@@ -251,9 +253,11 @@ export class BaseStorage extends BaseStorageAdapter {
251
253
  */
252
254
  async getVerbsByTarget(targetId) {
253
255
  await this.ensureInitialized();
254
- // Use the paginated getVerbs method with target filter
256
+ // CRITICAL: Fetch ALL verbs for this target, not just first page
257
+ // This is needed for delete operations to clean up all relationships
255
258
  const result = await this.getVerbs({
256
- filter: { targetId }
259
+ filter: { targetId },
260
+ pagination: { limit: Number.MAX_SAFE_INTEGER }
257
261
  });
258
262
  return result.items;
259
263
  }
@@ -262,9 +266,10 @@ export class BaseStorage extends BaseStorageAdapter {
262
266
  */
263
267
  async getVerbsByType(type) {
264
268
  await this.ensureInitialized();
265
- // Use the paginated getVerbs method with type filter
269
+ // Fetch ALL verbs of this type (no pagination limit)
266
270
  const result = await this.getVerbs({
267
- filter: { verbType: type }
271
+ filter: { verbType: type },
272
+ pagination: { limit: Number.MAX_SAFE_INTEGER }
268
273
  });
269
274
  return result.items;
270
275
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "3.20.3",
3
+ "version": "3.20.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",
@@ -81,11 +81,15 @@
81
81
  "format:check": "prettier --check \"src/**/*.{ts,js}\"",
82
82
  "migrate:logger": "tsx scripts/migrate-to-structured-logger.ts",
83
83
  "migrate:logger:dry": "tsx scripts/migrate-to-structured-logger.ts --dry-run",
84
- "release": "standard-version",
85
- "release:patch": "standard-version --release-as patch",
86
- "release:minor": "standard-version --release-as minor",
87
- "release:major": "standard-version --release-as major",
88
- "release:dry": "standard-version --dry-run"
84
+ "release": "./scripts/release.sh patch",
85
+ "release:patch": "./scripts/release.sh patch",
86
+ "release:minor": "./scripts/release.sh minor",
87
+ "release:major": "./scripts/release.sh major",
88
+ "release:dry": "./scripts/release.sh patch --dry-run",
89
+ "release:standard-version": "standard-version",
90
+ "release:standard-version:patch": "standard-version --release-as patch",
91
+ "release:standard-version:minor": "standard-version --release-as minor",
92
+ "release:standard-version:major": "standard-version --release-as major"
89
93
  },
90
94
  "keywords": [
91
95
  "ai-database",