@signaltree/core 5.1.1 → 5.1.3

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.
Files changed (92) hide show
  1. package/README.md +116 -55
  2. package/dist/constants.js +6 -0
  3. package/dist/deep-clone.js +80 -0
  4. package/dist/deep-equal.js +41 -0
  5. package/dist/enhancers/batching/lib/batching.js +161 -0
  6. package/dist/enhancers/computed/lib/computed.js +21 -0
  7. package/dist/enhancers/devtools/lib/devtools.js +321 -0
  8. package/dist/enhancers/entities/lib/entities.js +93 -0
  9. package/dist/enhancers/index.js +72 -0
  10. package/dist/enhancers/memoization/lib/memoization.js +410 -0
  11. package/dist/enhancers/presets/lib/presets.js +87 -0
  12. package/dist/enhancers/serialization/constants.js +15 -0
  13. package/dist/enhancers/serialization/lib/serialization.js +662 -0
  14. package/dist/enhancers/time-travel/lib/time-travel.js +193 -0
  15. package/dist/index.js +19 -0
  16. package/dist/is-built-in-object.js +23 -0
  17. package/dist/lib/async-helpers.js +77 -0
  18. package/dist/lib/constants.js +56 -0
  19. package/dist/lib/entity-signal.js +280 -0
  20. package/dist/lib/memory/memory-manager.js +164 -0
  21. package/dist/lib/path-notifier.js +106 -0
  22. package/dist/lib/performance/diff-engine.js +156 -0
  23. package/dist/lib/performance/path-index.js +156 -0
  24. package/dist/lib/performance/update-engine.js +188 -0
  25. package/dist/lib/security/security-validator.js +121 -0
  26. package/dist/lib/signal-tree.js +626 -0
  27. package/dist/lib/types.js +9 -0
  28. package/dist/lib/utils.js +261 -0
  29. package/dist/lru-cache.js +64 -0
  30. package/dist/parse-path.js +13 -0
  31. package/package.json +1 -1
  32. package/src/async-helpers.d.ts +8 -0
  33. package/src/batching.d.ts +16 -0
  34. package/src/computed.d.ts +12 -0
  35. package/src/constants.d.ts +14 -0
  36. package/src/devtools.d.ts +77 -0
  37. package/src/diff-engine.d.ts +33 -0
  38. package/src/enhancers/batching/index.d.ts +1 -0
  39. package/src/enhancers/batching/lib/batching.d.ts +16 -0
  40. package/src/enhancers/batching/test-setup.d.ts +3 -0
  41. package/src/enhancers/computed/index.d.ts +1 -0
  42. package/src/enhancers/computed/lib/computed.d.ts +12 -0
  43. package/src/enhancers/devtools/index.d.ts +1 -0
  44. package/src/enhancers/devtools/lib/devtools.d.ts +77 -0
  45. package/src/enhancers/devtools/test-setup.d.ts +3 -0
  46. package/src/enhancers/entities/index.d.ts +1 -0
  47. package/src/enhancers/entities/lib/entities.d.ts +20 -0
  48. package/src/enhancers/entities/test-setup.d.ts +3 -0
  49. package/src/enhancers/index.d.ts +3 -0
  50. package/src/enhancers/memoization/index.d.ts +1 -0
  51. package/src/enhancers/memoization/lib/memoization.d.ts +65 -0
  52. package/src/enhancers/memoization/test-setup.d.ts +3 -0
  53. package/src/enhancers/presets/index.d.ts +1 -0
  54. package/src/enhancers/presets/lib/presets.d.ts +11 -0
  55. package/src/enhancers/presets/test-setup.d.ts +3 -0
  56. package/src/enhancers/serialization/constants.d.ts +14 -0
  57. package/src/enhancers/serialization/index.d.ts +2 -0
  58. package/src/enhancers/serialization/lib/serialization.d.ts +59 -0
  59. package/src/enhancers/serialization/test-setup.d.ts +3 -0
  60. package/src/enhancers/time-travel/index.d.ts +1 -0
  61. package/src/enhancers/time-travel/lib/time-travel.d.ts +36 -0
  62. package/src/enhancers/time-travel/lib/utils.d.ts +1 -0
  63. package/src/enhancers/time-travel/test-setup.d.ts +3 -0
  64. package/src/enhancers/types.d.ts +74 -0
  65. package/src/entities.d.ts +20 -0
  66. package/src/entity-signal.d.ts +1 -0
  67. package/src/index.d.ts +18 -0
  68. package/src/lib/async-helpers.d.ts +8 -0
  69. package/src/lib/constants.d.ts +41 -0
  70. package/src/lib/entity-signal.d.ts +1 -0
  71. package/src/lib/memory/memory-manager.d.ts +30 -0
  72. package/src/lib/path-notifier.d.ts +4 -0
  73. package/src/lib/performance/diff-engine.d.ts +33 -0
  74. package/src/lib/performance/path-index.d.ts +25 -0
  75. package/src/lib/performance/update-engine.d.ts +32 -0
  76. package/src/lib/security/security-validator.d.ts +33 -0
  77. package/src/lib/signal-tree.d.ts +8 -0
  78. package/src/lib/types.d.ts +278 -0
  79. package/src/lib/utils.d.ts +28 -0
  80. package/src/memoization.d.ts +65 -0
  81. package/src/memory-manager.d.ts +30 -0
  82. package/src/path-index.d.ts +25 -0
  83. package/src/path-notifier.d.ts +12 -0
  84. package/src/presets.d.ts +11 -0
  85. package/src/security-validator.d.ts +33 -0
  86. package/src/serialization.d.ts +59 -0
  87. package/src/signal-tree.d.ts +8 -0
  88. package/src/test-setup.d.ts +3 -0
  89. package/src/time-travel.d.ts +36 -0
  90. package/src/types.d.ts +278 -0
  91. package/src/update-engine.d.ts +32 -0
  92. package/src/utils.d.ts +1 -0
