@signaltree/core 4.0.16 → 4.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.
Files changed (81) hide show
  1. package/README.md +27 -0
  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 +115 -0
  9. package/dist/enhancers/index.js +72 -0
  10. package/dist/enhancers/memoization/lib/memoization.js +379 -0
  11. package/dist/enhancers/middleware/lib/async-helpers.js +77 -0
  12. package/dist/enhancers/middleware/lib/middleware.js +136 -0
  13. package/dist/enhancers/presets/lib/presets.js +88 -0
  14. package/dist/enhancers/serialization/constants.js +15 -0
  15. package/dist/enhancers/serialization/lib/serialization.js +660 -0
  16. package/dist/enhancers/time-travel/lib/time-travel.js +193 -0
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +19 -0
  19. package/dist/is-built-in-object.js +23 -0
  20. package/dist/lib/constants.js +57 -0
  21. package/dist/lib/memory/memory-manager.js +164 -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 +631 -0
  27. package/dist/lib/types.js +3 -0
  28. package/dist/lib/utils.js +254 -0
  29. package/dist/lru-cache.js +64 -0
  30. package/dist/parse-path.js +13 -0
  31. package/package.json +30 -16
  32. package/src/enhancers/batching/index.d.ts +1 -0
  33. package/src/enhancers/batching/jest.config.d.ts +15 -0
  34. package/src/enhancers/batching/lib/batching.d.ts +16 -0
  35. package/src/enhancers/batching/test-setup.d.ts +1 -0
  36. package/src/enhancers/computed/index.d.ts +1 -0
  37. package/src/enhancers/computed/jest.config.d.ts +15 -0
  38. package/src/enhancers/computed/lib/computed.d.ts +12 -0
  39. package/src/enhancers/devtools/index.d.ts +1 -0
  40. package/src/enhancers/devtools/jest.config.d.ts +15 -0
  41. package/src/enhancers/devtools/lib/devtools.d.ts +77 -0
  42. package/src/enhancers/devtools/test-setup.d.ts +1 -0
  43. package/src/enhancers/entities/index.d.ts +1 -0
  44. package/src/enhancers/entities/jest.config.d.ts +15 -0
  45. package/src/enhancers/entities/lib/entities.d.ts +22 -0
  46. package/src/enhancers/entities/test-setup.d.ts +1 -0
  47. package/src/enhancers/index.d.ts +3 -0
  48. package/src/enhancers/memoization/index.d.ts +1 -0
  49. package/src/enhancers/memoization/jest.config.d.ts +15 -0
  50. package/src/enhancers/memoization/lib/memoization.d.ts +65 -0
  51. package/src/enhancers/memoization/test-setup.d.ts +1 -0
  52. package/src/enhancers/middleware/index.d.ts +2 -0
  53. package/src/enhancers/middleware/jest.config.d.ts +15 -0
  54. package/src/enhancers/middleware/lib/async-helpers.d.ts +8 -0
  55. package/src/enhancers/middleware/lib/middleware.d.ts +11 -0
  56. package/src/enhancers/middleware/test-setup.d.ts +1 -0
  57. package/src/enhancers/presets/index.d.ts +1 -0
  58. package/src/enhancers/presets/jest.config.d.ts +15 -0
  59. package/src/enhancers/presets/lib/presets.d.ts +11 -0
  60. package/src/enhancers/presets/test-setup.d.ts +1 -0
  61. package/src/enhancers/serialization/constants.d.ts +14 -0
  62. package/src/enhancers/serialization/index.d.ts +2 -0
  63. package/src/enhancers/serialization/jest.config.d.ts +15 -0
  64. package/src/enhancers/serialization/lib/serialization.d.ts +59 -0
  65. package/src/enhancers/serialization/test-setup.d.ts +1 -0
  66. package/src/enhancers/time-travel/index.d.ts +1 -0
  67. package/src/enhancers/time-travel/jest.config.d.ts +15 -0
  68. package/src/enhancers/time-travel/lib/time-travel.d.ts +36 -0
  69. package/src/enhancers/time-travel/lib/utils.d.ts +1 -0
  70. package/src/enhancers/time-travel/test-setup.d.ts +1 -0
  71. package/src/enhancers/types.d.ts +105 -0
  72. package/src/index.d.ts +17 -0
  73. package/src/lib/constants.d.ts +42 -0
  74. package/src/lib/memory/memory-manager.d.ts +30 -0
  75. package/src/lib/performance/diff-engine.d.ts +33 -0
  76. package/src/lib/performance/path-index.d.ts +25 -0
  77. package/src/lib/performance/update-engine.d.ts +32 -0
  78. package/src/lib/security/security-validator.d.ts +33 -0
  79. package/src/lib/signal-tree.d.ts +8 -0
  80. package/src/lib/types.d.ts +164 -0
  81. package/src/lib/utils.d.ts +28 -0
