@signaltree/enterprise 6.0.0 → 6.0.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/README.md CHANGED
@@ -20,9 +20,9 @@ npm install @signaltree/core @signaltree/enterprise
20
20
 
21
21
  ```typescript
22
22
  import { signalTree } from '@signaltree/core';
23
- import { withEnterprise } from '@signaltree/enterprise';
23
+ import { enterprise } from '@signaltree/enterprise';
24
24
 
25
- const tree = signalTree(largeState).with(withEnterprise());
25
+ const tree = signalTree(largeState).with(enterprise());
26
26
 
27
27
  // Use optimized bulk updates
28
28
  const result = tree.updateOptimized(newData, {
@@ -53,15 +53,15 @@ console.log(result.stats);
53
53
 
54
54
  ## API
55
55
 
56
- ### `withEnterprise()`
56
+ ### `enterprise()`
57
57
 
58
58
  Enhancer that adds enterprise optimizations to a SignalTree.
59
59
 
60
60
  ```typescript
61
61
  import { signalTree } from '@signaltree/core';
62
- import { withEnterprise } from '@signaltree/enterprise';
62
+ import { enterprise } from '@signaltree/enterprise';
63
63
 
64
- const tree = signalTree(initialState).with(withEnterprise());
64
+ const tree = signalTree(initialState).with(enterprise());
65
65
  ```
66
66
 
67
67
  ### `tree.updateOptimized(updates, options?)`
@@ -116,7 +116,7 @@ if (index) {
116
116
 
117
117
  ```typescript
118
118
  import { signalTree } from '@signaltree/core';
119
- import { withEnterprise } from '@signaltree/enterprise';
119
+ import { enterprise } from '@signaltree/enterprise';
120
120
 
121
121
  interface DashboardState {
122
122
  metrics: Record<string, number>;
@@ -125,7 +125,7 @@ interface DashboardState {
125
125
  // ... hundreds more properties
126
126
  }
127
127
 
128
- const dashboard = signalTree<DashboardState>(initialState).with(withEnterprise());
128
+ const dashboard = signalTree<DashboardState>(initialState).with(enterprise());
129
129
 
130
130
  // High-frequency updates from WebSocket
131
131
  socket.on('metrics', (newMetrics) => {
@@ -139,14 +139,14 @@ socket.on('metrics', (newMetrics) => {
139
139
 
140
140
  ```typescript
141
141
  import { signalTree } from '@signaltree/core';
142
- import { withEnterprise } from '@signaltree/enterprise';
142
+ import { enterprise } from '@signaltree/enterprise';
143
143
 
144
144
  const grid = signalTree({
145
145
  rows: [] as GridRow[],
146
146
  columns: [] as GridColumn[],
147
147
  filters: {} as FilterState,
148
148
  selection: new Set<string>(),
149
- }).with(withEnterprise());
149
+ }).with(enterprise());
150
150
 
151
151
  // Bulk update from API
152
152
  async function loadData() {
@@ -164,7 +164,7 @@ async function loadData() {
164
164
  ### Custom Equality for Complex Objects
165
165
 
166
166
  ```typescript
167
- const tree = signalTree(complexState).with(withEnterprise());
167
+ const tree = signalTree(complexState).with(enterprise());
168
168
 
169
169
  tree.updateOptimized(newState, {
170
170
  equalityFn: (a, b) => {
@@ -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,13 @@
1
+ import { __awaiter } from 'tslib';
2
+
3
+ function createMockPool() {
4
+ return {
5
+ run(fn, ...args) {
6
+ return __awaiter(this, void 0, void 0, function* () {
7
+ return fn(...args);
8
+ });
9
+ }
10
+ };
11
+ }
12
+
13
+ export { createMockPool };
@@ -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": "6.0.0",
3
+ "version": "6.0.1",
4
4
  "description": "Enterprise-grade optimizations for SignalTree. Provides diff-based updates, bulk operation optimization, and advanced change tracking for large-scale applications.",
5
5
  "type": "module",
6
6
  "sideEffects": false,