@signaltree/core 7.1.3 → 7.1.4

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 (100) hide show
  1. package/README.md +28 -13
  2. package/package.json +2 -2
  3. package/dist/constants.js +0 -6
  4. package/dist/deep-equal.js +0 -41
  5. package/dist/enhancers/batching/batching.js +0 -230
  6. package/dist/enhancers/devtools/devtools.js +0 -318
  7. package/dist/enhancers/effects/effects.js +0 -66
  8. package/dist/enhancers/entities/entities.js +0 -7
  9. package/dist/enhancers/index.js +0 -72
  10. package/dist/enhancers/memoization/memoization.js +0 -420
  11. package/dist/enhancers/presets/lib/presets.js +0 -27
  12. package/dist/enhancers/serialization/constants.js +0 -15
  13. package/dist/enhancers/serialization/serialization.js +0 -656
  14. package/dist/enhancers/time-travel/time-travel.js +0 -283
  15. package/dist/enhancers/time-travel/utils.js +0 -11
  16. package/dist/enhancers/utils/copy-tree-properties.js +0 -20
  17. package/dist/index.js +0 -26
  18. package/dist/is-built-in-object.js +0 -23
  19. package/dist/lib/async-helpers.js +0 -77
  20. package/dist/lib/constants.js +0 -56
  21. package/dist/lib/edit-session.js +0 -84
  22. package/dist/lib/entity-signal.js +0 -544
  23. package/dist/lib/internals/batch-scope.js +0 -8
  24. package/dist/lib/internals/derived-types.js +0 -5
  25. package/dist/lib/internals/materialize-markers.js +0 -72
  26. package/dist/lib/internals/merge-derived.js +0 -59
  27. package/dist/lib/markers/derived.js +0 -6
  28. package/dist/lib/markers/entity-map.js +0 -20
  29. package/dist/lib/markers/status.js +0 -71
  30. package/dist/lib/markers/stored.js +0 -111
  31. package/dist/lib/memory/memory-manager.js +0 -164
  32. package/dist/lib/path-notifier.js +0 -178
  33. package/dist/lib/presets.js +0 -20
  34. package/dist/lib/security/security-validator.js +0 -121
  35. package/dist/lib/signal-tree.js +0 -415
  36. package/dist/lib/types.js +0 -3
  37. package/dist/lib/utils.js +0 -264
  38. package/dist/lru-cache.js +0 -64
  39. package/dist/parse-path.js +0 -13
  40. package/src/enhancers/batching/batching.d.ts +0 -10
  41. package/src/enhancers/batching/batching.types.d.ts +0 -1
  42. package/src/enhancers/batching/index.d.ts +0 -1
  43. package/src/enhancers/batching/test-setup.d.ts +0 -3
  44. package/src/enhancers/devtools/devtools.d.ts +0 -68
  45. package/src/enhancers/devtools/devtools.types.d.ts +0 -1
  46. package/src/enhancers/devtools/index.d.ts +0 -1
  47. package/src/enhancers/devtools/test-setup.d.ts +0 -3
  48. package/src/enhancers/effects/effects.d.ts +0 -9
  49. package/src/enhancers/effects/effects.types.d.ts +0 -1
  50. package/src/enhancers/effects/index.d.ts +0 -1
  51. package/src/enhancers/entities/entities.d.ts +0 -7
  52. package/src/enhancers/entities/entities.types.d.ts +0 -1
  53. package/src/enhancers/entities/index.d.ts +0 -1
  54. package/src/enhancers/entities/test-setup.d.ts +0 -3
  55. package/src/enhancers/index.d.ts +0 -3
  56. package/src/enhancers/memoization/index.d.ts +0 -1
  57. package/src/enhancers/memoization/memoization.d.ts +0 -54
  58. package/src/enhancers/memoization/memoization.types.d.ts +0 -1
  59. package/src/enhancers/memoization/test-setup.d.ts +0 -3
  60. package/src/enhancers/presets/index.d.ts +0 -1
  61. package/src/enhancers/presets/lib/presets.d.ts +0 -8
  62. package/src/enhancers/serialization/constants.d.ts +0 -14
  63. package/src/enhancers/serialization/index.d.ts +0 -2
  64. package/src/enhancers/serialization/serialization.d.ts +0 -68
  65. package/src/enhancers/serialization/test-setup.d.ts +0 -3
  66. package/src/enhancers/test-helpers/types-equals.d.ts +0 -2
  67. package/src/enhancers/time-travel/index.d.ts +0 -1
  68. package/src/enhancers/time-travel/test-setup.d.ts +0 -3
  69. package/src/enhancers/time-travel/time-travel.d.ts +0 -10
  70. package/src/enhancers/time-travel/time-travel.types.d.ts +0 -1
  71. package/src/enhancers/time-travel/utils.d.ts +0 -2
  72. package/src/enhancers/types.d.ts +0 -1
  73. package/src/enhancers/typing/helpers-types.d.ts +0 -2
  74. package/src/enhancers/utils/copy-tree-properties.d.ts +0 -1
  75. package/src/index.d.ts +0 -26
  76. package/src/lib/async-helpers.d.ts +0 -8
  77. package/src/lib/constants.d.ts +0 -41
  78. package/src/lib/dev-proxy.d.ts +0 -3
  79. package/src/lib/edit-session.d.ts +0 -21
  80. package/src/lib/entity-signal.d.ts +0 -1
  81. package/src/lib/internals/batch-scope.d.ts +0 -3
  82. package/src/lib/internals/builder-types.d.ts +0 -13
  83. package/src/lib/internals/derived-types.d.ts +0 -18
  84. package/src/lib/internals/materialize-markers.d.ts +0 -5
  85. package/src/lib/internals/merge-derived.d.ts +0 -4
  86. package/src/lib/markers/derived.d.ts +0 -9
  87. package/src/lib/markers/entity-map.d.ts +0 -4
  88. package/src/lib/markers/index.d.ts +0 -3
  89. package/src/lib/markers/status.d.ts +0 -32
  90. package/src/lib/markers/stored.d.ts +0 -23
  91. package/src/lib/memory/memory-manager.d.ts +0 -30
  92. package/src/lib/path-notifier.d.ts +0 -34
  93. package/src/lib/performance/diff-engine.d.ts +0 -33
  94. package/src/lib/performance/path-index.d.ts +0 -25
  95. package/src/lib/performance/update-engine.d.ts +0 -32
  96. package/src/lib/presets.d.ts +0 -34
  97. package/src/lib/security/security-validator.d.ts +0 -33
  98. package/src/lib/signal-tree.d.ts +0 -6
  99. package/src/lib/types.d.ts +0 -300
  100. package/src/lib/utils.d.ts +0 -25
