@soulcraft/brainy 5.2.1 → 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,112 @@
1
+ /**
2
+ * VersionDiff - Deep Object Comparison for Entity Versions (v5.3.0)
3
+ *
4
+ * Provides deep diff between entity versions:
5
+ * - Field-level change detection
6
+ * - Nested object comparison
7
+ * - Array diffing
8
+ * - Type change detection
9
+ * - Human-readable diff output
10
+ *
11
+ * NO MOCKS - Production implementation
12
+ */
13
+ import type { NounMetadata } from '../coreTypes.js';
14
+ /**
15
+ * Types of changes in a diff
16
+ */
17
+ export type ChangeType = 'added' | 'removed' | 'modified' | 'type-changed';
18
+ /**
19
+ * A single field change in a diff
20
+ */
21
+ export interface FieldChange {
22
+ /** Path to the field (e.g., 'metadata.user.name') */
23
+ path: string;
24
+ /** Type of change */
25
+ type: ChangeType;
26
+ /** Old value (undefined for 'added') */
27
+ oldValue?: any;
28
+ /** New value (undefined for 'removed') */
29
+ newValue?: any;
30
+ /** Old type (for 'type-changed') */
31
+ oldType?: string;
32
+ /** New type (for 'type-changed') */
33
+ newType?: string;
34
+ }
35
+ /**
36
+ * Complete diff between two versions
37
+ */
38
+ export interface VersionDiff {
39
+ /** Entity ID being compared */
40
+ entityId: string;
41
+ /** From version number */
42
+ fromVersion: number;
43
+ /** To version number */
44
+ toVersion: number;
45
+ /** Fields that were added */
46
+ added: FieldChange[];
47
+ /** Fields that were removed */
48
+ removed: FieldChange[];
49
+ /** Fields that were modified */
50
+ modified: FieldChange[];
51
+ /** Fields whose type changed */
52
+ typeChanged: FieldChange[];
53
+ /** Total number of changes */
54
+ totalChanges: number;
55
+ /** Whether versions are identical */
56
+ identical: boolean;
57
+ }
58
+ /**
59
+ * Options for diff comparison
60
+ */
61
+ export interface DiffOptions {
62
+ /** Entity ID (for context in output) */
63
+ entityId: string;
64
+ /** From version number */
65
+ fromVersion: number;
66
+ /** To version number */
67
+ toVersion: number;
68
+ /** Ignore these fields in comparison */
69
+ ignoreFields?: string[];
70
+ /** Maximum depth for nested object comparison (default: 10) */
71
+ maxDepth?: number;
72
+ /** Include unchanged fields in output (default: false) */
73
+ includeUnchanged?: boolean;
74
+ }
75
+ /**
76
+ * Compare two entity versions and generate diff
77
+ *
78
+ * @param from Old version entity
79
+ * @param to New version entity
80
+ * @param options Diff options
81
+ * @returns Diff between versions
82
+ */
83
+ export declare function compareEntityVersions(from: NounMetadata, to: NounMetadata, options: DiffOptions): VersionDiff;
84
+ /**
85
+ * Format diff as human-readable string
86
+ *
87
+ * @param diff Diff to format
88
+ * @returns Formatted string
89
+ */
90
+ export declare function formatDiff(diff: VersionDiff): string;
91
+ /**
92
+ * Get summary statistics about a diff
93
+ */
94
+ export declare function getDiffStats(diff: VersionDiff): {
95
+ changedFields: number;
96
+ addedFields: number;
97
+ removedFields: number;
98
+ modifiedFields: number;
99
+ typeChangedFields: number;
100
+ };
101
+ /**
102
+ * Check if diff has any changes
103
+ */
104
+ export declare function hasChanges(diff: VersionDiff): boolean;
105
+ /**
106
+ * Get all changed field paths
107
+ */
108
+ export declare function getChangedPaths(diff: VersionDiff): string[];
109
+ /**
110
+ * Filter diff to only include specific paths
111
+ */
112
+ export declare function filterDiff(diff: VersionDiff, paths: string[]): VersionDiff;
@@ -0,0 +1,320 @@
1
+ /**
2
+ * VersionDiff - Deep Object Comparison for Entity Versions (v5.3.0)
3
+ *
4
+ * Provides deep diff between entity versions:
5
+ * - Field-level change detection
6
+ * - Nested object comparison
7
+ * - Array diffing
8
+ * - Type change detection
9
+ * - Human-readable diff output
10
+ *
11
+ * NO MOCKS - Production implementation
12
+ */
13
+ /**
14
+ * Compare two entity versions and generate diff
15
+ *
16
+ * @param from Old version entity
17
+ * @param to New version entity
18
+ * @param options Diff options
19
+ * @returns Diff between versions
20
+ */
21
+ export function compareEntityVersions(from, to, options) {
22
+ const added = [];
23
+ const removed = [];
24
+ const modified = [];
25
+ const typeChanged = [];
26
+ const ignoreFields = new Set(options.ignoreFields || []);
27
+ const maxDepth = options.maxDepth ?? 10;
28
+ // Compare objects recursively
29
+ compareObjects(from, to, '', added, removed, modified, typeChanged, ignoreFields, 0, maxDepth);
30
+ const totalChanges = added.length + removed.length + modified.length + typeChanged.length;
31
+ const identical = totalChanges === 0;
32
+ return {
33
+ entityId: options.entityId,
34
+ fromVersion: options.fromVersion,
35
+ toVersion: options.toVersion,
36
+ added,
37
+ removed,
38
+ modified,
39
+ typeChanged,
40
+ totalChanges,
41
+ identical
42
+ };
43
+ }
44
+ /**
45
+ * Recursively compare two objects
46
+ */
47
+ function compareObjects(from, to, path, added, removed, modified, typeChanged, ignoreFields, depth, maxDepth) {
48
+ if (depth >= maxDepth) {
49
+ // Hit max depth - treat as single value
50
+ if (!deepEqual(from, to)) {
51
+ modified.push({
52
+ path,
53
+ type: 'modified',
54
+ oldValue: from,
55
+ newValue: to
56
+ });
57
+ }
58
+ return;
59
+ }
60
+ // Get all keys from both objects
61
+ const fromKeys = new Set(Object.keys(from || {}));
62
+ const toKeys = new Set(Object.keys(to || {}));
63
+ const allKeys = new Set([...fromKeys, ...toKeys]);
64
+ for (const key of allKeys) {
65
+ const fieldPath = path ? `${path}.${key}` : key;
66
+ // Skip ignored fields
67
+ if (ignoreFields.has(fieldPath) || ignoreFields.has(key)) {
68
+ continue;
69
+ }
70
+ const fromHas = fromKeys.has(key);
71
+ const toHas = toKeys.has(key);
72
+ if (!fromHas && toHas) {
73
+ // Field added
74
+ added.push({
75
+ path: fieldPath,
76
+ type: 'added',
77
+ newValue: to[key]
78
+ });
79
+ }
80
+ else if (fromHas && !toHas) {
81
+ // Field removed
82
+ removed.push({
83
+ path: fieldPath,
84
+ type: 'removed',
85
+ oldValue: from[key]
86
+ });
87
+ }
88
+ else {
89
+ // Field exists in both - check for changes
90
+ const fromValue = from[key];
91
+ const toValue = to[key];
92
+ const fromType = getValueType(fromValue);
93
+ const toType = getValueType(toValue);
94
+ if (fromType !== toType) {
95
+ // Type changed
96
+ typeChanged.push({
97
+ path: fieldPath,
98
+ type: 'type-changed',
99
+ oldValue: fromValue,
100
+ newValue: toValue,
101
+ oldType: fromType,
102
+ newType: toType
103
+ });
104
+ }
105
+ else if (fromType === 'object' && toType === 'object') {
106
+ // Recursively compare nested objects
107
+ compareObjects(fromValue, toValue, fieldPath, added, removed, modified, typeChanged, ignoreFields, depth + 1, maxDepth);
108
+ }
109
+ else if (fromType === 'array' && toType === 'array') {
110
+ // Compare arrays
111
+ if (!arraysEqual(fromValue, toValue)) {
112
+ modified.push({
113
+ path: fieldPath,
114
+ type: 'modified',
115
+ oldValue: fromValue,
116
+ newValue: toValue
117
+ });
118
+ }
119
+ }
120
+ else {
121
+ // Primitive value comparison
122
+ if (!deepEqual(fromValue, toValue)) {
123
+ modified.push({
124
+ path: fieldPath,
125
+ type: 'modified',
126
+ oldValue: fromValue,
127
+ newValue: toValue
128
+ });
129
+ }
130
+ }
131
+ }
132
+ }
133
+ }
134
+ /**
135
+ * Get human-readable type of a value
136
+ */
137
+ function getValueType(value) {
138
+ if (value === null)
139
+ return 'null';
140
+ if (value === undefined)
141
+ return 'undefined';
142
+ if (Array.isArray(value))
143
+ return 'array';
144
+ return typeof value;
145
+ }
146
+ /**
147
+ * Deep equality check
148
+ */
149
+ function deepEqual(a, b) {
150
+ if (a === b)
151
+ return true;
152
+ if (a === null || b === null)
153
+ return false;
154
+ if (a === undefined || b === undefined)
155
+ return false;
156
+ const typeA = getValueType(a);
157
+ const typeB = getValueType(b);
158
+ if (typeA !== typeB)
159
+ return false;
160
+ if (typeA === 'array') {
161
+ return arraysEqual(a, b);
162
+ }
163
+ if (typeA === 'object') {
164
+ return objectsEqual(a, b);
165
+ }
166
+ // Primitive comparison
167
+ return a === b;
168
+ }
169
+ /**
170
+ * Compare arrays for equality
171
+ */
172
+ function arraysEqual(a, b) {
173
+ if (a.length !== b.length)
174
+ return false;
175
+ for (let i = 0; i < a.length; i++) {
176
+ if (!deepEqual(a[i], b[i])) {
177
+ return false;
178
+ }
179
+ }
180
+ return true;
181
+ }
182
+ /**
183
+ * Compare objects for equality
184
+ */
185
+ function objectsEqual(a, b) {
186
+ const keysA = Object.keys(a);
187
+ const keysB = Object.keys(b);
188
+ if (keysA.length !== keysB.length)
189
+ return false;
190
+ for (const key of keysA) {
191
+ if (!keysB.includes(key))
192
+ return false;
193
+ if (!deepEqual(a[key], b[key]))
194
+ return false;
195
+ }
196
+ return true;
197
+ }
198
+ /**
199
+ * Format diff as human-readable string
200
+ *
201
+ * @param diff Diff to format
202
+ * @returns Formatted string
203
+ */
204
+ export function formatDiff(diff) {
205
+ const lines = [];
206
+ lines.push(`Diff: ${diff.entityId} v${diff.fromVersion} → v${diff.toVersion}`);
207
+ lines.push('');
208
+ if (diff.identical) {
209
+ lines.push('No changes');
210
+ return lines.join('\n');
211
+ }
212
+ lines.push(`Total changes: ${diff.totalChanges}`);
213
+ lines.push('');
214
+ if (diff.added.length > 0) {
215
+ lines.push(`Added (${diff.added.length}):`);
216
+ for (const change of diff.added) {
217
+ lines.push(` + ${change.path}: ${formatValue(change.newValue)}`);
218
+ }
219
+ lines.push('');
220
+ }
221
+ if (diff.removed.length > 0) {
222
+ lines.push(`Removed (${diff.removed.length}):`);
223
+ for (const change of diff.removed) {
224
+ lines.push(` - ${change.path}: ${formatValue(change.oldValue)}`);
225
+ }
226
+ lines.push('');
227
+ }
228
+ if (diff.modified.length > 0) {
229
+ lines.push(`Modified (${diff.modified.length}):`);
230
+ for (const change of diff.modified) {
231
+ lines.push(` ~ ${change.path}:`);
232
+ lines.push(` ${formatValue(change.oldValue)}`);
233
+ lines.push(` → ${formatValue(change.newValue)}`);
234
+ }
235
+ lines.push('');
236
+ }
237
+ if (diff.typeChanged.length > 0) {
238
+ lines.push(`Type Changed (${diff.typeChanged.length}):`);
239
+ for (const change of diff.typeChanged) {
240
+ lines.push(` ! ${change.path}: ${change.oldType} → ${change.newType}`);
241
+ lines.push(` ${formatValue(change.oldValue)}`);
242
+ lines.push(` → ${formatValue(change.newValue)}`);
243
+ }
244
+ }
245
+ return lines.join('\n');
246
+ }
247
+ /**
248
+ * Format value for display
249
+ */
250
+ function formatValue(value) {
251
+ if (value === null)
252
+ return 'null';
253
+ if (value === undefined)
254
+ return 'undefined';
255
+ if (typeof value === 'string')
256
+ return `"${value}"`;
257
+ if (typeof value === 'object') {
258
+ try {
259
+ return JSON.stringify(value);
260
+ }
261
+ catch {
262
+ return '[Object]';
263
+ }
264
+ }
265
+ return String(value);
266
+ }
267
+ /**
268
+ * Get summary statistics about a diff
269
+ */
270
+ export function getDiffStats(diff) {
271
+ return {
272
+ changedFields: diff.totalChanges,
273
+ addedFields: diff.added.length,
274
+ removedFields: diff.removed.length,
275
+ modifiedFields: diff.modified.length,
276
+ typeChangedFields: diff.typeChanged.length
277
+ };
278
+ }
279
+ /**
280
+ * Check if diff has any changes
281
+ */
282
+ export function hasChanges(diff) {
283
+ return !diff.identical;
284
+ }
285
+ /**
286
+ * Get all changed field paths
287
+ */
288
+ export function getChangedPaths(diff) {
289
+ const paths = new Set();
290
+ for (const change of diff.added)
291
+ paths.add(change.path);
292
+ for (const change of diff.removed)
293
+ paths.add(change.path);
294
+ for (const change of diff.modified)
295
+ paths.add(change.path);
296
+ for (const change of diff.typeChanged)
297
+ paths.add(change.path);
298
+ return Array.from(paths).sort();
299
+ }
300
+ /**
301
+ * Filter diff to only include specific paths
302
+ */
303
+ export function filterDiff(diff, paths) {
304
+ const pathSet = new Set(paths);
305
+ const filterChanges = (changes) => changes.filter((c) => pathSet.has(c.path) || paths.some((p) => c.path.startsWith(p + '.')));
306
+ const added = filterChanges(diff.added);
307
+ const removed = filterChanges(diff.removed);
308
+ const modified = filterChanges(diff.modified);
309
+ const typeChanged = filterChanges(diff.typeChanged);
310
+ return {
311
+ ...diff,
312
+ added,
313
+ removed,
314
+ modified,
315
+ typeChanged,
316
+ totalChanges: added.length + removed.length + modified.length + typeChanged.length,
317
+ identical: added.length + removed.length + modified.length + typeChanged.length === 0
318
+ };
319
+ }
320
+ //# sourceMappingURL=VersionDiff.js.map
@@ -0,0 +1,126 @@
1
+ /**
2
+ * VersionIndex - Fast Version Lookup Using Existing Index Infrastructure (v5.3.0)
3
+ *
4
+ * Integrates with Brainy's existing index system:
5
+ * - Uses MetadataIndexManager for field indexing
6
+ * - Leverages UnifiedCache for memory management
7
+ * - Uses EntityIdMapper for efficient ID handling
8
+ * - Uses ChunkManager for adaptive chunking
9
+ * - Leverages Roaring Bitmaps for fast set operations
10
+ *
11
+ * Version metadata is stored as regular entities with type='_version'
12
+ * This allows us to use existing index infrastructure without modification!
13
+ *
14
+ * Fields indexed:
15
+ * - versionEntityId: Entity being versioned
16
+ * - versionBranch: Branch version was created on
17
+ * - versionNumber: Version number
18
+ * - versionTag: Optional user tag
19
+ * - versionTimestamp: Creation timestamp
20
+ * - versionCommitHash: Commit hash
21
+ *
22
+ * NO MOCKS - Production implementation
23
+ */
24
+ import type { EntityVersion } from './VersionManager.js';
25
+ import type { VersionQuery } from './VersionManager.js';
26
+ /**
27
+ * VersionIndex - Version lookup and querying using existing indexes
28
+ *
29
+ * Strategy: Store version metadata as special entities with type='_version'
30
+ * This leverages ALL existing index infrastructure automatically!
31
+ */
32
+ export declare class VersionIndex {
33
+ private brain;
34
+ private initialized;
35
+ constructor(brain: any);
36
+ /**
37
+ * Initialize version index
38
+ *
39
+ * No special setup needed - we use existing entity storage and indexes!
40
+ */
41
+ initialize(): Promise<void>;
42
+ /**
43
+ * Add version to index
44
+ *
45
+ * Stores version metadata as a special entity with type='_version'
46
+ * This automatically indexes it using existing MetadataIndexManager!
47
+ *
48
+ * @param version Version metadata
49
+ */
50
+ addVersion(version: EntityVersion): Promise<void>;
51
+ /**
52
+ * Get versions for an entity
53
+ *
54
+ * Uses existing MetadataIndexManager to query efficiently!
55
+ *
56
+ * @param query Version query
57
+ * @returns List of versions (newest first)
58
+ */
59
+ getVersions(query: VersionQuery): Promise<EntityVersion[]>;
60
+ /**
61
+ * Get specific version
62
+ *
63
+ * @param entityId Entity ID
64
+ * @param version Version number
65
+ * @param branch Branch name
66
+ * @returns Version metadata or null
67
+ */
68
+ getVersion(entityId: string, version: number, branch: string): Promise<EntityVersion | null>;
69
+ /**
70
+ * Get version by tag
71
+ *
72
+ * @param entityId Entity ID
73
+ * @param tag Version tag
74
+ * @param branch Branch name
75
+ * @returns Version metadata or null
76
+ */
77
+ getVersionByTag(entityId: string, tag: string, branch: string): Promise<EntityVersion | null>;
78
+ /**
79
+ * Get version count for entity
80
+ *
81
+ * @param entityId Entity ID
82
+ * @param branch Branch name
83
+ * @returns Number of versions
84
+ */
85
+ getVersionCount(entityId: string, branch: string): Promise<number>;
86
+ /**
87
+ * Remove version from index
88
+ *
89
+ * @param entityId Entity ID
90
+ * @param version Version number
91
+ * @param branch Branch name
92
+ */
93
+ removeVersion(entityId: string, version: number, branch: string): Promise<void>;
94
+ /**
95
+ * Convert entity to EntityVersion format
96
+ *
97
+ * @param entity Entity from storage
98
+ * @returns EntityVersion or null if invalid
99
+ */
100
+ private entityToVersion;
101
+ /**
102
+ * Generate unique ID for version entity
103
+ *
104
+ * Format: _version:{entityId}:{version}:{branch}
105
+ *
106
+ * @param entityId Entity ID
107
+ * @param version Version number
108
+ * @param branch Branch name
109
+ * @returns Version entity ID
110
+ */
111
+ private getVersionEntityId;
112
+ /**
113
+ * Get all versioned entities (for cleanup/debugging)
114
+ *
115
+ * @returns List of entity IDs that have versions
116
+ */
117
+ getVersionedEntities(): Promise<string[]>;
118
+ /**
119
+ * Clear all versions for an entity
120
+ *
121
+ * @param entityId Entity ID
122
+ * @param branch Branch name
123
+ * @returns Number of versions deleted
124
+ */
125
+ clearVersions(entityId: string, branch: string): Promise<number>;
126
+ }