@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 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 indexing** - Optimized signal lookup for large trees
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
- enhancedTree.updateOptimized = (updates, options) => {
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
- const result = updateEngine.update(signalTree.state, updates, options);
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.0.0",
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": "^9.0.0",
71
+ "@signaltree/core": "workspace:*",
72
72
  "tslib": "^2.0.0"
73
73
  },
74
74
  "peerDependenciesMeta": {},
75
75
  "devDependencies": {
76
- "@signaltree/core": "^9.0.0"
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 declare function enterprise<T = unknown>(): (tree: ISignalTree<T>) => ISignalTree<T> & EnterpriseEnhancedTree<T>;
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;