@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
@@ -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 };
@@ -0,0 +1,93 @@
1
+ import { EntitySignalImpl } from '../../../lib/entity-signal.js';
2
+ import { getPathNotifier } from '../../../lib/path-notifier.js';
3
+ import { isNodeAccessor } from '../../../lib/utils.js';
4
+
5
+ function isEntityMapMarker(value) {
6
+ return Boolean(value && typeof value === 'object' && value['__isEntityMap'] === true);
7
+ }
8
+ function isEntitySignal(value) {
9
+ return !!value && typeof value === 'object' && typeof value['addOne'] === 'function' && 'all' in value;
10
+ }
11
+ function materializeEntities(tree, notifier = getPathNotifier()) {
12
+ const registry = new Map();
13
+ const state = tree.state;
14
+ const visit = (parent, key, value, path) => {
15
+ const nextPath = [...path, key];
16
+ if (isEntityMapMarker(value)) {
17
+ const basePath = nextPath.join('.');
18
+ const config = value.__entityMapConfig ?? {};
19
+ const entitySignal = new EntitySignalImpl(config, notifier, basePath);
20
+ if (parent) {
21
+ try {
22
+ parent[key] = entitySignal;
23
+ } catch {}
24
+ }
25
+ try {
26
+ tree[key] = entitySignal;
27
+ } catch {}
28
+ registry.set(basePath, entitySignal);
29
+ return;
30
+ }
31
+ if (isNodeAccessor(value)) {
32
+ const nodeAsAny = value;
33
+ for (const childKey of Object.keys(nodeAsAny)) {
34
+ visit(nodeAsAny, childKey, nodeAsAny[childKey], nextPath);
35
+ }
36
+ return;
37
+ }
38
+ if (value && typeof value === 'object') {
39
+ for (const childKey of Object.keys(value)) {
40
+ visit(value, childKey, value[childKey], nextPath);
41
+ }
42
+ }
43
+ };
44
+ for (const key of Object.keys(state)) {
45
+ visit(state, key, state[key], []);
46
+ }
47
+ return registry;
48
+ }
49
+ function resolveEntitySignal(tree, registry, path) {
50
+ const pathStr = String(path);
51
+ const existing = registry.get(pathStr);
52
+ if (existing) return existing;
53
+ const segments = pathStr.split('.');
54
+ let current = tree.state;
55
+ for (const segment of segments) {
56
+ if (!current) break;
57
+ current = current[segment];
58
+ }
59
+ if (isEntitySignal(current)) {
60
+ registry.set(pathStr, current);
61
+ return current;
62
+ }
63
+ throw new Error(`Entity path '${pathStr}' is not configured. Define it with entityMap() in your initial state.`);
64
+ }
65
+ function withEntities(config = {}) {
66
+ const {
67
+ enabled = true
68
+ } = config;
69
+ return function enhanceWithEntities(tree) {
70
+ if (!enabled) {
71
+ return tree;
72
+ }
73
+ const registry = materializeEntities(tree);
74
+ const enhancedTree = Object.assign(tree, {
75
+ entities(path) {
76
+ return resolveEntitySignal(tree, registry, path);
77
+ }
78
+ });
79
+ return enhancedTree;
80
+ };
81
+ }
82
+ function enableEntities() {
83
+ return withEntities({
84
+ enabled: true
85
+ });
86
+ }
87
+ function withHighPerformanceEntities() {
88
+ return withEntities({
89
+ enabled: true
90
+ });
91
+ }
92
+
93
+ export { enableEntities, withEntities, withHighPerformanceEntities };
@@ -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 };