@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.
@@ -0,0 +1,432 @@
1
+ /**
2
+ * VersioningAPI - Public API for Entity Versioning (v5.3.0)
3
+ *
4
+ * User-friendly wrapper around VersionManager with:
5
+ * - Clean, simple API
6
+ * - Smart defaults
7
+ * - Error handling
8
+ * - Type safety
9
+ *
10
+ * Usage:
11
+ * const version = await brain.versions.save('entity-123', { tag: 'v1.0' })
12
+ * const versions = await brain.versions.list('entity-123')
13
+ * await brain.versions.restore('entity-123', 5)
14
+ * const diff = await brain.versions.compare('entity-123', 2, 5)
15
+ *
16
+ * NO MOCKS - Production implementation
17
+ */
18
+ import { VersionManager } from './VersionManager.js';
19
+ /**
20
+ * VersioningAPI - User-friendly versioning interface
21
+ */
22
+ export class VersioningAPI {
23
+ constructor(brain) {
24
+ this.brain = brain;
25
+ this.manager = new VersionManager(brain);
26
+ }
27
+ /**
28
+ * Save current state of entity as a new version
29
+ *
30
+ * Creates a version snapshot of the current entity state.
31
+ * Automatically handles deduplication - if content hasn't changed,
32
+ * returns the last version instead of creating a duplicate.
33
+ *
34
+ * @param entityId Entity ID to version
35
+ * @param options Save options
36
+ * @returns Created (or existing) version metadata
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * // Simple save
41
+ * const version = await brain.versions.save('user-123')
42
+ *
43
+ * // Save with tag and description
44
+ * const version = await brain.versions.save('user-123', {
45
+ * tag: 'v1.0',
46
+ * description: 'Initial release',
47
+ * author: 'alice'
48
+ * })
49
+ *
50
+ * // Save and create commit
51
+ * const version = await brain.versions.save('user-123', {
52
+ * tag: 'milestone-1',
53
+ * createCommit: true,
54
+ * commitMessage: 'Milestone 1 complete'
55
+ * })
56
+ * ```
57
+ */
58
+ async save(entityId, options = {}) {
59
+ return this.manager.save(entityId, options);
60
+ }
61
+ /**
62
+ * List all versions of an entity
63
+ *
64
+ * Returns versions sorted by version number (newest first).
65
+ * Supports filtering by tag, date range, and pagination.
66
+ *
67
+ * @param entityId Entity ID
68
+ * @param options Query options
69
+ * @returns List of versions (newest first)
70
+ *
71
+ * @example
72
+ * ```typescript
73
+ * // Get all versions
74
+ * const versions = await brain.versions.list('user-123')
75
+ *
76
+ * // Get last 10 versions
77
+ * const recent = await brain.versions.list('user-123', { limit: 10 })
78
+ *
79
+ * // Get tagged versions
80
+ * const tagged = await brain.versions.list('user-123', { tag: 'v*' })
81
+ *
82
+ * // Get versions from last 30 days
83
+ * const recent = await brain.versions.list('user-123', {
84
+ * startDate: Date.now() - 30 * 24 * 60 * 60 * 1000
85
+ * })
86
+ * ```
87
+ */
88
+ async list(entityId, options = {}) {
89
+ return this.manager.list(entityId, options);
90
+ }
91
+ /**
92
+ * Get specific version of an entity
93
+ *
94
+ * @param entityId Entity ID
95
+ * @param version Version number (1-indexed)
96
+ * @returns Version metadata or null if not found
97
+ *
98
+ * @example
99
+ * ```typescript
100
+ * const version = await brain.versions.getVersion('user-123', 5)
101
+ * if (version) {
102
+ * console.log(`Version ${version.version} created at ${new Date(version.timestamp)}`)
103
+ * }
104
+ * ```
105
+ */
106
+ async getVersion(entityId, version) {
107
+ return this.manager.getVersion(entityId, version);
108
+ }
109
+ /**
110
+ * Get version by tag
111
+ *
112
+ * @param entityId Entity ID
113
+ * @param tag Version tag
114
+ * @returns Version metadata or null if not found
115
+ *
116
+ * @example
117
+ * ```typescript
118
+ * const version = await brain.versions.getVersionByTag('user-123', 'v1.0')
119
+ * ```
120
+ */
121
+ async getVersionByTag(entityId, tag) {
122
+ return this.manager.getVersionByTag(entityId, tag);
123
+ }
124
+ /**
125
+ * Get latest version of an entity
126
+ *
127
+ * @param entityId Entity ID
128
+ * @returns Latest version or null if no versions exist
129
+ *
130
+ * @example
131
+ * ```typescript
132
+ * const latest = await brain.versions.getLatest('user-123')
133
+ * ```
134
+ */
135
+ async getLatest(entityId) {
136
+ return this.manager.getLatest(entityId);
137
+ }
138
+ /**
139
+ * Get version content without restoring
140
+ *
141
+ * Allows you to preview version data without modifying the current entity.
142
+ *
143
+ * @param entityId Entity ID
144
+ * @param version Version number or tag
145
+ * @returns Version content
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * // Preview version 5 without restoring
150
+ * const oldData = await brain.versions.getContent('user-123', 5)
151
+ * console.log('Version 5 had name:', oldData.name)
152
+ *
153
+ * // Compare with current
154
+ * const current = await brain.getNounMetadata('user-123')
155
+ * console.log('Current name:', current.name)
156
+ * ```
157
+ */
158
+ async getContent(entityId, version) {
159
+ // Get version metadata
160
+ let versionMeta;
161
+ if (typeof version === 'number') {
162
+ versionMeta = await this.manager.getVersion(entityId, version);
163
+ }
164
+ else {
165
+ versionMeta = await this.manager.getVersionByTag(entityId, version);
166
+ }
167
+ if (!versionMeta) {
168
+ throw new Error(`Version ${version} not found for entity ${entityId}`);
169
+ }
170
+ // Load version content
171
+ const versionStorage = this.manager.versionStorage;
172
+ const content = await versionStorage.loadVersion(versionMeta);
173
+ if (!content) {
174
+ throw new Error(`Version content not found for entity ${entityId} version ${version}`);
175
+ }
176
+ return content;
177
+ }
178
+ /**
179
+ * Restore entity to a specific version
180
+ *
181
+ * Overwrites current entity state with the specified version.
182
+ * Optionally creates a snapshot before restoring for undo capability.
183
+ *
184
+ * @param entityId Entity ID
185
+ * @param version Version number or tag to restore to
186
+ * @param options Restore options
187
+ * @returns Restored version metadata
188
+ *
189
+ * @example
190
+ * ```typescript
191
+ * // Simple restore
192
+ * await brain.versions.restore('user-123', 5)
193
+ *
194
+ * // Restore with safety snapshot
195
+ * await brain.versions.restore('user-123', 5, {
196
+ * createSnapshot: true,
197
+ * snapshotTag: 'before-restore'
198
+ * })
199
+ *
200
+ * // Restore by tag
201
+ * await brain.versions.restore('user-123', 'v1.0')
202
+ * ```
203
+ */
204
+ async restore(entityId, version, options = {}) {
205
+ return this.manager.restore(entityId, version, options);
206
+ }
207
+ /**
208
+ * Compare two versions of an entity
209
+ *
210
+ * Generates a deep diff showing added, removed, modified, and type-changed fields.
211
+ *
212
+ * @param entityId Entity ID
213
+ * @param fromVersion Version number or tag (older)
214
+ * @param toVersion Version number or tag (newer)
215
+ * @returns Diff between versions
216
+ *
217
+ * @example
218
+ * ```typescript
219
+ * // Compare version 2 to version 5
220
+ * const diff = await brain.versions.compare('user-123', 2, 5)
221
+ *
222
+ * console.log(`Added fields: ${diff.added.length}`)
223
+ * console.log(`Removed fields: ${diff.removed.length}`)
224
+ * console.log(`Modified fields: ${diff.modified.length}`)
225
+ *
226
+ * // Print human-readable diff
227
+ * import { formatDiff } from './VersionDiff.js'
228
+ * console.log(formatDiff(diff))
229
+ * ```
230
+ */
231
+ async compare(entityId, fromVersion, toVersion) {
232
+ return this.manager.compare(entityId, fromVersion, toVersion);
233
+ }
234
+ /**
235
+ * Prune old versions based on retention policy
236
+ *
237
+ * Removes old versions while preserving recent and tagged versions.
238
+ * Use dryRun to preview what would be deleted without actually deleting.
239
+ *
240
+ * @param entityId Entity ID (or '*' for all entities - NOT IMPLEMENTED YET)
241
+ * @param options Prune options
242
+ * @returns Count of deleted and kept versions
243
+ *
244
+ * @example
245
+ * ```typescript
246
+ * // Keep last 10 versions, delete rest
247
+ * const result = await brain.versions.prune('user-123', {
248
+ * keepRecent: 10
249
+ * })
250
+ * console.log(`Deleted ${result.deleted} versions`)
251
+ *
252
+ * // Keep last 30 days, preserve tagged versions
253
+ * const result = await brain.versions.prune('user-123', {
254
+ * keepAfter: Date.now() - 30 * 24 * 60 * 60 * 1000,
255
+ * keepTagged: true
256
+ * })
257
+ *
258
+ * // Dry run - see what would be deleted
259
+ * const result = await brain.versions.prune('user-123', {
260
+ * keepRecent: 5,
261
+ * dryRun: true
262
+ * })
263
+ * console.log(`Would delete ${result.deleted} versions`)
264
+ * ```
265
+ */
266
+ async prune(entityId, options) {
267
+ return this.manager.prune(entityId, options);
268
+ }
269
+ /**
270
+ * Get version count for an entity
271
+ *
272
+ * @param entityId Entity ID
273
+ * @returns Number of versions
274
+ *
275
+ * @example
276
+ * ```typescript
277
+ * const count = await brain.versions.count('user-123')
278
+ * console.log(`Entity has ${count} versions`)
279
+ * ```
280
+ */
281
+ async count(entityId) {
282
+ return this.manager.getVersionCount(entityId);
283
+ }
284
+ /**
285
+ * Check if entity has any versions
286
+ *
287
+ * @param entityId Entity ID
288
+ * @returns True if entity has versions
289
+ *
290
+ * @example
291
+ * ```typescript
292
+ * if (await brain.versions.hasVersions('user-123')) {
293
+ * console.log('Entity is versioned')
294
+ * }
295
+ * ```
296
+ */
297
+ async hasVersions(entityId) {
298
+ return this.manager.hasVersions(entityId);
299
+ }
300
+ /**
301
+ * Clear all versions for an entity
302
+ *
303
+ * WARNING: This permanently deletes all version history!
304
+ *
305
+ * @param entityId Entity ID
306
+ * @returns Number of versions deleted
307
+ *
308
+ * @example
309
+ * ```typescript
310
+ * const deleted = await brain.versions.clear('user-123')
311
+ * console.log(`Deleted ${deleted} versions`)
312
+ * ```
313
+ */
314
+ async clear(entityId) {
315
+ return this.manager.clear(entityId);
316
+ }
317
+ // ===== CONVENIENCE METHODS =====
318
+ /**
319
+ * Quick save with auto-generated tag
320
+ *
321
+ * Generates tag in format: 'auto-{timestamp}'
322
+ *
323
+ * @param entityId Entity ID
324
+ * @param description Optional description
325
+ * @returns Created version
326
+ */
327
+ async quickSave(entityId, description) {
328
+ return this.save(entityId, {
329
+ tag: `auto-${Date.now()}`,
330
+ description
331
+ });
332
+ }
333
+ /**
334
+ * Undo last change (restore to previous version)
335
+ *
336
+ * Safely restores to previous version with automatic snapshot.
337
+ *
338
+ * @param entityId Entity ID
339
+ * @returns Restored version metadata
340
+ */
341
+ async undo(entityId) {
342
+ const versions = await this.list(entityId, { limit: 2 });
343
+ if (versions.length < 2) {
344
+ // No previous version to restore to
345
+ return null;
346
+ }
347
+ const previousVersion = versions[1]; // Second most recent
348
+ return this.restore(entityId, previousVersion.version, {
349
+ createSnapshot: true,
350
+ snapshotTag: 'before-undo'
351
+ });
352
+ }
353
+ /**
354
+ * Get version history summary
355
+ *
356
+ * @param entityId Entity ID
357
+ * @returns Summary statistics
358
+ */
359
+ async history(entityId) {
360
+ const versions = await this.list(entityId);
361
+ const tagged = versions.filter((v) => v.tag !== undefined).length;
362
+ const branches = [...new Set(versions.map((v) => v.branch))];
363
+ return {
364
+ total: versions.length,
365
+ oldest: versions[versions.length - 1],
366
+ newest: versions[0],
367
+ tagged,
368
+ branches
369
+ };
370
+ }
371
+ /**
372
+ * Revert to previous version (alias for undo with better semantics)
373
+ *
374
+ * Restores entity to the previous version with automatic safety snapshot.
375
+ *
376
+ * @param entityId Entity ID
377
+ * @returns Restored version or null if not possible
378
+ *
379
+ * @example
380
+ * ```typescript
381
+ * // Made a mistake? Revert!
382
+ * await brain.update('user-123', { name: 'Wrong Name' })
383
+ * await brain.versions.revert('user-123') // Back to previous state
384
+ * ```
385
+ */
386
+ async revert(entityId) {
387
+ return this.undo(entityId);
388
+ }
389
+ /**
390
+ * Tag an existing version
391
+ *
392
+ * Updates the tag of an existing version.
393
+ * Note: This creates a new version with the tag, not modifying the existing one.
394
+ *
395
+ * @param entityId Entity ID
396
+ * @param version Version number
397
+ * @param tag New tag
398
+ * @returns Updated version
399
+ */
400
+ async tag(entityId, version, tag) {
401
+ // Get the version
402
+ const existingVersion = await this.getVersion(entityId, version);
403
+ if (!existingVersion) {
404
+ throw new Error(`Version ${version} not found for entity ${entityId}`);
405
+ }
406
+ // Create new version with tag
407
+ // Note: This is a limitation - we can't modify existing versions
408
+ // because they're immutable. Instead, we create a new version.
409
+ return this.save(entityId, {
410
+ tag,
411
+ description: `Tagged version ${version} as ${tag}`,
412
+ metadata: { originalVersion: version }
413
+ });
414
+ }
415
+ /**
416
+ * Get diff between entity's current state and a version
417
+ *
418
+ * @param entityId Entity ID
419
+ * @param version Version to compare against
420
+ * @returns Diff showing changes
421
+ */
422
+ async diffWithCurrent(entityId, version) {
423
+ // Save current state (will be deduplicated if unchanged)
424
+ const currentVersion = await this.save(entityId, {
425
+ tag: 'temp-compare',
426
+ description: 'Temporary version for comparison'
427
+ });
428
+ // Compare
429
+ return this.compare(entityId, version, currentVersion.version);
430
+ }
431
+ }
432
+ //# sourceMappingURL=VersioningAPI.js.map
@@ -135,7 +135,8 @@ export class VirtualFileSystem {
135
135
  path: '/',
136
136
  name: '',
137
137
  vfsType: 'directory',
138
- isVFS: true, // v4.3.3: Mark as VFS entity
138
+ isVFS: true, // v4.3.3: Mark as VFS entity (internal)
139
+ isVFSEntity: true, // v5.3.0: Explicit flag for developer filtering
139
140
  size: 0,
140
141
  permissions: 0o755,
141
142
  owner: 'root',
@@ -156,7 +157,8 @@ export class VirtualFileSystem {
156
157
  path: '/',
157
158
  name: '',
158
159
  vfsType: 'directory',
159
- isVFS: true, // v4.3.3: Mark as VFS entity
160
+ isVFS: true, // v4.3.3: Mark as VFS entity (internal)
161
+ isVFSEntity: true, // v5.3.0: Explicit flag for developer filtering
160
162
  size: 0,
161
163
  permissions: 0o755,
162
164
  owner: 'root',
@@ -255,7 +257,8 @@ export class VirtualFileSystem {
255
257
  name,
256
258
  parent: parentId,
257
259
  vfsType: 'file',
258
- isVFS: true, // v4.3.3: Mark as VFS entity
260
+ isVFS: true, // v4.3.3: Mark as VFS entity (internal)
261
+ isVFSEntity: true, // v5.3.0: Explicit flag for developer filtering
259
262
  size: buffer.length,
260
263
  mimeType,
261
264
  extension: this.getExtension(name),
@@ -550,7 +553,8 @@ export class VirtualFileSystem {
550
553
  name,
551
554
  parent: parentId,
552
555
  vfsType: 'directory',
553
- isVFS: true, // v4.3.3: Mark as VFS entity
556
+ isVFS: true, // v4.3.3: Mark as VFS entity (internal)
557
+ isVFSEntity: true, // v5.3.0: Explicit flag for developer filtering
554
558
  size: 0,
555
559
  permissions: options?.mode || this.config.permissions?.defaultDirectory || 0o755,
556
560
  owner: 'user',
@@ -28,6 +28,7 @@ export interface VFSMetadata {
28
28
  parent?: string;
29
29
  vfsType: 'file' | 'directory' | 'symlink';
30
30
  isVFS?: boolean;
31
+ isVFSEntity?: boolean;
31
32
  size: number;
32
33
  mimeType?: string;
33
34
  extension?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulcraft/brainy",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
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",