@soulcraft/brainy 5.3.5 → 5.4.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 +72 -0
- package/dist/brainy.d.ts +61 -0
- package/dist/brainy.js +188 -24
- package/dist/storage/adapters/azureBlobStorage.d.ts +13 -64
- package/dist/storage/adapters/azureBlobStorage.js +78 -388
- package/dist/storage/adapters/fileSystemStorage.d.ts +12 -78
- package/dist/storage/adapters/fileSystemStorage.js +49 -395
- package/dist/storage/adapters/gcsStorage.d.ts +13 -134
- package/dist/storage/adapters/gcsStorage.js +79 -557
- package/dist/storage/adapters/historicalStorageAdapter.d.ts +181 -0
- package/dist/storage/adapters/historicalStorageAdapter.js +332 -0
- package/dist/storage/adapters/memoryStorage.d.ts +4 -113
- package/dist/storage/adapters/memoryStorage.js +34 -471
- package/dist/storage/adapters/opfsStorage.d.ts +14 -127
- package/dist/storage/adapters/opfsStorage.js +44 -693
- package/dist/storage/adapters/r2Storage.d.ts +8 -41
- package/dist/storage/adapters/r2Storage.js +49 -237
- package/dist/storage/adapters/s3CompatibleStorage.d.ts +13 -111
- package/dist/storage/adapters/s3CompatibleStorage.js +77 -596
- package/dist/storage/baseStorage.d.ts +78 -38
- package/dist/storage/baseStorage.js +699 -23
- package/dist/storage/cow/BlobStorage.d.ts +2 -2
- package/dist/storage/cow/BlobStorage.js +4 -4
- package/dist/storage/storageFactory.d.ts +2 -3
- package/dist/storage/storageFactory.js +114 -66
- package/dist/vfs/types.d.ts +6 -2
- package/package.json +1 -1
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { GraphAdjacencyIndex } from '../graph/graphAdjacencyIndex.js';
|
|
6
6
|
import { BaseStorageAdapter } from './adapters/baseStorageAdapter.js';
|
|
7
7
|
import { validateNounType, validateVerbType } from '../utils/typeValidation.js';
|
|
8
|
-
import { NounType } from '../types/graphTypes.js';
|
|
8
|
+
import { NounType, TypeUtils, NOUN_TYPE_COUNT, VERB_TYPE_COUNT } from '../types/graphTypes.js';
|
|
9
9
|
import { getShardIdFromUuid } from './sharding.js';
|
|
10
10
|
import { RefManager } from './cow/RefManager.js';
|
|
11
11
|
import { BlobStorage } from './cow/BlobStorage.js';
|
|
@@ -32,6 +32,38 @@ export function getDirectoryPath(entityType, dataType) {
|
|
|
32
32
|
return dataType === 'vector' ? VERBS_DIR : VERBS_METADATA_DIR;
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Type-first path generators (v5.4.0)
|
|
37
|
+
* Built-in type-aware organization for all storage adapters
|
|
38
|
+
*/
|
|
39
|
+
/**
|
|
40
|
+
* Get type-first path for noun vectors
|
|
41
|
+
*/
|
|
42
|
+
function getNounVectorPath(type, id) {
|
|
43
|
+
const shard = getShardIdFromUuid(id);
|
|
44
|
+
return `entities/nouns/${type}/vectors/${shard}/${id}.json`;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get type-first path for noun metadata
|
|
48
|
+
*/
|
|
49
|
+
function getNounMetadataPath(type, id) {
|
|
50
|
+
const shard = getShardIdFromUuid(id);
|
|
51
|
+
return `entities/nouns/${type}/metadata/${shard}/${id}.json`;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get type-first path for verb vectors
|
|
55
|
+
*/
|
|
56
|
+
function getVerbVectorPath(type, id) {
|
|
57
|
+
const shard = getShardIdFromUuid(id);
|
|
58
|
+
return `entities/verbs/${type}/vectors/${shard}/${id}.json`;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Get type-first path for verb metadata
|
|
62
|
+
*/
|
|
63
|
+
function getVerbMetadataPath(type, id) {
|
|
64
|
+
const shard = getShardIdFromUuid(id);
|
|
65
|
+
return `entities/verbs/${type}/metadata/${shard}/${id}.json`;
|
|
66
|
+
}
|
|
35
67
|
/**
|
|
36
68
|
* Base storage adapter that implements common functionality
|
|
37
69
|
* This is an abstract class that should be extended by specific storage adapters
|
|
@@ -43,6 +75,14 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
43
75
|
this.readOnly = false;
|
|
44
76
|
this.currentBranch = 'main';
|
|
45
77
|
this.cowEnabled = false;
|
|
78
|
+
// Type-first indexing support (v5.4.0)
|
|
79
|
+
// Built into all storage adapters for billion-scale efficiency
|
|
80
|
+
this.nounCountsByType = new Uint32Array(NOUN_TYPE_COUNT); // 124 bytes
|
|
81
|
+
this.verbCountsByType = new Uint32Array(VERB_TYPE_COUNT); // 160 bytes
|
|
82
|
+
// Total: 284 bytes (99.76% reduction vs Map-based tracking)
|
|
83
|
+
// Type cache for O(1) lookups after first access
|
|
84
|
+
this.nounTypeCache = new Map();
|
|
85
|
+
this.verbTypeCache = new Map();
|
|
46
86
|
}
|
|
47
87
|
/**
|
|
48
88
|
* Analyze a storage key to determine its routing and path
|
|
@@ -116,6 +156,17 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
116
156
|
};
|
|
117
157
|
}
|
|
118
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Initialize the storage adapter (v5.4.0)
|
|
161
|
+
* Loads type statistics for built-in type-aware indexing
|
|
162
|
+
*
|
|
163
|
+
* IMPORTANT: If your adapter overrides init(), call await super.init() first!
|
|
164
|
+
*/
|
|
165
|
+
async init() {
|
|
166
|
+
// Load type statistics from storage (if they exist)
|
|
167
|
+
await this.loadTypeStatistics();
|
|
168
|
+
this.isInitialized = true;
|
|
169
|
+
}
|
|
119
170
|
/**
|
|
120
171
|
* Ensure the storage adapter is initialized
|
|
121
172
|
*/
|
|
@@ -274,6 +325,13 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
274
325
|
* @protected - Available to subclasses for COW implementation
|
|
275
326
|
*/
|
|
276
327
|
resolveBranchPath(basePath, branch) {
|
|
328
|
+
// CRITICAL FIX (v5.3.6): COW metadata (_cow/*) must NEVER be branch-scoped
|
|
329
|
+
// Refs, commits, and blobs are global metadata with their own internal branching.
|
|
330
|
+
// Branch-scoping COW paths causes fork() to write refs to wrong locations,
|
|
331
|
+
// leading to "Branch does not exist" errors on checkout (see Workshop bug report).
|
|
332
|
+
if (basePath.startsWith('_cow/')) {
|
|
333
|
+
return basePath; // COW metadata is global across all branches
|
|
334
|
+
}
|
|
277
335
|
if (!this.cowEnabled) {
|
|
278
336
|
return basePath; // COW disabled, use direct path
|
|
279
337
|
}
|
|
@@ -763,6 +821,90 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
763
821
|
};
|
|
764
822
|
}
|
|
765
823
|
}
|
|
824
|
+
/**
|
|
825
|
+
* Get nouns with pagination (v5.4.0: Type-first implementation)
|
|
826
|
+
*
|
|
827
|
+
* CRITICAL: This method is required for brain.find() to work!
|
|
828
|
+
* Iterates through all noun types to find entities.
|
|
829
|
+
*/
|
|
830
|
+
async getNounsWithPagination(options) {
|
|
831
|
+
await this.ensureInitialized();
|
|
832
|
+
const { limit, offset, filter } = options;
|
|
833
|
+
const allNouns = [];
|
|
834
|
+
// v5.4.0: Iterate through all noun types (type-first architecture)
|
|
835
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
836
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
837
|
+
// If filtering by type, skip other types
|
|
838
|
+
if (filter?.nounType) {
|
|
839
|
+
const filterTypes = Array.isArray(filter.nounType) ? filter.nounType : [filter.nounType];
|
|
840
|
+
if (!filterTypes.includes(type)) {
|
|
841
|
+
continue;
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const typeDir = `entities/nouns/${type}/vectors`;
|
|
845
|
+
try {
|
|
846
|
+
// List all noun files for this type
|
|
847
|
+
const nounFiles = await this.listObjectsInBranch(typeDir);
|
|
848
|
+
for (const nounPath of nounFiles) {
|
|
849
|
+
// Skip if not a .json file
|
|
850
|
+
if (!nounPath.endsWith('.json'))
|
|
851
|
+
continue;
|
|
852
|
+
try {
|
|
853
|
+
const noun = await this.readWithInheritance(nounPath);
|
|
854
|
+
if (noun) {
|
|
855
|
+
// Load metadata
|
|
856
|
+
const metadataPath = getNounMetadataPath(type, noun.id);
|
|
857
|
+
const metadata = await this.readWithInheritance(metadataPath);
|
|
858
|
+
if (metadata) {
|
|
859
|
+
// Apply service filter if specified
|
|
860
|
+
if (filter?.service) {
|
|
861
|
+
const services = Array.isArray(filter.service) ? filter.service : [filter.service];
|
|
862
|
+
if (metadata.service && !services.includes(metadata.service)) {
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
// Combine noun + metadata (v5.4.0: Extract standard fields to top-level)
|
|
867
|
+
allNouns.push({
|
|
868
|
+
...noun,
|
|
869
|
+
type: metadata.noun || type, // Required: Extract type from metadata
|
|
870
|
+
confidence: metadata.confidence,
|
|
871
|
+
weight: metadata.weight,
|
|
872
|
+
createdAt: metadata.createdAt
|
|
873
|
+
? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
|
|
874
|
+
: Date.now(),
|
|
875
|
+
updatedAt: metadata.updatedAt
|
|
876
|
+
? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
|
|
877
|
+
: Date.now(),
|
|
878
|
+
service: metadata.service,
|
|
879
|
+
data: metadata.data,
|
|
880
|
+
createdBy: metadata.createdBy,
|
|
881
|
+
metadata: metadata || {}
|
|
882
|
+
});
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
catch (error) {
|
|
887
|
+
// Skip nouns that fail to load
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
catch (error) {
|
|
892
|
+
// Skip types that have no data
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
// Apply pagination
|
|
896
|
+
const totalCount = allNouns.length;
|
|
897
|
+
const paginatedNouns = allNouns.slice(offset, offset + limit);
|
|
898
|
+
const hasMore = offset + limit < totalCount;
|
|
899
|
+
return {
|
|
900
|
+
items: paginatedNouns,
|
|
901
|
+
totalCount,
|
|
902
|
+
hasMore,
|
|
903
|
+
nextCursor: hasMore && paginatedNouns.length > 0
|
|
904
|
+
? paginatedNouns[paginatedNouns.length - 1].id
|
|
905
|
+
: undefined
|
|
906
|
+
};
|
|
907
|
+
}
|
|
766
908
|
/**
|
|
767
909
|
* Get verbs with pagination and filtering
|
|
768
910
|
* @param options Pagination and filtering options
|
|
@@ -1026,12 +1168,16 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1026
1168
|
*/
|
|
1027
1169
|
async saveNounMetadata_internal(id, metadata) {
|
|
1028
1170
|
await this.ensureInitialized();
|
|
1171
|
+
// v5.4.0: Extract and cache type for type-first routing
|
|
1172
|
+
const type = (metadata.noun || 'thing');
|
|
1173
|
+
this.nounTypeCache.set(id, type);
|
|
1174
|
+
// v5.4.0: Use type-first path
|
|
1175
|
+
const path = getNounMetadataPath(type, id);
|
|
1029
1176
|
// Determine if this is a new entity by checking if metadata already exists
|
|
1030
|
-
const
|
|
1031
|
-
const existingMetadata = await this.readWithInheritance(keyInfo.fullPath);
|
|
1177
|
+
const existingMetadata = await this.readWithInheritance(path);
|
|
1032
1178
|
const isNew = !existingMetadata;
|
|
1033
1179
|
// Save the metadata (COW-aware - writes to branch-specific path)
|
|
1034
|
-
await this.writeObjectToBranch(
|
|
1180
|
+
await this.writeObjectToBranch(path, metadata);
|
|
1035
1181
|
// CRITICAL FIX (v4.1.2): Increment count for new entities
|
|
1036
1182
|
// This runs AFTER metadata is saved, guaranteeing type information is available
|
|
1037
1183
|
// Uses synchronous increment since storage operations are already serialized
|
|
@@ -1046,21 +1192,65 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1046
1192
|
}
|
|
1047
1193
|
/**
|
|
1048
1194
|
* Get noun metadata from storage (v4.0.0: now typed)
|
|
1049
|
-
* Uses
|
|
1195
|
+
* v5.4.0: Uses type-first paths (must match saveNounMetadata_internal)
|
|
1050
1196
|
*/
|
|
1051
1197
|
async getNounMetadata(id) {
|
|
1052
1198
|
await this.ensureInitialized();
|
|
1053
|
-
|
|
1054
|
-
|
|
1199
|
+
// v5.4.0: Check type cache first (populated during save)
|
|
1200
|
+
const cachedType = this.nounTypeCache.get(id);
|
|
1201
|
+
if (cachedType) {
|
|
1202
|
+
const path = getNounMetadataPath(cachedType, id);
|
|
1203
|
+
return this.readWithInheritance(path);
|
|
1204
|
+
}
|
|
1205
|
+
// Fallback: search across all types (expensive but necessary if cache miss)
|
|
1206
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
1207
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
1208
|
+
const path = getNounMetadataPath(type, id);
|
|
1209
|
+
try {
|
|
1210
|
+
const metadata = await this.readWithInheritance(path);
|
|
1211
|
+
if (metadata) {
|
|
1212
|
+
// Cache the type for next time
|
|
1213
|
+
this.nounTypeCache.set(id, type);
|
|
1214
|
+
return metadata;
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
// Not in this type, continue searching
|
|
1219
|
+
}
|
|
1220
|
+
}
|
|
1221
|
+
return null;
|
|
1055
1222
|
}
|
|
1056
1223
|
/**
|
|
1057
1224
|
* Delete noun metadata from storage
|
|
1058
|
-
* Uses
|
|
1225
|
+
* v5.4.0: Uses type-first paths (must match saveNounMetadata_internal)
|
|
1059
1226
|
*/
|
|
1060
1227
|
async deleteNounMetadata(id) {
|
|
1061
1228
|
await this.ensureInitialized();
|
|
1062
|
-
|
|
1063
|
-
|
|
1229
|
+
// v5.4.0: Use cached type for path
|
|
1230
|
+
const cachedType = this.nounTypeCache.get(id);
|
|
1231
|
+
if (cachedType) {
|
|
1232
|
+
const path = getNounMetadataPath(cachedType, id);
|
|
1233
|
+
await this.deleteObjectFromBranch(path);
|
|
1234
|
+
// Remove from cache after deletion
|
|
1235
|
+
this.nounTypeCache.delete(id);
|
|
1236
|
+
return;
|
|
1237
|
+
}
|
|
1238
|
+
// If not in cache, search all types to find and delete
|
|
1239
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
1240
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
1241
|
+
const path = getNounMetadataPath(type, id);
|
|
1242
|
+
try {
|
|
1243
|
+
// Check if exists before deleting
|
|
1244
|
+
const exists = await this.readWithInheritance(path);
|
|
1245
|
+
if (exists) {
|
|
1246
|
+
await this.deleteObjectFromBranch(path);
|
|
1247
|
+
return;
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
catch (error) {
|
|
1251
|
+
// Not in this type, continue searching
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1064
1254
|
}
|
|
1065
1255
|
/**
|
|
1066
1256
|
* Save verb metadata to storage (v4.0.0: now typed)
|
|
@@ -1072,7 +1262,7 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1072
1262
|
}
|
|
1073
1263
|
/**
|
|
1074
1264
|
* Internal method for saving verb metadata (v4.0.0: now typed)
|
|
1075
|
-
* Uses
|
|
1265
|
+
* v5.4.0: Uses type-first paths (must match getVerbMetadata)
|
|
1076
1266
|
*
|
|
1077
1267
|
* CRITICAL (v4.1.2): Count synchronization happens here
|
|
1078
1268
|
* This ensures verb counts are updated AFTER metadata exists, fixing the race condition
|
|
@@ -1084,19 +1274,29 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1084
1274
|
*/
|
|
1085
1275
|
async saveVerbMetadata_internal(id, metadata) {
|
|
1086
1276
|
await this.ensureInitialized();
|
|
1277
|
+
// v5.4.0: Extract verb type from metadata for type-first path
|
|
1278
|
+
const verbType = metadata.verb;
|
|
1279
|
+
if (!verbType) {
|
|
1280
|
+
// Backward compatibility: fallback to old path if no verb type
|
|
1281
|
+
const keyInfo = this.analyzeKey(id, 'verb-metadata');
|
|
1282
|
+
await this.writeObjectToBranch(keyInfo.fullPath, metadata);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
// v5.4.0: Use type-first path
|
|
1286
|
+
const path = getVerbMetadataPath(verbType, id);
|
|
1087
1287
|
// Determine if this is a new verb by checking if metadata already exists
|
|
1088
|
-
const
|
|
1089
|
-
const existingMetadata = await this.readWithInheritance(keyInfo.fullPath);
|
|
1288
|
+
const existingMetadata = await this.readWithInheritance(path);
|
|
1090
1289
|
const isNew = !existingMetadata;
|
|
1091
1290
|
// Save the metadata (COW-aware - writes to branch-specific path)
|
|
1092
|
-
await this.writeObjectToBranch(
|
|
1291
|
+
await this.writeObjectToBranch(path, metadata);
|
|
1292
|
+
// v5.4.0: Cache verb type for faster lookups
|
|
1293
|
+
this.verbTypeCache.set(id, verbType);
|
|
1093
1294
|
// CRITICAL FIX (v4.1.2): Increment verb count for new relationships
|
|
1094
1295
|
// This runs AFTER metadata is saved
|
|
1095
|
-
// Verb type is now stored in metadata (as of v4.1.2) to avoid loading HNSWVerb
|
|
1096
1296
|
// Uses synchronous increment since storage operations are already serialized
|
|
1097
1297
|
// Fixes Bug #2: Count synchronization failure during relate() and import()
|
|
1098
|
-
if (isNew
|
|
1099
|
-
this.incrementVerbCount(
|
|
1298
|
+
if (isNew) {
|
|
1299
|
+
this.incrementVerbCount(verbType);
|
|
1100
1300
|
// Persist counts asynchronously (fire and forget)
|
|
1101
1301
|
this.scheduleCountPersist().catch(() => {
|
|
1102
1302
|
// Ignore persist errors - will retry on next operation
|
|
@@ -1105,21 +1305,497 @@ export class BaseStorage extends BaseStorageAdapter {
|
|
|
1105
1305
|
}
|
|
1106
1306
|
/**
|
|
1107
1307
|
* Get verb metadata from storage (v4.0.0: now typed)
|
|
1108
|
-
* Uses
|
|
1308
|
+
* v5.4.0: Uses type-first paths (must match saveVerbMetadata_internal)
|
|
1109
1309
|
*/
|
|
1110
1310
|
async getVerbMetadata(id) {
|
|
1111
1311
|
await this.ensureInitialized();
|
|
1112
|
-
|
|
1113
|
-
|
|
1312
|
+
// v5.4.0: Check type cache first (populated during save)
|
|
1313
|
+
const cachedType = this.verbTypeCache.get(id);
|
|
1314
|
+
if (cachedType) {
|
|
1315
|
+
const path = getVerbMetadataPath(cachedType, id);
|
|
1316
|
+
return this.readWithInheritance(path);
|
|
1317
|
+
}
|
|
1318
|
+
// Fallback: search across all types (expensive but necessary if cache miss)
|
|
1319
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1320
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1321
|
+
const path = getVerbMetadataPath(type, id);
|
|
1322
|
+
try {
|
|
1323
|
+
const metadata = await this.readWithInheritance(path);
|
|
1324
|
+
if (metadata) {
|
|
1325
|
+
// Cache the type for next time
|
|
1326
|
+
this.verbTypeCache.set(id, type);
|
|
1327
|
+
return metadata;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
catch (error) {
|
|
1331
|
+
// Not in this type, continue searching
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
return null;
|
|
1114
1335
|
}
|
|
1115
1336
|
/**
|
|
1116
1337
|
* Delete verb metadata from storage
|
|
1117
|
-
* Uses
|
|
1338
|
+
* v5.4.0: Uses type-first paths (must match saveVerbMetadata_internal)
|
|
1118
1339
|
*/
|
|
1119
1340
|
async deleteVerbMetadata(id) {
|
|
1120
1341
|
await this.ensureInitialized();
|
|
1121
|
-
|
|
1122
|
-
|
|
1342
|
+
// v5.4.0: Use cached type for path
|
|
1343
|
+
const cachedType = this.verbTypeCache.get(id);
|
|
1344
|
+
if (cachedType) {
|
|
1345
|
+
const path = getVerbMetadataPath(cachedType, id);
|
|
1346
|
+
await this.deleteObjectFromBranch(path);
|
|
1347
|
+
// Remove from cache after deletion
|
|
1348
|
+
this.verbTypeCache.delete(id);
|
|
1349
|
+
return;
|
|
1350
|
+
}
|
|
1351
|
+
// If not in cache, search all types to find and delete
|
|
1352
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1353
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1354
|
+
const path = getVerbMetadataPath(type, id);
|
|
1355
|
+
try {
|
|
1356
|
+
// Check if exists before deleting
|
|
1357
|
+
const exists = await this.readWithInheritance(path);
|
|
1358
|
+
if (exists) {
|
|
1359
|
+
await this.deleteObjectFromBranch(path);
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
catch (error) {
|
|
1364
|
+
// Not in this type, continue searching
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
// ============================================================================
|
|
1369
|
+
// TYPE-FIRST HELPER METHODS (v5.4.0)
|
|
1370
|
+
// Built-in type-aware support for all storage adapters
|
|
1371
|
+
// ============================================================================
|
|
1372
|
+
/**
|
|
1373
|
+
* Load type statistics from storage
|
|
1374
|
+
* Rebuilds type counts if needed (called during init)
|
|
1375
|
+
*/
|
|
1376
|
+
async loadTypeStatistics() {
|
|
1377
|
+
try {
|
|
1378
|
+
const stats = await this.readObjectFromPath(`${SYSTEM_DIR}/type-statistics.json`);
|
|
1379
|
+
if (stats) {
|
|
1380
|
+
// Restore counts from saved statistics
|
|
1381
|
+
if (stats.nounCounts && stats.nounCounts.length === NOUN_TYPE_COUNT) {
|
|
1382
|
+
this.nounCountsByType = new Uint32Array(stats.nounCounts);
|
|
1383
|
+
}
|
|
1384
|
+
if (stats.verbCounts && stats.verbCounts.length === VERB_TYPE_COUNT) {
|
|
1385
|
+
this.verbCountsByType = new Uint32Array(stats.verbCounts);
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
catch (error) {
|
|
1390
|
+
// No existing type statistics, starting fresh
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Save type statistics to storage
|
|
1395
|
+
* Periodically called when counts are updated
|
|
1396
|
+
*/
|
|
1397
|
+
async saveTypeStatistics() {
|
|
1398
|
+
const stats = {
|
|
1399
|
+
nounCounts: Array.from(this.nounCountsByType),
|
|
1400
|
+
verbCounts: Array.from(this.verbCountsByType),
|
|
1401
|
+
updatedAt: Date.now()
|
|
1402
|
+
};
|
|
1403
|
+
await this.writeObjectToPath(`${SYSTEM_DIR}/type-statistics.json`, stats);
|
|
1404
|
+
}
|
|
1405
|
+
/**
|
|
1406
|
+
* Get noun type from cache or metadata
|
|
1407
|
+
* Relies on nounTypeCache populated during metadata saves
|
|
1408
|
+
*/
|
|
1409
|
+
getNounType(noun) {
|
|
1410
|
+
// Check cache (populated when metadata is saved)
|
|
1411
|
+
const cached = this.nounTypeCache.get(noun.id);
|
|
1412
|
+
if (cached) {
|
|
1413
|
+
return cached;
|
|
1414
|
+
}
|
|
1415
|
+
// Default to 'thing' if unknown
|
|
1416
|
+
// This should only happen if saveNoun_internal is called before saveNounMetadata
|
|
1417
|
+
console.warn(`[BaseStorage] Unknown noun type for ${noun.id}, defaulting to 'thing'`);
|
|
1418
|
+
return 'thing';
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Get verb type from verb object
|
|
1422
|
+
* Verb type is a required field in HNSWVerb
|
|
1423
|
+
*/
|
|
1424
|
+
getVerbType(verb) {
|
|
1425
|
+
// v3.50.1+: verb is a required field in HNSWVerb
|
|
1426
|
+
if ('verb' in verb && verb.verb) {
|
|
1427
|
+
return verb.verb;
|
|
1428
|
+
}
|
|
1429
|
+
// Fallback for GraphVerb (type alias)
|
|
1430
|
+
if ('type' in verb && verb.type) {
|
|
1431
|
+
return verb.type;
|
|
1432
|
+
}
|
|
1433
|
+
// This should never happen with current data
|
|
1434
|
+
console.warn(`[BaseStorage] Verb missing type field for ${verb.id}, defaulting to 'relatedTo'`);
|
|
1435
|
+
return 'relatedTo';
|
|
1436
|
+
}
|
|
1437
|
+
// ============================================================================
|
|
1438
|
+
// ABSTRACT METHOD IMPLEMENTATIONS (v5.4.0)
|
|
1439
|
+
// Converted from abstract to concrete - all adapters now have built-in type-aware
|
|
1440
|
+
// ============================================================================
|
|
1441
|
+
/**
|
|
1442
|
+
* Save a noun to storage (type-first path)
|
|
1443
|
+
*/
|
|
1444
|
+
async saveNoun_internal(noun) {
|
|
1445
|
+
const type = this.getNounType(noun);
|
|
1446
|
+
const path = getNounVectorPath(type, noun.id);
|
|
1447
|
+
// Update type tracking
|
|
1448
|
+
const typeIndex = TypeUtils.getNounIndex(type);
|
|
1449
|
+
this.nounCountsByType[typeIndex]++;
|
|
1450
|
+
this.nounTypeCache.set(noun.id, type);
|
|
1451
|
+
// COW-aware write (v5.0.1): Use COW helper for branch isolation
|
|
1452
|
+
await this.writeObjectToBranch(path, noun);
|
|
1453
|
+
// Periodically save statistics (every 100 saves)
|
|
1454
|
+
if (this.nounCountsByType[typeIndex] % 100 === 0) {
|
|
1455
|
+
await this.saveTypeStatistics();
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Get a noun from storage (type-first path)
|
|
1460
|
+
*/
|
|
1461
|
+
async getNoun_internal(id) {
|
|
1462
|
+
// Try cache first
|
|
1463
|
+
const cachedType = this.nounTypeCache.get(id);
|
|
1464
|
+
if (cachedType) {
|
|
1465
|
+
const path = getNounVectorPath(cachedType, id);
|
|
1466
|
+
// COW-aware read (v5.0.1): Use COW helper for branch isolation
|
|
1467
|
+
return await this.readWithInheritance(path);
|
|
1468
|
+
}
|
|
1469
|
+
// Need to search across all types (expensive, but cached after first access)
|
|
1470
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
1471
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
1472
|
+
const path = getNounVectorPath(type, id);
|
|
1473
|
+
try {
|
|
1474
|
+
// COW-aware read (v5.0.1): Use COW helper for branch isolation
|
|
1475
|
+
const noun = await this.readWithInheritance(path);
|
|
1476
|
+
if (noun) {
|
|
1477
|
+
// Cache the type for next time
|
|
1478
|
+
this.nounTypeCache.set(id, type);
|
|
1479
|
+
return noun;
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
catch (error) {
|
|
1483
|
+
// Not in this type, continue searching
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
return null;
|
|
1487
|
+
}
|
|
1488
|
+
/**
|
|
1489
|
+
* Get nouns by noun type (O(1) with type-first paths!)
|
|
1490
|
+
*/
|
|
1491
|
+
async getNounsByNounType_internal(nounType) {
|
|
1492
|
+
const type = nounType;
|
|
1493
|
+
const prefix = `entities/nouns/${type}/vectors/`;
|
|
1494
|
+
// COW-aware list (v5.0.1): Use COW helper for branch isolation
|
|
1495
|
+
const paths = await this.listObjectsInBranch(prefix);
|
|
1496
|
+
// Load all nouns of this type
|
|
1497
|
+
const nouns = [];
|
|
1498
|
+
for (const path of paths) {
|
|
1499
|
+
try {
|
|
1500
|
+
// COW-aware read (v5.0.1): Use COW helper for branch isolation
|
|
1501
|
+
const noun = await this.readWithInheritance(path);
|
|
1502
|
+
if (noun) {
|
|
1503
|
+
nouns.push(noun);
|
|
1504
|
+
// Cache the type
|
|
1505
|
+
this.nounTypeCache.set(noun.id, type);
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
catch (error) {
|
|
1509
|
+
console.warn(`[BaseStorage] Failed to load noun from ${path}:`, error);
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
return nouns;
|
|
1513
|
+
}
|
|
1514
|
+
/**
|
|
1515
|
+
* Delete a noun from storage (type-first path)
|
|
1516
|
+
*/
|
|
1517
|
+
async deleteNoun_internal(id) {
|
|
1518
|
+
// Try cache first
|
|
1519
|
+
const cachedType = this.nounTypeCache.get(id);
|
|
1520
|
+
if (cachedType) {
|
|
1521
|
+
const path = getNounVectorPath(cachedType, id);
|
|
1522
|
+
// COW-aware delete (v5.0.1): Use COW helper for branch isolation
|
|
1523
|
+
await this.deleteObjectFromBranch(path);
|
|
1524
|
+
// Update counts
|
|
1525
|
+
const typeIndex = TypeUtils.getNounIndex(cachedType);
|
|
1526
|
+
if (this.nounCountsByType[typeIndex] > 0) {
|
|
1527
|
+
this.nounCountsByType[typeIndex]--;
|
|
1528
|
+
}
|
|
1529
|
+
this.nounTypeCache.delete(id);
|
|
1530
|
+
return;
|
|
1531
|
+
}
|
|
1532
|
+
// Search across all types
|
|
1533
|
+
for (let i = 0; i < NOUN_TYPE_COUNT; i++) {
|
|
1534
|
+
const type = TypeUtils.getNounFromIndex(i);
|
|
1535
|
+
const path = getNounVectorPath(type, id);
|
|
1536
|
+
try {
|
|
1537
|
+
// COW-aware delete (v5.0.1): Use COW helper for branch isolation
|
|
1538
|
+
await this.deleteObjectFromBranch(path);
|
|
1539
|
+
// Update counts
|
|
1540
|
+
if (this.nounCountsByType[i] > 0) {
|
|
1541
|
+
this.nounCountsByType[i]--;
|
|
1542
|
+
}
|
|
1543
|
+
this.nounTypeCache.delete(id);
|
|
1544
|
+
return;
|
|
1545
|
+
}
|
|
1546
|
+
catch (error) {
|
|
1547
|
+
// Not in this type, continue
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
/**
|
|
1552
|
+
* Save a verb to storage (type-first path)
|
|
1553
|
+
*/
|
|
1554
|
+
async saveVerb_internal(verb) {
|
|
1555
|
+
// Type is now a first-class field in HNSWVerb - no caching needed!
|
|
1556
|
+
const type = verb.verb;
|
|
1557
|
+
const path = getVerbVectorPath(type, verb.id);
|
|
1558
|
+
// Update type tracking
|
|
1559
|
+
const typeIndex = TypeUtils.getVerbIndex(type);
|
|
1560
|
+
this.verbCountsByType[typeIndex]++;
|
|
1561
|
+
this.verbTypeCache.set(verb.id, type);
|
|
1562
|
+
// COW-aware write (v5.0.1): Use COW helper for branch isolation
|
|
1563
|
+
await this.writeObjectToBranch(path, verb);
|
|
1564
|
+
// Periodically save statistics
|
|
1565
|
+
if (this.verbCountsByType[typeIndex] % 100 === 0) {
|
|
1566
|
+
await this.saveTypeStatistics();
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Get a verb from storage (type-first path)
|
|
1571
|
+
*/
|
|
1572
|
+
async getVerb_internal(id) {
|
|
1573
|
+
// Try cache first for O(1) retrieval
|
|
1574
|
+
const cachedType = this.verbTypeCache.get(id);
|
|
1575
|
+
if (cachedType) {
|
|
1576
|
+
const path = getVerbVectorPath(cachedType, id);
|
|
1577
|
+
// COW-aware read (v5.0.1): Use COW helper for branch isolation
|
|
1578
|
+
const verb = await this.readWithInheritance(path);
|
|
1579
|
+
return verb;
|
|
1580
|
+
}
|
|
1581
|
+
// Search across all types (only on first access)
|
|
1582
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1583
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1584
|
+
const path = getVerbVectorPath(type, id);
|
|
1585
|
+
try {
|
|
1586
|
+
// COW-aware read (v5.0.1): Use COW helper for branch isolation
|
|
1587
|
+
const verb = await this.readWithInheritance(path);
|
|
1588
|
+
if (verb) {
|
|
1589
|
+
// Cache the type for next time (read from verb.verb field)
|
|
1590
|
+
this.verbTypeCache.set(id, verb.verb);
|
|
1591
|
+
return verb;
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
catch (error) {
|
|
1595
|
+
// Not in this type, continue
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
return null;
|
|
1599
|
+
}
|
|
1600
|
+
/**
|
|
1601
|
+
* Get verbs by source (COW-aware implementation)
|
|
1602
|
+
* v5.4.0: Fixed to directly list verb files instead of directories
|
|
1603
|
+
*/
|
|
1604
|
+
async getVerbsBySource_internal(sourceId) {
|
|
1605
|
+
// v5.4.0: Type-first implementation - scan across all verb types
|
|
1606
|
+
// COW-aware: uses readWithInheritance for each verb
|
|
1607
|
+
await this.ensureInitialized();
|
|
1608
|
+
const results = [];
|
|
1609
|
+
// Iterate through all verb types
|
|
1610
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1611
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1612
|
+
const typeDir = `entities/verbs/${type}/vectors`;
|
|
1613
|
+
try {
|
|
1614
|
+
// v5.4.0 FIX: List all verb files directly (not shard directories)
|
|
1615
|
+
// listObjectsInBranch returns full paths to .json files, not directories
|
|
1616
|
+
const verbFiles = await this.listObjectsInBranch(typeDir);
|
|
1617
|
+
for (const verbPath of verbFiles) {
|
|
1618
|
+
// Skip if not a .json file
|
|
1619
|
+
if (!verbPath.endsWith('.json'))
|
|
1620
|
+
continue;
|
|
1621
|
+
try {
|
|
1622
|
+
const verb = await this.readWithInheritance(verbPath);
|
|
1623
|
+
if (verb && verb.sourceId === sourceId) {
|
|
1624
|
+
// v5.4.0: Use proper path helper instead of string replacement
|
|
1625
|
+
const metadataPath = getVerbMetadataPath(type, verb.id);
|
|
1626
|
+
const metadata = await this.readWithInheritance(metadataPath);
|
|
1627
|
+
// v5.4.0: Extract standard fields from metadata to top-level (like nouns)
|
|
1628
|
+
results.push({
|
|
1629
|
+
...verb,
|
|
1630
|
+
weight: metadata?.weight,
|
|
1631
|
+
confidence: metadata?.confidence,
|
|
1632
|
+
createdAt: metadata?.createdAt
|
|
1633
|
+
? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
|
|
1634
|
+
: Date.now(),
|
|
1635
|
+
updatedAt: metadata?.updatedAt
|
|
1636
|
+
? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
|
|
1637
|
+
: Date.now(),
|
|
1638
|
+
service: metadata?.service,
|
|
1639
|
+
createdBy: metadata?.createdBy,
|
|
1640
|
+
metadata: metadata || {}
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
catch (error) {
|
|
1645
|
+
// Skip verbs that fail to load
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
catch (error) {
|
|
1650
|
+
// Skip types that have no data
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
return results;
|
|
1654
|
+
}
|
|
1655
|
+
/**
|
|
1656
|
+
* Get verbs by target (COW-aware implementation)
|
|
1657
|
+
* v5.4.0: Fixed to directly list verb files instead of directories
|
|
1658
|
+
*/
|
|
1659
|
+
async getVerbsByTarget_internal(targetId) {
|
|
1660
|
+
// v5.4.0: Type-first implementation - scan across all verb types
|
|
1661
|
+
// COW-aware: uses readWithInheritance for each verb
|
|
1662
|
+
await this.ensureInitialized();
|
|
1663
|
+
const results = [];
|
|
1664
|
+
// Iterate through all verb types
|
|
1665
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1666
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1667
|
+
const typeDir = `entities/verbs/${type}/vectors`;
|
|
1668
|
+
try {
|
|
1669
|
+
// v5.4.0 FIX: List all verb files directly (not shard directories)
|
|
1670
|
+
// listObjectsInBranch returns full paths to .json files, not directories
|
|
1671
|
+
const verbFiles = await this.listObjectsInBranch(typeDir);
|
|
1672
|
+
for (const verbPath of verbFiles) {
|
|
1673
|
+
// Skip if not a .json file
|
|
1674
|
+
if (!verbPath.endsWith('.json'))
|
|
1675
|
+
continue;
|
|
1676
|
+
try {
|
|
1677
|
+
const verb = await this.readWithInheritance(verbPath);
|
|
1678
|
+
if (verb && verb.targetId === targetId) {
|
|
1679
|
+
// v5.4.0: Use proper path helper instead of string replacement
|
|
1680
|
+
const metadataPath = getVerbMetadataPath(type, verb.id);
|
|
1681
|
+
const metadata = await this.readWithInheritance(metadataPath);
|
|
1682
|
+
// v5.4.0: Extract standard fields from metadata to top-level (like nouns)
|
|
1683
|
+
results.push({
|
|
1684
|
+
...verb,
|
|
1685
|
+
weight: metadata?.weight,
|
|
1686
|
+
confidence: metadata?.confidence,
|
|
1687
|
+
createdAt: metadata?.createdAt
|
|
1688
|
+
? (typeof metadata.createdAt === 'number' ? metadata.createdAt : metadata.createdAt.seconds * 1000)
|
|
1689
|
+
: Date.now(),
|
|
1690
|
+
updatedAt: metadata?.updatedAt
|
|
1691
|
+
? (typeof metadata.updatedAt === 'number' ? metadata.updatedAt : metadata.updatedAt.seconds * 1000)
|
|
1692
|
+
: Date.now(),
|
|
1693
|
+
service: metadata?.service,
|
|
1694
|
+
createdBy: metadata?.createdBy,
|
|
1695
|
+
metadata: metadata || {}
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
catch (error) {
|
|
1700
|
+
// Skip verbs that fail to load
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
}
|
|
1704
|
+
catch (error) {
|
|
1705
|
+
// Skip types that have no data
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
return results;
|
|
1709
|
+
}
|
|
1710
|
+
/**
|
|
1711
|
+
* Get verbs by type (O(1) with type-first paths!)
|
|
1712
|
+
*/
|
|
1713
|
+
async getVerbsByType_internal(verbType) {
|
|
1714
|
+
const type = verbType;
|
|
1715
|
+
const prefix = `entities/verbs/${type}/vectors/`;
|
|
1716
|
+
// COW-aware list (v5.0.1): Use COW helper for branch isolation
|
|
1717
|
+
const paths = await this.listObjectsInBranch(prefix);
|
|
1718
|
+
const verbs = [];
|
|
1719
|
+
for (const path of paths) {
|
|
1720
|
+
try {
|
|
1721
|
+
// COW-aware read (v5.0.1): Use COW helper for branch isolation
|
|
1722
|
+
const hnswVerb = await this.readWithInheritance(path);
|
|
1723
|
+
if (!hnswVerb)
|
|
1724
|
+
continue;
|
|
1725
|
+
// Cache type from HNSWVerb for future O(1) retrievals
|
|
1726
|
+
this.verbTypeCache.set(hnswVerb.id, hnswVerb.verb);
|
|
1727
|
+
// Load metadata separately (optional in v4.0.0!)
|
|
1728
|
+
// FIX: Don't skip verbs without metadata - metadata is optional!
|
|
1729
|
+
const metadata = await this.getVerbMetadata(hnswVerb.id);
|
|
1730
|
+
// Create HNSWVerbWithMetadata (verbs don't have level field)
|
|
1731
|
+
// Convert connections from plain object to Map<number, Set<string>>
|
|
1732
|
+
const connectionsMap = new Map();
|
|
1733
|
+
if (hnswVerb.connections && typeof hnswVerb.connections === 'object') {
|
|
1734
|
+
for (const [level, ids] of Object.entries(hnswVerb.connections)) {
|
|
1735
|
+
connectionsMap.set(Number(level), new Set(ids));
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
// v4.8.0: Extract standard fields from metadata to top-level
|
|
1739
|
+
const metadataObj = (metadata || {});
|
|
1740
|
+
const { createdAt, updatedAt, confidence, weight, service, data, createdBy, ...customMetadata } = metadataObj;
|
|
1741
|
+
const verbWithMetadata = {
|
|
1742
|
+
id: hnswVerb.id,
|
|
1743
|
+
vector: [...hnswVerb.vector],
|
|
1744
|
+
connections: connectionsMap,
|
|
1745
|
+
verb: hnswVerb.verb,
|
|
1746
|
+
sourceId: hnswVerb.sourceId,
|
|
1747
|
+
targetId: hnswVerb.targetId,
|
|
1748
|
+
createdAt: createdAt || Date.now(),
|
|
1749
|
+
updatedAt: updatedAt || Date.now(),
|
|
1750
|
+
confidence: confidence,
|
|
1751
|
+
weight: weight,
|
|
1752
|
+
service: service,
|
|
1753
|
+
data: data,
|
|
1754
|
+
createdBy,
|
|
1755
|
+
metadata: customMetadata
|
|
1756
|
+
};
|
|
1757
|
+
verbs.push(verbWithMetadata);
|
|
1758
|
+
}
|
|
1759
|
+
catch (error) {
|
|
1760
|
+
console.warn(`[BaseStorage] Failed to load verb from ${path}:`, error);
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
return verbs;
|
|
1764
|
+
}
|
|
1765
|
+
/**
|
|
1766
|
+
* Delete a verb from storage (type-first path)
|
|
1767
|
+
*/
|
|
1768
|
+
async deleteVerb_internal(id) {
|
|
1769
|
+
// Try cache first
|
|
1770
|
+
const cachedType = this.verbTypeCache.get(id);
|
|
1771
|
+
if (cachedType) {
|
|
1772
|
+
const path = getVerbVectorPath(cachedType, id);
|
|
1773
|
+
// COW-aware delete (v5.0.1): Use COW helper for branch isolation
|
|
1774
|
+
await this.deleteObjectFromBranch(path);
|
|
1775
|
+
const typeIndex = TypeUtils.getVerbIndex(cachedType);
|
|
1776
|
+
if (this.verbCountsByType[typeIndex] > 0) {
|
|
1777
|
+
this.verbCountsByType[typeIndex]--;
|
|
1778
|
+
}
|
|
1779
|
+
this.verbTypeCache.delete(id);
|
|
1780
|
+
return;
|
|
1781
|
+
}
|
|
1782
|
+
// Search across all types
|
|
1783
|
+
for (let i = 0; i < VERB_TYPE_COUNT; i++) {
|
|
1784
|
+
const type = TypeUtils.getVerbFromIndex(i);
|
|
1785
|
+
const path = getVerbVectorPath(type, id);
|
|
1786
|
+
try {
|
|
1787
|
+
// COW-aware delete (v5.0.1): Use COW helper for branch isolation
|
|
1788
|
+
await this.deleteObjectFromBranch(path);
|
|
1789
|
+
if (this.verbCountsByType[i] > 0) {
|
|
1790
|
+
this.verbCountsByType[i]--;
|
|
1791
|
+
}
|
|
1792
|
+
this.verbTypeCache.delete(id);
|
|
1793
|
+
return;
|
|
1794
|
+
}
|
|
1795
|
+
catch (error) {
|
|
1796
|
+
// Continue
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1123
1799
|
}
|
|
1124
1800
|
/**
|
|
1125
1801
|
* Helper method to convert a Map to a plain object for serialization
|