package/README.md CHANGED
@@ -2,19 +2,34 @@
2
2
  <img src="https://raw.githubusercontent.com/JBorgia/signaltree/main/apps/demo/public/signaltree.svg" alt="SignalTree Logo" width="60" height="60" />
3
3
  </div>
4
4
 
5
- # SignalTree Core
5
+ # SignalTree: Reactive JSON
6
6
 
7
- Foundation package for SignalTree. Provides recursive typing, deep nesting support, and strong performance.
7
+ **JSON branches, reactive leaves.**
8
+
9
+ > No actions. No reducers. No selectors.
8
10
 
9
11
  ## What is @signaltree/core?
10
12
 
11
- SignalTree Core is a lightweight package that provides:
13
+ SignalTree treats application state as **reactive JSON** — a typed, dot-notation interface to plain JSON-like objects with fine-grained reactivity layered transparently on top.
14
+
15
+ You don't model state as actions, reducers, selectors, or classes — you model it as **data**.
16
+
17
+ ### Core Philosophy
18
+
19
+ | Principle | What It Means |
20
+ | ------------------------ | ---------------------------------------------------------------------------- |
21
+ | **State is Data** | Your state shape looks like JSON. No ceremony, no abstractions. |
22
+ | **Dot-Notation Access** | `tree.$.user.profile.name()` — fully type-safe, IDE-discoverable |
23
+ | **Invisible Reactivity** | You think in data paths, not subscriptions. Reactivity emerges naturally. |
24
+ | **Lazy by Design** | Signals created only where accessed. Types do heavy lifting at compile time. |
25
+
26
+ ### Technical Features
12
27
 
