@soulcraft/brainy 5.2.0 → 5.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/CHANGELOG.md CHANGED
@@ -2,6 +2,11 @@
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.0](https://github.com/soulcraftlabs/brainy/compare/v5.2.1...v5.3.0) (2025-11-04)
6
+
7
+ - feat: add entity versioning system with critical bug fixes (v5.3.0) (c488fa8)
8
+
9
+
5
10
  ### [5.2.0](https://github.com/soulcraftlabs/brainy/compare/v5.1.2...v5.2.0) (2025-11-03)
6
11
 
7
12
  - fix: update VFS test for v5.2.0 BlobStorage architecture (b3e3e5c)
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Versioning Augmentation (v5.3.0)
3
+ *
4
+ * Provides automatic entity versioning with configurable policies:
5
+ * - Auto-save on update()
6
+ * - Entity filtering
7
+ * - Retention policies
8
+ * - Event hooks
9
+ *
10
+ * NO MOCKS - Production implementation
11
+ */
12
+ import { BaseAugmentation } from './brainyAugmentation.js';
13
+ import { AugmentationManifest } from './manifest.js';
14
+ import type { SaveVersionOptions } from '../versioning/VersionManager.js';
15
+ export interface VersioningAugmentationConfig {
16
+ /** Enable auto-versioning */
17
+ enabled?: boolean;
18
+ /** Auto-save on update() operations */
19
+ onUpdate?: boolean;
20
+ /** Auto-save on add() operations (rarely needed) */
21
+ onAdd?: boolean;
22
+ /** Entity ID patterns to version (glob patterns) */
23
+ entities?: string[];
24
+ /** Entity ID patterns to exclude (glob patterns) */
25
+ excludeEntities?: string[];
26
+ /** Entity types to version */
27
+ types?: string[];
28
+ /** Entity types to exclude */
29
+ excludeTypes?: string[];
30
+ /** Retention policy: Keep N recent versions per entity */
31
+ keepRecent?: number;
32
+ /** Retention policy: Keep versions newer than timestamp */
33
+ keepAfter?: number;
34
+ /** Retention policy: Keep tagged versions (default: true) */
35
+ keepTagged?: boolean;
36
+ /** Tag prefix for auto-generated versions (default: 'auto-') */
37
+ tagPrefix?: string;
38
+ /** Auto-prune old versions after save */
39
+ autoPrune?: boolean;
40
+ /** Prune interval in milliseconds (default: 1 hour) */
41
+ pruneInterval?: number;
42
+ /** Event hooks */
43
+ hooks?: {
44
+ /** Called before saving version */
45
+ beforeSave?: (entityId: string, options: SaveVersionOptions) => Promise<SaveVersionOptions | null>;
46
+ /** Called after saving version */
47
+ afterSave?: (entityId: string, version: any) => Promise<void>;
48
+ /** Called before pruning */
49
+ beforePrune?: (entityId: string) => Promise<boolean>;
50
+ /** Called after pruning */
51
+ afterPrune?: (entityId: string, deleted: number) => Promise<void>;
52
+ };
53
+ }
54
+ /**
55
+ * Versioning Augmentation
56
+ *
57
+ * Automatically versions entities based on configurable policies.
58
+ */
59
+ export declare class VersioningAugmentation extends BaseAugmentation {
60
+ readonly name = "versioning";
61
+ readonly timing: "after";
62
+ readonly metadata: "readonly";
63
+ operations: any;
64
+ readonly priority = 50;
65
+ readonly category: "core";
66
+ readonly description = "Automatic entity versioning with configurable retention policies";
67
+ private versioningAPI?;
68
+ private brain?;
69
+ private pruneTimer?;
70
+ private versionCount;
71
+ constructor(config?: VersioningAugmentationConfig);
72
+ getManifest(): AugmentationManifest;
73
+ onAttach(brain: any): Promise<void>;
74
+ onDetach(): Promise<void>;
75
+ /**
76
+ * Execute method (required by BaseAugmentation)
77
+ */
78
+ execute(operation: string, params: any[], context: any): Promise<any>;
79
+ /**
80
+ * After operation hook - auto-save versions
81
+ */
82
+ after(operation: string, params: any[], result: any): Promise<any>;
83
+ /**
84
+ * Extract entity ID from operation params
85
+ */
86
+ private extractEntityId;
87
+ /**
88
+ * Check if entity should be versioned based on filters
89
+ */
90
+ private shouldVersionEntity;
91
+ /**
92
+ * Simple glob pattern matching
93
+ */
94
+ private matchPattern;
95
+ /**
96
+ * Prune old versions for an entity
97
+ */
98
+ private pruneEntity;
99
+ /**
100
+ * Start auto-prune timer
101
+ */
102
+ private startAutoPrune;
103
+ /**
104
+ * Prune all versioned entities
105
+ */
106
+ private pruneAllEntities;
107
+ /**
108
+ * Get augmentation statistics
109
+ */
110
+ getStats(): {
111
+ enabled: boolean;
112
+ versionsCreated: number;
113
+ entitiesPattern: string[];
114
+ excludePattern: string[];
115
+ retention: number;
116
+ };
117
+ }
118
+ /**
119
+ * Factory function for easy augmentation creation
120
+ */
121
+ export declare function createVersioningAugmentation(config?: VersioningAugmentationConfig): VersioningAugmentation;
@@ -0,0 +1,418 @@
1
+ /**
2
+ * Versioning Augmentation (v5.3.0)
3
+ *
4
+ * Provides automatic entity versioning with configurable policies:
5
+ * - Auto-save on update()
6
+ * - Entity filtering
7
+ * - Retention policies
8
+ * - Event hooks
9
+ *
10
+ * NO MOCKS - Production implementation
11
+ */
12
+ import { BaseAugmentation } from './brainyAugmentation.js';
13
+ /**
14
+ * Versioning Augmentation
15
+ *
16
+ * Automatically versions entities based on configurable policies.
17
+ */
18
+ export class VersioningAugmentation extends BaseAugmentation {
19
+ constructor(config = {}) {
20
+ super(config);
21
+ this.name = 'versioning';
22
+ this.timing = 'after'; // Run after operations complete
23
+ this.metadata = 'readonly';
24
+ this.operations = ['update', 'add']; // Version on update/add
25
+ this.priority = 50; // Medium priority
26
+ // Augmentation metadata
27
+ this.category = 'core';
28
+ this.description = 'Automatic entity versioning with configurable retention policies';
29
+ this.versionCount = 0;
30
+ // Merge with defaults
31
+ this.config = {
32
+ enabled: config.enabled ?? true,
33
+ onUpdate: config.onUpdate ?? true,
34
+ onAdd: config.onAdd ?? false,
35
+ entities: config.entities ?? ['*'], // Version all entities by default
36
+ excludeEntities: config.excludeEntities ?? ['_version:*'], // Exclude version metadata entities
37
+ types: config.types ?? [], // Empty = all types
38
+ excludeTypes: config.excludeTypes ?? [], // No need to exclude types (versions use 'state' type)
39
+ keepRecent: config.keepRecent ?? 10,
40
+ keepTagged: config.keepTagged ?? true,
41
+ tagPrefix: config.tagPrefix ?? 'auto-',
42
+ autoPrune: config.autoPrune ?? true,
43
+ pruneInterval: config.pruneInterval ?? 60 * 60 * 1000, // 1 hour
44
+ hooks: config.hooks ?? {}
45
+ };
46
+ }
47
+ getManifest() {
48
+ return {
49
+ id: 'versioning',
50
+ name: 'Entity Versioning',
51
+ version: '5.3.0',
52
+ description: 'Automatic entity versioning with retention policies',
53
+ longDescription: 'Provides automatic versioning of entities on update/add operations with configurable retention policies, entity filtering, and event hooks.',
54
+ category: 'storage',
55
+ configSchema: {
56
+ type: 'object',
57
+ properties: {
58
+ enabled: {
59
+ type: 'boolean',
60
+ default: true,
61
+ description: 'Enable automatic versioning'
62
+ },
63
+ onUpdate: {
64
+ type: 'boolean',
65
+ default: true,
66
+ description: 'Auto-save version on update()'
67
+ },
68
+ onAdd: {
69
+ type: 'boolean',
70
+ default: false,
71
+ description: 'Auto-save version on add()'
72
+ },
73
+ entities: {
74
+ type: 'array',
75
+ items: { type: 'string' },
76
+ default: ['*'],
77
+ description: 'Entity ID patterns to version (glob patterns)'
78
+ },
79
+ excludeEntities: {
80
+ type: 'array',
81
+ items: { type: 'string' },
82
+ default: ['_version:*'],
83
+ description: 'Entity ID patterns to exclude (version metadata IDs start with _version:)'
84
+ },
85
+ types: {
86
+ type: 'array',
87
+ items: { type: 'string' },
88
+ default: [],
89
+ description: 'Entity types to version (empty = all)'
90
+ },
91
+ excludeTypes: {
92
+ type: 'array',
93
+ items: { type: 'string' },
94
+ default: [],
95
+ description: 'Entity types to exclude (versions use standard state type)'
96
+ },
97
+ keepRecent: {
98
+ type: 'number',
99
+ default: 10,
100
+ minimum: 1,
101
+ description: 'Keep N recent versions per entity'
102
+ },
103
+ keepTagged: {
104
+ type: 'boolean',
105
+ default: true,
106
+ description: 'Keep tagged versions during pruning'
107
+ },
108
+ tagPrefix: {
109
+ type: 'string',
110
+ default: 'auto-',
111
+ description: 'Tag prefix for auto-generated versions'
112
+ },
113
+ autoPrune: {
114
+ type: 'boolean',
115
+ default: true,
116
+ description: 'Automatically prune old versions'
117
+ },
118
+ pruneInterval: {
119
+ type: 'number',
120
+ default: 3600000,
121
+ description: 'Prune interval in milliseconds (default: 1 hour)'
122
+ }
123
+ }
124
+ },
125
+ configDefaults: {
126
+ enabled: true,
127
+ onUpdate: true,
128
+ onAdd: false,
129
+ entities: ['*'],
130
+ excludeEntities: ['_version:*'],
131
+ types: [],
132
+ excludeTypes: [],
133
+ keepRecent: 10,
134
+ keepTagged: true,
135
+ tagPrefix: 'auto-',
136
+ autoPrune: true,
137
+ pruneInterval: 3600000
138
+ },
139
+ minBrainyVersion: '5.3.0',
140
+ keywords: ['versioning', 'history', 'audit', 'backup'],
141
+ documentation: 'https://docs.brainy.dev/versioning',
142
+ status: 'stable',
143
+ performance: {
144
+ memoryUsage: 'low',
145
+ cpuUsage: 'low',
146
+ networkUsage: 'none'
147
+ },
148
+ features: [
149
+ 'auto-versioning',
150
+ 'retention-policies',
151
+ 'entity-filtering',
152
+ 'event-hooks'
153
+ ],
154
+ enhancedOperations: ['update', 'add'],
155
+ metrics: [
156
+ {
157
+ name: 'versions_created',
158
+ type: 'counter',
159
+ description: 'Total versions created by augmentation'
160
+ },
161
+ {
162
+ name: 'versions_pruned',
163
+ type: 'counter',
164
+ description: 'Total versions pruned by retention policies'
165
+ }
166
+ ]
167
+ };
168
+ }
169
+ async onAttach(brain) {
170
+ // Store brain instance for entity fetching
171
+ this.brain = brain;
172
+ // Get VersioningAPI instance
173
+ this.versioningAPI = brain.versions;
174
+ // Start auto-prune timer if enabled
175
+ if (this.config.autoPrune && this.config.pruneInterval) {
176
+ this.startAutoPrune();
177
+ }
178
+ }
179
+ async onDetach() {
180
+ // Stop auto-prune timer
181
+ if (this.pruneTimer) {
182
+ clearInterval(this.pruneTimer);
183
+ this.pruneTimer = undefined;
184
+ }
185
+ }
186
+ /**
187
+ * Execute method (required by BaseAugmentation)
188
+ */
189
+ async execute(operation, params, context) {
190
+ // Versioning augmentation only runs in 'after' mode
191
+ // Actual logic is in after() method
192
+ return context.originalResult;
193
+ }
194
+ /**
195
+ * After operation hook - auto-save versions
196
+ */
197
+ async after(operation, params, result) {
198
+ if (!this.config.enabled || !this.versioningAPI) {
199
+ return result;
200
+ }
201
+ // Check if we should version this operation
202
+ if (operation === 'update' && !this.config.onUpdate) {
203
+ return result;
204
+ }
205
+ if (operation === 'add' && !this.config.onAdd) {
206
+ return result;
207
+ }
208
+ // Extract entity ID from params
209
+ const entityId = this.extractEntityId(operation, params);
210
+ if (!entityId) {
211
+ return result;
212
+ }
213
+ // Check if entity should be versioned
214
+ if (!(await this.shouldVersionEntity(entityId))) {
215
+ return result;
216
+ }
217
+ try {
218
+ // Prepare save options
219
+ let saveOptions = {
220
+ tag: `${this.config.tagPrefix}${Date.now()}`,
221
+ description: `Auto-saved after ${operation}`,
222
+ metadata: {
223
+ augmentation: 'versioning',
224
+ operation,
225
+ timestamp: Date.now()
226
+ }
227
+ };
228
+ // Call before-save hook
229
+ if (this.config.hooks?.beforeSave) {
230
+ const modifiedOptions = await this.config.hooks.beforeSave(entityId, saveOptions);
231
+ if (modifiedOptions === null) {
232
+ // Hook cancelled the save
233
+ return result;
234
+ }
235
+ saveOptions = modifiedOptions;
236
+ }
237
+ // Save version
238
+ const version = await this.versioningAPI.save(entityId, saveOptions);
239
+ this.versionCount++;
240
+ // Call after-save hook
241
+ if (this.config.hooks?.afterSave) {
242
+ await this.config.hooks.afterSave(entityId, version);
243
+ }
244
+ // Auto-prune if enabled
245
+ if (this.config.autoPrune) {
246
+ await this.pruneEntity(entityId);
247
+ }
248
+ }
249
+ catch (error) {
250
+ // Don't fail the operation if versioning fails
251
+ console.error(`Versioning augmentation error for ${entityId}:`, error);
252
+ }
253
+ return result;
254
+ }
255
+ /**
256
+ * Extract entity ID from operation params
257
+ */
258
+ extractEntityId(operation, params) {
259
+ if (operation === 'update') {
260
+ // update(id, data) or update({ id, ... })
261
+ if (typeof params[0] === 'string') {
262
+ return params[0];
263
+ }
264
+ if (params[0]?.id) {
265
+ return params[0].id;
266
+ }
267
+ }
268
+ if (operation === 'add') {
269
+ // add(data) - ID might be in data or result
270
+ if (params[0]?.id) {
271
+ return params[0].id;
272
+ }
273
+ }
274
+ return null;
275
+ }
276
+ /**
277
+ * Check if entity should be versioned based on filters
278
+ */
279
+ async shouldVersionEntity(entityId) {
280
+ // Check exclude patterns first (ID-based)
281
+ if (this.config.excludeEntities) {
282
+ for (const pattern of this.config.excludeEntities) {
283
+ if (this.matchPattern(entityId, pattern)) {
284
+ return false;
285
+ }
286
+ }
287
+ }
288
+ // Check include patterns (ID-based)
289
+ if (this.config.entities && this.config.entities.length > 0) {
290
+ let matched = false;
291
+ for (const pattern of this.config.entities) {
292
+ if (this.matchPattern(entityId, pattern)) {
293
+ matched = true;
294
+ break;
295
+ }
296
+ }
297
+ if (!matched)
298
+ return false;
299
+ }
300
+ // Check entity type filters (requires fetching entity)
301
+ if ((this.config.types && this.config.types.length > 0) ||
302
+ (this.config.excludeTypes && this.config.excludeTypes.length > 0)) {
303
+ try {
304
+ const entity = await this.brain?.getNounMetadata?.(entityId);
305
+ if (!entity)
306
+ return true; // If can't fetch, allow versioning
307
+ const entityType = entity.type;
308
+ // Check exclude types
309
+ if (this.config.excludeTypes && this.config.excludeTypes.includes(entityType)) {
310
+ return false;
311
+ }
312
+ // Check include types
313
+ if (this.config.types && this.config.types.length > 0) {
314
+ if (!this.config.types.includes(entityType)) {
315
+ return false;
316
+ }
317
+ }
318
+ }
319
+ catch (error) {
320
+ // If can't fetch entity, allow versioning (fail open)
321
+ console.error(`Failed to fetch entity ${entityId} for type filtering:`, error);
322
+ return true;
323
+ }
324
+ }
325
+ return true;
326
+ }
327
+ /**
328
+ * Simple glob pattern matching
329
+ */
330
+ matchPattern(value, pattern) {
331
+ if (pattern === '*')
332
+ return true;
333
+ if (!pattern.includes('*'))
334
+ return value === pattern;
335
+ // Convert glob to regex
336
+ const regex = new RegExp('^' + pattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
337
+ return regex.test(value);
338
+ }
339
+ /**
340
+ * Prune old versions for an entity
341
+ */
342
+ async pruneEntity(entityId) {
343
+ if (!this.versioningAPI)
344
+ return;
345
+ try {
346
+ // Call before-prune hook
347
+ if (this.config.hooks?.beforePrune) {
348
+ const shouldPrune = await this.config.hooks.beforePrune(entityId);
349
+ if (!shouldPrune)
350
+ return;
351
+ }
352
+ // Prune versions
353
+ const result = await this.versioningAPI.prune(entityId, {
354
+ keepRecent: this.config.keepRecent,
355
+ keepAfter: this.config.keepAfter,
356
+ keepTagged: this.config.keepTagged
357
+ });
358
+ // Call after-prune hook
359
+ if (result.deleted > 0 && this.config.hooks?.afterPrune) {
360
+ await this.config.hooks.afterPrune(entityId, result.deleted);
361
+ }
362
+ }
363
+ catch (error) {
364
+ console.error(`Failed to prune versions for ${entityId}:`, error);
365
+ }
366
+ }
367
+ /**
368
+ * Start auto-prune timer
369
+ */
370
+ startAutoPrune() {
371
+ if (this.pruneTimer) {
372
+ clearInterval(this.pruneTimer);
373
+ }
374
+ this.pruneTimer = setInterval(async () => {
375
+ await this.pruneAllEntities();
376
+ }, this.config.pruneInterval);
377
+ }
378
+ /**
379
+ * Prune all versioned entities
380
+ */
381
+ async pruneAllEntities() {
382
+ if (!this.versioningAPI)
383
+ return;
384
+ try {
385
+ // Get all versioned entities
386
+ const versionIndex = this.versioningAPI.manager.versionIndex;
387
+ const entities = await versionIndex.getVersionedEntities();
388
+ // Prune each entity
389
+ for (const entityId of entities) {
390
+ if (await this.shouldVersionEntity(entityId)) {
391
+ await this.pruneEntity(entityId);
392
+ }
393
+ }
394
+ }
395
+ catch (error) {
396
+ console.error('Auto-prune failed:', error);
397
+ }
398
+ }
399
+ /**
400
+ * Get augmentation statistics
401
+ */
402
+ getStats() {
403
+ return {
404
+ enabled: this.config.enabled,
405
+ versionsCreated: this.versionCount,
406
+ entitiesPattern: this.config.entities || [],
407
+ excludePattern: this.config.excludeEntities || [],
408
+ retention: this.config.keepRecent || 10
409
+ };
410
+ }
411
+ }
412
+ /**
413
+ * Factory function for easy augmentation creation
414
+ */
415
+ export function createVersioningAugmentation(config = {}) {
416
+ return new VersioningAugmentation(config);
417
+ }
418
+ //# sourceMappingURL=versioningAugmentation.js.map
package/dist/brainy.d.ts CHANGED
@@ -10,6 +10,7 @@ import { NaturalLanguageProcessor } from './neural/naturalLanguageProcessor.js';
10
10
  import { ExtractedEntity } from './neural/entityExtractor.js';
11
11
  import { TripleIntelligenceSystem } from './triple/TripleIntelligenceSystem.js';
12
12
  import { VirtualFileSystem } from './vfs/VirtualFileSystem.js';
13
+ import { VersioningAPI } from './versioning/VersioningAPI.js';
13
14
  import { Entity, Relation, Result, AddParams, UpdateParams, RelateParams, FindParams, SimilarParams, GetRelationsParams, AddManyParams, DeleteManyParams, RelateManyParams, BatchResult, BrainyConfig } from './types/brainy.types.js';
14
15
  import { NounType, VerbType } from './types/graphTypes.js';
15
16
  import { BrainyInterface } from './types/brainyInterface.js';
@@ -39,6 +40,7 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
39
40
  private _nlp?;
40
41
  private _extractor?;
41
42
  private _tripleIntelligence?;
43
+ private _versions?;
42
44
  private _vfs?;
43
45
  private initialized;
44
46
  private dimensions?;
@@ -956,6 +958,32 @@ export declare class Brainy<T = any> implements BrainyInterface<T> {
956
958
  * Neural API - Advanced AI operations
957
959
  */
958
960
  neural(): ImprovedNeuralAPI;
961
+ /**
962
+ * Versioning API - Entity version control (v5.3.0)
963
+ *
964
+ * Provides entity-level versioning with:
965
+ * - save() - Create version of entity
966
+ * - restore() - Restore entity to specific version
967
+ * - list() - List all versions of entity
968
+ * - compare() - Deep diff between versions
969
+ * - prune() - Remove old versions (retention policies)
970
+ *
971
+ * @example
972
+ * ```typescript
973
+ * // Save current state
974
+ * const version = await brain.versions.save('user-123', { tag: 'v1.0' })
975
+ *
976
+ * // List versions
977
+ * const versions = await brain.versions.list('user-123')
978
+ *
979
+ * // Restore to previous version
980
+ * await brain.versions.restore('user-123', 5)
981
+ *
982
+ * // Compare versions
983
+ * const diff = await brain.versions.compare('user-123', 2, 5)
984
+ * ```
985
+ */
986
+ get versions(): VersioningAPI;
959
987
  /**
960
988
  * Natural Language Processing API
961
989
  */