package/README.md CHANGED
@@ -81,6 +81,73 @@ Performance and bundle size vary by app shape, build tooling, device, and runtim
81
81
  - Use the **Benchmark Orchestrator** in the demo app to run calibrated, scenario-based benchmarks across supported libraries with **real-world frequency weighting**. It applies research-based multipliers derived from 40,000+ developer surveys and GitHub analysis, reports statistical summaries (median/p95/p99/stddev), alternates runs to reduce bias, and can export CSV/JSON. When available, memory usage is also reported.
82
82
  - Use the bundle analysis scripts in `scripts/` to measure your min+gz sizes. Sizes are approximate and depend on tree-shaking and configuration.
83
83
 
84
+ ## Best Practices (SignalTree-First)
85
+
86
+ > 📖 **Full guide**: [Implementation Patterns](https://github.com/JBorgia/signaltree/blob/main/docs/IMPLEMENTATION_PATTERNS.md)
87
+
88
+ Follow these principles for idiomatic SignalTree code:
89
+
90
+ ### 1. Expose signals directly (no computed wrappers)
91
+
92
+ ```typescript
93
+ const tree = signalTree(initialState).with(withEntities());
94
+ const $ = tree.$; // Shorthand for state access
95
+
96
+ // ✅ SignalTree-first: Direct signal exposure
97
+ return {
98
+ selectedUserId: $.selected.userId, // Direct from $ tree
99
+ loadingState: $.loading.state,
100
+ selectedUser, // Actual derived state (computed)
101
+ };
102
+
103
+ // ❌ Anti-pattern: Unnecessary computed wrappers
104
+ return {
105
+ selectedUserId: computed(() => $.selected.userId()), // Adds indirection
106
+ };
107
+ ```
108
+
109
+ ### 2. Use `ReturnType` inference (SignalTree-first)
110
+
111
+ ```typescript
112
+ // Let SignalTree infer the type - no manual interface needed!
113
+ import type { createUserTree } from './user.tree';
114
+ export type UserTree = ReturnType<typeof createUserTree>;
115
+
116
+ // Factory function - no explicit return type needed
117
+ export function createUserTree() {
118
+ const tree = signalTree(initialState).with(withEntities());
119
+ return {
120
+ selectedUserId: tree.$.selected.userId, // Type inferred automatically
121
+ // ...
122
+ };
123
+ }
124
+ ```
125
+
126
+ ### 3. Use `computed()` only for derived state
127
+
128
+ ```typescript
129
+ // ✅ Correct: Derived from multiple signals
130
+ const selectedUser = computed(() => {
131
+ const id = $.selected.userId();
132
+ return id ? $.users.byId(id)() : null;
133
+ });
134
+
135
+ // ❌ Wrong: Wrapping an existing signal
136
+ const selectedUserId = computed(() => $.selected.userId()); // Unnecessary!
137
+ ```
138
+
139
+ ### 4. Use EntitySignal API directly
140
+
141
+ ```typescript
142
+ // ✅ SignalTree-native
143
+ const user = $.users.byId(123)(); // O(1) lookup
144
+ const allUsers = $.users.all()(); // Get all
145
+ $.users.setAll(usersFromApi); // Replace all
146
+
147
+ // ❌ NgRx-style (avoid)
148
+ const user = entityMap()[123]; // Requires intermediate object
149
+ ```
150
+
84
151
  ## Quick start
85
152
 
86
153
  ### Installation
@@ -552,22 +619,20 @@ const tree = signalTree({ count: 0 }).with(
552
619
  import { signalTree, withBatching, withMemoization, withEntities } from '@signaltree/core';
553
620
 
554
621
  const tree = signalTree({
555
- products: [] as Product[],
622
+ products: entityMap<Product>(),
556
623
  ui: { loading: false },
557
- }).with(
558
- withBatching(), // Batch updates for optimal rendering
559
- withMemoization(), // Cache expensive computations
560
- withEntities() // Efficient CRUD operations
561
- );
624
+ })
625
+ .with(withEntities()) // Efficient CRUD operations (auto-detects entityMap)
626
+ .with(withBatching()); // Batch updates for optimal rendering
562
627
 
