@signaltree/enterprise 4.0.9 → 4.0.12
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/package.json +1 -1
- package/src/index.js +10 -0
- package/src/index.js.map +1 -0
- package/src/lib/diff-engine.d.ts +108 -0
- package/src/lib/diff-engine.js +236 -0
- package/src/lib/diff-engine.js.map +1 -0
- package/src/lib/enterprise-enhancer.d.ts +81 -0
- package/src/lib/enterprise-enhancer.js +78 -0
- package/src/lib/enterprise-enhancer.js.map +1 -0
- package/src/lib/enterprise.d.ts +1 -0
- package/src/lib/enterprise.js +7 -0
- package/src/lib/enterprise.js.map +1 -0
- package/src/lib/path-index.d.ts +119 -0
- package/src/lib/path-index.js +265 -0
- package/src/lib/path-index.js.map +1 -0
- package/src/lib/scheduler.d.ts +2 -0
- package/src/lib/scheduler.js +25 -0
- package/src/lib/scheduler.js.map +1 -0
- package/src/lib/thread-pools.d.ts +4 -0
- package/src/lib/thread-pools.js +14 -0
- package/src/lib/thread-pools.js.map +1 -0
- package/src/lib/update-engine.d.ts +115 -0
- package/src/lib/update-engine.js +287 -0
- package/src/lib/update-engine.js.map +1 -0
- package/src/test-setup.d.ts +1 -0
- package/src/test-setup.js +8 -0
- package/src/test-setup.js.map +1 -0
- package/LICENSE +0 -54
- package/src/lib/diff-engine.spec.ts +0 -384
- package/src/lib/diff-engine.ts +0 -351
- package/src/lib/enterprise-enhancer.ts +0 -136
- package/src/lib/enterprise.spec.ts +0 -7
- package/src/lib/enterprise.ts +0 -3
- package/src/lib/path-index.spec.ts +0 -290
- package/src/lib/path-index.ts +0 -320
- package/src/lib/scheduler.ts +0 -16
- package/src/lib/thread-pools.ts +0 -11
- package/src/lib/update-engine.spec.ts +0 -93
- package/src/lib/update-engine.ts +0 -399
- package/src/test-setup.ts +0 -6
- package/src/types/signaltree-core.d.ts +0 -4
- /package/src/{index.ts → index.d.ts} +0 -0
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
import { OptimizedUpdateEngine } from './update-engine';
|
|
2
|
-
|
|
3
|
-
describe("OptimizedUpdateEngine", () => {
|
|
4
|
-
it("should detect simple changes", () => {
|
|
5
|
-
const tree = { name: "Alice", age: 30 };
|
|
6
|
-
const engine = new OptimizedUpdateEngine(tree);
|
|
7
|
-
|
|
8
|
-
const result = engine.update(tree, { age: 31 });
|
|
9
|
-
|
|
10
|
-
expect(result.changed).toBe(true);
|
|
11
|
-
expect(result.changedPaths.some((p) => p.includes("age"))).toBe(true);
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it("should return false when no changes", () => {
|
|
15
|
-
const tree = { name: "Alice" };
|
|
16
|
-
const engine = new OptimizedUpdateEngine(tree);
|
|
17
|
-
|
|
18
|
-
const result = engine.update(tree, { name: "Alice" });
|
|
19
|
-
|
|
20
|
-
expect(result.changed).toBe(false);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it("should handle nested objects", () => {
|
|
24
|
-
const tree = {
|
|
25
|
-
user: {
|
|
26
|
-
profile: { name: "Alice", age: 30 },
|
|
27
|
-
},
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
const engine = new OptimizedUpdateEngine(tree);
|
|
31
|
-
const result = engine.update(tree, {
|
|
32
|
-
user: { profile: { age: 31 } },
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
expect(result.changed).toBe(true);
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it("should respect maxDepth option", () => {
|
|
39
|
-
const tree = {
|
|
40
|
-
level1: {
|
|
41
|
-
level2: {
|
|
42
|
-
level3: { deep: "value" },
|
|
43
|
-
},
|
|
44
|
-
},
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const engine = new OptimizedUpdateEngine(tree);
|
|
48
|
-
const result = engine.update(
|
|
49
|
-
tree,
|
|
50
|
-
{
|
|
51
|
-
level1: {
|
|
52
|
-
level2: {
|
|
53
|
-
level3: { deep: "changed" },
|
|
54
|
-
},
|
|
55
|
-
},
|
|
56
|
-
},
|
|
57
|
-
{ maxDepth: 2 }
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
expect(result.changed).toBe(false);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
it("should return index statistics", () => {
|
|
64
|
-
const tree = { a: 1, b: 2 };
|
|
65
|
-
const engine = new OptimizedUpdateEngine(tree);
|
|
66
|
-
|
|
67
|
-
const stats = engine.getIndexStats();
|
|
68
|
-
|
|
69
|
-
expect(stats).toHaveProperty("hits");
|
|
70
|
-
expect(stats).toHaveProperty("misses");
|
|
71
|
-
expect(stats).toHaveProperty("hitRate");
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("should handle large objects efficiently", () => {
|
|
75
|
-
const largeObj: Record<string, unknown> = {};
|
|
76
|
-
for (let i = 0; i < 1000; i++) {
|
|
77
|
-
largeObj["field" + i] = i;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const updates: Record<string, unknown> = {};
|
|
81
|
-
for (let i = 0; i < 1000; i++) {
|
|
82
|
-
updates["field" + i] = i + 1000;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const engine = new OptimizedUpdateEngine(largeObj);
|
|
86
|
-
const start = performance.now();
|
|
87
|
-
const result = engine.update(largeObj, updates);
|
|
88
|
-
const duration = performance.now() - start;
|
|
89
|
-
|
|
90
|
-
expect(result.changed).toBe(true);
|
|
91
|
-
expect(duration).toBeLessThan(200);
|
|
92
|
-
});
|
|
93
|
-
});
|
package/src/lib/update-engine.ts
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
import { isSignal } from '@angular/core';
|
|
2
|
-
|
|
3
|
-
import { ChangeType, DiffEngine } from './diff-engine';
|
|
4
|
-
import { PathIndex } from './path-index';
|
|
5
|
-
|
|
6
|
-
import type { WritableSignal } from '@angular/core';
|
|
7
|
-
import type { Change, DiffOptions } from './diff-engine';
|
|
8
|
-
import type { Path } from './path-index';
|
|
9
|
-
/**
|
|
10
|
-
* OptimizedUpdateEngine - High-performance tree updates
|
|
11
|
-
* @packageDocumentation
|
|
12
|
-
*/
|
|
13
|
-
/**
|
|
14
|
-
* Update options
|
|
15
|
-
*/
|
|
16
|
-
export interface UpdateOptions extends DiffOptions {
|
|
17
|
-
/** Whether to batch updates */
|
|
18
|
-
batch?: boolean;
|
|
19
|
-
|
|
20
|
-
/** Batch size for chunked updates */
|
|
21
|
-
batchSize?: number;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Update result
|
|
26
|
-
*/
|
|
27
|
-
export interface UpdateResult {
|
|
28
|
-
/** Whether any changes were made */
|
|
29
|
-
changed: boolean;
|
|
30
|
-
|
|
31
|
-
/** Update duration in milliseconds */
|
|
32
|
-
duration: number;
|
|
33
|
-
|
|
34
|
-
/** List of changed paths */
|
|
35
|
-
changedPaths: string[];
|
|
36
|
-
|
|
37
|
-
/** Update statistics */
|
|
38
|
-
stats?: {
|
|
39
|
-
totalPaths: number;
|
|
40
|
-
optimizedPaths: number;
|
|
41
|
-
batchedUpdates: number;
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Patch to apply
|
|
47
|
-
*/
|
|
48
|
-
interface Patch {
|
|
49
|
-
type: ChangeType;
|
|
50
|
-
path: Path;
|
|
51
|
-
value?: unknown;
|
|
52
|
-
oldValue?: unknown;
|
|
53
|
-
priority: number;
|
|
54
|
-
signal?: WritableSignal<unknown> | null;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Apply result (internal)
|
|
59
|
-
*/
|
|
60
|
-
interface ApplyResult {
|
|
61
|
-
appliedPaths: string[];
|
|
62
|
-
updateCount: number;
|
|
63
|
-
batchCount: number;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* OptimizedUpdateEngine
|
|
68
|
-
*
|
|
69
|
-
* High-performance update engine using path indexing and diffing to minimize
|
|
70
|
-
* unnecessary signal updates.
|
|
71
|
-
*
|
|
72
|
-
* Features:
|
|
73
|
-
* - Diff-based updates (only update what changed)
|
|
74
|
-
* - Path indexing for O(k) lookups
|
|
75
|
-
* - Automatic batching for large updates
|
|
76
|
-
* - Priority-based patch ordering
|
|
77
|
-
* - Skip unchanged values
|
|
78
|
-
*
|
|
79
|
-
* @example
|
|
80
|
-
* ```ts
|
|
81
|
-
* const tree = signalTree(data, { useLazySignals: true });
|
|
82
|
-
* const engine = new OptimizedUpdateEngine(tree);
|
|
83
|
-
*
|
|
84
|
-
* // Optimized update - only changes what's different
|
|
85
|
-
* const result = engine.update({
|
|
86
|
-
* user: { name: 'Alice' } // Only updates if name changed
|
|
87
|
-
* });
|
|
88
|
-
*
|
|
89
|
-
* console.log(result.changedPaths); // ['user.name']
|
|
90
|
-
* console.log(result.duration); // ~2ms
|
|
91
|
-
* ```
|
|
92
|
-
*/
|
|
93
|
-
export class OptimizedUpdateEngine {
|
|
94
|
-
private pathIndex: PathIndex;
|
|
95
|
-
private diffEngine: DiffEngine;
|
|
96
|
-
|
|
97
|
-
constructor(tree: unknown) {
|
|
98
|
-
this.pathIndex = new PathIndex();
|
|
99
|
-
this.diffEngine = new DiffEngine();
|
|
100
|
-
|
|
101
|
-
// Build initial index
|
|
102
|
-
this.pathIndex.buildFromTree(tree);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
/**
|
|
106
|
-
* Update tree with optimizations
|
|
107
|
-
*
|
|
108
|
-
* @param tree - Current tree state
|
|
109
|
-
* @param updates - Updates to apply
|
|
110
|
-
* @param options - Update options
|
|
111
|
-
* @returns Update result
|
|
112
|
-
*/
|
|
113
|
-
update(
|
|
114
|
-
tree: unknown,
|
|
115
|
-
updates: unknown,
|
|
116
|
-
options: UpdateOptions = {}
|
|
117
|
-
): UpdateResult {
|
|
118
|
-
const startTime = performance.now();
|
|
119
|
-
|
|
120
|
-
// Step 1: Generate diff to find actual changes
|
|
121
|
-
const diffOptions: Partial<DiffOptions> = {};
|
|
122
|
-
if (options.maxDepth !== undefined) diffOptions.maxDepth = options.maxDepth;
|
|
123
|
-
if (options.ignoreArrayOrder !== undefined)
|
|
124
|
-
diffOptions.ignoreArrayOrder = options.ignoreArrayOrder;
|
|
125
|
-
if (options.equalityFn !== undefined)
|
|
126
|
-
diffOptions.equalityFn = options.equalityFn;
|
|
127
|
-
|
|
128
|
-
const diff = this.diffEngine.diff(tree, updates, diffOptions);
|
|
129
|
-
|
|
130
|
-
if (diff.changes.length === 0) {
|
|
131
|
-
// No actual changes, skip update entirely
|
|
132
|
-
return {
|
|
133
|
-
changed: false,
|
|
134
|
-
duration: performance.now() - startTime,
|
|
135
|
-
changedPaths: [],
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Step 2: Convert diff to optimized patches
|
|
140
|
-
const patches = this.createPatches(diff.changes);
|
|
141
|
-
|
|
142
|
-
// Step 3: Sort patches for optimal application order
|
|
143
|
-
const sortedPatches = this.sortPatches(patches);
|
|
144
|
-
|
|
145
|
-
// Step 4: Apply patches with optional batching
|
|
146
|
-
const result = options.batch
|
|
147
|
-
? this.batchApplyPatches(tree, sortedPatches, options.batchSize)
|
|
148
|
-
: this.applyPatches(tree, sortedPatches);
|
|
149
|
-
|
|
150
|
-
const duration = performance.now() - startTime;
|
|
151
|
-
|
|
152
|
-
return {
|
|
153
|
-
changed: true,
|
|
154
|
-
duration,
|
|
155
|
-
changedPaths: result.appliedPaths,
|
|
156
|
-
stats: {
|
|
157
|
-
totalPaths: diff.changes.length,
|
|
158
|
-
optimizedPaths: patches.length,
|
|
159
|
-
batchedUpdates: result.batchCount,
|
|
160
|
-
},
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Rebuild path index from current tree state
|
|
166
|
-
*
|
|
167
|
-
* @param tree - Current tree
|
|
168
|
-
*/
|
|
169
|
-
rebuildIndex(tree: unknown): void {
|
|
170
|
-
this.pathIndex.clear();
|
|
171
|
-
this.pathIndex.buildFromTree(tree);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Get path index statistics
|
|
176
|
-
*/
|
|
177
|
-
getIndexStats(): ReturnType<PathIndex['getStats']> {
|
|
178
|
-
return this.pathIndex.getStats();
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* Creates optimized patches from diff changes
|
|
183
|
-
*/
|
|
184
|
-
private createPatches(changes: Change[]): Patch[] {
|
|
185
|
-
const patches: Patch[] = [];
|
|
186
|
-
const processedPaths = new Set<string>();
|
|
187
|
-
|
|
188
|
-
for (const change of changes) {
|
|
189
|
-
const pathStr = change.path.join('.');
|
|
190
|
-
|
|
191
|
-
// Skip if parent path already processed (optimization)
|
|
192
|
-
let skipPath = false;
|
|
193
|
-
for (const processed of processedPaths) {
|
|
194
|
-
if (pathStr.startsWith(processed + '.')) {
|
|
195
|
-
skipPath = true;
|
|
196
|
-
break;
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (skipPath) {
|
|
201
|
-
continue;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Create patch based on change type
|
|
205
|
-
const patch = this.createPatch(change);
|
|
206
|
-
patches.push(patch);
|
|
207
|
-
|
|
208
|
-
// Mark path as processed
|
|
209
|
-
processedPaths.add(pathStr);
|
|
210
|
-
|
|
211
|
-
// If this is an object replacement, skip child paths
|
|
212
|
-
if (
|
|
213
|
-
change.type === ChangeType.REPLACE &&
|
|
214
|
-
typeof change.value === 'object'
|
|
215
|
-
) {
|
|
216
|
-
processedPaths.add(pathStr);
|
|
217
|
-
}
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
return patches;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/**
|
|
224
|
-
* Creates a single patch from a change
|
|
225
|
-
*/
|
|
226
|
-
private createPatch(change: Change): Patch {
|
|
227
|
-
return {
|
|
228
|
-
type: change.type,
|
|
229
|
-
path: change.path,
|
|
230
|
-
value: change.value,
|
|
231
|
-
oldValue: change.oldValue,
|
|
232
|
-
priority: this.calculatePriority(change),
|
|
233
|
-
signal: this.pathIndex.get(change.path),
|
|
234
|
-
};
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Calculates update priority for optimal ordering
|
|
239
|
-
*/
|
|
240
|
-
private calculatePriority(change: Change): number {
|
|
241
|
-
let priority = 0;
|
|
242
|
-
|
|
243
|
-
// Shallow updates have higher priority
|
|
244
|
-
priority += (10 - change.path.length) * 10;
|
|
245
|
-
|
|
246
|
-
// Array updates have lower priority (more expensive)
|
|
247
|
-
if (change.path.some((p) => typeof p === 'number')) {
|
|
248
|
-
priority -= 20;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// Replace operations have higher priority than nested updates
|
|
252
|
-
if (change.type === ChangeType.REPLACE) {
|
|
253
|
-
priority += 30;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
return priority;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Sorts patches for optimal application
|
|
261
|
-
*/
|
|
262
|
-
private sortPatches(patches: Patch[]): Patch[] {
|
|
263
|
-
return patches.sort((a, b) => {
|
|
264
|
-
// Sort by priority (higher first)
|
|
265
|
-
if (a.priority !== b.priority) {
|
|
266
|
-
return b.priority - a.priority;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Then by path depth (shallow first)
|
|
270
|
-
return a.path.length - b.path.length;
|
|
271
|
-
});
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
/**
|
|
275
|
-
* Applies patches directly (no batching)
|
|
276
|
-
*/
|
|
277
|
-
private applyPatches(tree: unknown, patches: Patch[]): ApplyResult {
|
|
278
|
-
const appliedPaths: string[] = [];
|
|
279
|
-
let updateCount = 0;
|
|
280
|
-
|
|
281
|
-
for (const patch of patches) {
|
|
282
|
-
if (this.applyPatch(patch, tree)) {
|
|
283
|
-
appliedPaths.push(patch.path.join('.'));
|
|
284
|
-
updateCount++;
|
|
285
|
-
}
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return {
|
|
289
|
-
appliedPaths,
|
|
290
|
-
updateCount,
|
|
291
|
-
batchCount: 1,
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Applies patches with batching for better performance
|
|
297
|
-
*/
|
|
298
|
-
private batchApplyPatches(
|
|
299
|
-
tree: unknown,
|
|
300
|
-
patches: Patch[],
|
|
301
|
-
batchSize = 50
|
|
302
|
-
): ApplyResult {
|
|
303
|
-
const batches: Patch[][] = [];
|
|
304
|
-
|
|
305
|
-
for (let i = 0; i < patches.length; i += batchSize) {
|
|
306
|
-
batches.push(patches.slice(i, i + batchSize));
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
const appliedPaths: string[] = [];
|
|
310
|
-
let updateCount = 0;
|
|
311
|
-
|
|
312
|
-
// Process patches in batches
|
|
313
|
-
for (const currentBatch of batches) {
|
|
314
|
-
for (const patch of currentBatch) {
|
|
315
|
-
if (this.applyPatch(patch, tree)) {
|
|
316
|
-
appliedPaths.push(patch.path.join('.'));
|
|
317
|
-
updateCount++;
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
return {
|
|
323
|
-
appliedPaths,
|
|
324
|
-
updateCount,
|
|
325
|
-
batchCount: batches.length,
|
|
326
|
-
};
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
/**
|
|
330
|
-
* Applies a single patch to the tree object
|
|
331
|
-
*/
|
|
332
|
-
private applyPatch(patch: Patch, tree: unknown): boolean {
|
|
333
|
-
try {
|
|
334
|
-
// First, try to update via signal if available
|
|
335
|
-
if (patch.signal && isSignal(patch.signal) && 'set' in patch.signal) {
|
|
336
|
-
const currentValue = patch.signal();
|
|
337
|
-
|
|
338
|
-
// Only update if value actually changed
|
|
339
|
-
if (this.isEqual(currentValue, patch.value)) {
|
|
340
|
-
return false;
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
// Update the signal - this will handle reactivity
|
|
344
|
-
(patch.signal as WritableSignal<unknown>).set(patch.value);
|
|
345
|
-
|
|
346
|
-
// After successful ADD, update the index
|
|
347
|
-
if (patch.type === ChangeType.ADD && patch.value !== undefined) {
|
|
348
|
-
this.pathIndex.set(patch.path, patch.signal);
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
return true;
|
|
352
|
-
} // Fallback: Navigate to parent object and update directly
|
|
353
|
-
let current: Record<string, unknown> = tree as Record<string, unknown>;
|
|
354
|
-
for (let i = 0; i < patch.path.length - 1; i++) {
|
|
355
|
-
const key = patch.path[i];
|
|
356
|
-
current = current[key] as Record<string, unknown>;
|
|
357
|
-
if (!current || typeof current !== 'object') {
|
|
358
|
-
return false;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
const lastKey = patch.path[patch.path.length - 1];
|
|
363
|
-
|
|
364
|
-
// Only update if value actually changed
|
|
365
|
-
if (this.isEqual(current[lastKey], patch.value)) {
|
|
366
|
-
return false;
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Apply update directly to object
|
|
370
|
-
current[lastKey] = patch.value;
|
|
371
|
-
return true;
|
|
372
|
-
} catch (error) {
|
|
373
|
-
console.error(`Failed to apply patch at ${patch.path.join('.')}:`, error);
|
|
374
|
-
return false;
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
/**
|
|
378
|
-
* Check equality
|
|
379
|
-
*/
|
|
380
|
-
private isEqual(a: unknown, b: unknown): boolean {
|
|
381
|
-
// Fast path for primitives
|
|
382
|
-
if (a === b) {
|
|
383
|
-
return true;
|
|
384
|
-
}
|
|
385
|
-
if (typeof a !== typeof b) {
|
|
386
|
-
return false;
|
|
387
|
-
}
|
|
388
|
-
if (typeof a !== 'object' || a === null || b === null) {
|
|
389
|
-
return false;
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
// Deep equality for objects (simple version)
|
|
393
|
-
try {
|
|
394
|
-
return JSON.stringify(a) === JSON.stringify(b);
|
|
395
|
-
} catch {
|
|
396
|
-
return false;
|
|
397
|
-
}
|
|
398
|
-
}
|
|
399
|
-
}
|
package/src/test-setup.ts
DELETED
|
File without changes
|