@signaltree/enterprise 9.0.0 → 9.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/README.md +66 -2
- package/dist/lib/enterprise-enhancer.js +43 -3
- package/package.json +3 -3
- package/src/lib/enterprise-enhancer.d.ts +9 -1
package/README.md
CHANGED
|
@@ -7,7 +7,9 @@ Enterprise-grade optimizations for SignalTree. Designed for large-scale applicat
|
|
|
7
7
|
- **Diff-based updates** - Only update signals that actually changed
|
|
8
8
|
- **Bulk operation optimization** - 2-5x faster for large state updates
|
|
9
9
|
- **Advanced change tracking** - Detailed statistics and monitoring
|
|
10
|
-
- **Path
|
|
10
|
+
- **Path-change subscriptions** - React to specific dot-paths changing (9.1+)
|
|
11
|
+
- **Snapshot / restore** - Cheap structured-clone snapshots with diff-engine restore (9.1+)
|
|
12
|
+
- **Auto-optimize threshold** - Route large updates through the diff engine automatically (9.1+)
|
|
11
13
|
- **Lazy initialization** - Zero overhead until first use
|
|
12
14
|
|
|
13
15
|
## Installation
|
|
@@ -58,7 +60,7 @@ console.log(result.stats);
|
|
|
58
60
|
|
|
59
61
|
## API
|
|
60
62
|
|
|
61
|
-
### `enterprise()`
|
|
63
|
+
### `enterprise(options?)`
|
|
62
64
|
|
|
63
65
|
Enhancer that adds enterprise optimizations to a SignalTree.
|
|
64
66
|
|
|
@@ -67,6 +69,19 @@ import { signalTree } from '@signaltree/core';
|
|
|
67
69
|
import { enterprise } from '@signaltree/enterprise';
|
|
68
70
|
|
|
69
71
|
const tree = signalTree(initialState).with(enterprise());
|
|
72
|
+
|
|
73
|
+
// Or with auto-optimize threshold (9.1+):
|
|
74
|
+
const tree2 = signalTree(initialState).with(enterprise({ autoOptimizeThreshold: 100 }));
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Options (9.1+):**
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
{
|
|
81
|
+
autoOptimizeThreshold?: number; // If set, tree.updateAuto(...) routes through
|
|
82
|
+
// updateOptimized when the payload has at
|
|
83
|
+
// least this many top-level keys.
|
|
84
|
+
}
|
|
70
85
|
```
|
|
71
86
|
|
|
72
87
|
### `tree.updateOptimized(updates, options?)`
|
|
@@ -106,6 +121,9 @@ Performs optimized bulk updates using diff-based change detection.
|
|
|
106
121
|
|
|
107
122
|
### `tree.getPathIndex()`
|
|
108
123
|
|
|
124
|
+
> **Deprecated (9.1+):** Path-index access is an internal detail and will be
|
|
125
|
+
> removed in a future major. Use `onPathChange` for change observation.
|
|
126
|
+
|
|
109
127
|
Get the PathIndex for debugging/monitoring. Returns `null` if `updateOptimized` hasn't been called yet (lazy initialization).
|
|
110
128
|
|
|
111
129
|
```typescript
|
|
@@ -115,6 +133,52 @@ if (index) {
|
|
|
115
133
|
}
|
|
116
134
|
```
|
|
117
135
|
|
|
136
|
+
### `tree.onPathChange(listener)` (9.1+)
|
|
137
|
+
|
|
138
|
+
Subscribe to dot-paths that change on each `updateOptimized` (or
|
|
139
|
+
`updateAuto` when it routes through the diff engine). Returns an
|
|
140
|
+
unsubscribe function.
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
const off = tree.onPathChange((paths) => {
|
|
144
|
+
console.log('changed:', paths); // e.g. ['user.name', 'cart.items.0.qty']
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
tree.updateOptimized({ user: { name: 'Ada' } });
|
|
148
|
+
// → listener fires with ['user.name']
|
|
149
|
+
|
|
150
|
+
off(); // stop listening
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### `tree.snapshot()` / `tree.restore(snap)` (9.1+)
|
|
154
|
+
|
|
155
|
+
Capture and restore the entire state via a `structuredClone`. `restore`
|
|
156
|
+
routes through the diff engine, so listeners and stats fire as if the
|
|
157
|
+
restored values were a normal optimized update.
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
const snap = tree.snapshot();
|
|
161
|
+
|
|
162
|
+
tree.updateOptimized({ user: { name: 'Grace' } });
|
|
163
|
+
|
|
164
|
+
// later... roll back
|
|
165
|
+
tree.restore(snap);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### `tree.updateAuto(updates)` (9.1+)
|
|
169
|
+
|
|
170
|
+
When `enterprise({ autoOptimizeThreshold: N })` is configured, payloads
|
|
171
|
+
with `≥ N` top-level keys are routed through `updateOptimized`; smaller
|
|
172
|
+
payloads use the regular fast path. Without a threshold this is a plain
|
|
173
|
+
`update`.
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
const tree = signalTree(initialState).with(enterprise({ autoOptimizeThreshold: 50 }));
|
|
177
|
+
|
|
178
|
+
tree.updateAuto({ a: 1, b: 2 }); // fast path
|
|
179
|
+
tree.updateAuto(largeServerPayload); // diff engine
|
|
180
|
+
```
|
|
181
|
+
|
|
118
182
|
## Examples
|
|
119
183
|
|
|
120
184
|
### Real-time Dashboard
|
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import { PathIndex } from './path-index.js';
|
|
2
2
|
import { OptimizedUpdateEngine } from './update-engine.js';
|
|
3
3
|
|
|
4
|
-
function enterprise() {
|
|
4
|
+
function enterprise(options = {}) {
|
|
5
5
|
return tree => {
|
|
6
6
|
let pathIndex = null;
|
|
7
7
|
let updateEngine = null;
|
|
8
|
+
const listeners = new Set();
|
|
8
9
|
const signalTree = tree;
|
|
9
10
|
const enhancedTree = tree;
|
|
10
|
-
|
|
11
|
+
const ensureEngine = () => {
|
|
11
12
|
if (!updateEngine) {
|
|
12
13
|
pathIndex = new PathIndex();
|
|
13
14
|
pathIndex.buildFromTree(signalTree.state);
|
|
14
15
|
updateEngine = new OptimizedUpdateEngine(signalTree.state);
|
|
15
16
|
}
|
|
16
|
-
|
|
17
|
+
return updateEngine;
|
|
18
|
+
};
|
|
19
|
+
const notifyListeners = paths => {
|
|
20
|
+
if (!listeners.size || !paths.length) return;
|
|
21
|
+
for (const fn of Array.from(listeners)) {
|
|
22
|
+
try {
|
|
23
|
+
fn(paths);
|
|
24
|
+
} catch (_a) {}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
const runOptimized = (updates, opts) => {
|
|
28
|
+
const engine = ensureEngine();
|
|
29
|
+
const result = engine.update(signalTree.state, updates, opts);
|
|
17
30
|
if (result.changed && pathIndex) {
|
|
18
31
|
if (result.changedPaths.length) {
|
|
19
32
|
pathIndex.incrementalUpdate(signalTree.state, result.changedPaths);
|
|
@@ -22,8 +35,35 @@ function enterprise() {
|
|
|
22
35
|
pathIndex.buildFromTree(signalTree.state);
|
|
23
36
|
}
|
|
24
37
|
}
|
|
38
|
+
notifyListeners(result.changedPaths);
|
|
25
39
|
return result;
|
|
26
40
|
};
|
|
41
|
+
enhancedTree.updateOptimized = runOptimized;
|
|
42
|
+
enhancedTree.onPathChange = listener => {
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
return () => listeners.delete(listener);
|
|
45
|
+
};
|
|
46
|
+
enhancedTree.snapshot = () => {
|
|
47
|
+
const current = tree();
|
|
48
|
+
return structuredClone(current);
|
|
49
|
+
};
|
|
50
|
+
enhancedTree.restore = snapshot => {
|
|
51
|
+
return runOptimized(snapshot);
|
|
52
|
+
};
|
|
53
|
+
const threshold = options.autoOptimizeThreshold;
|
|
54
|
+
if (threshold && threshold > 0) {
|
|
55
|
+
const originalCall = tree.bind(tree);
|
|
56
|
+
const wrapped = function (arg) {
|
|
57
|
+
if (arguments.length === 1 && arg && typeof arg === 'object' && !Array.isArray(arg) && Object.keys(arg).length >= threshold) {
|
|
58
|
+
runOptimized(arg);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
return originalCall(arg);
|
|
62
|
+
};
|
|
63
|
+
enhancedTree.updateAuto = wrapped;
|
|
64
|
+
} else {
|
|
65
|
+
enhancedTree.updateAuto = arg => tree(arg);
|
|
66
|
+
}
|
|
27
67
|
enhancedTree.getPathIndex = () => pathIndex;
|
|
28
68
|
return enhancedTree;
|
|
29
69
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/enterprise",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.1.0",
|
|
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",
|
|
@@ -68,12 +68,12 @@
|
|
|
68
68
|
},
|
|
69
69
|
"peerDependencies": {
|
|
70
70
|
"@angular/core": "^20.0.0",
|
|
71
|
-
"@signaltree/core": "
|
|
71
|
+
"@signaltree/core": "workspace:*",
|
|
72
72
|
"tslib": "^2.0.0"
|
|
73
73
|
},
|
|
74
74
|
"peerDependenciesMeta": {},
|
|
75
75
|
"devDependencies": {
|
|
76
|
-
"@signaltree/core": "
|
|
76
|
+
"@signaltree/core": "workspace:*"
|
|
77
77
|
},
|
|
78
78
|
"publishConfig": {
|
|
79
79
|
"access": "public"
|
|
@@ -2,7 +2,11 @@ import { PathIndex } from './path-index';
|
|
|
2
2
|
import { UpdateResult } from './update-engine';
|
|
3
3
|
import type { Signal } from '@angular/core';
|
|
4
4
|
import type { ISignalTree } from '@signaltree/core';
|
|
5
|
-
export
|
|
5
|
+
export type PathChangeListener = (changedPaths: readonly string[]) => void;
|
|
6
|
+
export interface EnterpriseOptions {
|
|
7
|
+
autoOptimizeThreshold?: number;
|
|
8
|
+
}
|
|
9
|
+
export declare function enterprise<T = unknown>(options?: EnterpriseOptions): (tree: ISignalTree<T>) => ISignalTree<T> & EnterpriseEnhancedTree<T>;
|
|
6
10
|
export interface EnterpriseEnhancedTree<T> {
|
|
7
11
|
updateOptimized(updates: Partial<T>, options?: {
|
|
8
12
|
maxDepth?: number;
|
|
@@ -11,6 +15,10 @@ export interface EnterpriseEnhancedTree<T> {
|
|
|
11
15
|
autoBatch?: boolean;
|
|
12
16
|
batchSize?: number;
|
|
13
17
|
}): UpdateResult;
|
|
18
|
+
onPathChange(listener: PathChangeListener): () => void;
|
|
19
|
+
snapshot(): unknown;
|
|
20
|
+
restore(snapshot: unknown): UpdateResult;
|
|
21
|
+
updateAuto(updates: unknown): unknown;
|
|
14
22
|
getPathIndex(): PathIndex<Signal<unknown>> | null;
|
|
15
23
|
}
|
|
16
24
|
export declare const withEnterprise: typeof enterprise;
|