563
- // Now supports advanced operations
564
- tree.batchUpdate((state) => ({
565
- products: [...state.products, newProduct],
566
- ui: { loading: false },
567
- }));
628
+ // Entity CRUD operations
629
+ tree.$.products.addOne(newProduct);
630
+ tree.$.products.setAll(productsFromApi);
568
631
 
569
- const products = tree.entities<Product>('products');
570
- products.selectBy((p) => p.category === 'electronics');
632
+ // Entity queries
633
+ const electronics = tree.$.products
634
+ .all()()
635
+ .filter((p) => p.category === 'electronics');
571
636
  ```
572
637
 
573
638
  **Full-Stack Application:**
@@ -644,17 +709,20 @@ const customTree = signalTree(state, TREE_PRESETS.DASHBOARD);
644
709
  SignalTree Core includes all enhancer functionality built-in. No separate packages needed:
645
710
 
646
711
  ```typescript
647
- import { signalTree, withEntities } from '@signaltree/core';
712
+ import { signalTree, entityMap, withEntities } from '@signaltree/core';
648
713
 
649
- const tree = signalTree({ users: [] as User[] });
714
+ // Without entityMap - use manual array updates
715
+ const basic = signalTree({ users: [] as User[] });
716
+ basic.$.users.update((users) => [...users, newUser]);
650
717
 
651
- // Without enhancer - use manual CRUD
652
- tree.$.users.update((users) => [...users, newUser]);
718
+ // With entityMap + withEntities - use entity helpers
719
+ const enhanced = signalTree({
720
+ users: entityMap<User>(),
721
+ }).with(withEntities());
653
722
 
654
- // With enhancer - use entity helpers
655
- const enhanced = tree.with(withEntities());
656
- const users = enhanced.entities<User>('users');
657
- users.add(newUser); // ✅ Advanced CRUD operations
723
+ enhanced.$.users.addOne(newUser); // Advanced CRUD operations
724
+ enhanced.$.users.byId(123)(); // ✅ O(1) lookups
725
+ enhanced.$.users.all()(); // ✅ Get all as array
658
726
  ```
659
727
 
660
728
  Core includes several performance optimizations:
@@ -843,58 +911,49 @@ const filteredProducts = computed(() => {
843
911
  ### Data Management Composition
844
912
 
845
913
  ```typescript