package/README.md CHANGED
@@ -17,6 +17,33 @@ SignalTree Core is a lightweight package that provides:
17
17
  - Small API surface with zero-cost abstractions
18
18
  - Compact bundle size suited for production
19
19
 
20
+ ## Import guidance (tree-shaking)
21
+
22
+ Modern bundlers (webpack 5+, esbuild, Rollup, Vite) **automatically tree-shake barrel imports** from `@signaltree/core`. Both import styles produce identical bundle sizes:
23
+
24
+ ```ts
25
+ // ✅ Recommended: Simple and clean
26
+ import { signalTree, withBatching } from '@signaltree/core';
27
+
28
+ // ✅ Also fine: Explicit subpath (same bundle size)
29
+ import { signalTree } from '@signaltree/core';
30
+ import { withBatching } from '@signaltree/core/enhancers/batching';
31
+ ```
32
+
33
+ **Measured impact** (with modern bundlers):
34
+
35
+ - Core only: ~8.5 KB gzipped
36
+ - Core + batching: ~9.3 KB gzipped (barrel vs subpath: identical)
37
+ - Unused enhancers: **automatically excluded** by tree-shaking
38
+
39
+ **When to use subpath imports:**
40
+
41
+ - Older bundlers (webpack <5) with poor tree-shaking
42
+ - Explicit control over what gets included
43
+ - Personal/team preference for clarity
44
+
45
+ This repo's ESLint rule is **disabled by default** since testing confirms effective tree-shaking with barrel imports.
46
+
20
47
  ### Callable leaf signals (DX sugar only)
21
48
 
22
49
  SignalTree provides TypeScript support for callable syntax on leaf signals as developer experience sugar:
