@soulcraft/brainy 2.1.0 → 2.3.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/dist/augmentations/AugmentationMetadataContract.d.ts +94 -0
- package/dist/augmentations/AugmentationMetadataContract.js +306 -0
- package/dist/augmentations/apiServerAugmentation.d.ts +1 -0
- package/dist/augmentations/apiServerAugmentation.js +1 -0
- package/dist/augmentations/batchProcessingAugmentation.d.ts +1 -0
- package/dist/augmentations/batchProcessingAugmentation.js +1 -0
- package/dist/augmentations/brainyAugmentation.d.ts +16 -0
- package/dist/augmentations/cacheAugmentation.d.ts +1 -0
- package/dist/augmentations/cacheAugmentation.js +1 -0
- package/dist/augmentations/conduitAugmentations.d.ts +1 -0
- package/dist/augmentations/conduitAugmentations.js +1 -0
- package/dist/augmentations/connectionPoolAugmentation.d.ts +1 -0
- package/dist/augmentations/connectionPoolAugmentation.js +1 -0
- package/dist/augmentations/entityRegistryAugmentation.d.ts +2 -0
- package/dist/augmentations/entityRegistryAugmentation.js +2 -0
- package/dist/augmentations/indexAugmentation.d.ts +1 -0
- package/dist/augmentations/indexAugmentation.js +1 -0
- package/dist/augmentations/intelligentVerbScoringAugmentation.d.ts +4 -0
- package/dist/augmentations/intelligentVerbScoringAugmentation.js +4 -0
- package/dist/augmentations/metadataEnforcer.d.ts +20 -0
- package/dist/augmentations/metadataEnforcer.js +171 -0
- package/dist/augmentations/metricsAugmentation.d.ts +2 -7
- package/dist/augmentations/metricsAugmentation.js +1 -0
- package/dist/augmentations/monitoringAugmentation.d.ts +1 -0
- package/dist/augmentations/monitoringAugmentation.js +1 -0
- package/dist/augmentations/neuralImport.d.ts +4 -0
- package/dist/augmentations/neuralImport.js +4 -0
- package/dist/augmentations/requestDeduplicatorAugmentation.d.ts +1 -0
- package/dist/augmentations/requestDeduplicatorAugmentation.js +1 -0
- package/dist/augmentations/serverSearchAugmentations.d.ts +2 -0
- package/dist/augmentations/serverSearchAugmentations.js +2 -0
- package/dist/augmentations/storageAugmentation.d.ts +1 -0
- package/dist/augmentations/storageAugmentation.js +1 -0
- package/dist/augmentations/synapseAugmentation.d.ts +4 -0
- package/dist/augmentations/synapseAugmentation.js +4 -0
- package/dist/augmentations/walAugmentation.d.ts +1 -0
- package/dist/augmentations/walAugmentation.js +1 -0
- package/dist/brainyData.d.ts +28 -1
- package/dist/brainyData.js +229 -83
- package/dist/triple/TripleIntelligence.d.ts +4 -0
- package/dist/triple/TripleIntelligence.js +39 -9
- package/dist/utils/deletedItemsIndex.d.ts +59 -0
- package/dist/utils/deletedItemsIndex.js +98 -0
- package/dist/utils/ensureDeleted.d.ts +38 -0
- package/dist/utils/ensureDeleted.js +79 -0
- package/dist/utils/metadataFilter.js +5 -0
- package/dist/utils/metadataIndex.d.ts +4 -0
- package/dist/utils/metadataIndex.js +45 -0
- package/dist/utils/metadataNamespace.d.ts +113 -0
- package/dist/utils/metadataNamespace.js +162 -0
- package/dist/utils/periodicCleanup.d.ts +87 -0
- package/dist/utils/periodicCleanup.js +219 -0
- package/package.json +9 -3
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dedicated index for tracking soft-deleted items
|
|
3
|
+
* This is MUCH more efficient than checking every item in the database
|
|
4
|
+
*
|
|
5
|
+
* Performance characteristics:
|
|
6
|
+
* - Add deleted item: O(1)
|
|
7
|
+
* - Remove deleted item: O(1)
|
|
8
|
+
* - Check if deleted: O(1)
|
|
9
|
+
* - Get all deleted: O(d) where d = number of deleted items << total items
|
|
10
|
+
*/
|
|
11
|
+
export class DeletedItemsIndex {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.deletedIds = new Set();
|
|
14
|
+
this.deletedCount = 0;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Mark an item as deleted
|
|
18
|
+
*/
|
|
19
|
+
markDeleted(id) {
|
|
20
|
+
if (!this.deletedIds.has(id)) {
|
|
21
|
+
this.deletedIds.add(id);
|
|
22
|
+
this.deletedCount++;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Mark an item as not deleted (restored)
|
|
27
|
+
*/
|
|
28
|
+
markRestored(id) {
|
|
29
|
+
if (this.deletedIds.delete(id)) {
|
|
30
|
+
this.deletedCount--;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Check if an item is deleted - O(1)
|
|
35
|
+
*/
|
|
36
|
+
isDeleted(id) {
|
|
37
|
+
return this.deletedIds.has(id);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get all deleted item IDs - O(d)
|
|
41
|
+
*/
|
|
42
|
+
getAllDeleted() {
|
|
43
|
+
return Array.from(this.deletedIds);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Filter out deleted items from results - O(k) where k = result count
|
|
47
|
+
*/
|
|
48
|
+
filterDeleted(items) {
|
|
49
|
+
if (this.deletedCount === 0) {
|
|
50
|
+
// Fast path - no deleted items
|
|
51
|
+
return items;
|
|
52
|
+
}
|
|
53
|
+
return items.filter(item => {
|
|
54
|
+
const id = item.id;
|
|
55
|
+
return id ? !this.deletedIds.has(id) : true;
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get statistics
|
|
60
|
+
*/
|
|
61
|
+
getStats() {
|
|
62
|
+
return {
|
|
63
|
+
deletedCount: this.deletedCount,
|
|
64
|
+
memoryUsage: this.deletedCount * 100 // Rough estimate: 100 bytes per ID
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Clear all deleted items (for testing)
|
|
69
|
+
*/
|
|
70
|
+
clear() {
|
|
71
|
+
this.deletedIds.clear();
|
|
72
|
+
this.deletedCount = 0;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Serialize for persistence
|
|
76
|
+
*/
|
|
77
|
+
serialize() {
|
|
78
|
+
return JSON.stringify(Array.from(this.deletedIds));
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Deserialize from persistence
|
|
82
|
+
*/
|
|
83
|
+
deserialize(data) {
|
|
84
|
+
try {
|
|
85
|
+
const ids = JSON.parse(data);
|
|
86
|
+
this.deletedIds = new Set(ids);
|
|
87
|
+
this.deletedCount = this.deletedIds.size;
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
console.warn('Failed to deserialize deleted items index');
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Global singleton for deleted items tracking
|
|
96
|
+
*/
|
|
97
|
+
export const deletedItemsIndex = new DeletedItemsIndex();
|
|
98
|
+
//# sourceMappingURL=deletedItemsIndex.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility to ensure all metadata has the deleted field set properly
|
|
3
|
+
* This is CRITICAL for O(1) soft delete filtering performance
|
|
4
|
+
*
|
|
5
|
+
* Uses _brainy namespace to avoid conflicts with user metadata
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Ensure metadata has internal Brainy fields set
|
|
9
|
+
* @param metadata The metadata object (could be null/undefined)
|
|
10
|
+
* @param preserveExisting If true, preserve existing deleted value
|
|
11
|
+
* @returns Metadata with internal fields guaranteed
|
|
12
|
+
*/
|
|
13
|
+
export declare function ensureDeletedField(metadata: any, preserveExisting?: boolean): any;
|
|
14
|
+
/**
|
|
15
|
+
* Mark an item as soft deleted
|
|
16
|
+
* @param metadata The metadata object
|
|
17
|
+
* @returns Metadata with _brainy.deleted=true
|
|
18
|
+
*/
|
|
19
|
+
export declare function markAsDeleted(metadata: any): any;
|
|
20
|
+
/**
|
|
21
|
+
* Mark an item as restored (not deleted)
|
|
22
|
+
* @param metadata The metadata object
|
|
23
|
+
* @returns Metadata with _brainy.deleted=false
|
|
24
|
+
*/
|
|
25
|
+
export declare function markAsRestored(metadata: any): any;
|
|
26
|
+
/**
|
|
27
|
+
* Check if an item is deleted
|
|
28
|
+
* @param metadata The metadata object
|
|
29
|
+
* @returns true if deleted, false otherwise (including if field missing)
|
|
30
|
+
*/
|
|
31
|
+
export declare function isDeleted(metadata: any): boolean;
|
|
32
|
+
/**
|
|
33
|
+
* Check if an item is active (not deleted)
|
|
34
|
+
* @param metadata The metadata object
|
|
35
|
+
* @returns true if not deleted (default), false if deleted
|
|
36
|
+
*/
|
|
37
|
+
export declare function isActive(metadata: any): boolean;
|
|
38
|
+
export declare const BRAINY_DELETED_FIELD = "_brainy.deleted";
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility to ensure all metadata has the deleted field set properly
|
|
3
|
+
* This is CRITICAL for O(1) soft delete filtering performance
|
|
4
|
+
*
|
|
5
|
+
* Uses _brainy namespace to avoid conflicts with user metadata
|
|
6
|
+
*/
|
|
7
|
+
const BRAINY_NAMESPACE = '_brainy';
|
|
8
|
+
/**
|
|
9
|
+
* Ensure metadata has internal Brainy fields set
|
|
10
|
+
* @param metadata The metadata object (could be null/undefined)
|
|
11
|
+
* @param preserveExisting If true, preserve existing deleted value
|
|
12
|
+
* @returns Metadata with internal fields guaranteed
|
|
13
|
+
*/
|
|
14
|
+
export function ensureDeletedField(metadata, preserveExisting = true) {
|
|
15
|
+
// Handle null/undefined metadata
|
|
16
|
+
if (!metadata) {
|
|
17
|
+
return {
|
|
18
|
+
[BRAINY_NAMESPACE]: {
|
|
19
|
+
deleted: false,
|
|
20
|
+
version: 1
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
// Clone to avoid mutation
|
|
25
|
+
const result = { ...metadata };
|
|
26
|
+
// Ensure _brainy namespace exists
|
|
27
|
+
if (!result[BRAINY_NAMESPACE]) {
|
|
28
|
+
result[BRAINY_NAMESPACE] = {};
|
|
29
|
+
}
|
|
30
|
+
// Set deleted field if not present
|
|
31
|
+
if (!('deleted' in result[BRAINY_NAMESPACE])) {
|
|
32
|
+
result[BRAINY_NAMESPACE].deleted = false;
|
|
33
|
+
}
|
|
34
|
+
else if (!preserveExisting) {
|
|
35
|
+
// Force to false if not preserving
|
|
36
|
+
result[BRAINY_NAMESPACE].deleted = false;
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mark an item as soft deleted
|
|
42
|
+
* @param metadata The metadata object
|
|
43
|
+
* @returns Metadata with _brainy.deleted=true
|
|
44
|
+
*/
|
|
45
|
+
export function markAsDeleted(metadata) {
|
|
46
|
+
const result = ensureDeletedField(metadata);
|
|
47
|
+
result[BRAINY_NAMESPACE].deleted = true;
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Mark an item as restored (not deleted)
|
|
52
|
+
* @param metadata The metadata object
|
|
53
|
+
* @returns Metadata with _brainy.deleted=false
|
|
54
|
+
*/
|
|
55
|
+
export function markAsRestored(metadata) {
|
|
56
|
+
const result = ensureDeletedField(metadata);
|
|
57
|
+
result[BRAINY_NAMESPACE].deleted = false;
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if an item is deleted
|
|
62
|
+
* @param metadata The metadata object
|
|
63
|
+
* @returns true if deleted, false otherwise (including if field missing)
|
|
64
|
+
*/
|
|
65
|
+
export function isDeleted(metadata) {
|
|
66
|
+
return metadata?.[BRAINY_NAMESPACE]?.deleted === true;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if an item is active (not deleted)
|
|
70
|
+
* @param metadata The metadata object
|
|
71
|
+
* @returns true if not deleted (default), false if deleted
|
|
72
|
+
*/
|
|
73
|
+
export function isActive(metadata) {
|
|
74
|
+
// If no deleted field or deleted=false, item is active
|
|
75
|
+
return !isDeleted(metadata);
|
|
76
|
+
}
|
|
77
|
+
// Export the namespace constant for use in queries
|
|
78
|
+
export const BRAINY_DELETED_FIELD = `${BRAINY_NAMESPACE}.deleted`;
|
|
79
|
+
//# sourceMappingURL=ensureDeleted.js.map
|
|
@@ -24,8 +24,13 @@ function matchesQuery(value, query) {
|
|
|
24
24
|
case 'notEquals':
|
|
25
25
|
case 'isNot':
|
|
26
26
|
case 'ne':
|
|
27
|
+
// Special handling: if value is undefined and operand is not undefined,
|
|
28
|
+
// they are not equal (so the condition passes)
|
|
29
|
+
// This ensures items without a 'deleted' field match 'deleted !== true'
|
|
27
30
|
if (value === operand)
|
|
28
31
|
return false;
|
|
32
|
+
// If value is undefined and operand is not, they're not equal (pass)
|
|
33
|
+
// If both are undefined, they're equal (fail, handled above)
|
|
29
34
|
break;
|
|
30
35
|
// Comparison operators
|
|
31
36
|
case 'greaterThan':
|
|
@@ -107,6 +107,10 @@ export declare class MetadataIndexManager {
|
|
|
107
107
|
* Remove item from metadata indexes
|
|
108
108
|
*/
|
|
109
109
|
removeFromIndex(id: string, metadata?: any): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* Get all IDs in the index
|
|
112
|
+
*/
|
|
113
|
+
getAllIds(): Promise<string[]>;
|
|
110
114
|
/**
|
|
111
115
|
* Get IDs for a specific field-value combination with caching
|
|
112
116
|
*/
|
|
@@ -412,6 +412,36 @@ export class MetadataIndexManager {
|
|
|
412
412
|
}
|
|
413
413
|
}
|
|
414
414
|
}
|
|
415
|
+
/**
|
|
416
|
+
* Get all IDs in the index
|
|
417
|
+
*/
|
|
418
|
+
async getAllIds() {
|
|
419
|
+
// Collect all unique IDs from all index entries
|
|
420
|
+
const allIds = new Set();
|
|
421
|
+
// First, add all IDs from the in-memory cache
|
|
422
|
+
for (const entry of this.indexCache.values()) {
|
|
423
|
+
entry.ids.forEach(id => allIds.add(id));
|
|
424
|
+
}
|
|
425
|
+
// If storage has a method to get all nouns, use it as the source of truth
|
|
426
|
+
// This ensures we include items that might not be indexed yet
|
|
427
|
+
if (this.storage && typeof this.storage.getNouns === 'function') {
|
|
428
|
+
try {
|
|
429
|
+
const result = await this.storage.getNouns({
|
|
430
|
+
pagination: { limit: 100000 }
|
|
431
|
+
});
|
|
432
|
+
if (result && result.items) {
|
|
433
|
+
result.items.forEach((item) => {
|
|
434
|
+
if (item.id)
|
|
435
|
+
allIds.add(item.id);
|
|
436
|
+
});
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
// Fall back to using only indexed IDs
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return Array.from(allIds);
|
|
444
|
+
}
|
|
415
445
|
/**
|
|
416
446
|
* Get IDs for a specific field-value combination with caching
|
|
417
447
|
*/
|
|
@@ -638,6 +668,21 @@ export class MetadataIndexManager {
|
|
|
638
668
|
fieldResults = Array.from(allIds);
|
|
639
669
|
}
|
|
640
670
|
break;
|
|
671
|
+
// Negation operators
|
|
672
|
+
case 'notEquals':
|
|
673
|
+
case 'isNot':
|
|
674
|
+
case 'ne':
|
|
675
|
+
// For notEquals, we need all IDs EXCEPT those matching the value
|
|
676
|
+
// This is especially important for soft delete: deleted !== true
|
|
677
|
+
// should include items without a deleted field
|
|
678
|
+
// First, get all IDs in the database
|
|
679
|
+
const allItemIds = await this.getAllIds();
|
|
680
|
+
// Then get IDs that match the value we want to exclude
|
|
681
|
+
const excludeIds = await this.getIds(field, operand);
|
|
682
|
+
const excludeSet = new Set(excludeIds);
|
|
683
|
+
// Return all IDs except those to exclude
|
|
684
|
+
fieldResults = allItemIds.filter(id => !excludeSet.has(id));
|
|
685
|
+
break;
|
|
641
686
|
}
|
|
642
687
|
}
|
|
643
688
|
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean Metadata Architecture for Brainy 2.2
|
|
3
|
+
* No backward compatibility - doing it RIGHT from the start!
|
|
4
|
+
*/
|
|
5
|
+
export declare const BRAINY_NS: "_brainy";
|
|
6
|
+
export declare const AUG_NS: "_augmentations";
|
|
7
|
+
export declare const AUDIT_NS: "_audit";
|
|
8
|
+
export declare const DELETED_FIELD: "_brainy.deleted";
|
|
9
|
+
export declare const INDEXED_FIELD: "_brainy.indexed";
|
|
10
|
+
export declare const VERSION_FIELD: "_brainy.version";
|
|
11
|
+
/**
|
|
12
|
+
* Internal Brainy metadata structure
|
|
13
|
+
* These fields are ALWAYS present and indexed for O(1) access
|
|
14
|
+
*/
|
|
15
|
+
export interface BrainyInternalMetadata {
|
|
16
|
+
deleted: boolean;
|
|
17
|
+
indexed: boolean;
|
|
18
|
+
version: number;
|
|
19
|
+
created: number;
|
|
20
|
+
updated: number;
|
|
21
|
+
partition?: number;
|
|
22
|
+
domain?: string;
|
|
23
|
+
priority?: number;
|
|
24
|
+
ttl?: number;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Complete metadata structure with namespaces
|
|
28
|
+
*/
|
|
29
|
+
export interface NamespacedMetadata<T = any> {
|
|
30
|
+
[key: string]: any;
|
|
31
|
+
[BRAINY_NS]: BrainyInternalMetadata;
|
|
32
|
+
[AUG_NS]?: {
|
|
33
|
+
[augmentationName: string]: any;
|
|
34
|
+
};
|
|
35
|
+
[AUDIT_NS]?: Array<{
|
|
36
|
+
timestamp: number;
|
|
37
|
+
augmentation: string;
|
|
38
|
+
field: string;
|
|
39
|
+
oldValue: any;
|
|
40
|
+
newValue: any;
|
|
41
|
+
}>;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Create properly namespaced metadata
|
|
45
|
+
* This is called for EVERY noun/verb creation
|
|
46
|
+
*/
|
|
47
|
+
export declare function createNamespacedMetadata<T = any>(userMetadata?: T): NamespacedMetadata<T>;
|
|
48
|
+
/**
|
|
49
|
+
* Update metadata while preserving namespaces
|
|
50
|
+
*/
|
|
51
|
+
export declare function updateNamespacedMetadata<T = any>(existing: NamespacedMetadata<T>, updates: Partial<T>): NamespacedMetadata<T>;
|
|
52
|
+
/**
|
|
53
|
+
* Soft delete a noun (O(1) operation)
|
|
54
|
+
*/
|
|
55
|
+
export declare function markDeleted<T = any>(metadata: NamespacedMetadata<T>): NamespacedMetadata<T>;
|
|
56
|
+
/**
|
|
57
|
+
* Restore a soft-deleted noun (O(1) operation)
|
|
58
|
+
*/
|
|
59
|
+
export declare function markRestored<T = any>(metadata: NamespacedMetadata<T>): NamespacedMetadata<T>;
|
|
60
|
+
/**
|
|
61
|
+
* Check if a noun is deleted (O(1) check)
|
|
62
|
+
*/
|
|
63
|
+
export declare function isDeleted<T = any>(metadata: NamespacedMetadata<T>): boolean;
|
|
64
|
+
/**
|
|
65
|
+
* Get user metadata without internal fields
|
|
66
|
+
* Used by augmentations to get clean user data
|
|
67
|
+
*/
|
|
68
|
+
export declare function getUserMetadata<T = any>(metadata: NamespacedMetadata<T>): T;
|
|
69
|
+
/**
|
|
70
|
+
* Set augmentation data in isolated namespace
|
|
71
|
+
*/
|
|
72
|
+
export declare function setAugmentationData<T = any>(metadata: NamespacedMetadata<T>, augmentationName: string, data: any): NamespacedMetadata<T>;
|
|
73
|
+
/**
|
|
74
|
+
* Add audit entry for tracking
|
|
75
|
+
*/
|
|
76
|
+
export declare function addAuditEntry<T = any>(metadata: NamespacedMetadata<T>, entry: {
|
|
77
|
+
augmentation: string;
|
|
78
|
+
field: string;
|
|
79
|
+
oldValue: any;
|
|
80
|
+
newValue: any;
|
|
81
|
+
}): NamespacedMetadata<T>;
|
|
82
|
+
/**
|
|
83
|
+
* INDEXING EXPLANATION:
|
|
84
|
+
*
|
|
85
|
+
* The MetadataIndex flattens nested objects into dot-notation keys:
|
|
86
|
+
*
|
|
87
|
+
* Input metadata:
|
|
88
|
+
* {
|
|
89
|
+
* name: "Django",
|
|
90
|
+
* _brainy: {
|
|
91
|
+
* deleted: false,
|
|
92
|
+
* indexed: true
|
|
93
|
+
* }
|
|
94
|
+
* }
|
|
95
|
+
*
|
|
96
|
+
* Creates index entries:
|
|
97
|
+
* - "name" -> "django" -> Set([id1, id2...])
|
|
98
|
+
* - "_brainy.deleted" -> "false" -> Set([id1, id2...]) // O(1) lookup!
|
|
99
|
+
* - "_brainy.indexed" -> "true" -> Set([id1, id2...])
|
|
100
|
+
*
|
|
101
|
+
* Query: { "_brainy.deleted": false }
|
|
102
|
+
* Lookup: index["_brainy.deleted"]["false"] -> Set of IDs in O(1)
|
|
103
|
+
*
|
|
104
|
+
* This is why namespacing doesn't hurt performance - it's all flattened!
|
|
105
|
+
*/
|
|
106
|
+
/**
|
|
107
|
+
* Fields that should ALWAYS be indexed for O(1) access
|
|
108
|
+
*/
|
|
109
|
+
export declare const ALWAYS_INDEXED_FIELDS: ("_brainy.deleted" | "_brainy.indexed" | "_brainy.version")[];
|
|
110
|
+
/**
|
|
111
|
+
* Fields that should use sorted index for O(log n) range queries
|
|
112
|
+
*/
|
|
113
|
+
export declare const SORTED_INDEX_FIELDS: string[];
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Clean Metadata Architecture for Brainy 2.2
|
|
3
|
+
* No backward compatibility - doing it RIGHT from the start!
|
|
4
|
+
*/
|
|
5
|
+
// Namespace constants
|
|
6
|
+
export const BRAINY_NS = '_brainy';
|
|
7
|
+
export const AUG_NS = '_augmentations';
|
|
8
|
+
export const AUDIT_NS = '_audit';
|
|
9
|
+
// Field paths for O(1) indexing
|
|
10
|
+
export const DELETED_FIELD = `${BRAINY_NS}.deleted`;
|
|
11
|
+
export const INDEXED_FIELD = `${BRAINY_NS}.indexed`;
|
|
12
|
+
export const VERSION_FIELD = `${BRAINY_NS}.version`;
|
|
13
|
+
/**
|
|
14
|
+
* Create properly namespaced metadata
|
|
15
|
+
* This is called for EVERY noun/verb creation
|
|
16
|
+
*/
|
|
17
|
+
export function createNamespacedMetadata(userMetadata) {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
// Start with user metadata or empty object
|
|
20
|
+
const result = userMetadata ? { ...userMetadata } : {};
|
|
21
|
+
// ALWAYS add internal namespace with required fields
|
|
22
|
+
result[BRAINY_NS] = {
|
|
23
|
+
deleted: false, // CRITICAL: Always false for new items
|
|
24
|
+
indexed: true, // New items are indexed
|
|
25
|
+
version: 1, // Current schema version
|
|
26
|
+
created: now,
|
|
27
|
+
updated: now
|
|
28
|
+
};
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Update metadata while preserving namespaces
|
|
33
|
+
*/
|
|
34
|
+
export function updateNamespacedMetadata(existing, updates) {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
// Merge user fields
|
|
37
|
+
const result = {
|
|
38
|
+
...existing,
|
|
39
|
+
...updates
|
|
40
|
+
};
|
|
41
|
+
// Preserve internal namespace but update timestamp
|
|
42
|
+
result[BRAINY_NS] = {
|
|
43
|
+
...existing[BRAINY_NS],
|
|
44
|
+
updated: now
|
|
45
|
+
};
|
|
46
|
+
// Preserve augmentation namespace
|
|
47
|
+
if (existing[AUG_NS]) {
|
|
48
|
+
result[AUG_NS] = existing[AUG_NS];
|
|
49
|
+
}
|
|
50
|
+
// Preserve audit trail
|
|
51
|
+
if (existing[AUDIT_NS]) {
|
|
52
|
+
result[AUDIT_NS] = existing[AUDIT_NS];
|
|
53
|
+
}
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Soft delete a noun (O(1) operation)
|
|
58
|
+
*/
|
|
59
|
+
export function markDeleted(metadata) {
|
|
60
|
+
return {
|
|
61
|
+
...metadata,
|
|
62
|
+
[BRAINY_NS]: {
|
|
63
|
+
...metadata[BRAINY_NS],
|
|
64
|
+
deleted: true,
|
|
65
|
+
updated: Date.now()
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Restore a soft-deleted noun (O(1) operation)
|
|
71
|
+
*/
|
|
72
|
+
export function markRestored(metadata) {
|
|
73
|
+
return {
|
|
74
|
+
...metadata,
|
|
75
|
+
[BRAINY_NS]: {
|
|
76
|
+
...metadata[BRAINY_NS],
|
|
77
|
+
deleted: false,
|
|
78
|
+
updated: Date.now()
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Check if a noun is deleted (O(1) check)
|
|
84
|
+
*/
|
|
85
|
+
export function isDeleted(metadata) {
|
|
86
|
+
return metadata[BRAINY_NS]?.deleted === true;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Get user metadata without internal fields
|
|
90
|
+
* Used by augmentations to get clean user data
|
|
91
|
+
*/
|
|
92
|
+
export function getUserMetadata(metadata) {
|
|
93
|
+
const { [BRAINY_NS]: _, [AUG_NS]: __, [AUDIT_NS]: ___, ...userMeta } = metadata;
|
|
94
|
+
return userMeta;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Set augmentation data in isolated namespace
|
|
98
|
+
*/
|
|
99
|
+
export function setAugmentationData(metadata, augmentationName, data) {
|
|
100
|
+
const result = { ...metadata };
|
|
101
|
+
if (!result[AUG_NS]) {
|
|
102
|
+
result[AUG_NS] = {};
|
|
103
|
+
}
|
|
104
|
+
result[AUG_NS][augmentationName] = data;
|
|
105
|
+
return result;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Add audit entry for tracking
|
|
109
|
+
*/
|
|
110
|
+
export function addAuditEntry(metadata, entry) {
|
|
111
|
+
const result = { ...metadata };
|
|
112
|
+
if (!result[AUDIT_NS]) {
|
|
113
|
+
result[AUDIT_NS] = [];
|
|
114
|
+
}
|
|
115
|
+
result[AUDIT_NS].push({
|
|
116
|
+
...entry,
|
|
117
|
+
timestamp: Date.now()
|
|
118
|
+
});
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* INDEXING EXPLANATION:
|
|
123
|
+
*
|
|
124
|
+
* The MetadataIndex flattens nested objects into dot-notation keys:
|
|
125
|
+
*
|
|
126
|
+
* Input metadata:
|
|
127
|
+
* {
|
|
128
|
+
* name: "Django",
|
|
129
|
+
* _brainy: {
|
|
130
|
+
* deleted: false,
|
|
131
|
+
* indexed: true
|
|
132
|
+
* }
|
|
133
|
+
* }
|
|
134
|
+
*
|
|
135
|
+
* Creates index entries:
|
|
136
|
+
* - "name" -> "django" -> Set([id1, id2...])
|
|
137
|
+
* - "_brainy.deleted" -> "false" -> Set([id1, id2...]) // O(1) lookup!
|
|
138
|
+
* - "_brainy.indexed" -> "true" -> Set([id1, id2...])
|
|
139
|
+
*
|
|
140
|
+
* Query: { "_brainy.deleted": false }
|
|
141
|
+
* Lookup: index["_brainy.deleted"]["false"] -> Set of IDs in O(1)
|
|
142
|
+
*
|
|
143
|
+
* This is why namespacing doesn't hurt performance - it's all flattened!
|
|
144
|
+
*/
|
|
145
|
+
/**
|
|
146
|
+
* Fields that should ALWAYS be indexed for O(1) access
|
|
147
|
+
*/
|
|
148
|
+
export const ALWAYS_INDEXED_FIELDS = [
|
|
149
|
+
DELETED_FIELD, // For soft delete filtering
|
|
150
|
+
INDEXED_FIELD, // For index management
|
|
151
|
+
VERSION_FIELD // For schema versioning
|
|
152
|
+
];
|
|
153
|
+
/**
|
|
154
|
+
* Fields that should use sorted index for O(log n) range queries
|
|
155
|
+
*/
|
|
156
|
+
export const SORTED_INDEX_FIELDS = [
|
|
157
|
+
`${BRAINY_NS}.created`,
|
|
158
|
+
`${BRAINY_NS}.updated`,
|
|
159
|
+
`${BRAINY_NS}.priority`,
|
|
160
|
+
`${BRAINY_NS}.ttl`
|
|
161
|
+
];
|
|
162
|
+
//# sourceMappingURL=metadataNamespace.js.map
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Periodic Cleanup for Soft-Deleted Items
|
|
3
|
+
*
|
|
4
|
+
* SAFETY-FIRST APPROACH:
|
|
5
|
+
* - Maintains durability guarantees (storage-first)
|
|
6
|
+
* - Coordinates HNSW and metadata index consistency
|
|
7
|
+
* - Isolated from live operations
|
|
8
|
+
* - Graceful failure handling
|
|
9
|
+
*/
|
|
10
|
+
import type { StorageAdapter } from '../coreTypes.js';
|
|
11
|
+
import type { HNSWIndex } from '../hnsw/hnswIndex.js';
|
|
12
|
+
import type { MetadataIndexManager } from './metadataIndex.js';
|
|
13
|
+
export interface CleanupConfig {
|
|
14
|
+
/** Age in milliseconds after which soft-deleted items are eligible for cleanup */
|
|
15
|
+
maxAge: number;
|
|
16
|
+
/** Maximum number of items to clean up in one batch */
|
|
17
|
+
batchSize: number;
|
|
18
|
+
/** Interval between cleanup runs (milliseconds) */
|
|
19
|
+
cleanupInterval: number;
|
|
20
|
+
/** Whether to run cleanup automatically */
|
|
21
|
+
enabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface CleanupStats {
|
|
24
|
+
itemsProcessed: number;
|
|
25
|
+
itemsDeleted: number;
|
|
26
|
+
errors: number;
|
|
27
|
+
lastRun: number;
|
|
28
|
+
nextRun: number;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Coordinates safe cleanup of old soft-deleted items across all indexes
|
|
32
|
+
*
|
|
33
|
+
* CRITICAL SAFETY FEATURES:
|
|
34
|
+
* 1. Storage-first deletion (durability)
|
|
35
|
+
* 2. Index consistency coordination
|
|
36
|
+
* 3. Batch processing with limits
|
|
37
|
+
* 4. Error isolation and recovery
|
|
38
|
+
*/
|
|
39
|
+
export declare class PeriodicCleanup {
|
|
40
|
+
private storage;
|
|
41
|
+
private hnswIndex;
|
|
42
|
+
private metadataIndex;
|
|
43
|
+
private config;
|
|
44
|
+
private stats;
|
|
45
|
+
private cleanupTimer;
|
|
46
|
+
private running;
|
|
47
|
+
constructor(storage: StorageAdapter, hnswIndex: HNSWIndex, metadataIndex: MetadataIndexManager | null, config?: Partial<CleanupConfig>);
|
|
48
|
+
/**
|
|
49
|
+
* Start periodic cleanup
|
|
50
|
+
*/
|
|
51
|
+
start(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Stop periodic cleanup
|
|
54
|
+
*/
|
|
55
|
+
stop(): void;
|
|
56
|
+
/**
|
|
57
|
+
* Run cleanup manually
|
|
58
|
+
*/
|
|
59
|
+
runNow(): Promise<CleanupStats>;
|
|
60
|
+
/**
|
|
61
|
+
* Get current cleanup statistics
|
|
62
|
+
*/
|
|
63
|
+
getStats(): CleanupStats;
|
|
64
|
+
private scheduleNext;
|
|
65
|
+
/**
|
|
66
|
+
* CRITICAL: Coordinated cleanup across all indexes
|
|
67
|
+
*
|
|
68
|
+
* SAFETY PROTOCOL:
|
|
69
|
+
* 1. Find eligible items (old + soft-deleted)
|
|
70
|
+
* 2. Remove from storage FIRST (durability)
|
|
71
|
+
* 3. Remove from HNSW (graph consistency)
|
|
72
|
+
* 4. Remove from metadata index (search consistency)
|
|
73
|
+
* 5. Track stats and errors
|
|
74
|
+
*/
|
|
75
|
+
private performCleanup;
|
|
76
|
+
/**
|
|
77
|
+
* Find items eligible for cleanup (old + soft-deleted)
|
|
78
|
+
*/
|
|
79
|
+
private findEligibleItems;
|
|
80
|
+
/**
|
|
81
|
+
* Process a batch of items for cleanup
|
|
82
|
+
*
|
|
83
|
+
* CRITICAL: This maintains the durability-first approach:
|
|
84
|
+
* Storage → HNSW → Metadata Index
|
|
85
|
+
*/
|
|
86
|
+
private processBatch;
|
|
87
|
+
}
|