@signaltree/enterprise 13.4.0 → 13.5.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 +72 -229
- package/dist/index.js +0 -2
- package/dist/lib/update-engine.js +23 -3
- package/package.json +3 -13
- package/src/index.d.ts +0 -2
- package/dist/lib/scheduler.js +0 -76
- package/dist/lib/thread-pools.js +0 -13
- package/src/lib/scheduler.d.ts +0 -18
- package/src/lib/thread-pools.d.ts +0 -4
package/README.md
CHANGED
|
@@ -1,272 +1,115 @@
|
|
|
1
1
|
# @signaltree/enterprise
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> # ⚠️ Deprecated as of 13.5.0
|
|
4
|
+
>
|
|
5
|
+
> **Use `tree.updateAndReport()` from `@signaltree/core`.** They are built in, need no enhancer, add no bundle, and are faster than this package.
|
|
6
|
+
>
|
|
7
|
+
> This package remains published so existing installs keep resolving, and will receive security fixes only. No new features, and the array defect below is not being fixed.
|
|
4
8
|
|
|
5
|
-
##
|
|
9
|
+
## Why it was retired
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
- **Bulk operation optimization** - 2-5x faster for large state updates
|
|
9
|
-
- **Advanced change tracking** - Detailed statistics and monitoring
|
|
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+)
|
|
13
|
-
- **Lazy initialization** - Zero overhead until first use
|
|
11
|
+
**The headline performance claim was inverted.** Measured against `tree.updateAndReport()` — which returns the same changed paths — `updateOptimized()` is:
|
|
14
12
|
|
|
15
|
-
|
|
13
|
+
| Workload (2,000 leaves) | `updateOptimized()` | `updateAndReport()` | Result |
|
|
14
|
+
| ----------------------------- | ------------------- | ------------------- | ------------------ |
|
|
15
|
+
| 10% of leaves changed | ~0.53 ms | ~0.08 ms | **~7x slower** |
|
|
16
|
+
| identical re-fetch (all no-op)| ~0.14 ms | ~0.07 ms | **~2x slower** |
|
|
17
|
+
| every leaf changed | ~24-27 ms | ~0.12-0.17 ms | **~160-190x slower** |
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
## Type Definitions
|
|
22
|
-
|
|
23
|
-
Type declarations are shipped as `src/**/*.d.ts` and referenced by the package exports.
|
|
24
|
-
No extra build step is needed to consume types.
|
|
25
|
-
|
|
26
|
-
## Quick Start
|
|
27
|
-
|
|
28
|
-
```typescript
|
|
29
|
-
import { signalTree } from '@signaltree/core';
|
|
30
|
-
import { enterprise } from '@signaltree/enterprise';
|
|
31
|
-
|
|
32
|
-
const tree = signalTree(largeState).with(enterprise());
|
|
19
|
+
At 500 leaves the same workloads measure ~4-4.5x, ~1.2-1.6x and ~43x. The ratio
|
|
20
|
+
grows with tree size in every workload, which is the opposite of the scaling
|
|
21
|
+
story the package was sold on ("use it at 500+ signals").
|
|
33
22
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
23
|
+
The long-standing "2-5x faster" claim was never measured against core. Numbers
|
|
24
|
+
are means over 50 timed iterations after warm-up, payloads generated outside the
|
|
25
|
+
timed loop; they will differ on your hardware, so treat the RATIOS as the
|
|
26
|
+
finding, not the absolute times.
|
|
39
27
|
|
|
40
|
-
|
|
41
|
-
// { totalChanges: 45, adds: 10, updates: 30, deletes: 5 }
|
|
42
|
-
```
|
|
43
|
-
|
|
44
|
-
## When to Use
|
|
45
|
-
|
|
46
|
-
### ✅ Use @signaltree/enterprise when:
|
|
47
|
-
|
|
48
|
-
- You have 500+ signals in your state tree
|
|
49
|
-
- Bulk updates happen at high frequency (60Hz+)
|
|
50
|
-
- You need real-time dashboards or data feeds
|
|
51
|
-
- You're building enterprise-scale applications
|
|
52
|
-
- You need detailed update monitoring and statistics
|
|
53
|
-
|
|
54
|
-
### ❌ Skip @signaltree/enterprise when:
|
|
28
|
+
This is structural, not a tuning problem. Core leaves are `signal(value, { equal })` — deep equality plus a reference-equality short-circuit — so **"only write what actually changed" is already core behaviour, for free**. The diff engine walks the whole state to decide which writes to skip, and the writes it skips were already no-ops. No amount of optimization changes that shape; the work it does is work core does not need to do.
|
|
55
29
|
|
|
56
|
-
|
|
57
|
-
- Infrequent state updates
|
|
58
|
-
- Startup/prototype projects
|
|
59
|
-
- Bundle size is critical (adds +2.4KB gzipped)
|
|
30
|
+
Two further reasons:
|
|
60
31
|
|
|
61
|
-
|
|
32
|
+
- **It no longer offers anything core lacks.** `changedPaths` and `updateAndReport()` return the same information, and `onPathChange` now ships in core.
|
|
33
|
+
- **It has no independent runtime dependency**, which puts it on the wrong side of the packaging rule in [RFC 0007](../../docs/rfcs/0007-packaging-principle-and-ng-forms-reslice.md) — a package should exist because it pulls in a dependency core should not.
|
|
62
34
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
Enhancer that adds enterprise optimizations to a SignalTree.
|
|
35
|
+
## Migration
|
|
66
36
|
|
|
67
37
|
```typescript
|
|
38
|
+
// Before
|
|
68
39
|
import { signalTree } from '@signaltree/core';
|
|
69
40
|
import { enterprise } from '@signaltree/enterprise';
|
|
70
41
|
|
|
71
|
-
const tree = signalTree(
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
}
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
### `tree.updateOptimized(updates, options?)`
|
|
88
|
-
|
|
89
|
-
Performs optimized bulk updates using diff-based change detection.
|
|
90
|
-
|
|
91
|
-
**Parameters:**
|
|
92
|
-
|
|
93
|
-
- `updates: Partial<T>` - The new state values
|
|
94
|
-
- `options?: UpdateOptions` - Configuration options
|
|
95
|
-
|
|
96
|
-
**Options:**
|
|
97
|
-
|
|
98
|
-
```typescript
|
|
99
|
-
{
|
|
100
|
-
maxDepth?: number; // Maximum depth to traverse (default: 100)
|
|
101
|
-
ignoreArrayOrder?: boolean; // Ignore array element order (default: false)
|
|
102
|
-
equalityFn?: (a, b) => boolean; // Custom equality function
|
|
103
|
-
autoBatch?: boolean; // Automatically batch updates (default: true)
|
|
104
|
-
batchSize?: number; // Patches per batch (default: 10)
|
|
105
|
-
}
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
**Returns:**
|
|
109
|
-
|
|
110
|
-
```typescript
|
|
111
|
-
{
|
|
112
|
-
changed: boolean; // Whether any changes were made
|
|
113
|
-
stats: {
|
|
114
|
-
totalChanges: number; // Total number of changes
|
|
115
|
-
adds: number; // New properties added
|
|
116
|
-
updates: number; // Properties updated
|
|
117
|
-
deletes: number; // Properties deleted
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### `tree.getPathIndex()`
|
|
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.
|
|
42
|
+
const tree = signalTree(state).with(enterprise());
|
|
43
|
+
const result = tree.updateOptimized(payload);
|
|
44
|
+
if (result.changed) sync(result.changedPaths);
|
|
126
45
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
```typescript
|
|
130
|
-
const index = tree.getPathIndex();
|
|
131
|
-
if (index) {
|
|
132
|
-
console.log('Path index active');
|
|
133
|
-
}
|
|
134
|
-
```
|
|
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 }));
|
|
46
|
+
// After — no enhancer, no extra bundle
|
|
47
|
+
import { signalTree } from '@signaltree/core';
|
|
177
48
|
|
|
178
|
-
tree
|
|
179
|
-
tree.
|
|
49
|
+
const tree = signalTree(state);
|
|
50
|
+
const changed = tree.updateAndReport(payload);
|
|
51
|
+
if (changed.length) sync(changed);
|
|
180
52
|
```
|
|
181
53
|
|
|
182
|
-
|
|
54
|
+
| Enterprise | Core replacement |
|
|
55
|
+
| ----------------------------------------- | --------------------------------------------------- |
|
|
56
|
+
| `tree.updateOptimized(p)` | `tree.updateAndReport(p)` — returns changed paths |
|
|
57
|
+
| `tree.onPathChange(fn)` | **no direct replacement yet** — use `tree.updateAndReport(p)` at the call site; a subscription API is being designed |
|
|
58
|
+
| `tree.snapshot()` | `const snap = tree()` |
|
|
59
|
+
| `tree.restore(snap)` | `tree(snap)` |
|
|
60
|
+
| `tree.updateAuto(p)` | `tree(p)` |
|
|
61
|
+
| `tree.getPathIndex()` | no replacement — was debug-only, already deprecated |
|
|
62
|
+
| `enterprise({ autoOptimizeThreshold: n })` | drop it — core has one write path |
|
|
183
63
|
|
|
184
|
-
###
|
|
64
|
+
### Two rows above are an IMPROVEMENT, not an equivalence
|
|
185
65
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
import { enterprise } from '@signaltree/enterprise';
|
|
66
|
+
`restore()` and `updateAuto()` do not merely have a core equal — the core form
|
|
67
|
+
is **more correct**, because both inherit the array defect:
|
|
189
68
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
69
|
+
- `tree.restore(snap)` → `tree(snap)`. Restoring `{a, b:{c,d}, arr:[1,2,3]}`
|
|
70
|
+
after mutating it leaves `arr` at its mutated value while reporting
|
|
71
|
+
`arr.0`, `arr.1`, `arr.2` as changed — three paths it never wrote. `tree(snap)`
|
|
72
|
+
restores the array. Neither deletes keys absent from the snapshot; both merge.
|
|
73
|
+
- `tree.updateAuto(p)` → `tree(p)`, but only exactly equivalent when NO
|
|
74
|
+
`autoOptimizeThreshold` is set (then `updateAuto` is a plain passthrough). With
|
|
75
|
+
a threshold, a payload over it routes through the diff engine and drops array
|
|
76
|
+
writes; `tree(p)` applies them.
|
|
196
77
|
|
|
197
|
-
|
|
78
|
+
### Three behaviour differences worth knowing
|
|
198
79
|
|
|
199
|
-
|
|
200
|
-
socket.on('metrics', (newMetrics) => {
|
|
201
|
-
const result = dashboard.updateOptimized({ metrics: newMetrics }, { ignoreArrayOrder: true });
|
|
80
|
+
**1. `updateAndReport()` is stricter about what counts as a change.** It reports only paths whose leaf signal actually accepted the write. A re-fetched payload identical to what you already hold reports `[]`; the diff engine reported every key in it. If you were counting on the old numbers, they were counting no-ops.
|
|
202
81
|
|
|
203
|
-
|
|
204
|
-
});
|
|
205
|
-
```
|
|
82
|
+
**2. `onPathChange` in core fires for every root write** — the call form `tree({...})`, `batchUpdate()` and `updateAndReport()` — not just `updateOptimized()`. It does **not** fire for direct leaf writes (`tree.$.a.b.set(x)`), which bypass the root.
|
|
206
83
|
|
|
207
|
-
|
|
84
|
+
**3. `snapshot()` here used `structuredClone`,** so it threw `DataCloneError` on any tree holding a function. `tree()` has no such limit.
|
|
208
85
|
|
|
209
|
-
|
|
210
|
-
import { signalTree } from '@signaltree/core';
|
|
211
|
-
import { enterprise } from '@signaltree/enterprise';
|
|
212
|
-
|
|
213
|
-
const grid = signalTree({
|
|
214
|
-
rows: [] as GridRow[],
|
|
215
|
-
columns: [] as GridColumn[],
|
|
216
|
-
filters: {} as FilterState,
|
|
217
|
-
selection: new Set<string>(),
|
|
218
|
-
}).with(enterprise());
|
|
86
|
+
## Known defect (not being fixed)
|
|
219
87
|
|
|
220
|
-
|
|
221
|
-
async function loadData() {
|
|
222
|
-
const data = await fetchGridData();
|
|
88
|
+
`updateOptimized()` **silently drops writes that target an array.** It reports `changed: true`, lists the paths, and writes nothing.
|
|
223
89
|
|
|
224
|
-
|
|
225
|
-
maxDepth: 5,
|
|
226
|
-
autoBatch: true,
|
|
227
|
-
});
|
|
90
|
+
An array in a SignalTree is a single leaf — one `WritableSignal<T[]>` — while the diff engine is a general-purpose differ emitting element-level paths (`users.1`). The apply step cannot consume those against a leaf, so it bails and reports success anyway.
|
|
228
91
|
|
|
229
|
-
|
|
230
|
-
}
|
|
231
|
-
```
|
|
92
|
+
Two fixes were attempted and both withdrawn, each having introduced defects worse than the one it closed (silent truncation and prototype injection in the first; dotted-key data loss and spurious writes in the second). Given the package is superseded, it stays documented rather than fixed.
|
|
232
93
|
|
|
233
|
-
|
|
94
|
+
**Workaround** — write the array through its leaf:
|
|
234
95
|
|
|
235
96
|
```typescript
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
tree.updateOptimized(newState, {
|
|
239
|
-
equalityFn: (a, b) => {
|
|
240
|
-
// Custom deep equality for specific object types
|
|
241
|
-
if (a instanceof Date && b instanceof Date) {
|
|
242
|
-
return a.getTime() === b.getTime();
|
|
243
|
-
}
|
|
244
|
-
return a === b;
|
|
245
|
-
},
|
|
246
|
-
});
|
|
97
|
+
tree.$.users.set(nextUsers); // works
|
|
98
|
+
tree.updateOptimized({ users: nextUsers }); // silently does nothing
|
|
247
99
|
```
|
|
248
100
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
**Bundle Size:**
|
|
101
|
+
Or migrate to `updateAndReport()`, which handles arrays correctly.
|
|
252
102
|
|
|
253
|
-
|
|
254
|
-
- Zero overhead until first `updateOptimized()` call (lazy initialization)
|
|
103
|
+
## Removed in 13.5.0
|
|
255
104
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
- 2-5x faster for bulk updates on large state trees
|
|
259
|
-
- Scales efficiently with tree depth and complexity
|
|
260
|
-
- Minimal memory overhead with path indexing
|
|
105
|
+
`./scheduler` and `./thread-pools` subpath exports are gone. Both were dead — no caller anywhere in the repo and no tests. (They were *mentioned* in the v9 plan and on a demo page, but never documented as an API.) And `thread-pools` only ever exported `createMockPool()`, a test double that should never have shipped to production consumers. `scheduler`'s advertised "yield to the event loop" was `await Promise.resolve()`, a microtask, which does not yield.
|
|
261
106
|
|
|
262
107
|
## License
|
|
263
108
|
|
|
264
|
-
Business Source License 1.1 (BSL-1.1)
|
|
265
|
-
|
|
266
|
-
Converts to MIT license on the Change Date specified in the license.
|
|
109
|
+
Business Source License 1.1 (BSL-1.1) — see [LICENSE](../../LICENSE). Converts to MIT on the Change Date specified in the license.
|
|
267
110
|
|
|
268
|
-
## Related
|
|
111
|
+
## Related packages
|
|
269
112
|
|
|
270
|
-
- [@signaltree/core](../core)
|
|
271
|
-
- [@signaltree/ng-forms](../ng-forms)
|
|
272
|
-
- [@signaltree/callable-syntax](../callable-syntax)
|
|
113
|
+
- [@signaltree/core](../core) — where `updateAndReport()` and `onPathChange()` now live
|
|
114
|
+
- [@signaltree/ng-forms](../ng-forms) — Angular forms integration
|
|
115
|
+
- [@signaltree/callable-syntax](../callable-syntax) — callable syntax transform
|
package/dist/index.js
CHANGED
|
@@ -2,5 +2,3 @@ export { ChangeType, DiffEngine } from './lib/diff-engine.js';
|
|
|
2
2
|
export { PathIndex } from './lib/path-index.js';
|
|
3
3
|
export { OptimizedUpdateEngine } from './lib/update-engine.js';
|
|
4
4
|
export { enterprise } from './lib/enterprise-enhancer.js';
|
|
5
|
-
export { configureScheduler, getSchedulerMetrics, postTask } from './lib/scheduler.js';
|
|
6
|
-
export { createMockPool } from './lib/thread-pools.js';
|
|
@@ -3,6 +3,10 @@ import { isTraversableNode, isBuiltInObject } from '@signaltree/core';
|
|
|
3
3
|
import { DiffEngine, ChangeType } from './diff-engine.js';
|
|
4
4
|
import { PathIndex } from './path-index.js';
|
|
5
5
|
|
|
6
|
+
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
7
|
+
function isUnsafeKey(key) {
|
|
8
|
+
return typeof key === 'string' && UNSAFE_KEYS.has(key);
|
|
9
|
+
}
|
|
6
10
|
class OptimizedUpdateEngine {
|
|
7
11
|
constructor(tree) {
|
|
8
12
|
this.pathIndex = new PathIndex();
|
|
@@ -150,13 +154,18 @@ class OptimizedUpdateEngine {
|
|
|
150
154
|
let current = tree;
|
|
151
155
|
for (let i = 0; i < patch.path.length - 1; i++) {
|
|
152
156
|
const key = patch.path[i];
|
|
157
|
+
if (isUnsafeKey(key)) return false;
|
|
158
|
+
if (!Object.prototype.hasOwnProperty.call(current, key)) {
|
|
159
|
+
return false;
|
|
160
|
+
}
|
|
153
161
|
current = current[key];
|
|
154
162
|
if (!isTraversableNode(current)) {
|
|
155
163
|
return false;
|
|
156
164
|
}
|
|
157
165
|
}
|
|
158
166
|
const lastKey = patch.path[patch.path.length - 1];
|
|
159
|
-
|
|
167
|
+
if (isUnsafeKey(lastKey)) return false;
|
|
168
|
+
const target = Object.prototype.hasOwnProperty.call(current, lastKey) ? current[lastKey] : undefined;
|
|
160
169
|
if (isTraversableNode(target)) {
|
|
161
170
|
if (isSignal(target)) {
|
|
162
171
|
const leaf = target;
|
|
@@ -171,10 +180,19 @@ class OptimizedUpdateEngine {
|
|
|
171
180
|
}
|
|
172
181
|
return false;
|
|
173
182
|
}
|
|
174
|
-
if (this.isEqual(
|
|
183
|
+
if (this.isEqual(target, patch.value)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
const existing = Object.getOwnPropertyDescriptor(current, lastKey);
|
|
187
|
+
if (!existing || !existing.enumerable) {
|
|
175
188
|
return false;
|
|
176
189
|
}
|
|
177
|
-
current
|
|
190
|
+
Object.defineProperty(current, lastKey, {
|
|
191
|
+
value: patch.value,
|
|
192
|
+
enumerable: true,
|
|
193
|
+
writable: true,
|
|
194
|
+
configurable: true
|
|
195
|
+
});
|
|
178
196
|
return true;
|
|
179
197
|
} catch (error) {
|
|
180
198
|
console.error(`Failed to apply patch at ${patch.path.join('.')}:`, error);
|
|
@@ -192,6 +210,8 @@ class OptimizedUpdateEngine {
|
|
|
192
210
|
if (isTraversableNode(node) && value && typeof value === 'object') {
|
|
193
211
|
let changed = false;
|
|
194
212
|
for (const [key, child] of Object.entries(value)) {
|
|
213
|
+
if (isUnsafeKey(key)) continue;
|
|
214
|
+
if (!Object.prototype.hasOwnProperty.call(node, key)) continue;
|
|
195
215
|
changed = this.applyDeepToNode(node[key], child) || changed;
|
|
196
216
|
}
|
|
197
217
|
return changed;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@signaltree/enterprise",
|
|
3
|
-
"version": "13.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "13.5.0",
|
|
4
|
+
"description": "DEPRECATED — use tree.updateAndReport() in @signaltree/core instead. The diff engine is measurably slower than the core API that replaced it.",
|
|
5
5
|
"license": "BUSL-1.1",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"sideEffects": false,
|
|
@@ -33,16 +33,6 @@
|
|
|
33
33
|
"types": "./src/lib/enterprise-enhancer.d.ts",
|
|
34
34
|
"import": "./dist/lib/enterprise-enhancer.js",
|
|
35
35
|
"default": "./dist/lib/enterprise-enhancer.js"
|
|
36
|
-
},
|
|
37
|
-
"./scheduler": {
|
|
38
|
-
"types": "./src/lib/scheduler.d.ts",
|
|
39
|
-
"import": "./dist/lib/scheduler.js",
|
|
40
|
-
"default": "./dist/lib/scheduler.js"
|
|
41
|
-
},
|
|
42
|
-
"./thread-pools": {
|
|
43
|
-
"types": "./src/lib/thread-pools.d.ts",
|
|
44
|
-
"import": "./dist/lib/thread-pools.js",
|
|
45
|
-
"default": "./dist/lib/thread-pools.js"
|
|
46
36
|
}
|
|
47
37
|
},
|
|
48
38
|
"keywords": [
|
|
@@ -68,7 +58,7 @@
|
|
|
68
58
|
},
|
|
69
59
|
"peerDependencies": {
|
|
70
60
|
"@angular/core": "^20.0.0 || ^21.0.0 || ^22.0.0",
|
|
71
|
-
"@signaltree/core": "^13.
|
|
61
|
+
"@signaltree/core": "^13.5.0",
|
|
72
62
|
"tslib": "^2.0.0"
|
|
73
63
|
},
|
|
74
64
|
"peerDependenciesMeta": {},
|
package/src/index.d.ts
CHANGED
package/dist/lib/scheduler.js
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
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 };
|
package/dist/lib/thread-pools.js
DELETED
package/src/lib/scheduler.d.ts
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
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 {};
|