@signaltree/enterprise 7.1.4 → 7.3.1
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/dist/index.d.ts +1 -0
- package/dist/index.js +6 -0
- package/dist/lib/diff-engine.js +239 -0
- package/dist/lib/enterprise-enhancer.js +33 -0
- package/dist/lib/path-index.js +264 -0
- package/dist/lib/scheduler.js +76 -0
- package/dist/lib/thread-pools.js +13 -0
- package/dist/lib/update-engine.js +198 -0
- package/package.json +2 -1
- package/src/diff-engine.d.ts +2 -0
- package/src/enterprise-enhancer.d.ts +2 -0
- package/src/index.d.ts +6 -0
- package/src/lib/diff-engine.d.ts +43 -0
- package/src/lib/enterprise-enhancer.d.ts +16 -0
- package/src/lib/enterprise.d.ts +1 -0
- package/src/lib/path-index.d.ts +31 -0
- package/src/lib/scheduler.d.ts +18 -0
- package/src/lib/thread-pools.d.ts +4 -0
- package/src/lib/update-engine.d.ts +32 -0
- package/src/path-index.d.ts +2 -0
- package/src/scheduler.d.ts +2 -0
- package/src/signaltree-core.d.ts +4 -0
- package/src/test-setup.d.ts +3 -0
- package/src/thread-pools.d.ts +2 -0
- package/src/update-engine.d.ts +2 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./src/index";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ChangeType, DiffEngine } from './lib/diff-engine.js';
|
|
2
|
+
export { PathIndex } from './lib/path-index.js';
|
|
3
|
+
export { OptimizedUpdateEngine } from './lib/update-engine.js';
|
|
4
|
+
export { enterprise, withEnterprise } from './lib/enterprise-enhancer.js';
|
|
5
|
+
export { configureScheduler, getSchedulerMetrics, postTask } from './lib/scheduler.js';
|
|
6
|
+
export { createMockPool } from './lib/thread-pools.js';
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
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
|
+
class DiffEngine {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.defaultOptions = {
|
|
11
|
+
maxDepth: 100,
|
|
12
|
+
detectDeletions: false,
|
|
13
|
+
ignoreArrayOrder: false,
|
|
14
|
+
equalityFn: (a, b) => a === b,
|
|
15
|
+
keyValidator: undefined,
|
|
16
|
+
instrumentation: false
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
diff(current, updates, options = {}) {
|
|
20
|
+
const opts = Object.assign(Object.assign({}, this.defaultOptions), options);
|
|
21
|
+
const changes = [];
|
|
22
|
+
const visited = new WeakSet();
|
|
23
|
+
const metrics = {
|
|
24
|
+
elementComparisons: 0,
|
|
25
|
+
prefixFastPathHits: 0,
|
|
26
|
+
wholeArrayReplaceHits: 0,
|
|
27
|
+
traversals: 0,
|
|
28
|
+
suffixFastPathHits: 0,
|
|
29
|
+
segmentSkips: 0,
|
|
30
|
+
samplesTaken: 0
|
|
31
|
+
};
|
|
32
|
+
this.traverse(current, updates, [], changes, visited, opts, 0, metrics);
|
|
33
|
+
return {
|
|
34
|
+
changes,
|
|
35
|
+
hasChanges: changes.length > 0,
|
|
36
|
+
instrumentation: opts.instrumentation ? metrics : undefined
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
traverse(curr, upd, path, changes, visited, opts, depth, metrics) {
|
|
40
|
+
if (opts.instrumentation) metrics.traversals++;
|
|
41
|
+
if (depth > opts.maxDepth) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (curr === upd) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
if (typeof upd !== 'object' || upd === null) {
|
|
48
|
+
if (!opts.equalityFn(curr, upd)) {
|
|
49
|
+
changes.push({
|
|
50
|
+
type: curr === undefined ? ChangeType.ADD : ChangeType.UPDATE,
|
|
51
|
+
path: [...path],
|
|
52
|
+
value: upd,
|
|
53
|
+
oldValue: curr
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (visited.has(upd)) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
visited.add(upd);
|
|
62
|
+
if (Array.isArray(upd)) {
|
|
63
|
+
this.diffArrays(curr, upd, path, changes, visited, opts, depth, metrics);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!curr || typeof curr !== 'object' || Array.isArray(curr)) {
|
|
67
|
+
changes.push({
|
|
68
|
+
type: ChangeType.REPLACE,
|
|
69
|
+
path: [...path],
|
|
70
|
+
value: upd,
|
|
71
|
+
oldValue: curr
|
|
72
|
+
});
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const currObj = curr;
|
|
76
|
+
const updObj = upd;
|
|
77
|
+
for (const key in updObj) {
|
|
78
|
+
if (Object.prototype.hasOwnProperty.call(updObj, key)) {
|
|
79
|
+
if (opts.keyValidator && !opts.keyValidator(key)) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
this.traverse(currObj[key], updObj[key], [...path, key], changes, visited, opts, depth + 1, metrics);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
if (opts.detectDeletions) {
|
|
86
|
+
for (const key in currObj) {
|
|
87
|
+
if (Object.prototype.hasOwnProperty.call(currObj, key) && !(key in updObj)) {
|
|
88
|
+
changes.push({
|
|
89
|
+
type: ChangeType.DELETE,
|
|
90
|
+
path: [...path, key],
|
|
91
|
+
oldValue: currObj[key]
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
diffArrays(curr, upd, path, changes, visited, opts, depth, metrics) {
|
|
98
|
+
if (!Array.isArray(curr)) {
|
|
99
|
+
changes.push({
|
|
100
|
+
type: ChangeType.REPLACE,
|
|
101
|
+
path: [...path],
|
|
102
|
+
value: upd,
|
|
103
|
+
oldValue: curr
|
|
104
|
+
});
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (opts.ignoreArrayOrder) {
|
|
108
|
+
this.diffArraysUnordered(curr, upd, path, changes, opts);
|
|
109
|
+
} else {
|
|
110
|
+
this.diffArraysOrdered(curr, upd, path, changes, visited, opts, depth, metrics);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
diffArraysOrdered(curr, upd, path, changes, visited, opts, depth, metrics) {
|
|
114
|
+
if (curr === upd) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (curr.length === upd.length) {
|
|
118
|
+
let identical = true;
|
|
119
|
+
for (let i = 0; i < curr.length; i++) {
|
|
120
|
+
if (opts.instrumentation) metrics.elementComparisons++;
|
|
121
|
+
if (curr[i] !== upd[i]) {
|
|
122
|
+
identical = false;
|
|
123
|
+
break;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (identical) return;
|
|
127
|
+
}
|
|
128
|
+
const currLen = curr.length;
|
|
129
|
+
const updLen = upd.length;
|
|
130
|
+
const minLen = Math.min(currLen, updLen);
|
|
131
|
+
if (minLen > 0) {
|
|
132
|
+
let prefixIdentical = true;
|
|
133
|
+
for (let i = 0; i < minLen; i++) {
|
|
134
|
+
if (opts.instrumentation) metrics.elementComparisons++;
|
|
135
|
+
if (curr[i] !== upd[i]) {
|
|
136
|
+
prefixIdentical = false;
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
if (prefixIdentical && currLen !== updLen) {
|
|
141
|
+
if (opts.instrumentation) metrics.prefixFastPathHits++;
|
|
142
|
+
if (updLen > currLen) {
|
|
143
|
+
for (let i = currLen; i < updLen; i++) {
|
|
144
|
+
changes.push({
|
|
145
|
+
type: ChangeType.ADD,
|
|
146
|
+
path: [...path, i],
|
|
147
|
+
value: upd[i]
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
} else if (currLen > updLen && opts.detectDeletions) {
|
|
151
|
+
for (let i = updLen; i < currLen; i++) {
|
|
152
|
+
changes.push({
|
|
153
|
+
type: ChangeType.DELETE,
|
|
154
|
+
path: [...path, i],
|
|
155
|
+
oldValue: curr[i]
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
const LARGE_ARRAY_THRESHOLD = 1024;
|
|
163
|
+
const REPLACE_MISMATCH_RATIO = 0.4;
|
|
164
|
+
if (currLen >= LARGE_ARRAY_THRESHOLD && updLen >= LARGE_ARRAY_THRESHOLD && currLen === updLen) {
|
|
165
|
+
let mismatches = 0;
|
|
166
|
+
for (let i = 0; i < currLen; i++) {
|
|
167
|
+
if (opts.instrumentation) metrics.elementComparisons++;
|
|
168
|
+
if (curr[i] !== upd[i]) {
|
|
169
|
+
mismatches++;
|
|
170
|
+
if (mismatches / currLen > REPLACE_MISMATCH_RATIO) {
|
|
171
|
+
changes.push({
|
|
172
|
+
type: ChangeType.REPLACE,
|
|
173
|
+
path: [...path],
|
|
174
|
+
value: upd,
|
|
175
|
+
oldValue: curr
|
|
176
|
+
});
|
|
177
|
+
if (opts.instrumentation) metrics.wholeArrayReplaceHits++;
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const maxLength = Math.max(currLen, updLen);
|
|
184
|
+
for (let i = 0; i < maxLength; i++) {
|
|
185
|
+
if (i >= upd.length) {
|
|
186
|
+
if (opts.detectDeletions) {
|
|
187
|
+
changes.push({
|
|
188
|
+
type: ChangeType.DELETE,
|
|
189
|
+
path: [...path, i],
|
|
190
|
+
oldValue: curr[i]
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
} else if (i >= curr.length) {
|
|
194
|
+
changes.push({
|
|
195
|
+
type: ChangeType.ADD,
|
|
196
|
+
path: [...path, i],
|
|
197
|
+
value: upd[i]
|
|
198
|
+
});
|
|
199
|
+
} else {
|
|
200
|
+
this.traverse(curr[i], upd[i], [...path, i], changes, visited, opts, depth + 1, metrics);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
diffArraysUnordered(curr, upd, path, changes, opts) {
|
|
205
|
+
const currSet = new Set(curr.map(v => this.stringify(v)));
|
|
206
|
+
const updSet = new Set(upd.map(v => this.stringify(v)));
|
|
207
|
+
upd.forEach((value, index) => {
|
|
208
|
+
const str = this.stringify(value);
|
|
209
|
+
if (!currSet.has(str)) {
|
|
210
|
+
changes.push({
|
|
211
|
+
type: ChangeType.ADD,
|
|
212
|
+
path: [...path, index],
|
|
213
|
+
value
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
if (opts.detectDeletions) {
|
|
218
|
+
curr.forEach((value, index) => {
|
|
219
|
+
const str = this.stringify(value);
|
|
220
|
+
if (!updSet.has(str)) {
|
|
221
|
+
changes.push({
|
|
222
|
+
type: ChangeType.DELETE,
|
|
223
|
+
path: [...path, index],
|
|
224
|
+
oldValue: value
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
stringify(value) {
|
|
231
|
+
try {
|
|
232
|
+
return JSON.stringify(value);
|
|
233
|
+
} catch (_a) {
|
|
234
|
+
return String(value);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export { ChangeType, DiffEngine };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { PathIndex } from './path-index.js';
|
|
2
|
+
import { OptimizedUpdateEngine } from './update-engine.js';
|
|
3
|
+
|
|
4
|
+
function enterprise() {
|
|
5
|
+
return tree => {
|
|
6
|
+
let pathIndex = null;
|
|
7
|
+
let updateEngine = null;
|
|
8
|
+
const signalTree = tree;
|
|
9
|
+
const enhancedTree = tree;
|
|
10
|
+
enhancedTree.updateOptimized = (updates, options) => {
|
|
11
|
+
if (!updateEngine) {
|
|
12
|
+
pathIndex = new PathIndex();
|
|
13
|
+
pathIndex.buildFromTree(signalTree.state);
|
|
14
|
+
updateEngine = new OptimizedUpdateEngine(signalTree.state);
|
|
15
|
+
}
|
|
16
|
+
const result = updateEngine.update(signalTree.state, updates, options);
|
|
17
|
+
if (result.changed && pathIndex) {
|
|
18
|
+
if (result.changedPaths.length) {
|
|
19
|
+
pathIndex.incrementalUpdate(signalTree.state, result.changedPaths);
|
|
20
|
+
} else {
|
|
21
|
+
pathIndex.clear();
|
|
22
|
+
pathIndex.buildFromTree(signalTree.state);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return result;
|
|
26
|
+
};
|
|
27
|
+
enhancedTree.getPathIndex = () => pathIndex;
|
|
28
|
+
return enhancedTree;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
const withEnterprise = Object.assign(enterprise, {});
|
|
32
|
+
|
|
33
|
+
export { enterprise, withEnterprise };
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { isSignal } from '@angular/core';
|
|
2
|
+
|
|
3
|
+
class TrieNode {
|
|
4
|
+
constructor() {
|
|
5
|
+
this.value = null;
|
|
6
|
+
this.children = new Map();
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
class PathIndex {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.root = new TrieNode();
|
|
12
|
+
this.pathCache = new Map();
|
|
13
|
+
this.stats = {
|
|
14
|
+
hits: 0,
|
|
15
|
+
misses: 0,
|
|
16
|
+
sets: 0,
|
|
17
|
+
cleanups: 0
|
|
18
|
+
};
|
|
19
|
+
this.enableInstrumentation = false;
|
|
20
|
+
this.instrumentation = {
|
|
21
|
+
incrementalUpdates: 0,
|
|
22
|
+
fullRebuilds: 0,
|
|
23
|
+
nodesTouched: 0,
|
|
24
|
+
deletions: 0,
|
|
25
|
+
rebuildDurationNs: 0
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
set(path, signal) {
|
|
29
|
+
const pathStr = this.pathToString(path);
|
|
30
|
+
const ref = new WeakRef(signal);
|
|
31
|
+
let node = this.root;
|
|
32
|
+
for (const segment of path) {
|
|
33
|
+
const key = String(segment);
|
|
34
|
+
if (!node.children.has(key)) {
|
|
35
|
+
node.children.set(key, new TrieNode());
|
|
36
|
+
}
|
|
37
|
+
const nextNode = node.children.get(key);
|
|
38
|
+
if (!nextNode) {
|
|
39
|
+
throw new Error(`Failed to get node for key: ${key}`);
|
|
40
|
+
}
|
|
41
|
+
node = nextNode;
|
|
42
|
+
}
|
|
43
|
+
node.value = ref;
|
|
44
|
+
this.pathCache.set(pathStr, ref);
|
|
45
|
+
this.stats.sets++;
|
|
46
|
+
}
|
|
47
|
+
get(path) {
|
|
48
|
+
const pathStr = this.pathToString(path);
|
|
49
|
+
const cached = this.pathCache.get(pathStr);
|
|
50
|
+
if (cached) {
|
|
51
|
+
const value = cached.deref();
|
|
52
|
+
if (value) {
|
|
53
|
+
this.stats.hits++;
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
this.pathCache.delete(pathStr);
|
|
57
|
+
this.stats.cleanups++;
|
|
58
|
+
}
|
|
59
|
+
let node = this.root;
|
|
60
|
+
for (const segment of path) {
|
|
61
|
+
const key = String(segment);
|
|
62
|
+
node = node.children.get(key);
|
|
63
|
+
if (!node) {
|
|
64
|
+
this.stats.misses++;
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (node.value) {
|
|
69
|
+
const value = node.value.deref();
|
|
70
|
+
if (value) {
|
|
71
|
+
this.pathCache.set(pathStr, node.value);
|
|
72
|
+
this.stats.hits++;
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
node.value = null;
|
|
76
|
+
this.stats.cleanups++;
|
|
77
|
+
}
|
|
78
|
+
this.stats.misses++;
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
has(path) {
|
|
82
|
+
return this.get(path) !== null;
|
|
83
|
+
}
|
|
84
|
+
getByPrefix(prefix) {
|
|
85
|
+
const results = new Map();
|
|
86
|
+
let node = this.root;
|
|
87
|
+
for (const segment of prefix) {
|
|
88
|
+
const key = String(segment);
|
|
89
|
+
node = node.children.get(key);
|
|
90
|
+
if (!node) {
|
|
91
|
+
return results;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
this.collectDescendants(node, [], results);
|
|
95
|
+
return results;
|
|
96
|
+
}
|
|
97
|
+
delete(path) {
|
|
98
|
+
const pathStr = this.pathToString(path);
|
|
99
|
+
this.pathCache.delete(pathStr);
|
|
100
|
+
let node = this.root;
|
|
101
|
+
const nodes = [node];
|
|
102
|
+
for (const segment of path) {
|
|
103
|
+
const key = String(segment);
|
|
104
|
+
node = node.children.get(key);
|
|
105
|
+
if (!node) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
nodes.push(node);
|
|
109
|
+
}
|
|
110
|
+
const hadValue = node.value !== null;
|
|
111
|
+
node.value = null;
|
|
112
|
+
for (let i = nodes.length - 1; i > 0; i--) {
|
|
113
|
+
const current = nodes[i];
|
|
114
|
+
if (current.value === null && current.children.size === 0) {
|
|
115
|
+
const parent = nodes[i - 1];
|
|
116
|
+
const segment = path[i - 1];
|
|
117
|
+
parent.children.delete(String(segment));
|
|
118
|
+
} else {
|
|
119
|
+
break;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
return hadValue;
|
|
123
|
+
}
|
|
124
|
+
clear() {
|
|
125
|
+
this.root = new TrieNode();
|
|
126
|
+
this.pathCache.clear();
|
|
127
|
+
if (this.enableInstrumentation) this.instrumentation.fullRebuilds++;
|
|
128
|
+
}
|
|
129
|
+
getStats() {
|
|
130
|
+
const total = this.stats.hits + this.stats.misses;
|
|
131
|
+
const hitRate = total > 0 ? this.stats.hits / total : 0;
|
|
132
|
+
return Object.assign(Object.assign({}, this.stats), {
|
|
133
|
+
hitRate,
|
|
134
|
+
cacheSize: this.pathCache.size
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
buildFromTree(tree, path = []) {
|
|
138
|
+
if (!tree) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
if (isSignal(tree)) {
|
|
142
|
+
this.set(path, tree);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if (typeof tree !== 'object') {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
for (const [key, value] of Object.entries(tree)) {
|
|
149
|
+
this.buildFromTree(value, [...path, key]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
pathToString(path) {
|
|
153
|
+
return path.join('.');
|
|
154
|
+
}
|
|
155
|
+
collectDescendants(node, currentPath, results) {
|
|
156
|
+
if (node.value) {
|
|
157
|
+
const value = node.value.deref();
|
|
158
|
+
if (value) {
|
|
159
|
+
results.set(this.pathToString(currentPath), value);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
for (const [key, child] of node.children) {
|
|
163
|
+
this.collectDescendants(child, [...currentPath, key], results);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
deleteSubtree(path) {
|
|
167
|
+
if (path.length === 0) {
|
|
168
|
+
this.clear();
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
let node = this.root;
|
|
172
|
+
const nodes = [node];
|
|
173
|
+
for (const segment of path) {
|
|
174
|
+
node = node.children.get(String(segment));
|
|
175
|
+
if (!node) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
nodes.push(node);
|
|
179
|
+
}
|
|
180
|
+
const toClean = [];
|
|
181
|
+
const collectPaths = (n, current) => {
|
|
182
|
+
if (n.value) {
|
|
183
|
+
toClean.push(this.pathToString(current));
|
|
184
|
+
}
|
|
185
|
+
for (const [key, child] of n.children) {
|
|
186
|
+
collectPaths(child, [...current, key]);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
collectPaths(nodes[nodes.length - 1], path);
|
|
190
|
+
for (const p of toClean) {
|
|
191
|
+
this.pathCache.delete(p);
|
|
192
|
+
}
|
|
193
|
+
const parent = nodes[nodes.length - 2];
|
|
194
|
+
parent.children.delete(String(path[path.length - 1]));
|
|
195
|
+
if (this.enableInstrumentation) this.instrumentation.deletions += toClean.length || 1;
|
|
196
|
+
}
|
|
197
|
+
incrementalUpdate(rootTree, changedPaths) {
|
|
198
|
+
const start = this.enableInstrumentation ? performance.now() : 0;
|
|
199
|
+
if (!changedPaths.length) return;
|
|
200
|
+
const FULL_REBUILD_THRESHOLD = 2000;
|
|
201
|
+
if (changedPaths.length > FULL_REBUILD_THRESHOLD || changedPaths.includes('')) {
|
|
202
|
+
this.clear();
|
|
203
|
+
this.buildFromTree(rootTree);
|
|
204
|
+
if (this.enableInstrumentation) {
|
|
205
|
+
this.instrumentation.rebuildDurationNs += (performance.now() - start) * 1e6;
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
const ordered = [...changedPaths].sort((a, b) => a.length - b.length);
|
|
210
|
+
const effective = [];
|
|
211
|
+
for (const p of ordered) {
|
|
212
|
+
if (!effective.some(ep => p !== ep && p.startsWith(ep + '.'))) {
|
|
213
|
+
effective.push(p);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
for (const pathStr of effective) {
|
|
217
|
+
const segments = pathStr === '' ? [] : pathStr.split('.');
|
|
218
|
+
let subtree = rootTree;
|
|
219
|
+
for (const seg of segments) {
|
|
220
|
+
if (!subtree || typeof subtree !== 'object') {
|
|
221
|
+
subtree = undefined;
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
subtree = subtree[seg];
|
|
225
|
+
}
|
|
226
|
+
if (subtree === undefined || subtree === null) {
|
|
227
|
+
this.deleteSubtree(segments);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (isSignal(subtree)) {
|
|
231
|
+
this.set(segments, subtree);
|
|
232
|
+
if (this.enableInstrumentation) this.instrumentation.nodesTouched++;
|
|
233
|
+
continue;
|
|
234
|
+
}
|
|
235
|
+
if (typeof subtree === 'object') {
|
|
236
|
+
this.deleteSubtree(segments);
|
|
237
|
+
this.buildFromTree(subtree, segments);
|
|
238
|
+
if (this.enableInstrumentation) this.instrumentation.nodesTouched++;
|
|
239
|
+
} else {
|
|
240
|
+
this.deleteSubtree(segments);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
if (this.enableInstrumentation) {
|
|
244
|
+
this.instrumentation.incrementalUpdates++;
|
|
245
|
+
this.instrumentation.rebuildDurationNs += (performance.now() - start) * 1e6;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
setInstrumentation(enabled) {
|
|
249
|
+
this.enableInstrumentation = enabled;
|
|
250
|
+
}
|
|
251
|
+
getInstrumentation(reset = false) {
|
|
252
|
+
const snapshot = Object.assign({}, this.instrumentation);
|
|
253
|
+
if (reset) {
|
|
254
|
+
this.instrumentation.incrementalUpdates = 0;
|
|
255
|
+
this.instrumentation.fullRebuilds = 0;
|
|
256
|
+
this.instrumentation.nodesTouched = 0;
|
|
257
|
+
this.instrumentation.deletions = 0;
|
|
258
|
+
this.instrumentation.rebuildDurationNs = 0;
|
|
259
|
+
}
|
|
260
|
+
return snapshot;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export { PathIndex };
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { __awaiter } from 'tslib';
|
|
2
|
+
|
|
3
|
+
const defaultConfig = {
|
|
4
|
+
yieldEveryTasks: 500,
|
|
5
|
+
yieldEveryMs: 8,
|
|
6
|
+
instrumentation: false
|
|
7
|
+
};
|
|
8
|
+
let config = Object.assign({}, defaultConfig);
|
|
9
|
+
let metrics = {
|
|
10
|
+
drainCycles: 0,
|
|
11
|
+
tasksExecuted: 0,
|
|
12
|
+
maxQueueLength: 0,
|
|
13
|
+
yields: 0,
|
|
14
|
+
lastDrainDurationMs: 0,
|
|
15
|
+
totalDrainDurationMs: 0
|
|
16
|
+
};
|
|
17
|
+
const q = [];
|
|
18
|
+
let draining = false;
|
|
19
|
+
function configureScheduler(newConfig) {
|
|
20
|
+
config = Object.assign(Object.assign({}, config), newConfig);
|
|
21
|
+
}
|
|
22
|
+
function getSchedulerMetrics(reset = false) {
|
|
23
|
+
const snapshot = Object.assign({}, metrics);
|
|
24
|
+
if (reset) {
|
|
25
|
+
metrics = {
|
|
26
|
+
drainCycles: 0,
|
|
27
|
+
tasksExecuted: 0,
|
|
28
|
+
maxQueueLength: 0,
|
|
29
|
+
yields: 0,
|
|
30
|
+
lastDrainDurationMs: 0,
|
|
31
|
+
totalDrainDurationMs: 0
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return snapshot;
|
|
35
|
+
}
|
|
36
|
+
function postTask(t) {
|
|
37
|
+
q.push(t);
|
|
38
|
+
if (config.instrumentation && q.length > metrics.maxQueueLength) {
|
|
39
|
+
metrics.maxQueueLength = q.length;
|
|
40
|
+
}
|
|
41
|
+
if (!draining) {
|
|
42
|
+
draining = true;
|
|
43
|
+
Promise.resolve().then(drain);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
function drain() {
|
|
47
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
48
|
+
const start = config.instrumentation ? performance.now() : 0;
|
|
49
|
+
if (config.instrumentation) metrics.drainCycles++;
|
|
50
|
+
let tasksSinceYield = 0;
|
|
51
|
+
while (q.length) {
|
|
52
|
+
const t = q.shift();
|
|
53
|
+
if (!t) break;
|
|
54
|
+
try {
|
|
55
|
+
t();
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.error('[EnterpriseScheduler]', e);
|
|
58
|
+
}
|
|
59
|
+
tasksSinceYield++;
|
|
60
|
+
if (config.instrumentation) metrics.tasksExecuted++;
|
|
61
|
+
if (tasksSinceYield >= config.yieldEveryTasks || config.instrumentation && performance.now() - start >= config.yieldEveryMs) {
|
|
62
|
+
if (config.instrumentation) metrics.yields++;
|
|
63
|
+
tasksSinceYield = 0;
|
|
64
|
+
yield Promise.resolve();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (config.instrumentation) {
|
|
68
|
+
const duration = performance.now() - start;
|
|
69
|
+
metrics.lastDrainDurationMs = duration;
|
|
70
|
+
metrics.totalDrainDurationMs += duration;
|
|
71
|
+
}
|
|
72
|
+
draining = false;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export { configureScheduler, getSchedulerMetrics, postTask };
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { isSignal } from '@angular/core';
|
|
2
|
+
import { DiffEngine, ChangeType } from './diff-engine.js';
|
|
3
|
+
import { PathIndex } from './path-index.js';
|
|
4
|
+
|
|
5
|
+
class OptimizedUpdateEngine {
|
|
6
|
+
constructor(tree) {
|
|
7
|
+
this.pathIndex = new PathIndex();
|
|
8
|
+
this.diffEngine = new DiffEngine();
|
|
9
|
+
this.pathIndex.buildFromTree(tree);
|
|
10
|
+
}
|
|
11
|
+
update(tree, updates, options = {}) {
|
|
12
|
+
const startTime = performance.now();
|
|
13
|
+
const diffOptions = {};
|
|
14
|
+
if (options.maxDepth !== undefined) diffOptions.maxDepth = options.maxDepth;
|
|
15
|
+
if (options.ignoreArrayOrder !== undefined) diffOptions.ignoreArrayOrder = options.ignoreArrayOrder;
|
|
16
|
+
if (options.equalityFn !== undefined) diffOptions.equalityFn = options.equalityFn;
|
|
17
|
+
const diff = this.diffEngine.diff(tree, updates, diffOptions);
|
|
18
|
+
if (diff.changes.length === 0) {
|
|
19
|
+
return {
|
|
20
|
+
changed: false,
|
|
21
|
+
duration: performance.now() - startTime,
|
|
22
|
+
changedPaths: []
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const patches = this.createPatches(diff.changes);
|
|
26
|
+
const sortedPatches = this.sortPatches(patches);
|
|
27
|
+
const result = options.batch ? this.batchApplyPatches(tree, sortedPatches, options.batchSize) : this.applyPatches(tree, sortedPatches);
|
|
28
|
+
const duration = performance.now() - startTime;
|
|
29
|
+
return {
|
|
30
|
+
changed: true,
|
|
31
|
+
duration,
|
|
32
|
+
changedPaths: result.appliedPaths,
|
|
33
|
+
stats: {
|
|
34
|
+
totalPaths: diff.changes.length,
|
|
35
|
+
optimizedPaths: patches.length,
|
|
36
|
+
batchedUpdates: result.batchCount
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
rebuildIndex(tree) {
|
|
41
|
+
this.pathIndex.clear();
|
|
42
|
+
this.pathIndex.buildFromTree(tree);
|
|
43
|
+
}
|
|
44
|
+
getIndexStats() {
|
|
45
|
+
return this.pathIndex.getStats();
|
|
46
|
+
}
|
|
47
|
+
createPatches(changes) {
|
|
48
|
+
const patches = [];
|
|
49
|
+
const processedPaths = new Set();
|
|
50
|
+
for (const change of changes) {
|
|
51
|
+
const pathStr = change.path.join('.');
|
|
52
|
+
let skipPath = false;
|
|
53
|
+
for (const processed of processedPaths) {
|
|
54
|
+
if (pathStr.startsWith(processed + '.')) {
|
|
55
|
+
skipPath = true;
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (skipPath) {
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
const patch = this.createPatch(change);
|
|
63
|
+
patches.push(patch);
|
|
64
|
+
processedPaths.add(pathStr);
|
|
65
|
+
if (change.type === ChangeType.REPLACE && typeof change.value === 'object') {
|
|
66
|
+
processedPaths.add(pathStr);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return patches;
|
|
70
|
+
}
|
|
71
|
+
createPatch(change) {
|
|
72
|
+
return {
|
|
73
|
+
type: change.type,
|
|
74
|
+
path: change.path,
|
|
75
|
+
value: change.value,
|
|
76
|
+
oldValue: change.oldValue,
|
|
77
|
+
priority: this.calculatePriority(change),
|
|
78
|
+
signal: this.pathIndex.get(change.path)
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
calculatePriority(change) {
|
|
82
|
+
let priority = 0;
|
|
83
|
+
priority += (10 - change.path.length) * 10;
|
|
84
|
+
if (change.path.some(p => typeof p === 'number')) {
|
|
85
|
+
priority -= 20;
|
|
86
|
+
}
|
|
87
|
+
if (change.type === ChangeType.REPLACE) {
|
|
88
|
+
priority += 30;
|
|
89
|
+
}
|
|
90
|
+
return priority;
|
|
91
|
+
}
|
|
92
|
+
sortPatches(patches) {
|
|
93
|
+
return patches.sort((a, b) => {
|
|
94
|
+
if (a.priority !== b.priority) {
|
|
95
|
+
return b.priority - a.priority;
|
|
96
|
+
}
|
|
97
|
+
return a.path.length - b.path.length;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
applyPatches(tree, patches) {
|
|
101
|
+
const appliedPaths = [];
|
|
102
|
+
let updateCount = 0;
|
|
103
|
+
for (const patch of patches) {
|
|
104
|
+
if (this.applyPatch(patch, tree)) {
|
|
105
|
+
appliedPaths.push(patch.path.join('.'));
|
|
106
|
+
updateCount++;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
appliedPaths,
|
|
111
|
+
updateCount,
|
|
112
|
+
batchCount: 1
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
batchApplyPatches(tree, patches, batchSize = 50) {
|
|
116
|
+
const batches = [];
|
|
117
|
+
for (let i = 0; i < patches.length; i += batchSize) {
|
|
118
|
+
batches.push(patches.slice(i, i + batchSize));
|
|
119
|
+
}
|
|
120
|
+
const appliedPaths = [];
|
|
121
|
+
let updateCount = 0;
|
|
122
|
+
for (const currentBatch of batches) {
|
|
123
|
+
for (const patch of currentBatch) {
|
|
124
|
+
if (this.applyPatch(patch, tree)) {
|
|
125
|
+
appliedPaths.push(patch.path.join('.'));
|
|
126
|
+
updateCount++;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {
|
|
131
|
+
appliedPaths,
|
|
132
|
+
updateCount,
|
|
133
|
+
batchCount: batches.length
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
applyPatch(patch, tree) {
|
|
137
|
+
try {
|
|
138
|
+
if (patch.signal && isSignal(patch.signal) && 'set' in patch.signal) {
|
|
139
|
+
const currentValue = patch.signal();
|
|
140
|
+
if (this.isEqual(currentValue, patch.value)) {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
patch.signal.set(patch.value);
|
|
144
|
+
if (patch.type === ChangeType.ADD && patch.value !== undefined) {
|
|
145
|
+
this.pathIndex.set(patch.path, patch.signal);
|
|
146
|
+
}
|
|
147
|
+
return true;
|
|
148
|
+
}
|
|
149
|
+
let current = tree;
|
|
150
|
+
for (let i = 0; i < patch.path.length - 1; i++) {
|
|
151
|
+
const key = patch.path[i];
|
|
152
|
+
current = current[key];
|
|
153
|
+
if (!current || typeof current !== 'object') {
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const lastKey = patch.path[patch.path.length - 1];
|
|
158
|
+
if (this.isEqual(current[lastKey], patch.value)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
161
|
+
current[lastKey] = patch.value;
|
|
162
|
+
return true;
|
|
163
|
+
} catch (error) {
|
|
164
|
+
console.error(`Failed to apply patch at ${patch.path.join('.')}:`, error);
|
|
165
|
+
return false;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
isEqual(a, b) {
|
|
169
|
+
if (a === b) return true;
|
|
170
|
+
if (typeof a !== typeof b) return false;
|
|
171
|
+
if (a === null || b === null) return false;
|
|
172
|
+
if (typeof a !== 'object') return false;
|
|
173
|
+
const ao = a;
|
|
174
|
+
const bo = b;
|
|
175
|
+
if (Array.isArray(ao) && Array.isArray(bo)) {
|
|
176
|
+
if (ao.length !== bo.length) return false;
|
|
177
|
+
for (let i = 0; i < ao.length; i++) {
|
|
178
|
+
if (ao[i] !== bo[i]) return false;
|
|
179
|
+
}
|
|
180
|
+
return true;
|
|
181
|
+
}
|
|
182
|
+
const aKeys = Object.keys(ao);
|
|
183
|
+
const bKeys = Object.keys(bo);
|
|
184
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
185
|
+
for (let i = 0; i < aKeys.length; i++) {
|
|
186
|
+
const k = aKeys[i];
|
|
187
|
+
if (!(k in bo)) return false;
|
|
188
|
+
if (ao[k] !== bo[k]) return false;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
192
|
+
} catch (_a) {
|
|
193
|
+
return false;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export { OptimizedUpdateEngine };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/enterprise",
|
|
3
|
-
"version": "7.1
|
|
3
|
+
"version": "7.3.1",
|
|
4
4
|
"description": "Enterprise optimizations for SignalTree reactive JSON. Diff-based updates, bulk operations, and advanced change tracking for large-scale applications.",
|
|
5
5
|
"license": "BSL-1.1",
|
|
6
6
|
"type": "module",
|
|
@@ -98,6 +98,7 @@
|
|
|
98
98
|
"files": [
|
|
99
99
|
"dist/**/*.js",
|
|
100
100
|
"dist/**/*.d.ts",
|
|
101
|
+
"src/**/*.d.ts",
|
|
101
102
|
"README.md"
|
|
102
103
|
]
|
|
103
104
|
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
instrumentation?: {
|
|
18
|
+
elementComparisons: number;
|
|
19
|
+
prefixFastPathHits: number;
|
|
20
|
+
wholeArrayReplaceHits: number;
|
|
21
|
+
traversals: number;
|
|
22
|
+
suffixFastPathHits: number;
|
|
23
|
+
segmentSkips: number;
|
|
24
|
+
samplesTaken: number;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export interface DiffOptions {
|
|
28
|
+
maxDepth?: number;
|
|
29
|
+
detectDeletions?: boolean;
|
|
30
|
+
ignoreArrayOrder?: boolean;
|
|
31
|
+
equalityFn?: (a: unknown, b: unknown) => boolean;
|
|
32
|
+
keyValidator?: (key: string) => boolean;
|
|
33
|
+
instrumentation?: boolean;
|
|
34
|
+
}
|
|
35
|
+
export declare class DiffEngine {
|
|
36
|
+
private defaultOptions;
|
|
37
|
+
diff(current: unknown, updates: unknown, options?: DiffOptions): Diff;
|
|
38
|
+
private traverse;
|
|
39
|
+
private diffArrays;
|
|
40
|
+
private diffArraysOrdered;
|
|
41
|
+
private diffArraysUnordered;
|
|
42
|
+
private stringify;
|
|
43
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { PathIndex } from './path-index';
|
|
2
|
+
import { UpdateResult } from './update-engine';
|
|
3
|
+
import type { Signal } from '@angular/core';
|
|
4
|
+
import type { ISignalTree } from '@signaltree/core';
|
|
5
|
+
export declare function enterprise<T = unknown>(): (tree: ISignalTree<T>) => ISignalTree<T> & EnterpriseEnhancedTree<T>;
|
|
6
|
+
export interface EnterpriseEnhancedTree<T> {
|
|
7
|
+
updateOptimized(updates: Partial<T>, options?: {
|
|
8
|
+
maxDepth?: number;
|
|
9
|
+
ignoreArrayOrder?: boolean;
|
|
10
|
+
equalityFn?: (a: unknown, b: unknown) => boolean;
|
|
11
|
+
autoBatch?: boolean;
|
|
12
|
+
batchSize?: number;
|
|
13
|
+
}): UpdateResult;
|
|
14
|
+
getPathIndex(): PathIndex<Signal<unknown>> | null;
|
|
15
|
+
}
|
|
16
|
+
export declare const withEnterprise: typeof enterprise;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function enterprise(): string;
|
|
@@ -0,0 +1,31 @@
|
|
|
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
|
+
private enableInstrumentation;
|
|
9
|
+
private instrumentation;
|
|
10
|
+
set(path: Path, signal: T): void;
|
|
11
|
+
get(path: Path): T | null;
|
|
12
|
+
has(path: Path): boolean;
|
|
13
|
+
getByPrefix(prefix: Path): Map<string, T>;
|
|
14
|
+
delete(path: Path): boolean;
|
|
15
|
+
clear(): void;
|
|
16
|
+
getStats(): {
|
|
17
|
+
hits: number;
|
|
18
|
+
misses: number;
|
|
19
|
+
sets: number;
|
|
20
|
+
cleanups: number;
|
|
21
|
+
hitRate: number;
|
|
22
|
+
cacheSize: number;
|
|
23
|
+
};
|
|
24
|
+
buildFromTree(tree: unknown, path?: Path): void;
|
|
25
|
+
private pathToString;
|
|
26
|
+
private collectDescendants;
|
|
27
|
+
deleteSubtree(path: Path): void;
|
|
28
|
+
incrementalUpdate(rootTree: unknown, changedPaths: string[]): void;
|
|
29
|
+
setInstrumentation(enabled: boolean): void;
|
|
30
|
+
getInstrumentation(reset?: boolean): typeof this.instrumentation;
|
|
31
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type Task = () => void;
|
|
2
|
+
interface SchedulerConfig {
|
|
3
|
+
yieldEveryTasks?: number;
|
|
4
|
+
yieldEveryMs?: number;
|
|
5
|
+
instrumentation?: boolean;
|
|
6
|
+
}
|
|
7
|
+
interface SchedulerMetrics {
|
|
8
|
+
drainCycles: number;
|
|
9
|
+
tasksExecuted: number;
|
|
10
|
+
maxQueueLength: number;
|
|
11
|
+
yields: number;
|
|
12
|
+
lastDrainDurationMs: number;
|
|
13
|
+
totalDrainDurationMs: number;
|
|
14
|
+
}
|
|
15
|
+
export declare function configureScheduler(newConfig: SchedulerConfig): void;
|
|
16
|
+
export declare function getSchedulerMetrics(reset?: boolean): SchedulerMetrics;
|
|
17
|
+
export declare function postTask(t: Task): void;
|
|
18
|
+
export {};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// Removed shim: previous local module augmentation caused stray declaration
|
|
2
|
+
// files that break TypeScript resolution during packaging. If a module
|
|
3
|
+
// augmentation is required again, prefer adding path mapping or proper
|
|
4
|
+
// project references instead of committing a local `.d.ts` into `src/`.
|