846
- import { signalTree, withEntities } from '@signaltree/core';
914
+ import { signalTree, entityMap, withEntities } from '@signaltree/core';
847
915
 
848
916
  // Add data management capabilities (+2.77KB total)
849
917
  const tree = signalTree({
850
- users: [] as User[],
851
- posts: [] as Post[],
852
- ui: { loading: false, error: null },
853
- }).with(
854
- withEntities() // Advanced CRUD operations
855
- );
918
+ users: entityMap<User>(),
919
+ posts: entityMap<Post>(),
920
+ ui: { loading: false, error: null as string | null },
921
+ }).with(withEntities());
856
922
 
857
- // Advanced entity operations
858
- const users = tree.entities<User>('users');
859
- users.add(newUser);
860
- users.selectBy((u) => u.active);
861
- users.updateMany([{ id: '1', changes: { status: 'active' } }]);
923
+ // Advanced entity operations via tree.$ accessor
924
+ tree.$.users.addOne(newUser);
925
+ tree.$.users.selectBy((u) => u.active);
926
+ tree.$.users.updateMany([{ id: '1', changes: { status: 'active' } }]);
862
927
 
863
- // Entity helpers support nested paths for organized state structures
928
+ // Entity helpers work with nested structures
864
929
  // Example: deeply nested entities in a domain-driven design pattern
865
930
  const appTree = signalTree({
866
931
  app: {
867
932
  data: {
868
- users: [] as User[],
869
- products: [] as Product[],
933
+ users: entityMap<User>(),
934
+ products: entityMap<Product>(),
870
935
  },
871
936
  },
872
937
  admin: {
873
938
  data: {
874
- logs: [] as AuditLog[],
875
- reports: [] as Report[],
939
+ logs: entityMap<AuditLog>(),
940
+ reports: entityMap<Report>(),
876
941
  },
877
942
  },
878
- });
943
+ }).with(withEntities());
879
944
 
880
- // Access deeply nested entities using dot notation
881
- const appUsers = appTree.entities<User>('app.data.users');
882
- const appProducts = appTree.entities<Product>('app.data.products');
883
- const adminLogs = appTree.entities<AuditLog>('admin.data.logs');
884
- const adminReports = appTree.entities<Report>('admin.data.reports');
885
-
886
- // All entity methods work seamlessly with nested paths
887
- appUsers.selectBy((u) => u.isAdmin); // Filtered signal
888
- appProducts.selectTotal(); // Count signal
889
- adminLogs.selectAll(); // All items signal
890
- adminReports.selectIds(); // ID array signal
945
+ // Access nested entities using tree.$ accessor
946
+ appTree.$.app.data.users.selectBy((u) => u.isAdmin); // Filtered signal
947
+ appTree.$.app.data.products.selectTotal(); // Count signal
948
+ appTree.$.admin.data.logs.all()(); // All items as array
949
+ appTree.$.admin.data.reports.selectIds(); // ID array signal
891
950
 
892
951
  // For async operations, use manual async or async helpers
