@signaltree/core 3.0.2 → 3.1.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/package.json +59 -12
- package/src/enhancers/batching/index.d.ts +1 -0
- package/src/enhancers/batching/index.js +1 -0
- package/src/enhancers/batching/jest.config.d.ts +15 -0
- package/src/enhancers/batching/jest.config.js +21 -0
- package/src/enhancers/batching/lib/batching.d.ts +16 -0
- package/src/enhancers/batching/lib/batching.js +155 -0
- package/src/enhancers/batching/test-setup.d.ts +1 -0
- package/src/enhancers/batching/test-setup.js +5 -0
- package/src/enhancers/computed/index.d.ts +1 -0
- package/src/enhancers/computed/index.js +1 -0
- package/src/enhancers/computed/jest.config.d.ts +15 -0
- package/src/enhancers/computed/jest.config.js +21 -0
- package/src/enhancers/computed/lib/computed.d.ts +12 -0
- package/src/enhancers/computed/lib/computed.js +18 -0
- package/src/enhancers/devtools/index.d.ts +1 -0
- package/src/enhancers/devtools/index.js +1 -0
- package/src/enhancers/devtools/jest.config.d.ts +15 -0
- package/src/enhancers/devtools/jest.config.js +21 -0
- package/src/enhancers/devtools/lib/devtools.d.ts +77 -0
- package/src/enhancers/devtools/lib/devtools.js +278 -0
- package/src/enhancers/devtools/test-setup.d.ts +1 -0
- package/src/enhancers/devtools/test-setup.js +5 -0
- package/src/enhancers/entities/index.d.ts +1 -0
- package/src/enhancers/entities/index.js +1 -0
- package/src/enhancers/entities/jest.config.d.ts +15 -0
- package/src/enhancers/entities/jest.config.js +21 -0
- package/src/enhancers/entities/lib/entities.d.ts +22 -0
- package/src/enhancers/entities/lib/entities.js +110 -0
- package/src/enhancers/entities/test-setup.d.ts +1 -0
- package/src/enhancers/entities/test-setup.js +5 -0
- package/src/enhancers/index.d.ts +3 -0
- package/src/enhancers/index.js +84 -0
- package/src/enhancers/memoization/index.d.ts +1 -0
- package/src/enhancers/memoization/index.js +1 -0
- package/src/enhancers/memoization/jest.config.d.ts +15 -0
- package/src/enhancers/memoization/jest.config.js +21 -0
- package/src/enhancers/memoization/lib/memoization.d.ts +65 -0
- package/src/enhancers/memoization/lib/memoization.js +391 -0
- package/src/enhancers/memoization/test-setup.d.ts +1 -0
- package/src/enhancers/memoization/test-setup.js +5 -0
- package/src/enhancers/middleware/index.d.ts +2 -0
- package/src/enhancers/middleware/index.js +2 -0
- package/src/enhancers/middleware/jest.config.d.ts +15 -0
- package/src/enhancers/middleware/jest.config.js +21 -0
- package/src/enhancers/middleware/lib/async-helpers.d.ts +8 -0
- package/src/enhancers/middleware/lib/async-helpers.js +85 -0
- package/src/enhancers/middleware/lib/middleware.d.ts +11 -0
- package/src/enhancers/middleware/lib/middleware.js +179 -0
- package/src/enhancers/middleware/test-setup.d.ts +1 -0
- package/src/enhancers/middleware/test-setup.js +5 -0
- package/src/enhancers/presets/index.d.ts +1 -0
- package/src/enhancers/presets/index.js +1 -0
- package/src/enhancers/presets/jest.config.d.ts +15 -0
- package/src/enhancers/presets/jest.config.js +21 -0
- package/src/enhancers/presets/lib/presets.d.ts +11 -0
- package/src/enhancers/presets/lib/presets.js +102 -0
- package/src/enhancers/presets/test-setup.d.ts +1 -0
- package/src/enhancers/presets/test-setup.js +5 -0
- package/src/enhancers/serialization/constants.d.ts +14 -0
- package/src/enhancers/serialization/constants.js +14 -0
- package/src/enhancers/serialization/index.d.ts +2 -0
- package/src/enhancers/serialization/index.js +2 -0
- package/src/enhancers/serialization/jest.config.d.ts +15 -0
- package/src/enhancers/serialization/jest.config.js +21 -0
- package/src/enhancers/serialization/lib/serialization.d.ts +59 -0
- package/src/enhancers/serialization/lib/serialization.js +668 -0
- package/src/enhancers/serialization/test-setup.d.ts +1 -0
- package/src/enhancers/serialization/test-setup.js +5 -0
- package/src/enhancers/time-travel/index.d.ts +1 -0
- package/src/enhancers/time-travel/index.js +1 -0
- package/src/enhancers/time-travel/jest.config.d.ts +15 -0
- package/src/enhancers/time-travel/jest.config.js +21 -0
- package/src/enhancers/time-travel/lib/time-travel.d.ts +36 -0
- package/src/enhancers/time-travel/lib/time-travel.js +192 -0
- package/src/enhancers/time-travel/lib/utils.d.ts +1 -0
- package/src/enhancers/time-travel/lib/utils.js +1 -0
- package/src/enhancers/time-travel/test-setup.d.ts +1 -0
- package/src/enhancers/time-travel/test-setup.js +5 -0
- package/src/enhancers/types.d.ts +105 -0
- package/src/enhancers/types.js +0 -0
- package/src/index.d.ts +16 -0
- package/src/index.js +15 -0
- package/src/lib/constants.d.ts +42 -0
- package/src/lib/constants.js +61 -0
- package/src/lib/memory/memory-manager.d.ts +30 -0
- package/src/lib/memory/memory-manager.js +166 -0
- package/src/lib/performance/diff-engine.d.ts +33 -0
- package/src/lib/performance/diff-engine.js +156 -0
- package/src/lib/performance/path-index.d.ts +25 -0
- package/src/lib/performance/path-index.js +154 -0
- package/src/lib/performance/update-engine.d.ts +32 -0
- package/src/lib/performance/update-engine.js +193 -0
- package/src/lib/security/security-validator.d.ts +33 -0
- package/src/lib/security/security-validator.js +139 -0
- package/src/lib/signal-tree.d.ts +8 -0
- package/{fesm2022/signaltree-core.mjs → src/lib/signal-tree.js} +100 -573
- package/src/lib/types.d.ts +164 -0
- package/src/lib/types.js +9 -0
- package/src/lib/utils.d.ts +27 -0
- package/src/lib/utils.js +286 -0
- package/README.md +0 -1455
- package/fesm2022/signaltree-core.mjs.map +0 -1
- package/index.d.ts +0 -216
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export class SignalMemoryManager {
|
|
2
|
+
cache = new Map();
|
|
3
|
+
registry = null;
|
|
4
|
+
config;
|
|
5
|
+
stats = {
|
|
6
|
+
cleanedUpSignals: 0,
|
|
7
|
+
peakCachedSignals: 0,
|
|
8
|
+
manualDisposes: 0,
|
|
9
|
+
};
|
|
10
|
+
constructor(config = {}) {
|
|
11
|
+
this.config = {
|
|
12
|
+
enableAutoCleanup: config.enableAutoCleanup ?? true,
|
|
13
|
+
debugMode: config.debugMode ?? false,
|
|
14
|
+
onCleanup: config.onCleanup ??
|
|
15
|
+
(() => {
|
|
16
|
+
}),
|
|
17
|
+
};
|
|
18
|
+
if (this.config.enableAutoCleanup &&
|
|
19
|
+
typeof FinalizationRegistry !== 'undefined') {
|
|
20
|
+
this.registry = new FinalizationRegistry((path) => {
|
|
21
|
+
this.handleCleanup(path);
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
if (this.config.debugMode) {
|
|
25
|
+
console.log('[SignalMemoryManager] Initialized', {
|
|
26
|
+
autoCleanup: this.config.enableAutoCleanup,
|
|
27
|
+
hasRegistry: !!this.registry,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
cacheSignal(path, signal) {
|
|
32
|
+
const ref = new WeakRef(signal);
|
|
33
|
+
const entry = {
|
|
34
|
+
ref,
|
|
35
|
+
path,
|
|
36
|
+
cachedAt: Date.now(),
|
|
37
|
+
};
|
|
38
|
+
this.cache.set(path, entry);
|
|
39
|
+
if (this.registry) {
|
|
40
|
+
this.registry.register(signal, path, signal);
|
|
41
|
+
}
|
|
42
|
+
const currentSize = this.cache.size;
|
|
43
|
+
if (currentSize > this.stats.peakCachedSignals) {
|
|
44
|
+
this.stats.peakCachedSignals = currentSize;
|
|
45
|
+
}
|
|
46
|
+
if (this.config.debugMode) {
|
|
47
|
+
console.log(`[SignalMemoryManager] Cached signal: ${path}`, {
|
|
48
|
+
cacheSize: currentSize,
|
|
49
|
+
peak: this.stats.peakCachedSignals,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
getSignal(path) {
|
|
54
|
+
const entry = this.cache.get(path);
|
|
55
|
+
if (!entry) {
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
const signal = entry.ref.deref();
|
|
59
|
+
if (!signal) {
|
|
60
|
+
this.cache.delete(path);
|
|
61
|
+
if (this.config.debugMode) {
|
|
62
|
+
console.log(`[SignalMemoryManager] Signal GC'd: ${path}`);
|
|
63
|
+
}
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
return signal;
|
|
67
|
+
}
|
|
68
|
+
hasSignal(path) {
|
|
69
|
+
return this.cache.has(path);
|
|
70
|
+
}
|
|
71
|
+
removeSignal(path) {
|
|
72
|
+
const entry = this.cache.get(path);
|
|
73
|
+
if (!entry) {
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
const signal = entry.ref.deref();
|
|
77
|
+
if (signal && this.registry) {
|
|
78
|
+
this.registry.unregister(signal);
|
|
79
|
+
}
|
|
80
|
+
this.cache.delete(path);
|
|
81
|
+
if (this.config.debugMode) {
|
|
82
|
+
console.log(`[SignalMemoryManager] Removed signal: ${path}`);
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
handleCleanup(path) {
|
|
87
|
+
this.cache.delete(path);
|
|
88
|
+
this.stats.cleanedUpSignals++;
|
|
89
|
+
const currentStats = this.getStats();
|
|
90
|
+
if (this.config.debugMode) {
|
|
91
|
+
console.log(`[SignalMemoryManager] Auto cleanup: ${path}`, currentStats);
|
|
92
|
+
}
|
|
93
|
+
this.config.onCleanup(path, currentStats);
|
|
94
|
+
}
|
|
95
|
+
getStats() {
|
|
96
|
+
let validSignals = 0;
|
|
97
|
+
for (const [path, entry] of this.cache.entries()) {
|
|
98
|
+
if (entry.ref.deref()) {
|
|
99
|
+
validSignals++;
|
|
100
|
+
}
|
|
101
|
+
else {
|
|
102
|
+
this.cache.delete(path);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const estimatedMemoryBytes = validSignals * 100;
|
|
106
|
+
return {
|
|
107
|
+
cachedSignals: validSignals,
|
|
108
|
+
cleanedUpSignals: this.stats.cleanedUpSignals,
|
|
109
|
+
peakCachedSignals: this.stats.peakCachedSignals,
|
|
110
|
+
manualDisposes: this.stats.manualDisposes,
|
|
111
|
+
estimatedMemoryBytes,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
dispose() {
|
|
115
|
+
if (this.config.debugMode) {
|
|
116
|
+
console.log('[SignalMemoryManager] Disposing', {
|
|
117
|
+
cachedSignals: this.cache.size,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (this.registry) {
|
|
121
|
+
for (const entry of this.cache.values()) {
|
|
122
|
+
const signal = entry.ref.deref();
|
|
123
|
+
if (signal) {
|
|
124
|
+
this.registry.unregister(signal);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
this.cache.clear();
|
|
129
|
+
this.stats.manualDisposes++;
|
|
130
|
+
if (this.config.debugMode) {
|
|
131
|
+
console.log('[SignalMemoryManager] Disposed', this.getStats());
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
getCachedPaths() {
|
|
135
|
+
const paths = [];
|
|
136
|
+
for (const [path, entry] of this.cache.entries()) {
|
|
137
|
+
if (entry.ref.deref()) {
|
|
138
|
+
paths.push(path);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return paths;
|
|
142
|
+
}
|
|
143
|
+
clearStale() {
|
|
144
|
+
let removed = 0;
|
|
145
|
+
for (const [path, entry] of this.cache.entries()) {
|
|
146
|
+
if (!entry.ref.deref()) {
|
|
147
|
+
this.cache.delete(path);
|
|
148
|
+
removed++;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (this.config.debugMode && removed > 0) {
|
|
152
|
+
console.log(`[SignalMemoryManager] Cleared ${removed} stale entries`);
|
|
153
|
+
}
|
|
154
|
+
return removed;
|
|
155
|
+
}
|
|
156
|
+
resetStats() {
|
|
157
|
+
this.stats = {
|
|
158
|
+
cleanedUpSignals: 0,
|
|
159
|
+
peakCachedSignals: 0,
|
|
160
|
+
manualDisposes: 0,
|
|
161
|
+
};
|
|
162
|
+
if (this.config.debugMode) {
|
|
163
|
+
console.log('[SignalMemoryManager] Stats reset');
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { Path } from './path-index';
|
|
2
|
+
export declare enum ChangeType {
|
|
3
|
+
ADD = "add",
|
|
4
|
+
UPDATE = "update",
|
|
5
|
+
DELETE = "delete",
|
|
6
|
+
REPLACE = "replace"
|
|
7
|
+
}
|
|
8
|
+
export interface Change {
|
|
9
|
+
type: ChangeType;
|
|
10
|
+
path: Path;
|
|
11
|
+
value?: unknown;
|
|
12
|
+
oldValue?: unknown;
|
|
13
|
+
}
|
|
14
|
+
export interface Diff {
|
|
15
|
+
changes: Change[];
|
|
16
|
+
hasChanges: boolean;
|
|
17
|
+
}
|
|
18
|
+
export interface DiffOptions {
|
|
19
|
+
maxDepth?: number;
|
|
20
|
+
detectDeletions?: boolean;
|
|
21
|
+
ignoreArrayOrder?: boolean;
|
|
22
|
+
equalityFn?: (a: unknown, b: unknown) => boolean;
|
|
23
|
+
keyValidator?: (key: string) => boolean;
|
|
24
|
+
}
|
|
25
|
+
export declare class DiffEngine {
|
|
26
|
+
private defaultOptions;
|
|
27
|
+
diff(current: unknown, updates: unknown, options?: DiffOptions): Diff;
|
|
28
|
+
private traverse;
|
|
29
|
+
private diffArrays;
|
|
30
|
+
private diffArraysOrdered;
|
|
31
|
+
private diffArraysUnordered;
|
|
32
|
+
private stringify;
|
|
33
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
export var ChangeType;
|
|
2
|
+
(function (ChangeType) {
|
|
3
|
+
ChangeType["ADD"] = "add";
|
|
4
|
+
ChangeType["UPDATE"] = "update";
|
|
5
|
+
ChangeType["DELETE"] = "delete";
|
|
6
|
+
ChangeType["REPLACE"] = "replace";
|
|
7
|
+
})(ChangeType || (ChangeType = {}));
|
|
8
|
+
export class DiffEngine {
|
|
9
|
+
defaultOptions = {
|
|
10
|
+
maxDepth: 100,
|
|
11
|
+
detectDeletions: false,
|
|
12
|
+
ignoreArrayOrder: false,
|
|
13
|
+
equalityFn: (a, b) => a === b,
|
|
14
|
+
keyValidator: undefined,
|
|
15
|
+
};
|
|
16
|
+
diff(current, updates, options = {}) {
|
|
17
|
+
const opts = { ...this.defaultOptions, ...options };
|
|
18
|
+
const changes = [];
|
|
19
|
+
const visited = new WeakSet();
|
|
20
|
+
this.traverse(current, updates, [], changes, visited, opts, 0);
|
|
21
|
+
return {
|
|
22
|
+
changes,
|
|
23
|
+
hasChanges: changes.length > 0,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
traverse(curr, upd, path, changes, visited, opts, depth) {
|
|
27
|
+
if (depth > opts.maxDepth) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (typeof upd !== 'object' || upd === null) {
|
|
31
|
+
if (!opts.equalityFn(curr, upd)) {
|
|
32
|
+
changes.push({
|
|
33
|
+
type: curr === undefined ? ChangeType.ADD : ChangeType.UPDATE,
|
|
34
|
+
path: [...path],
|
|
35
|
+
value: upd,
|
|
36
|
+
oldValue: curr,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (visited.has(upd)) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
visited.add(upd);
|
|
45
|
+
if (Array.isArray(upd)) {
|
|
46
|
+
this.diffArrays(curr, upd, path, changes, visited, opts, depth);
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!curr || typeof curr !== 'object' || Array.isArray(curr)) {
|
|
50
|
+
changes.push({
|
|
51
|
+
type: ChangeType.REPLACE,
|
|
52
|
+
path: [...path],
|
|
53
|
+
value: upd,
|
|
54
|
+
oldValue: curr,
|
|
55
|
+
});
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const currObj = curr;
|
|
59
|
+
const updObj = upd;
|
|
60
|
+
for (const key in updObj) {
|
|
61
|
+
if (Object.prototype.hasOwnProperty.call(updObj, key)) {
|
|
62
|
+
if (opts.keyValidator && !opts.keyValidator(key)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
this.traverse(currObj[key], updObj[key], [...path, key], changes, visited, opts, depth + 1);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (opts.detectDeletions) {
|
|
69
|
+
for (const key in currObj) {
|
|
70
|
+
if (Object.prototype.hasOwnProperty.call(currObj, key) &&
|
|
71
|
+
!(key in updObj)) {
|
|
72
|
+
changes.push({
|
|
73
|
+
type: ChangeType.DELETE,
|
|
74
|
+
path: [...path, key],
|
|
75
|
+
oldValue: currObj[key],
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
diffArrays(curr, upd, path, changes, visited, opts, depth) {
|
|
82
|
+
if (!Array.isArray(curr)) {
|
|
83
|
+
changes.push({
|
|
84
|
+
type: ChangeType.REPLACE,
|
|
85
|
+
path: [...path],
|
|
86
|
+
value: upd,
|
|
87
|
+
oldValue: curr,
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (opts.ignoreArrayOrder) {
|
|
92
|
+
this.diffArraysUnordered(curr, upd, path, changes, opts);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
this.diffArraysOrdered(curr, upd, path, changes, visited, opts, depth);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
diffArraysOrdered(curr, upd, path, changes, visited, opts, depth) {
|
|
99
|
+
const maxLength = Math.max(curr.length, upd.length);
|
|
100
|
+
for (let i = 0; i < maxLength; i++) {
|
|
101
|
+
if (i >= upd.length) {
|
|
102
|
+
if (opts.detectDeletions) {
|
|
103
|
+
changes.push({
|
|
104
|
+
type: ChangeType.DELETE,
|
|
105
|
+
path: [...path, i],
|
|
106
|
+
oldValue: curr[i],
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else if (i >= curr.length) {
|
|
111
|
+
changes.push({
|
|
112
|
+
type: ChangeType.ADD,
|
|
113
|
+
path: [...path, i],
|
|
114
|
+
value: upd[i],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
this.traverse(curr[i], upd[i], [...path, i], changes, visited, opts, depth + 1);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
diffArraysUnordered(curr, upd, path, changes, opts) {
|
|
123
|
+
const currSet = new Set(curr.map((v) => this.stringify(v)));
|
|
124
|
+
const updSet = new Set(upd.map((v) => this.stringify(v)));
|
|
125
|
+
upd.forEach((value, index) => {
|
|
126
|
+
const str = this.stringify(value);
|
|
127
|
+
if (!currSet.has(str)) {
|
|
128
|
+
changes.push({
|
|
129
|
+
type: ChangeType.ADD,
|
|
130
|
+
path: [...path, index],
|
|
131
|
+
value,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if (opts.detectDeletions) {
|
|
136
|
+
curr.forEach((value, index) => {
|
|
137
|
+
const str = this.stringify(value);
|
|
138
|
+
if (!updSet.has(str)) {
|
|
139
|
+
changes.push({
|
|
140
|
+
type: ChangeType.DELETE,
|
|
141
|
+
path: [...path, index],
|
|
142
|
+
oldValue: value,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
stringify(value) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.stringify(value);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return String(value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WritableSignal } from '@angular/core';
|
|
2
|
+
export type PathSegment = string | number;
|
|
3
|
+
export type Path = PathSegment[];
|
|
4
|
+
export declare class PathIndex<T extends object = WritableSignal<any>> {
|
|
5
|
+
private root;
|
|
6
|
+
private pathCache;
|
|
7
|
+
private stats;
|
|
8
|
+
set(path: Path, signal: T): void;
|
|
9
|
+
get(path: Path): T | null;
|
|
10
|
+
has(path: Path): boolean;
|
|
11
|
+
getByPrefix(prefix: Path): Map<string, T>;
|
|
12
|
+
delete(path: Path): boolean;
|
|
13
|
+
clear(): void;
|
|
14
|
+
getStats(): {
|
|
15
|
+
hits: number;
|
|
16
|
+
misses: number;
|
|
17
|
+
sets: number;
|
|
18
|
+
cleanups: number;
|
|
19
|
+
hitRate: number;
|
|
20
|
+
cacheSize: number;
|
|
21
|
+
};
|
|
22
|
+
buildFromTree(tree: unknown, path?: Path): void;
|
|
23
|
+
private pathToString;
|
|
24
|
+
private collectDescendants;
|
|
25
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { isSignal } from '@angular/core';
|
|
2
|
+
class TrieNode {
|
|
3
|
+
value = null;
|
|
4
|
+
children = new Map();
|
|
5
|
+
}
|
|
6
|
+
export class PathIndex {
|
|
7
|
+
root = new TrieNode();
|
|
8
|
+
pathCache = new Map();
|
|
9
|
+
stats = {
|
|
10
|
+
hits: 0,
|
|
11
|
+
misses: 0,
|
|
12
|
+
sets: 0,
|
|
13
|
+
cleanups: 0,
|
|
14
|
+
};
|
|
15
|
+
set(path, signal) {
|
|
16
|
+
const pathStr = this.pathToString(path);
|
|
17
|
+
const ref = new WeakRef(signal);
|
|
18
|
+
let node = this.root;
|
|
19
|
+
for (const segment of path) {
|
|
20
|
+
const key = String(segment);
|
|
21
|
+
if (!node.children.has(key)) {
|
|
22
|
+
node.children.set(key, new TrieNode());
|
|
23
|
+
}
|
|
24
|
+
const nextNode = node.children.get(key);
|
|
25
|
+
if (!nextNode) {
|
|
26
|
+
throw new Error(`Failed to get node for key: ${key}`);
|
|
27
|
+
}
|
|
28
|
+
node = nextNode;
|
|
29
|
+
}
|
|
30
|
+
node.value = ref;
|
|
31
|
+
this.pathCache.set(pathStr, ref);
|
|
32
|
+
this.stats.sets++;
|
|
33
|
+
}
|
|
34
|
+
get(path) {
|
|
35
|
+
const pathStr = this.pathToString(path);
|
|
36
|
+
const cached = this.pathCache.get(pathStr);
|
|
37
|
+
if (cached) {
|
|
38
|
+
const value = cached.deref();
|
|
39
|
+
if (value) {
|
|
40
|
+
this.stats.hits++;
|
|
41
|
+
return value;
|
|
42
|
+
}
|
|
43
|
+
this.pathCache.delete(pathStr);
|
|
44
|
+
this.stats.cleanups++;
|
|
45
|
+
}
|
|
46
|
+
let node = this.root;
|
|
47
|
+
for (const segment of path) {
|
|
48
|
+
const key = String(segment);
|
|
49
|
+
node = node.children.get(key);
|
|
50
|
+
if (!node) {
|
|
51
|
+
this.stats.misses++;
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (node.value) {
|
|
56
|
+
const value = node.value.deref();
|
|
57
|
+
if (value) {
|
|
58
|
+
this.pathCache.set(pathStr, node.value);
|
|
59
|
+
this.stats.hits++;
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
node.value = null;
|
|
63
|
+
this.stats.cleanups++;
|
|
64
|
+
}
|
|
65
|
+
this.stats.misses++;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
has(path) {
|
|
69
|
+
return this.get(path) !== null;
|
|
70
|
+
}
|
|
71
|
+
getByPrefix(prefix) {
|
|
72
|
+
const results = new Map();
|
|
73
|
+
let node = this.root;
|
|
74
|
+
for (const segment of prefix) {
|
|
75
|
+
const key = String(segment);
|
|
76
|
+
node = node.children.get(key);
|
|
77
|
+
if (!node) {
|
|
78
|
+
return results;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
this.collectDescendants(node, [], results);
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
delete(path) {
|
|
85
|
+
const pathStr = this.pathToString(path);
|
|
86
|
+
this.pathCache.delete(pathStr);
|
|
87
|
+
let node = this.root;
|
|
88
|
+
const nodes = [node];
|
|
89
|
+
for (const segment of path) {
|
|
90
|
+
const key = String(segment);
|
|
91
|
+
node = node.children.get(key);
|
|
92
|
+
if (!node) {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
nodes.push(node);
|
|
96
|
+
}
|
|
97
|
+
const hadValue = node.value !== null;
|
|
98
|
+
node.value = null;
|
|
99
|
+
for (let i = nodes.length - 1; i > 0; i--) {
|
|
100
|
+
const current = nodes[i];
|
|
101
|
+
if (current.value === null && current.children.size === 0) {
|
|
102
|
+
const parent = nodes[i - 1];
|
|
103
|
+
const segment = path[i - 1];
|
|
104
|
+
parent.children.delete(String(segment));
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return hadValue;
|
|
111
|
+
}
|
|
112
|
+
clear() {
|
|
113
|
+
this.root = new TrieNode();
|
|
114
|
+
this.pathCache.clear();
|
|
115
|
+
}
|
|
116
|
+
getStats() {
|
|
117
|
+
const total = this.stats.hits + this.stats.misses;
|
|
118
|
+
const hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
119
|
+
return {
|
|
120
|
+
...this.stats,
|
|
121
|
+
hitRate,
|
|
122
|
+
cacheSize: this.pathCache.size,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
buildFromTree(tree, path = []) {
|
|
126
|
+
if (!tree) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (isSignal(tree)) {
|
|
130
|
+
this.set(path, tree);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (typeof tree !== 'object') {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
137
|
+
this.buildFromTree(value, [...path, key]);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
pathToString(path) {
|
|
141
|
+
return path.join('.');
|
|
142
|
+
}
|
|
143
|
+
collectDescendants(node, currentPath, results) {
|
|
144
|
+
if (node.value) {
|
|
145
|
+
const value = node.value.deref();
|
|
146
|
+
if (value) {
|
|
147
|
+
results.set(this.pathToString(currentPath), value);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
for (const [key, child] of node.children) {
|
|
151
|
+
this.collectDescendants(child, [...currentPath, key], results);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { PathIndex } from './path-index';
|
|
2
|
+
import type { DiffOptions } from './diff-engine';
|
|
3
|
+
export interface UpdateOptions extends DiffOptions {
|
|
4
|
+
batch?: boolean;
|
|
5
|
+
batchSize?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface UpdateResult {
|
|
8
|
+
changed: boolean;
|
|
9
|
+
duration: number;
|
|
10
|
+
changedPaths: string[];
|
|
11
|
+
stats?: {
|
|
12
|
+
totalPaths: number;
|
|
13
|
+
optimizedPaths: number;
|
|
14
|
+
batchedUpdates: number;
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
export declare class OptimizedUpdateEngine {
|
|
18
|
+
private pathIndex;
|
|
19
|
+
private diffEngine;
|
|
20
|
+
constructor(tree: unknown);
|
|
21
|
+
update(tree: unknown, updates: unknown, options?: UpdateOptions): UpdateResult;
|
|
22
|
+
rebuildIndex(tree: unknown): void;
|
|
23
|
+
getIndexStats(): ReturnType<PathIndex['getStats']>;
|
|
24
|
+
private createPatches;
|
|
25
|
+
private createPatch;
|
|
26
|
+
private calculatePriority;
|
|
27
|
+
private sortPatches;
|
|
28
|
+
private applyPatches;
|
|
29
|
+
private batchApplyPatches;
|
|
30
|
+
private applyPatch;
|
|
31
|
+
private isEqual;
|
|
32
|
+
}
|