@@ -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 };
@@ -0,0 +1,321 @@
1
+ import { signal } from '@angular/core';
2
+
3
+ function createActivityTracker() {
4
+ const modules = new Map();
5
+ return {
6
+ trackMethodCall: (module, method, duration) => {
7
+ const existing = modules.get(module);
8
+ if (existing) {
9
+ existing.lastActivity = new Date();
10
+ existing.operationCount++;
11
+ existing.averageExecutionTime = (existing.averageExecutionTime * (existing.operationCount - 1) + duration) / existing.operationCount;
12
+ } else {
13
+ modules.set(module, {
14
+ name: module,
15
+ methods: [method],
16
+ addedAt: new Date(),
17
+ lastActivity: new Date(),
18
+ operationCount: 1,
19
+ averageExecutionTime: duration,
20
+ errorCount: 0
21
+ });
22
+ }
23
+ },
24
+ trackError: (module, error, context) => {
25
+ const existing = modules.get(module);
26
+ if (existing) {
27
+ existing.errorCount++;
28
+ }
29
+ console.error(`❌ [${module}] Error${context ? ` in ${context}` : ''}:`, error);
30
+ },
31
+ getModuleActivity: module => modules.get(module),
32
+ getAllModules: () => Array.from(modules.values())
33
+ };
34
+ }
35
+ function createCompositionLogger() {
36
+ const logs = [];
37
+ const addLog = (module, type, data) => {
38
+ logs.push({
39
+ timestamp: new Date(),
40
+ module,
41
+ type,
42
+ data
43
+ });
44
+ if (logs.length > 1000) {
45
+ logs.splice(0, logs.length - 1000);
46
+ }
47
+ };
48
+ return {
49
+ logComposition: (modules, action) => {
50
+ addLog('core', 'composition', {
51
+ modules,
52
+ action
53
+ });
54
+ console.log(`🔗 Composition ${action}:`, modules.join(' → '));
55
+ },
56
+ logMethodExecution: (module, method, args, result) => {
57
+ addLog(module, 'method', {
58
+ method,
59
+ args,
60
+ result
61
+ });
62
+ console.debug(`🔧 [${module}] ${method}`, {
63
+ args,
64
+ result
65
+ });
66
+ },
67
+ logStateChange: (module, path, oldValue, newValue) => {
68
+ addLog(module, 'state', {
69
+ path,
70
+ oldValue,
71
+ newValue
72
+ });
73
+ console.debug(`📝 [${module}] State change at ${path}:`, {
74
+ from: oldValue,
75
+ to: newValue
76
+ });
77
+ },
78
+ logPerformanceWarning: (module, operation, duration, threshold) => {
79
+ addLog(module, 'performance', {
80
+ operation,
81
+ duration,
82
+ threshold
83
+ });
84
+ console.warn(`⚠️ [${module}] Slow ${operation}: ${duration.toFixed(2)}ms (threshold: ${threshold}ms)`);
85
+ },
86
+ exportLogs: () => [...logs]
87
+ };
88
+ }
89
+ function createModularMetrics() {
90
+ const metricsSignal = signal({
91
+ totalUpdates: 0,
92
+ moduleUpdates: {},
93
+ modulePerformance: {},
94
+ compositionChain: [],
95
+ signalGrowth: {},
96
+ memoryDelta: {},
97
+ moduleCacheStats: {}
98
+ });
99
+ return {
100
+ signal: metricsSignal.asReadonly(),
101
+ updateMetrics: updates => {
102
+ metricsSignal.update(current => ({
103
+ ...current,
104
+ ...updates
105
+ }));
106
+ },
107
+ trackModuleUpdate: (module, duration) => {
108
+ metricsSignal.update(current => ({
109
+ ...current,
110
+ totalUpdates: current.totalUpdates + 1,
111
+ moduleUpdates: {
112
+ ...current.moduleUpdates,
113
+ [module]: (current.moduleUpdates[module] || 0) + 1
114
+ },
115
+ modulePerformance: {
116
+ ...current.modulePerformance,
117
+ [module]: duration
118
+ }
119
+ }));
120
+ }
121
+ };
122
+ }
123
+ function withDevTools(config = {}) {
124
+ const {
125
+ enabled = true,
126
+ treeName = 'ModularSignalTree',
127
+ enableBrowserDevTools = true,
128
+ enableLogging = true,
129
+ performanceThreshold = 16
130
+ } = config;
131
+ return tree => {
132
+ if (!enabled) {
133
+ const createNoopInterface = () => ({
134
+ activityTracker: {
135
+ trackMethodCall: () => undefined,
136
+ trackError: () => undefined,
137
+ getModuleActivity: () => undefined,
138
+ getAllModules: () => []
139
+ },
140
+ logger: {
141
+ logComposition: () => undefined,
142
+ logMethodExecution: () => undefined,
143
+ logStateChange: () => undefined,
144
+ logPerformanceWarning: () => undefined,
145
+ exportLogs: () => []
146
+ },
147
+ metrics: signal({
148
+ totalUpdates: 0,
149
+ moduleUpdates: {},
150
+ modulePerformance: {},
151
+ compositionChain: [],
152
+ signalGrowth: {},
153
+ memoryDelta: {},
154
+ moduleCacheStats: {}
155
+ }).asReadonly(),
156
+ trackComposition: () => undefined,
157
+ startModuleProfiling: () => '',
158
+ endModuleProfiling: () => undefined,
159
+ connectDevTools: () => undefined,
160
+ exportDebugSession: () => ({
161
+ metrics: {
162
+ totalUpdates: 0,
163
+ moduleUpdates: {},
164
+ modulePerformance: {},
165
+ compositionChain: [],
166
+ signalGrowth: {},
167
+ memoryDelta: {},
168
+ moduleCacheStats: {}
169
+ },
170
+ modules: [],
171
+ logs: [],
172
+ compositionHistory: []
173
+ })
174
+ });
175
+ return Object.assign(tree, {
176
+ __devTools: createNoopInterface()
177
+ });
178
+ }
179
+ const activityTracker = createActivityTracker();
180
+ const logger = enableLogging ? createCompositionLogger() : {
181
+ logComposition: () => undefined,
182
+ logMethodExecution: () => undefined,
183
+ logStateChange: () => undefined,
184
+ logPerformanceWarning: () => undefined,
185
+ exportLogs: () => []
186
+ };
187
+ const metrics = createModularMetrics();
188
+ const compositionHistory = [];
189
+ const activeProfiles = new Map();
190
+ let browserDevTools = null;
191
+ if (enableBrowserDevTools && typeof window !== 'undefined' && '__REDUX_DEVTOOLS_EXTENSION__' in window) {
192
+ const devToolsExt = window['__REDUX_DEVTOOLS_EXTENSION__'];
193
+ const connection = devToolsExt.connect({
194
+ name: treeName,
195
+ features: {
196
+ dispatch: true,
197
+ jump: true,
198
+ skip: true
199
+ }
200
+ });
201
+ browserDevTools = {
202
+ send: connection.send
203
+ };
204
+ }
205
+ const originalTreeCall = tree.bind(tree);
206
+ const enhancedTree = function (...args) {
207
+ if (args.length === 0) {
208
+ return originalTreeCall();
209
+ } else {
210
+ const startTime = performance.now();
211
+ let result;
212
+ if (args.length === 1) {
213
+ const arg = args[0];
214
+ if (typeof arg === 'function') {
215
+ result = originalTreeCall(arg);
216
+ } else {
217
+ result = originalTreeCall(arg);
218
+ }
219
+ }
220
+ const duration = performance.now() - startTime;
221
+ const newState = originalTreeCall();
222
+ metrics.trackModuleUpdate('core', duration);
223
+ if (duration > performanceThreshold) {
224
+ logger.logPerformanceWarning('core', 'update', duration, performanceThreshold);
225
+ }
226
+ if (browserDevTools) {
227
+ browserDevTools.send('UPDATE', newState);
228
+ }
229
+ return result;
230
+ }
231
+ };
232
+ Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
233
+ Object.assign(enhancedTree, tree);
234
+ if ('state' in tree) {
235
+ Object.defineProperty(enhancedTree, 'state', {
236
+ value: tree.state,
237
+ enumerable: false,
238
+ configurable: true
239
+ });
240
+ }
241
+ if ('$' in tree) {
242
+ Object.defineProperty(enhancedTree, '$', {
243
+ value: tree['$'],
244
+ enumerable: false,
245
+ configurable: true
246
+ });
247
+ }
248
+ const devToolsInterface = {
249
+ activityTracker,
250
+ logger,
251
+ metrics: metrics.signal,
252
+ trackComposition: modules => {
253
+ compositionHistory.push({
254
+ timestamp: new Date(),
255
+ chain: [...modules]
256
+ });
257
+ metrics.updateMetrics({
258
+ compositionChain: modules
259
+ });
260
+ logger.logComposition(modules, 'with');
261
+ },
262
+ startModuleProfiling: module => {
263
+ const profileId = `${module}_${Date.now()}`;
264
+ activeProfiles.set(profileId, {
265
+ module,
266
+ operation: 'profile',
267
+ startTime: performance.now()
268
+ });
269
+ return profileId;
270
+ },
271
+ endModuleProfiling: profileId => {
272
+ const profile = activeProfiles.get(profileId);
273
+ if (profile) {
274
+ const duration = performance.now() - profile.startTime;
275
+ activityTracker.trackMethodCall(profile.module, profile.operation, duration);
276
+ activeProfiles.delete(profileId);
277
+ }
278
+ },
279
+ connectDevTools: name => {
280
+ if (browserDevTools) {
281
+ browserDevTools.send('@@INIT', originalTreeCall());
282
+ console.log(`🔗 Connected to Redux DevTools as "${name}"`);
283
+ }
284
+ },
285
+ exportDebugSession: () => ({
286
+ metrics: metrics.signal(),
287
+ modules: activityTracker.getAllModules(),
288
+ logs: logger.exportLogs(),
289
+ compositionHistory: [...compositionHistory]
290
+ })
291
+ };
292
+ return Object.assign(enhancedTree, {
293
+ __devTools: devToolsInterface
294
+ });
295
+ };
296
+ }
297
+ function enableDevTools(treeName = 'SignalTree') {
298
+ return withDevTools({
299
+ treeName,
300
+ enabled: true
301
+ });
302
+ }
303
+ function withFullDevTools(treeName = 'SignalTree') {
304
+ return withDevTools({
305
+ treeName,
306
+ enabled: true,
307
+ enableBrowserDevTools: true,
308
+ enableLogging: true,
309
+ performanceThreshold: 10
310
+ });
311
+ }
312
+ function withProductionDevTools() {
313
+ return withDevTools({
314
+ enabled: true,
315
+ enableBrowserDevTools: false,
316
+ enableLogging: false,
317
+ performanceThreshold: 50
318
+ });
319
+ }
320
+
321
+ export { enableDevTools, withDevTools, withFullDevTools, withProductionDevTools };