@signaltree/core 6.0.0 → 6.0.1

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 (78) hide show
  1. package/README.md +76 -76
  2. package/dist/constants.js +6 -0
  3. package/dist/deep-equal.js +41 -0
  4. package/dist/enhancers/batching/batching.js +189 -0
  5. package/dist/enhancers/devtools/devtools.js +306 -0
  6. package/dist/enhancers/effects/effects.js +66 -0
  7. package/dist/enhancers/entities/entities.js +51 -0
  8. package/dist/enhancers/index.js +72 -0
  9. package/dist/enhancers/memoization/memoization.js +420 -0
  10. package/dist/enhancers/presets/lib/presets.js +27 -0
  11. package/dist/enhancers/serialization/constants.js +15 -0
  12. package/dist/enhancers/serialization/serialization.js +656 -0
  13. package/dist/enhancers/time-travel/time-travel.js +231 -0
  14. package/dist/enhancers/time-travel/utils.js +11 -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 +283 -0
  20. package/dist/lib/memory/memory-manager.js +164 -0
  21. package/dist/lib/path-notifier.js +106 -0
  22. package/dist/lib/presets.js +21 -0
  23. package/dist/lib/security/security-validator.js +121 -0
  24. package/dist/lib/signal-tree.js +277 -0
  25. package/dist/lib/types.js +9 -0
  26. package/dist/lib/utils.js +299 -0
  27. package/dist/lru-cache.js +64 -0
  28. package/dist/parse-path.js +13 -0
  29. package/package.json +1 -1
  30. package/src/enhancers/batching/batching.d.ts +11 -0
  31. package/src/enhancers/batching/batching.types.d.ts +1 -0
  32. package/src/enhancers/batching/index.d.ts +1 -0
  33. package/src/enhancers/batching/test-setup.d.ts +3 -0
  34. package/src/enhancers/devtools/devtools.d.ts +68 -0
  35. package/src/enhancers/devtools/devtools.types.d.ts +1 -0
  36. package/src/enhancers/devtools/index.d.ts +1 -0
  37. package/src/enhancers/devtools/test-setup.d.ts +3 -0
  38. package/src/enhancers/effects/effects.d.ts +9 -0
  39. package/src/enhancers/effects/effects.types.d.ts +1 -0
  40. package/src/enhancers/effects/index.d.ts +1 -0
  41. package/src/enhancers/entities/entities.d.ts +11 -0
  42. package/src/enhancers/entities/entities.types.d.ts +1 -0
  43. package/src/enhancers/entities/index.d.ts +1 -0
  44. package/src/enhancers/entities/test-setup.d.ts +3 -0
  45. package/src/enhancers/index.d.ts +3 -0
  46. package/src/enhancers/memoization/index.d.ts +1 -0
  47. package/src/enhancers/memoization/memoization.d.ts +54 -0
  48. package/src/enhancers/memoization/memoization.types.d.ts +1 -0
  49. package/src/enhancers/memoization/test-setup.d.ts +3 -0
  50. package/src/enhancers/presets/index.d.ts +1 -0
  51. package/src/enhancers/presets/lib/presets.d.ts +8 -0
  52. package/src/enhancers/serialization/constants.d.ts +14 -0
  53. package/src/enhancers/serialization/index.d.ts +2 -0
  54. package/src/enhancers/serialization/serialization.d.ts +68 -0
  55. package/src/enhancers/serialization/test-setup.d.ts +3 -0
  56. package/src/enhancers/test-helpers/types-equals.d.ts +2 -0
  57. package/src/enhancers/time-travel/index.d.ts +1 -0
  58. package/src/enhancers/time-travel/test-setup.d.ts +3 -0
  59. package/src/enhancers/time-travel/time-travel.d.ts +10 -0
  60. package/src/enhancers/time-travel/time-travel.types.d.ts +1 -0
  61. package/src/enhancers/time-travel/utils.d.ts +2 -0
  62. package/src/enhancers/types.d.ts +1 -0
  63. package/src/enhancers/typing/helpers-types.d.ts +2 -0
  64. package/src/index.d.ts +17 -0
  65. package/src/lib/async-helpers.d.ts +8 -0
  66. package/src/lib/constants.d.ts +41 -0
  67. package/src/lib/dev-proxy.d.ts +3 -0
  68. package/src/lib/entity-signal.d.ts +1 -0
  69. package/src/lib/memory/memory-manager.d.ts +30 -0
  70. package/src/lib/path-notifier.d.ts +4 -0
  71. package/src/lib/performance/diff-engine.d.ts +33 -0
  72. package/src/lib/performance/path-index.d.ts +25 -0
  73. package/src/lib/performance/update-engine.d.ts +32 -0
  74. package/src/lib/presets.d.ts +34 -0
  75. package/src/lib/security/security-validator.d.ts +33 -0
  76. package/src/lib/signal-tree.d.ts +3 -0
  77. package/src/lib/types.d.ts +301 -0
  78. package/src/lib/utils.d.ts +32 -0