13
28
  - Recursive typing with deep nesting and accurate type inference
14
29
  - Fast operations with sub‑millisecond measurements at 5–20+ levels
15
30
  - Strong TypeScript safety across nested structures
16
31
  - Memory efficiency via structural sharing and lazy signals
17
- - Small API surface with zero-cost abstractions
32
+ - Small API surface with minimal runtime overhead
18
33
  - Compact bundle size suited for production
19
34
 
20
35
  ## Import guidance (tree-shaking)
@@ -936,7 +951,7 @@ For larger applications, you may want to organize derived tiers into separate fi
936
951
 
937
952
  SignalTree provides two utilities for external derived functions:
938
953
 
939
- - **`externalDerived<TTree>()`** - Curried helper function that provides type context for your derived function
954
+ - **`derivedFrom<TTree>()`** - Curried helper function that provides type context for your derived function
940
955
  - **`WithDerived<TTree, TDerivedFn>`** - Type utility to build intermediate tree types
941
956
 
942
957
  ```typescript
@@ -967,11 +982,11 @@ export function createAppTree() {
967
982
  ```typescript
968
983
  // derived/tier-entity-resolution.ts
969
984
  import { computed } from '@angular/core';
970
- import { externalDerived } from '@signaltree/core';
985
+ import { derivedFrom } from '@signaltree/core';
971
986
  import type { AppTreeBase } from '../app-tree';
972
987
 