893
952
  async function fetchUsers() {
894
953
  tree.$.ui.loading.set(true);
895
954
  try {
896
955
  const users = await api.getUsers();
897
- tree.$.users.set(users);
956
+ tree.$.users.setAll(users);
898
957
  } catch (error) {
899
958
  tree.$.ui.error.set(error.message);
900
959
  } finally {
@@ -936,10 +995,10 @@ const tree = signalTree({
936
995
  );
937
996
 
938
997
  // Rich feature set available
939
- const users = tree.entities<User>('app.data.users');
940
998
  async function fetchUser(id: string) {
941
999
  return await api.getUser(id);
942
1000
  }
1001
+ tree.$.app.data.users.byId(userId)(); // O(1) lookup
943
1002
  tree.undo(); // Time travel
944
1003
  tree.save(); // Persistence
945
1004
  ```
@@ -1308,9 +1367,11 @@ tree.effect(fn); // Create reactive effects
1308
1367
  tree.subscribe(fn); // Manual subscriptions
1309
1368
  tree.destroy(); // Cleanup resources
1310
1369
 
1311
- // Extended features (built into @signaltree/core)
1312
- tree.entities<T>(key); // Entity helpers (use withEntities enhancer)
1313
- // For async operations, use manual async or async helpers like createAsyncOperation or trackAsync
1370
+ // Entity helpers (when using entityMap + withEntities)
1371
+ // tree.$.users.addOne(user); // Add single entity
1372
+ // tree.$.users.byId(id)(); // O(1) lookup by ID
1373
+ // tree.$.users.all()(); // Get all as array
1374
+ // tree.$.users.selectBy(pred); // Filtered signal
1314
1375
  ```
1315
1376
 
1316
1377
  ## Extending with enhancers
@@ -0,0 +1,6 @@
1
+ const SHARED_DEFAULTS = Object.freeze({
2
+ PATH_CACHE_SIZE: 1000
3
+ });
4
+ const DEFAULT_PATH_CACHE_SIZE = SHARED_DEFAULTS.PATH_CACHE_SIZE;
5
+
6
+ export { DEFAULT_PATH_CACHE_SIZE, SHARED_DEFAULTS };
@@ -0,0 +1,80 @@
1
+ const globalStructuredClone = typeof globalThis === 'object' && globalThis !== null ? globalThis.structuredClone : undefined;
2
+ function deepClone(value) {
3
+ if (globalStructuredClone) {
4
+ try {
5
+ return globalStructuredClone(value);
6
+ } catch (_a) {}
7
+ }
8
+ return cloneValue(value, new WeakMap());
9
+ }
10
+ function cloneValue(value, seen) {
11
+ if (value === null || typeof value !== 'object') {
12
+ return value;
13
+ }
14
+ if (typeof value === 'function') {
15
+ return value;
16
+ }
17
+ const existing = seen.get(value);
18
+ if (existing) {
19
+ return existing;
20
+ }
21
+ if (value instanceof Date) {
22
+ return new Date(value.getTime());
23
+ }
24
+ if (value instanceof RegExp) {
25
+ return new RegExp(value.source, value.flags);
26
+ }
27
+ if (value instanceof Map) {
28
+ const result = new Map();
29
+ seen.set(value, result);
30
+ for (const [key, entryValue] of value) {
31
+ result.set(cloneValue(key, seen), cloneValue(entryValue, seen));
32
+ }
33
+ return result;
34
+ }
35
+ if (value instanceof Set) {
36
+ const result = new Set();
37
+ seen.set(value, result);
38
+ for (const entry of value) {
39
+ result.add(cloneValue(entry, seen));
40
+ }
41
+ return result;
42
+ }
43
+ if (Array.isArray(value)) {
44
+ const result = new Array(value.length);
45
+ seen.set(value, result);
46
+ for (let i = 0; i < value.length; i++) {
47
+ result[i] = cloneValue(value[i], seen);
48
+ }
49
+ return result;
50
+ }
51
+ if (ArrayBuffer.isView(value)) {
52
+ if (value instanceof DataView) {
53
+ const bufferClone = cloneValue(value.buffer, seen);
54
+ return new DataView(bufferClone, value.byteOffset, value.byteLength);
55
+ }
56
+ const viewWithSlice = value;
57
+ if (typeof viewWithSlice.slice === 'function') {
58
+ return viewWithSlice.slice();
59
+ }
60
+ const bufferClone = cloneValue(value.buffer, seen);
61
+ return new value.constructor(bufferClone, value.byteOffset, value.length);
62
+ }
63
+ if (value instanceof ArrayBuffer) {
64
+ return value.slice(0);
65
+ }
66
+ const proto = Object.getPrototypeOf(value);
67
+ const result = proto ? Object.create(proto) : {};
68
+ seen.set(value, result);
69
+ for (const key of Reflect.ownKeys(value)) {
70
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
71
+ if (!descriptor) continue;
72
+ if ('value' in descriptor) {
73
+ descriptor.value = cloneValue(descriptor.value, seen);
74
+ }
75
+ Object.defineProperty(result, key, descriptor);
76
+ }
77
+ return result;
78
+ }
79
+
80
+ export { deepClone };
@@ -0,0 +1,41 @@
1
+ function deepEqual(a, b) {
2
+ if (a === b) return true;
3
+ if (a == null || b == null) return a === b;
4
+ const typeA = typeof a;
5
+ const typeB = typeof b;
6
+ if (typeA !== typeB) return false;
7
+ if (typeA !== 'object') return false;
8
+ if (a instanceof Date && b instanceof Date) {
9
+ return a.getTime() === b.getTime();
10
+ }
11
+ if (a instanceof RegExp && b instanceof RegExp) {
12
+ return a.source === b.source && a.flags === b.flags;
13
+ }
14
+ if (a instanceof Map && b instanceof Map) {
15
+ if (a.size !== b.size) return false;
16
+ for (const [key, value] of a) {
17
+ if (!b.has(key) || !deepEqual(value, b.get(key))) return false;
18
+ }
19
+ return true;
20
+ }
21
+ if (a instanceof Set && b instanceof Set) {
22
+ if (a.size !== b.size) return false;
23
+ for (const value of a) {
24
+ if (!b.has(value)) return false;
25
+ }
26
+ return true;
27
+ }
28
+ if (Array.isArray(a)) {
29
+ if (!Array.isArray(b) || a.length !== b.length) return false;
30
+ return a.every((item, index) => deepEqual(item, b[index]));
31
+ }
32
+ if (Array.isArray(b)) return false;
33
+ const objA = a;
34
+ const objB = b;
35
+ const keysA = Object.keys(objA);
36
+ const keysB = Object.keys(objB);
37
+ if (keysA.length !== keysB.length) return false;
38
+ return keysA.every(key => key in objB && deepEqual(objA[key], objB[key]));
39
+ }
40
+
41
+ export { deepEqual };
@@ -0,0 +1,161 @@
1
+ import { isNodeAccessor } from '../../../lib/utils.js';
2
+
3
+ let updateQueue = [];
4
+ let isUpdating = false;
5
+ let flushTimeoutId;
6
+ let currentBatchingConfig = {};
7
+ function addToQueue(update, config = currentBatchingConfig) {
8
+ const maxSize = config.maxBatchSize ?? 100;
9
+ if (update.path) {
10
+ updateQueue = updateQueue.filter(existing => existing.path !== update.path);
11
+ }
12
+ updateQueue.push(update);
13
+ if (updateQueue.length > maxSize) {
14
+ flushUpdates();
15
+ return true;
16
+ }
17
+ scheduleFlush(config);
18
+ return false;
19
+ }
20
+ function scheduleFlush(config) {
21
+ if (flushTimeoutId !== undefined) {
22
+ clearTimeout(flushTimeoutId);
23
+ }
24
+ const delay = config.autoFlushDelay ?? 16;
25
+ flushTimeoutId = setTimeout(() => {
26
+ flushUpdates();
27
+ }, delay);
28
+ }
29
+ function flushUpdates() {
30
+ if (isUpdating) return;
31
+ let queue;
32
+ do {
33
+ if (updateQueue.length === 0) return;
34
+ isUpdating = true;
35
+ queue = updateQueue;
36
+ updateQueue = [];
37
+ if (flushTimeoutId !== undefined) {
38
+ clearTimeout(flushTimeoutId);
39
+ flushTimeoutId = undefined;
40
+ }
41
+ queue.sort((a, b) => (b.depth ?? 0) - (a.depth ?? 0));
42
+ try {
43
+ queue.forEach(({
44
+ fn
45
+ }) => fn());
46
+ } finally {
47
+ isUpdating = false;
48
+ }
49
+ } while (updateQueue.length > 0);
50
+ }
51
+ function batchUpdates(fn, path) {
52
+ const startTime = performance.now();
53
+ const depth = 0;
54
+ const update = {
55
+ fn,
56
+ startTime,
57
+ depth,
58
+ path
59
+ };
60
+ const wasFlushed = addToQueue(update, currentBatchingConfig);
61
+ if (!wasFlushed) {
62
+ const isTimedOut = currentBatchingConfig.batchTimeoutMs && updateQueue.length > 0 && startTime - updateQueue[0].startTime >= currentBatchingConfig.batchTimeoutMs;
63
+ if (isTimedOut) {
64
+ flushUpdates();
65
+ } else if (!isUpdating && updateQueue.length > 0) {
66
+ queueMicrotask(() => {
67
+ flushUpdates();
68
+ });
69
+ }
70
+ }
71
+ }
72
+ function withBatching(config = {}) {
73
+ const {
74
+ enabled = true
75
+ } = config;
76
+ currentBatchingConfig = {
77
+ ...currentBatchingConfig,
78
+ ...config
79
+ };
80
+ return tree => {
81
+ if (!enabled) {
82
+ return tree;
83
+ }
84
+ const originalTreeCall = tree.bind(tree);
85
+ const enhancedTree = function (...args) {
86
+ if (args.length === 0) {
87
+ return originalTreeCall();
88
+ } else {
89
+ batchUpdates(() => {
90
+ if (args.length === 1) {
91
+ const arg = args[0];
92
+ if (typeof arg === 'function') {
93
+ originalTreeCall(arg);
94
+ } else {
95
+ originalTreeCall(arg);
96
+ }
97
+ }
98
+ });
99
+ }
100
+ };
101
+ Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
102
+ Object.assign(enhancedTree, tree);
103
+ if ('state' in tree) {
104
+ Object.defineProperty(enhancedTree, 'state', {
105
+ value: tree.state,
106
+ enumerable: false,
107
+ configurable: true
108
+ });
109
+ }
110
+ if ('$' in tree) {
111
+ Object.defineProperty(enhancedTree, '$', {
112
+ value: tree['$'],
113
+ enumerable: false,
114
+ configurable: true
115
+ });
116
+ }
117
+ enhancedTree.batchUpdate = updater => {
118
+ batchUpdates(() => {
119
+ const current = originalTreeCall();
120
+ const updates = updater(current);
121
+ Object.entries(updates).forEach(([key, value]) => {
122
+ const property = enhancedTree.state[key];
123
+ if (property && 'set' in property) {
124
+ property.set(value);
125
+ } else if (isNodeAccessor(property)) {
126
+ property(value);
127
+ }
128
+ });
129
+ });
130
+ };
131
+ return enhancedTree;
132
+ };
133
+ }
134
+ function withHighPerformanceBatching() {
135
+ return withBatching({
136
+ enabled: true,
137
+ maxBatchSize: 200,
138
+ batchTimeoutMs: 0
139
+ });
140
+ }
141
+ function flushBatchedUpdates() {
142
+ if (updateQueue.length > 0) {
143
+ const queue = updateQueue.slice();
144
+ updateQueue = [];
145
+ isUpdating = false;
146
+ queue.sort((a, b) => (b.depth ?? 0) - (a.depth ?? 0));
147
+ queue.forEach(({
148
+ fn
149
+ }) => {
150
+ fn();
151
+ });
152
+ }
153
+ }
154
+ function hasPendingUpdates() {
155
+ return updateQueue.length > 0;
156
+ }
157
+ function getBatchQueueSize() {
158
+ return updateQueue.length;
159
+ }
160
+
161
+ export { flushBatchedUpdates, getBatchQueueSize, hasPendingUpdates, withBatching, withHighPerformanceBatching };
@@ -0,0 +1,21 @@
1
+ import { computed } from '@angular/core';
2
+ import { createEnhancer } from '../../index.js';
3
+
4
+ function computedEnhancer(_config = {}) {
5
+ return createEnhancer({
6
+ name: 'computed',
7
+ provides: ['computed'],
8
+ requires: []
9
+ }, tree => {
10
+ const computedTree = tree;
11
+ computedTree.computed = function (computeFn) {
12
+ return computed(() => computeFn(tree.state));
13
+ };
14
+ return computedTree;
15
+ });
16
+ }
17
+ function createComputed(dependencies, computeFn) {
18
+ return computed(computeFn);
19
+ }
20
+
21
+ export { computedEnhancer, createComputed };