@@ -0,0 +1,306 @@
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 createNoopLogger() {
90
+ return {
91
+ logComposition: () => {},
92
+ logMethodExecution: () => {},
93
+ logStateChange: () => {},
94
+ logPerformanceWarning: () => {},
95
+ exportLogs: () => []
96
+ };
97
+ }
98
+ function createModularMetrics() {
99
+ const metricsSignal = signal({
100
+ totalUpdates: 0,
101
+ moduleUpdates: {},
102
+ modulePerformance: {},
103
+ compositionChain: [],
104
+ signalGrowth: {},
105
+ memoryDelta: {},
106
+ moduleCacheStats: {}
107
+ });
108
+ return {
109
+ signal: metricsSignal.asReadonly(),
110
+ updateMetrics: updates => {
111
+ metricsSignal.update(current => ({
112
+ ...current,
113
+ ...updates
114
+ }));
115
+ },
116
+ trackModuleUpdate: (module, duration) => {
117
+ metricsSignal.update(current => ({
118
+ ...current,
119
+ totalUpdates: current.totalUpdates + 1,
120
+ moduleUpdates: {
121
+ ...current.moduleUpdates,
122
+ [module]: (current.moduleUpdates[module] || 0) + 1
123
+ },
124
+ modulePerformance: {
125
+ ...current.modulePerformance,
126
+ [module]: duration
127
+ }
128
+ }));
129
+ }
130
+ };
131
+ }
132
+ function devTools(config = {}) {
133
+ const {
134
+ enabled = true,
135
+ treeName = 'SignalTree',
136
+ name,
137
+ enableBrowserDevTools = true,
138
+ enableLogging = true,
139
+ performanceThreshold = 16
140
+ } = config;
141
+ const displayName = name ?? treeName;
142
+ return tree => {
143
+ if (!enabled) {
144
+ const noopMethods = {
145
+ connectDevTools() {},
146
+ disconnectDevTools() {}
147
+ };
148
+ return Object.assign(tree, noopMethods);
149
+ }
150
+ const activityTracker = createActivityTracker();
151
+ const logger = enableLogging ? createCompositionLogger() : createNoopLogger();
152
+ const metrics = createModularMetrics();
153
+ const compositionHistory = [];
154
+ const activeProfiles = new Map();
155
+ let browserDevTools = null;
156
+ const initBrowserDevTools = () => {
157
+ if (!enableBrowserDevTools || typeof window === 'undefined' || !('__REDUX_DEVTOOLS_EXTENSION__' in window)) {
158
+ return;
159
+ }
160
+ try {
161
+ const devToolsExt = window['__REDUX_DEVTOOLS_EXTENSION__'];
162
+ const connection = devToolsExt.connect({
163
+ name: displayName,
164
+ features: {
165
+ dispatch: true,
166
+ jump: true,
167
+ skip: true
168
+ }
169
+ });
170
+ browserDevTools = {
171
+ send: connection.send
172
+ };
173
+ browserDevTools.send('@@INIT', tree());
174
+ console.log(`🔗 Connected to Redux DevTools as "${displayName}"`);
175
+ } catch (e) {
176
+ console.warn('[SignalTree] Failed to connect to Redux DevTools:', e);
177
+ }
178
+ };
179
+ const originalTreeCall = tree.bind(tree);
180
+ const enhancedTree = function (...args) {
181
+ if (args.length === 0) {
182
+ return originalTreeCall();
183
+ }
184
+ const startTime = performance.now();
185
+ let result;
186
+ if (args.length === 1) {
187
+ const arg = args[0];
188
+ if (typeof arg === 'function') {
189
+ result = originalTreeCall(arg);
190
+ } else {
191
+ result = originalTreeCall(arg);
192
+ }
193
+ }
194
+ const duration = performance.now() - startTime;
195
+ const newState = originalTreeCall();
196
+ metrics.trackModuleUpdate('core', duration);
197
+ if (duration > performanceThreshold) {
198
+ logger.logPerformanceWarning('core', 'update', duration, performanceThreshold);
199
+ }
200
+ if (browserDevTools) {
201
+ browserDevTools.send('UPDATE', newState);
202
+ }
203
+ return result;
204
+ };
205
+ Object.setPrototypeOf(enhancedTree, Object.getPrototypeOf(tree));
206
+ Object.assign(enhancedTree, tree);
207
+ if ('state' in tree) {
208
+ Object.defineProperty(enhancedTree, 'state', {
209
+ value: tree.state,
210
+ enumerable: false,
211
+ configurable: true
212
+ });
213
+ }
214
+ if ('$' in tree) {
215
+ Object.defineProperty(enhancedTree, '$', {
216
+ value: tree.$,
217
+ enumerable: false,
218
+ configurable: true
219
+ });
220
+ }
221
+ const devToolsInterface = {
222
+ activityTracker,
223
+ logger,
224
+ metrics: metrics.signal,
225
+ trackComposition: modules => {
226
+ compositionHistory.push({
227
+ timestamp: new Date(),
228
+ chain: [...modules]
229
+ });
230
+ metrics.updateMetrics({
231
+ compositionChain: modules
232
+ });
233
+ logger.logComposition(modules, 'with');
234
+ },
235
+ startModuleProfiling: module => {
236
+ const profileId = `${module}_${Date.now()}`;
237
+ activeProfiles.set(profileId, {
238
+ module,
239
+ operation: 'profile',
240
+ startTime: performance.now()
241
+ });
242
+ return profileId;
243
+ },
244
+ endModuleProfiling: profileId => {
245
+ const profile = activeProfiles.get(profileId);
246
+ if (profile) {
247
+ const duration = performance.now() - profile.startTime;
248
+ activityTracker.trackMethodCall(profile.module, profile.operation, duration);
249
+ activeProfiles.delete(profileId);
250
+ }
251
+ },
252
+ connectDevTools: name => {
253
+ if (browserDevTools) {
254
+ browserDevTools.send('@@INIT', originalTreeCall());
255
+ console.log(`🔗 Connected to Redux DevTools as "${name}"`);
256
+ }
257
+ },
258
+ exportDebugSession: () => ({
259
+ metrics: metrics.signal(),
260
+ modules: activityTracker.getAllModules(),
261
+ logs: logger.exportLogs(),
262
+ compositionHistory: [...compositionHistory]
263
+ })
264
+ };
265
+ const methods = {
266
+ connectDevTools() {
267
+ initBrowserDevTools();
268
+ },
269
+ disconnectDevTools() {
270
+ browserDevTools = null;
271
+ }
272
+ };
273
+ enhancedTree['__devTools'] = devToolsInterface;
274
+ return Object.assign(enhancedTree, methods);
275
+ };
276
+ }
277
+ function enableDevTools(treeName = 'SignalTree') {
278
+ return devTools({
279
+ treeName,
280
+ enabled: true
281
+ });
282
+ }
283
+ function fullDevTools(treeName = 'SignalTree') {
284
+ return devTools({
285
+ treeName,
286
+ enabled: true,
287
+ enableBrowserDevTools: true,
288
+ enableLogging: true,
289
+ performanceThreshold: 10
290
+ });
291
+ }
292
+ function productionDevTools() {
293
+ return devTools({
294
+ enabled: true,
295
+ enableBrowserDevTools: false,
296
+ enableLogging: false,
297
+ performanceThreshold: 50
298
+ });
299
+ }
300
+ Object.assign(devTools, {
301
+ production: productionDevTools,
302
+ full: fullDevTools,
303
+ enable: enableDevTools
304
+ });
305
+
306
+ export { devTools, enableDevTools, fullDevTools, productionDevTools };
@@ -0,0 +1,66 @@
1
+ import { effect, untracked } from '@angular/core';
2
+
3
+ function effects(config = {}) {
4
+ const {
5
+ enabled = true
6
+ } = config;
7
+ return tree => {
8
+ const cleanupFns = [];
9
+ const methods = {
10
+ effect(effectFn) {
11
+ if (!enabled) {
12
+ return () => {};
13
+ }
14
+ let innerCleanup;
15
+ const effectRef = effect(() => {
16
+ const state = tree();
17
+ if (innerCleanup) {
18
+ untracked(() => innerCleanup());
19
+ }
20
+ innerCleanup = untracked(() => effectFn(state));
21
+ });
22
+ const cleanup = () => {
23
+ if (innerCleanup) {
24
+ innerCleanup();
25
+ }
26
+ effectRef.destroy();
27
+ };
28
+ cleanupFns.push(cleanup);
29
+ return cleanup;
30
+ },
31
+ subscribe(fn) {
32
+ if (!enabled) {
33
+ return () => {};
34
+ }
35
+ const effectRef = effect(() => {
36
+ const state = tree();
37
+ untracked(() => fn(state));
38
+ });
39
+ const cleanup = () => {
40
+ effectRef.destroy();
41
+ };
42
+ cleanupFns.push(cleanup);
43
+ return cleanup;
44
+ }
45
+ };
46
+ const originalDestroy = tree.destroy?.bind(tree);
47
+ tree.destroy = () => {
48
+ cleanupFns.forEach(fn => fn());
49
+ cleanupFns.length = 0;
50
+ if (originalDestroy) {
51
+ originalDestroy();
52
+ }
53
+ };
54
+ return Object.assign(tree, methods);
55
+ };
56
+ }
57
+ function enableEffects() {
58
+ return effects({
59
+ enabled: true
60
+ });
61
+ }
62
+ Object.assign((config = {}) => effects(config), {
63
+ enable: enableEffects
64
+ });
65
+
66
+ export { effects, enableEffects };
@@ -0,0 +1,51 @@
1
+ import { createEntitySignal } from '../../lib/entity-signal.js';
2
+ import { getPathNotifier } from '../../lib/path-notifier.js';
3
+
4
+ function isEntityMapMarker(value) {
5
+ return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
6
+ }
7
+ function entities(config = {}) {
8
+ const {
9
+ enabled = true
10
+ } = config;
11
+ return tree => {
12
+ if (!enabled) {
13
+ tree.__entitiesEnabled = true;
14
+ return tree;
15
+ }
16
+ const notifier = getPathNotifier();
17
+ function materialize(node, path = []) {
18
+ if (!node || typeof node !== 'object') return;
19
+ for (const [k, v] of Object.entries(node)) {
20
+ if (isEntityMapMarker(v)) {
21
+ const cfg = v.__entityMapConfig ?? {};
22
+ const sig = createEntitySignal(cfg, notifier, path.concat(k).join('.'));
23
+ try {
24
+ node[k] = sig;
25
+ } catch {}
26
+ try {
27
+ tree[k] = sig;
28
+ } catch {}
29
+ } else if (typeof v === 'object' && v !== null && !Array.isArray(v)) {
30
+ materialize(v, path.concat(k));
31
+ }
32
+ }
33
+ }
34
+ materialize(tree.state);
35
+ materialize(tree.$);
36
+ tree.__entitiesEnabled = true;
37
+ return tree;
38
+ };
39
+ }
40
+ function enableEntities() {
41
+ return entities();
42
+ }
43
+ function highPerformanceEntities() {
44
+ return entities();
45
+ }
46
+ Object.assign(entities, {
47
+ highPerformance: highPerformanceEntities,
48
+ enable: enableEntities
49
+ });
50
+
51
+ export { enableEntities, entities, highPerformanceEntities };
@@ -0,0 +1,72 @@
1
+ import { SIGNAL_TREE_MESSAGES } from '../lib/constants.js';
2
+ import { ENHANCER_META } from '../lib/types.js';
3
+
4
+ function createEnhancer(meta, enhancerFn) {
5
+ const fn = enhancerFn;
6
+ try {
7
+ fn.metadata = meta;
8
+ try {
9
+ fn[ENHANCER_META] = meta;
10
+ } catch {}
11
+ } catch {}
12
+ return fn;
13
+ }
14
+ function resolveEnhancerOrder(enhancers, availableCapabilities = new Set(), debugMode = false) {
15
+ const nodes = enhancers.map((e, idx) => ({
16
+ fn: e,
17
+ name: e.metadata && e.metadata.name ? String(e.metadata.name) : `enhancer#${idx}`,
18
+ requires: new Set(e.metadata?.requires ?? []),
19
+ provides: new Set(e.metadata?.provides ?? [])
20
+ }));
21
+ const adj = new Map();
22
+ const nameToNode = new Map();
23
+ for (const n of nodes) {
24
+ nameToNode.set(n.name, n);
25
+ adj.set(n.name, new Set());
26
+ }
27
+ for (const a of nodes) {
28
+ for (const b of nodes) {
29
+ if (a === b) continue;
30
+ for (const req of b.requires) {
31
+ if (availableCapabilities.has(req)) continue;
32
+ if (a.provides.has(req)) {
33
+ const set = adj.get(a.name);
34
+ if (set) set.add(b.name);
35
+ }
36
+ }
37
+ }
38
+ }
39
+ const inDegree = new Map();
40
+ for (const entry of adj.keys()) inDegree.set(entry, 0);
41
+ for (const [, outs] of adj) {
42
+ for (const to of outs) inDegree.set(to, (inDegree.get(to) || 0) + 1);
43
+ }
44
+ const queue = [];
45
+ for (const [name, deg] of inDegree.entries()) {
46
+ if (deg === 0) queue.push(name);
47
+ }
48
+ const ordered = [];
49
+ while (queue.length > 0) {
50
+ const n = queue.shift();
51
+ if (!n) break;
52
+ ordered.push(n);
53
+ const outs = adj.get(n);
54
+ if (!outs) continue;
55
+ for (const m of outs) {
56
+ inDegree.set(m, (inDegree.get(m) || 0) - 1);
57
+ if (inDegree.get(m) === 0) queue.push(m);
58
+ }
59
+ }
60
+ if (ordered.length !== nodes.length) {
61
+ if (debugMode) {
62
+ console.warn(SIGNAL_TREE_MESSAGES.ENHANCER_CYCLE_DETECTED);
63
+ }
64
+ return enhancers;
65
+ }
66
+ return ordered.map(name => {
67
+ const n = nameToNode.get(name);
68
+ return n ? n.fn : enhancers[0];
69
+ });
70
+ }
71
+
72
+ export { createEnhancer, resolveEnhancerOrder };