973
- // externalDerived provides the type context for $ via curried syntax
974
- export const entityResolutionDerived = externalDerived<AppTreeBase>()(($) => ({
988
+ // derivedFrom provides the type context for $ via curried syntax
989
+ export const entityResolutionDerived = derivedFrom<AppTreeBase>()(($) => ({
975
990
  selectedUser: computed(() => {
976
991
  const id = $.selectedUserId();
977
992
  return id != null ? $.users.byId(id)?.() ?? null : null;
@@ -982,11 +997,11 @@ export const entityResolutionDerived = externalDerived<AppTreeBase>()(($) => ({
982
997
  ```typescript
983
998
  // derived/tier-complex-logic.ts
984
999
  import { computed } from '@angular/core';
985
- import { externalDerived } from '@signaltree/core';
1000
+ import { derivedFrom } from '@signaltree/core';
986
1001
  import type { AppTreeWithTier1 } from '../app-tree';
987
1002
 
988
1003
  // This tier has access to $.selectedUser from Tier 1
989
- export const complexLogicDerived = externalDerived<AppTreeWithTier1>()(($) => ({
1004
+ export const complexLogicDerived = derivedFrom<AppTreeWithTier1>()(($) => ({
990
1005
  isAdmin: computed(() => $.selectedUser()?.role === 'admin'),
991
1006
  displayName: computed(() => {
992
1007
  const user = $.selectedUser();
@@ -1006,13 +1021,13 @@ export function myDerived($) {
1006
1021
  return { foo: computed(() => $.bar()) }; // Error: $ has no properties
1007
1022
  }
1008
1023
 
1009
- // ✅ externalDerived provides the type context (curried syntax)
1010
- export const myDerived = externalDerived<AppTreeBase>()(($) => ({
1024
+ // ✅ derivedFrom provides the type context (curried syntax)
1025
+ export const myDerived = derivedFrom<AppTreeBase>()(($) => ({
1011
1026
  foo: computed(() => $.bar()), // $ is properly typed
1012
1027
  }));
1013
1028
  ```
1014
1029
 
1015
- **Key point**: `externalDerived` is **only needed for functions defined in separate files**. Inline functions automatically inherit types from the chain. Note the curried syntax: `externalDerived<TreeType>()(fn)` - this allows TypeScript to infer the return type while you specify the tree type.
1030
+ **Key point**: `derivedFrom` is **only needed for functions defined in separate files**. Inline functions automatically inherit types from the chain. Note the curried syntax: `derivedFrom<TreeType>()(fn)` - this allows TypeScript to infer the return type while you specify the tree type.
1016
1031
 
1017
1032
  ## Error handling examples
1018
1033
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@signaltree/core",
3
- "version": "7.1.3",
4
- "description": "Lightweight, type-safe signal-based state management for Angular. Core package providing hierarchical signal trees, basic entity management, and async actions.",
3
+ "version": "7.1.4",
4
+ "description": "Reactive JSON for Angular. JSON branches, reactive leaves. No actions. No reducers. No selectors.",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "sideEffects": false,
package/dist/constants.js DELETED
@@ -1,6 +0,0 @@
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 };
@@ -1,41 +0,0 @@
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 };
@@ -1,230 +0,0 @@
1
- import { copyTreeProperties } from '../utils/copy-tree-properties.js';
2
-
3
- function batching(config = {}) {
4
- const enabled = config.enabled ?? true;
5
- const notificationDelayMs = config.notificationDelayMs ?? 0;
6
- return tree => {
7
- if (!enabled) {
8
- const passthrough = {
9
- batch: fn => fn(),
10
- coalesce: fn => fn(),
11
- hasPendingNotifications: () => false,
12
- flushNotifications: () => {}
13
- };
14
- const enhanced = tree;
15
- Object.assign(enhanced, passthrough);
16
- enhanced.batchUpdate = updater => {
17
- if (typeof tree.batchUpdate === 'function') {
18
- tree.batchUpdate(updater);
19
- } else {
20
- updater(tree());
21
- }
22
- };
23
- return enhanced;
24
- }
25
- let notificationPending = false;
26
- let notificationTimeoutId;
27
- let inBatch = false;
28
- let inCoalesce = false;
29
- const coalescedUpdates = new Map();
30
- const scheduleNotification = () => {
31
- if (notificationPending) return;
32
- notificationPending = true;
33
- if (notificationDelayMs > 0) {
34
- notificationTimeoutId = setTimeout(flushNotificationsInternal, notificationDelayMs);
35
- } else {
36
- queueMicrotask(flushNotificationsInternal);
37
- }
38
- };
39
- const flushNotificationsInternal = () => {
40
- if (!notificationPending) return;
41
- notificationPending = false;
42
- if (notificationTimeoutId !== undefined) {
43
- clearTimeout(notificationTimeoutId);
44
- notificationTimeoutId = undefined;
45
- }
46
- if (tree.__notifyChangeDetection) {
47
- tree.__notifyChangeDetection();
48
- }
49
- };
50
- const flushCoalescedUpdates = () => {
51
- const updates = Array.from(coalescedUpdates.values());
52
- coalescedUpdates.clear();
53
- updates.forEach(fn => {
54
- try {
55
- fn();
56
- } catch (e) {
57
- console.error('[SignalTree] Error in coalesced update:', e);
58
- }
59
- });
60
- };
61
- const wrapSignalSetters = (node, path = '') => {
62
- if (!node || typeof node !== 'object') return;
63
- if (typeof node.set === 'function' && !node.__batchingWrapped) {
64
- const originalSet = node.set.bind(node);
65
- node.set = value => {
66
- if (inCoalesce) {
67
- coalescedUpdates.set(path, () => originalSet(value));
68
- } else {
69
- originalSet(value);
70
- }
71
- if (!inBatch) {
72
- scheduleNotification();
73
- }
74
- };
75
- node.__batchingWrapped = true;
76
- }
77
- if (typeof node.update === 'function' && !node.__batchingUpdateWrapped) {
78
- const originalUpdate = node.update.bind(node);
79
- node.update = updater => {
80
- if (inCoalesce) {
81
- coalescedUpdates.set(`${path}:update:${Date.now()}`, () => originalUpdate(updater));
82
- } else {
83
- originalUpdate(updater);
84
- }
85
- if (!inBatch) {
86
- scheduleNotification();
87
- }
88
- };
89
- node.__batchingUpdateWrapped = true;
90
- }
91
- for (const key of Object.keys(node)) {
92
- if (key.startsWith('_') || key === 'set' || key === 'update') continue;
93
- const child = node[key];
94
- if (child && typeof child === 'object') {
95
- wrapSignalSetters(child, path ? `${path}.${key}` : key);
96
- }
97
- }
98
- };
99
- if (tree.$) {
100
- wrapSignalSetters(tree.$);
101
- }
102
- const batchingMethods = {
103
- batch(fn) {
104
- const wasBatching = inBatch;
105
- inBatch = true;
106
- try {
107
- fn();
108
- } finally {
109
- inBatch = wasBatching;
110
- if (!inBatch) {
111
- scheduleNotification();
112
- }
113
- }
114
- },
115
- coalesce(fn) {
116
- const wasCoalescing = inCoalesce;
117
- const wasBatching = inBatch;
118
- inCoalesce = true;
119
- inBatch = true;
120
- try {
121
- fn();
122
- } finally {
123
- inCoalesce = wasCoalescing;
124
- inBatch = wasBatching;
125
- if (!wasCoalescing) {
126
- flushCoalescedUpdates();
127
- }
128
- if (!inBatch) {
129
- scheduleNotification();
130
- }
131
- }
132
- },
133
- hasPendingNotifications() {
134
- return notificationPending;
135
- },
136
- flushNotifications() {
137
- flushNotificationsInternal();
138
- }
139
- };
140
- const originalTreeCall = tree.bind(tree);
141
- const enhancedTree = function (...args) {
142
- if (args.length === 0) {
143
- return originalTreeCall();
144
- } else {
145
- if (args.length === 1) {
146
- const arg = args[0];
147
- if (typeof arg === 'function') {
148
- originalTreeCall(arg);
149
- } else {
150
- originalTreeCall(arg);
151
- }
152
- }
153
- if (!inBatch) {
154
- scheduleNotification();
155
- }
156
- }
157
- };
158
- Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
159
- Object.assign(enhancedTree, tree);
160
- try {
161
- copyTreeProperties(tree, enhancedTree);
162
- } catch {}
163
- Object.defineProperty(enhancedTree, 'with', {
164
- value: function (enhancer) {
165
- if (typeof enhancer !== 'function') {
166
- throw new Error('Enhancer must be a function');
167
- }
168
- return enhancer(enhancedTree);
169
- },
170
- writable: false,
171
- enumerable: false,
172
- configurable: true
173
- });
174
- if ('state' in tree) {
175
- Object.defineProperty(enhancedTree, 'state', {
176
- value: tree.state,
177
- enumerable: false,
178
- configurable: true
179
- });
180
- }
181
- if ('$' in tree) {
182
- Object.defineProperty(enhancedTree, '$', {
183
- value: tree.$,
184
- enumerable: false,
185
- configurable: true
186
- });
187
- }
188
- Object.assign(enhancedTree, batchingMethods);
189
- enhancedTree.batchUpdate = updater => {
190
- enhancedTree.batch(() => {
191
- const current = originalTreeCall();
192
- const updates = updater(current);
193
- Object.entries(updates).forEach(([key, value]) => {
194
- const property = enhancedTree.state[key];
195
- if (property && typeof property.set === 'function') {
196
- property.set(value);
197
- } else if (typeof property === 'function') {
198
- property(value);
199
- }
200
- });
201
- });
202
- };
203
- return enhancedTree;
204
- };
205
- }
206
- function highPerformanceBatching() {
207
- return batching({
208
- enabled: true,
209
- notificationDelayMs: 0
210
- });
211
- }
212
- function batchingWithConfig(config = {}) {
213
- return batching(config);
214
- }
215
- function flushBatchedUpdates() {
216
- console.warn('[SignalTree] flushBatchedUpdates() is deprecated. Use tree.flushNotifications() instead.');
217
- }
218
- function hasPendingUpdates() {
219
- console.warn('[SignalTree] hasPendingUpdates() is deprecated. Use tree.hasPendingNotifications() instead.');
220
- return false;
221
- }
222
- function getBatchQueueSize() {
223
- console.warn('[SignalTree] getBatchQueueSize() is deprecated. Signal writes are now synchronous.');
224
- return 0;
225
- }
226
- Object.assign((config = {}) => batching(config), {
227
- highPerformance: highPerformanceBatching
228
- });
229
-
230
- export { batching, batchingWithConfig, flushBatchedUpdates, getBatchQueueSize, hasPendingUpdates, highPerformanceBatching };
@@ -1,318 +0,0 @@
1
- import { signal } from '@angular/core';
2
- import { copyTreeProperties } from '../utils/copy-tree-properties.js';
3
-
4
- function createActivityTracker() {
5
- const modules = new Map();
6
- return {
7
- trackMethodCall: (module, method, duration) => {
8
- const existing = modules.get(module);
9
- if (existing) {
10
- existing.lastActivity = new Date();
11
- existing.operationCount++;
12
- existing.averageExecutionTime = (existing.averageExecutionTime * (existing.operationCount - 1) + duration) / existing.operationCount;
13
- } else {
14
- modules.set(module, {
15
- name: module,
16
- methods: [method],
17
- addedAt: new Date(),
18
- lastActivity: new Date(),
19
- operationCount: 1,
20
- averageExecutionTime: duration,
21
- errorCount: 0
22
- });
23
- }
24
- },
25
- trackError: (module, error, context) => {
26
- const existing = modules.get(module);
27
- if (existing) {
28
- existing.errorCount++;
29
- }
30
- console.error(`❌ [${module}] Error${context ? ` in ${context}` : ''}:`, error);
31
- },
32
- getModuleActivity: module => modules.get(module),
33
- getAllModules: () => Array.from(modules.values())
34
- };
35
- }
36
- function createCompositionLogger() {
37
- const logs = [];
38
- const addLog = (module, type, data) => {
39
- logs.push({
40
- timestamp: new Date(),
41
- module,
42
- type,
43
- data
44
- });
45
- if (logs.length > 1000) {
46
- logs.splice(0, logs.length - 1000);
47
- }
48
- };
49
- return {
50
- logComposition: (modules, action) => {
51
- addLog('core', 'composition', {
52
- modules,
53
- action
54
- });
55
- console.log(`🔗 Composition ${action}:`, modules.join(' → '));
56
- },
57
- logMethodExecution: (module, method, args, result) => {
58
- addLog(module, 'method', {
59
- method,
60
- args,
61
- result
62
- });
63
- console.debug(`🔧 [${module}] ${method}`, {
64
- args,
65
- result
66
- });
67
- },
68
- logStateChange: (module, path, oldValue, newValue) => {
69
- addLog(module, 'state', {
70
- path,
71
- oldValue,
72
- newValue
73
- });
74
- console.debug(`📝 [${module}] State change at ${path}:`, {
75
- from: oldValue,
76
- to: newValue
77
- });
78
- },
79
- logPerformanceWarning: (module, operation, duration, threshold) => {
80
- addLog(module, 'performance', {
81
- operation,
82
- duration,
83
- threshold
84
- });
85
- console.warn(`⚠️ [${module}] Slow ${operation}: ${duration.toFixed(2)}ms (threshold: ${threshold}ms)`);
86
- },
87
- exportLogs: () => [...logs]
88
- };
89
- }
90
- function createNoopLogger() {
91
- return {
92
- logComposition: () => {},
93
- logMethodExecution: () => {},
94
- logStateChange: () => {},
95
- logPerformanceWarning: () => {},
96
- exportLogs: () => []
97
- };
98
- }
99
- function createModularMetrics() {
100
- const metricsSignal = signal({
101
- totalUpdates: 0,
102
- moduleUpdates: {},
103
- modulePerformance: {},
104
- compositionChain: [],
105
- signalGrowth: {},
106
- memoryDelta: {},
107
- moduleCacheStats: {}
108
- });
109
- return {
110
- signal: metricsSignal.asReadonly(),
111
- updateMetrics: updates => {
112
- metricsSignal.update(current => ({
113
- ...current,
114
- ...updates
115
- }));
116
- },
117
- trackModuleUpdate: (module, duration) => {
118
- metricsSignal.update(current => ({
119
- ...current,
120
- totalUpdates: current.totalUpdates + 1,
121
- moduleUpdates: {
122
- ...current.moduleUpdates,
123
- [module]: (current.moduleUpdates[module] || 0) + 1
124
- },
125
- modulePerformance: {
126
- ...current.modulePerformance,
127
- [module]: duration
128
- }
129
- }));
130
- }
131
- };
132
- }
133
- function devTools(config = {}) {
134
- const {
135
- enabled = true,
136
- treeName = 'SignalTree',
137
- name,
138
- enableBrowserDevTools = true,
139
- enableLogging = true,
140
- performanceThreshold = 16
141
- } = config;
142
- const displayName = name ?? treeName;
143
- return tree => {
144
- if (!enabled) {
145
- const noopMethods = {
146
- connectDevTools() {},
147
- disconnectDevTools() {}
148
- };
149
- return Object.assign(tree, noopMethods);
150
- }
151
- const activityTracker = createActivityTracker();
152
- const logger = enableLogging ? createCompositionLogger() : createNoopLogger();
153
- const metrics = createModularMetrics();
154
- const compositionHistory = [];
155
- const activeProfiles = new Map();
156
- let browserDevTools = null;
157
- const initBrowserDevTools = () => {
158
- if (!enableBrowserDevTools || typeof window === 'undefined' || !('__REDUX_DEVTOOLS_EXTENSION__' in window)) {
159
- return;
160
- }
161
- try {
162
- const devToolsExt = window['__REDUX_DEVTOOLS_EXTENSION__'];
163
- const connection = devToolsExt.connect({
164
- name: displayName,
165
- features: {
166
- dispatch: true,
167
- jump: true,
168
- skip: true
169
- }
170
- });
171
- browserDevTools = {
172
- send: connection.send
173
- };
174
- browserDevTools.send('@@INIT', tree());
175
- console.log(`🔗 Connected to Redux DevTools as "${displayName}"`);
176
- } catch (e) {
177
- console.warn('[SignalTree] Failed to connect to Redux DevTools:', e);
178
- }
179
- };
180
- const originalTreeCall = tree.bind(tree);
181
- const enhancedTree = function (...args) {
182
- if (args.length === 0) {
183
- return originalTreeCall();
184
- }
185
- const startTime = performance.now();
186
- let result;
187
- if (args.length === 1) {
188
- const arg = args[0];
189
- if (typeof arg === 'function') {
190
- result = originalTreeCall(arg);
191
- } else {
192
- result = originalTreeCall(arg);
193
- }
194
- }
195
- const duration = performance.now() - startTime;
196
- const newState = originalTreeCall();
197
- metrics.trackModuleUpdate('core', duration);
198
- if (duration > performanceThreshold) {
199
- logger.logPerformanceWarning('core', 'update', duration, performanceThreshold);
200
- }
201
- if (browserDevTools) {
202
- browserDevTools.send('UPDATE', newState);
203
- }
204
- return result;
205
- };
206
- Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
207
- copyTreeProperties(tree, enhancedTree);
208
- Object.defineProperty(enhancedTree, 'with', {
209
- value: function (enhancer) {
210
- if (typeof enhancer !== 'function') {
211
- throw new Error('Enhancer must be a function');
212
- }
213
- return enhancer(enhancedTree);
214
- },
215
- writable: false,
216
- enumerable: false,
217
- configurable: true
218
- });
219
- if ('state' in tree) {
220
- Object.defineProperty(enhancedTree, 'state', {
221
- value: tree.state,
222
- enumerable: false,
223
- configurable: true
224
- });
225
- }
226
- if ('$' in tree) {
227
- Object.defineProperty(enhancedTree, '$', {
228
- value: tree.$,
229
- enumerable: false,
230
- configurable: true
231
- });
232
- }
233
- const devToolsInterface = {
234
- activityTracker,
235
- logger,
236
- metrics: metrics.signal,
237
- trackComposition: modules => {
238
- compositionHistory.push({
239
- timestamp: new Date(),
240
- chain: [...modules]
241
- });
242
- metrics.updateMetrics({
243
- compositionChain: modules
244
- });
245
- logger.logComposition(modules, 'with');
246
- },
247
- startModuleProfiling: module => {
248
- const profileId = `${module}_${Date.now()}`;
249
- activeProfiles.set(profileId, {
250
- module,
251
- operation: 'profile',
252
- startTime: performance.now()
253
- });
254
- return profileId;
255
- },
256
- endModuleProfiling: profileId => {
257
- const profile = activeProfiles.get(profileId);
258
- if (profile) {
259
- const duration = performance.now() - profile.startTime;
260
- activityTracker.trackMethodCall(profile.module, profile.operation, duration);
261
- activeProfiles.delete(profileId);
262
- }
263
- },
264
- connectDevTools: name => {
265
- if (browserDevTools) {
266
- browserDevTools.send('@@INIT', originalTreeCall());
267
- console.log(`🔗 Connected to Redux DevTools as "${name}"`);
268
- }
269
- },
270
- exportDebugSession: () => ({
271
- metrics: metrics.signal(),
272
- modules: activityTracker.getAllModules(),
273
- logs: logger.exportLogs(),
274
- compositionHistory: [...compositionHistory]
275
- })
276
- };
277
- const methods = {
278
- connectDevTools() {
279
- initBrowserDevTools();
280
- },
281
- disconnectDevTools() {
282
- browserDevTools = null;
283
- }
284
- };
285
- enhancedTree['__devTools'] = devToolsInterface;
286
- return Object.assign(enhancedTree, methods);
287
- };
288
- }
289
- function enableDevTools(treeName = 'SignalTree') {
290
- return devTools({
291
- treeName,
292
- enabled: true
293
- });
294
- }
295
- function fullDevTools(treeName = 'SignalTree') {
296
- return devTools({
297
- treeName,
298
- enabled: true,
299
- enableBrowserDevTools: true,
300
- enableLogging: true,
301
- performanceThreshold: 10
302
- });
303
- }
304
- function productionDevTools() {
305
- return devTools({
306
- enabled: true,
307
- enableBrowserDevTools: false,
308
- enableLogging: false,
309
- performanceThreshold: 50
310
- });
311
- }
312
- Object.assign(devTools, {
313
- production: productionDevTools,
314
- full: fullDevTools,
315
- enable: enableDevTools
316
- });
317
-
318
- export { devTools, enableDevTools, fullDevTools, productionDevTools };