@signaltree/core 7.1.2 → 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 (99) hide show
  1. package/README.md +138 -6
  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 -24
  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 -25
  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/materialize-markers.js +0 -72
  25. package/dist/lib/internals/merge-derived.js +0 -59
  26. package/dist/lib/markers/derived.js +0 -6
  27. package/dist/lib/markers/entity-map.js +0 -20
  28. package/dist/lib/markers/status.js +0 -71
  29. package/dist/lib/markers/stored.js +0 -111
  30. package/dist/lib/memory/memory-manager.js +0 -164
  31. package/dist/lib/path-notifier.js +0 -178
  32. package/dist/lib/presets.js +0 -21
  33. package/dist/lib/security/security-validator.js +0 -121
  34. package/dist/lib/signal-tree.js +0 -415
  35. package/dist/lib/types.js +0 -3
  36. package/dist/lib/utils.js +0 -264
  37. package/dist/lru-cache.js +0 -64
  38. package/dist/parse-path.js +0 -13
  39. package/src/enhancers/batching/batching.d.ts +0 -10
  40. package/src/enhancers/batching/batching.types.d.ts +0 -1
  41. package/src/enhancers/batching/index.d.ts +0 -1
  42. package/src/enhancers/batching/test-setup.d.ts +0 -3
  43. package/src/enhancers/devtools/devtools.d.ts +0 -68
  44. package/src/enhancers/devtools/devtools.types.d.ts +0 -1
  45. package/src/enhancers/devtools/index.d.ts +0 -1
  46. package/src/enhancers/devtools/test-setup.d.ts +0 -3
  47. package/src/enhancers/effects/effects.d.ts +0 -9
  48. package/src/enhancers/effects/effects.types.d.ts +0 -1
  49. package/src/enhancers/effects/index.d.ts +0 -1
  50. package/src/enhancers/entities/entities.d.ts +0 -11
  51. package/src/enhancers/entities/entities.types.d.ts +0 -1
  52. package/src/enhancers/entities/index.d.ts +0 -1
  53. package/src/enhancers/entities/test-setup.d.ts +0 -3
  54. package/src/enhancers/index.d.ts +0 -3
  55. package/src/enhancers/memoization/index.d.ts +0 -1
  56. package/src/enhancers/memoization/memoization.d.ts +0 -54
  57. package/src/enhancers/memoization/memoization.types.d.ts +0 -1
  58. package/src/enhancers/memoization/test-setup.d.ts +0 -3
  59. package/src/enhancers/presets/index.d.ts +0 -1
  60. package/src/enhancers/presets/lib/presets.d.ts +0 -8
  61. package/src/enhancers/serialization/constants.d.ts +0 -14
  62. package/src/enhancers/serialization/index.d.ts +0 -2
  63. package/src/enhancers/serialization/serialization.d.ts +0 -68
  64. package/src/enhancers/serialization/test-setup.d.ts +0 -3
  65. package/src/enhancers/test-helpers/types-equals.d.ts +0 -2
  66. package/src/enhancers/time-travel/index.d.ts +0 -1
  67. package/src/enhancers/time-travel/test-setup.d.ts +0 -3
  68. package/src/enhancers/time-travel/time-travel.d.ts +0 -10
  69. package/src/enhancers/time-travel/time-travel.types.d.ts +0 -1
  70. package/src/enhancers/time-travel/utils.d.ts +0 -2
  71. package/src/enhancers/types.d.ts +0 -1
  72. package/src/enhancers/typing/helpers-types.d.ts +0 -2
  73. package/src/enhancers/utils/copy-tree-properties.d.ts +0 -1
  74. package/src/index.d.ts +0 -25
  75. package/src/lib/async-helpers.d.ts +0 -8
  76. package/src/lib/constants.d.ts +0 -41
  77. package/src/lib/dev-proxy.d.ts +0 -3
  78. package/src/lib/edit-session.d.ts +0 -21
  79. package/src/lib/entity-signal.d.ts +0 -1
  80. package/src/lib/internals/batch-scope.d.ts +0 -3
  81. package/src/lib/internals/builder-types.d.ts +0 -13
  82. package/src/lib/internals/derived-types.d.ts +0 -10
  83. package/src/lib/internals/materialize-markers.d.ts +0 -5
  84. package/src/lib/internals/merge-derived.d.ts +0 -4
  85. package/src/lib/markers/derived.d.ts +0 -9
  86. package/src/lib/markers/entity-map.d.ts +0 -4
  87. package/src/lib/markers/index.d.ts +0 -3
  88. package/src/lib/markers/status.d.ts +0 -32
  89. package/src/lib/markers/stored.d.ts +0 -23
  90. package/src/lib/memory/memory-manager.d.ts +0 -30
  91. package/src/lib/path-notifier.d.ts +0 -34
  92. package/src/lib/performance/diff-engine.d.ts +0 -33
  93. package/src/lib/performance/path-index.d.ts +0 -25
  94. package/src/lib/performance/update-engine.d.ts +0 -32
  95. package/src/lib/presets.d.ts +0 -34
  96. package/src/lib/security/security-validator.d.ts +0 -33
  97. package/src/lib/signal-tree.d.ts +0 -6
  98. package/src/lib/types.d.ts +0 -300
  99. package/src/lib/utils.d.ts +0 -25
@@ -1,71 +0,0 @@
1
- import { signal, computed } from '@angular/core';
2
- import { registerMarkerProcessor } from '../internals/materialize-markers.js';
3
-
4
- const STATUS_MARKER = Symbol('STATUS_MARKER');
5
- var LoadingState;
6
- (function (LoadingState) {
7
- LoadingState["NotLoaded"] = "NOT_LOADED";
8
- LoadingState["Loading"] = "LOADING";
9
- LoadingState["Loaded"] = "LOADED";
10
- LoadingState["Error"] = "ERROR";
11
- })(LoadingState || (LoadingState = {}));
12
- let statusRegistered = false;
13
- function status(initialState = LoadingState.NotLoaded) {
14
- if (!statusRegistered) {
15
- statusRegistered = true;
16
- registerMarkerProcessor(isStatusMarker, createStatusSignal);
17
- }
18
- return {
19
- [STATUS_MARKER]: true,
20
- initialState
21
- };
22
- }
23
- function isStatusMarker(value) {
24
- return value !== null && typeof value === 'object' && STATUS_MARKER in value && value[STATUS_MARKER] === true;
25
- }
26
- function createStatusSignal(marker) {
27
- const stateSignal = signal(marker.initialState);
28
- const errorSignal = signal(null);
29
- let _isNotLoaded = null;
30
- let _isLoading = null;
31
- let _isLoaded = null;
32
- let _isError = null;
33
- return {
34
- state: stateSignal,
35
- error: errorSignal,
36
- get isNotLoaded() {
37
- return _isNotLoaded ??= computed(() => stateSignal() === LoadingState.NotLoaded);
38
- },
39
- get isLoading() {
40
- return _isLoading ??= computed(() => stateSignal() === LoadingState.Loading);
41
- },
42
- get isLoaded() {
43
- return _isLoaded ??= computed(() => stateSignal() === LoadingState.Loaded);
44
- },
45
- get isError() {
46
- return _isError ??= computed(() => stateSignal() === LoadingState.Error);
47
- },
48
- setNotLoaded() {
49
- stateSignal.set(LoadingState.NotLoaded);
50
- errorSignal.set(null);
51
- },
52
- setLoading() {
53
- stateSignal.set(LoadingState.Loading);
54
- errorSignal.set(null);
55
- },
56
- setLoaded() {
57
- stateSignal.set(LoadingState.Loaded);
58
- errorSignal.set(null);
59
- },
60
- setError(err) {
61
- stateSignal.set(LoadingState.Error);
62
- errorSignal.set(err);
63
- },
64
- reset() {
65
- stateSignal.set(LoadingState.NotLoaded);
66
- errorSignal.set(null);
67
- }
68
- };
69
- }
70
-
71
- export { LoadingState, STATUS_MARKER, createStatusSignal, isStatusMarker, status };
@@ -1,111 +0,0 @@
1
- import { signal } from '@angular/core';
2
- import { registerMarkerProcessor } from '../internals/materialize-markers.js';
3
-
4
- const STORED_MARKER = Symbol('STORED_MARKER');
5
- let storedRegistered = false;
6
- function stored(key, defaultValue, options = {}) {
7
- if (!storedRegistered) {
8
- storedRegistered = true;
9
- registerMarkerProcessor(isStoredMarker, createStoredSignal);
10
- }
11
- return {
12
- [STORED_MARKER]: true,
13
- key,
14
- defaultValue,
15
- options
16
- };
17
- }
18
- function isStoredMarker(value) {
19
- return value !== null && typeof value === 'object' && STORED_MARKER in value && value[STORED_MARKER] === true;
20
- }
21
- function createStoredSignal(marker) {
22
- const {
23
- key,
24
- defaultValue,
25
- options: {
26
- serialize = JSON.stringify,
27
- deserialize = JSON.parse,
28
- debounceMs = 100
29
- }
30
- } = marker;
31
- const storage = marker.options.storage !== undefined ? marker.options.storage : typeof localStorage !== 'undefined' ? localStorage : null;
32
- let initialValue = defaultValue;
33
- if (storage) {
34
- try {
35
- const storedValue = storage.getItem(key);
36
- if (storedValue !== null) {
37
- initialValue = deserialize(storedValue);
38
- }
39
- } catch (e) {
40
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
41
- console.warn(`SignalTree: Failed to read "${key}" from storage`, e);
42
- }
43
- }
44
- }
45
- const sig = signal(initialValue);
46
- let pendingWrite = null;
47
- let pendingValue;
48
- const saveToStorage = value => {
49
- if (!storage) return;
50
- if (debounceMs === 0) {
51
- queueMicrotask(() => {
52
- try {
53
- storage.setItem(key, serialize(value));
54
- } catch (e) {
55
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
56
- console.warn(`SignalTree: Failed to save "${key}" to storage`, e);
57
- }
58
- }
59
- });
60
- return;
61
- }
62
- pendingValue = value;
63
- if (pendingWrite !== null) {
64
- clearTimeout(pendingWrite);
65
- }
66
- pendingWrite = setTimeout(() => {
67
- pendingWrite = null;
68
- queueMicrotask(() => {
69
- try {
70
- storage.setItem(key, serialize(pendingValue));
71
- } catch (e) {
72
- if (typeof ngDevMode === 'undefined' || ngDevMode) {
73
- console.warn(`SignalTree: Failed to save "${key}" to storage`, e);
74
- }
75
- }
76
- });
77
- }, debounceMs);
78
- };
79
- const storedSignal = () => sig();
80
- storedSignal.set = value => {
81
- sig.set(value);
82
- saveToStorage(value);
83
- };
84
- storedSignal.update = fn => {
85
- const newValue = fn(sig());
86
- sig.set(newValue);
87
- saveToStorage(newValue);
88
- };
89
- storedSignal.clear = () => {
90
- sig.set(defaultValue);
91
- if (storage) {
92
- storage.removeItem(key);
93
- }
94
- };
95
- storedSignal.reload = () => {
96
- if (!storage) return;
97
- try {
98
- const storedValue = storage.getItem(key);
99
- if (storedValue !== null) {
100
- sig.set(deserialize(storedValue));
101
- } else {
102
- sig.set(defaultValue);
103
- }
104
- } catch {
105
- sig.set(defaultValue);
106
- }
107
- };
108
- return storedSignal;
109
- }
110
-
111
- export { STORED_MARKER, createStoredSignal, isStoredMarker, stored };
@@ -1,164 +0,0 @@
1
- class SignalMemoryManager {
2
- cache = new Map();
3
- registry = null;
4
- config;
5
- stats = {
6
- cleanedUpSignals: 0,
7
- peakCachedSignals: 0,
8
- manualDisposes: 0
9
- };
10
- constructor(config = {}) {
11
- this.config = {
12
- enableAutoCleanup: config.enableAutoCleanup ?? true,
13
- debugMode: config.debugMode ?? false,
14
- onCleanup: config.onCleanup ?? (() => {})
15
- };
16
- if (this.config.enableAutoCleanup && typeof FinalizationRegistry !== 'undefined') {
17
- this.registry = new FinalizationRegistry(path => {
18
- this.handleCleanup(path);
19
- });
20
- }
21
- if (this.config.debugMode) {
22
- console.log('[SignalMemoryManager] Initialized', {
23
- autoCleanup: this.config.enableAutoCleanup,
24
- hasRegistry: !!this.registry
25
- });
26
- }
27
- }
28
- cacheSignal(path, signal) {
29
- const ref = new WeakRef(signal);
30
- const entry = {
31
- ref,
32
- path,
33
- cachedAt: Date.now()
34
- };
35
- this.cache.set(path, entry);
36
- if (this.registry) {
37
- this.registry.register(signal, path, signal);
38
- }
39
- const currentSize = this.cache.size;
40
- if (currentSize > this.stats.peakCachedSignals) {
41
- this.stats.peakCachedSignals = currentSize;
42
- }
43
- if (this.config.debugMode) {
44
- console.log(`[SignalMemoryManager] Cached signal: ${path}`, {
45
- cacheSize: currentSize,
46
- peak: this.stats.peakCachedSignals
47
- });
48
- }
49
- }
50
- getSignal(path) {
51
- const entry = this.cache.get(path);
52
- if (!entry) {
53
- return undefined;
54
- }
55
- const signal = entry.ref.deref();
56
- if (!signal) {
57
- this.cache.delete(path);
58
- if (this.config.debugMode) {
59
- console.log(`[SignalMemoryManager] Signal GC'd: ${path}`);
60
- }
61
- return undefined;
62
- }
63
- return signal;
64
- }
65
- hasSignal(path) {
66
- return this.cache.has(path);
67
- }
68
- removeSignal(path) {
69
- const entry = this.cache.get(path);
70
- if (!entry) {
71
- return false;
72
- }
73
- const signal = entry.ref.deref();
74
- if (signal && this.registry) {
75
- this.registry.unregister(signal);
76
- }
77
- this.cache.delete(path);
78
- if (this.config.debugMode) {
79
- console.log(`[SignalMemoryManager] Removed signal: ${path}`);
80
- }
81
- return true;
82
- }
83
- handleCleanup(path) {
84
- this.cache.delete(path);
85
- this.stats.cleanedUpSignals++;
86
- const currentStats = this.getStats();
87
- if (this.config.debugMode) {
88
- console.log(`[SignalMemoryManager] Auto cleanup: ${path}`, currentStats);
89
- }
90
- this.config.onCleanup(path, currentStats);
91
- }
92
- getStats() {
93
- let validSignals = 0;
94
- for (const [path, entry] of this.cache.entries()) {
95
- if (entry.ref.deref()) {
96
- validSignals++;
97
- } else {
98
- this.cache.delete(path);
99
- }
100
- }
101
- const estimatedMemoryBytes = validSignals * 100;
102
- return {
103
- cachedSignals: validSignals,
104
- cleanedUpSignals: this.stats.cleanedUpSignals,
105
- peakCachedSignals: this.stats.peakCachedSignals,
106
- manualDisposes: this.stats.manualDisposes,
107
- estimatedMemoryBytes
108
- };
109
- }
110
- dispose() {
111
- if (this.config.debugMode) {
112
- console.log('[SignalMemoryManager] Disposing', {
113
- cachedSignals: this.cache.size
114
- });
115
- }
116
- if (this.registry) {
117
- for (const entry of this.cache.values()) {
118
- const signal = entry.ref.deref();
119
- if (signal) {
120
- this.registry.unregister(signal);
121
- }
122
- }
123
- }
124
- this.cache.clear();
125
- this.stats.manualDisposes++;
126
- if (this.config.debugMode) {
127
- console.log('[SignalMemoryManager] Disposed', this.getStats());
128
- }
129
- }
130
- getCachedPaths() {
131
- const paths = [];
132
- for (const [path, entry] of this.cache.entries()) {
133
- if (entry.ref.deref()) {
134
- paths.push(path);
135
- }
136
- }
137
- return paths;
138
- }
139
- clearStale() {
140
- let removed = 0;
141
- for (const [path, entry] of this.cache.entries()) {
142
- if (!entry.ref.deref()) {
143
- this.cache.delete(path);
144
- removed++;
145
- }
146
- }
147
- if (this.config.debugMode && removed > 0) {
148
- console.log(`[SignalMemoryManager] Cleared ${removed} stale entries`);
149
- }
150
- return removed;
151
- }
152
- resetStats() {
153
- this.stats = {
154
- cleanedUpSignals: 0,
155
- peakCachedSignals: 0,
156
- manualDisposes: 0
157
- };
158
- if (this.config.debugMode) {
159
- console.log('[SignalMemoryManager] Stats reset');
160
- }
161
- }
162
- }
163
-
164
- export { SignalMemoryManager };
@@ -1,178 +0,0 @@
1
- class PathNotifier {
2
- subscribers = new Map();
3
- interceptors = new Map();
4
- batchingEnabled = true;
5
- pendingFlush = false;
6
- pending = new Map();
7
- firstValues = new Map();
8
- flushCallbacks = new Set();
9
- constructor(options) {
10
- if (options && options.batching === false) this.batchingEnabled = false;
11
- }
12
- setBatchingEnabled(enabled) {
13
- this.batchingEnabled = enabled;
14
- }
15
- isBatchingEnabled() {
16
- return this.batchingEnabled;
17
- }
18
- subscribe(pattern, handler) {
19
- if (!this.subscribers.has(pattern)) {
20
- this.subscribers.set(pattern, new Set());
21
- }
22
- const handlers = this.subscribers.get(pattern);
23
- if (!handlers) {
24
- return () => {};
25
- }
26
- handlers.add(handler);
27
- return () => {
28
- handlers.delete(handler);
29
- if (handlers.size === 0) {
30
- this.subscribers.delete(pattern);
31
- }
32
- };
33
- }
34
- intercept(pattern, interceptor) {
35
- if (!this.interceptors.has(pattern)) {
36
- this.interceptors.set(pattern, new Set());
37
- }
38
- const interceptors = this.interceptors.get(pattern);
39
- if (!interceptors) {
40
- return () => {};
41
- }
42
- interceptors.add(interceptor);
43
- return () => {
44
- interceptors.delete(interceptor);
45
- if (interceptors.size === 0) {
46
- this.interceptors.delete(pattern);
47
- }
48
- };
49
- }
50
- notify(path, value, prev) {
51
- if (!this.batchingEnabled) {
52
- return this._runNotify(path, value, prev);
53
- }
54
- if (!this.pending.has(path)) {
55
- this.firstValues.set(path, prev);
56
- }
57
- this.pending.set(path, {
58
- newValue: value,
59
- oldValue: this.firstValues.get(path)
60
- });
61
- if (!this.pendingFlush) {
62
- this.pendingFlush = true;
63
- queueMicrotask(() => this.flush());
64
- }
65
- return {
66
- blocked: false,
67
- value
68
- };
69
- }
70
- _runNotify(path, value, prev) {
71
- let blocked = false;
72
- let transformed = value;
73
- for (const [pattern, interceptorSet] of this.interceptors) {
74
- if (this.matches(pattern, path)) {
75
- for (const interceptor of interceptorSet) {
76
- const result = interceptor(transformed, prev, path);
77
- if (result.block) {
78
- blocked = true;
79
- }
80
- if (result.transform !== undefined) {
81
- transformed = result.transform;
82
- }
83
- }
84
- }
85
- }
86
- if (blocked) {
87
- return {
88
- blocked: true,
89
- value: prev
90
- };
91
- }
92
- for (const [pattern, handlers] of this.subscribers) {
93
- if (this.matches(pattern, path)) {
94
- for (const handler of handlers) {
95
- handler(transformed, prev, path);
96
- }
97
- }
98
- }
99
- return {
100
- blocked: false,
101
- value: transformed
102
- };
103
- }
104
- flush() {
105
- const toNotify = new Map(this.pending);
106
- this.pending.clear();
107
- this.firstValues.clear();
108
- this.pendingFlush = false;
109
- for (const [path, {
110
- newValue,
111
- oldValue
112
- }] of toNotify) {
113
- if (newValue === oldValue) continue;
114
- const res = this._runNotify(path, newValue, oldValue);
115
- if (res.blocked) ;
116
- }
117
- for (const cb of Array.from(this.flushCallbacks)) {
118
- try {
119
- cb();
120
- } catch {}
121
- }
122
- }
123
- flushSync() {
124
- while (this.pending.size > 0 || this.pendingFlush) {
125
- if (this.pendingFlush && this.pending.size === 0) {
126
- this.pendingFlush = false;
127
- break;
128
- }
129
- this.flush();
130
- }
131
- }
132
- onFlush(callback) {
133
- this.flushCallbacks.add(callback);
134
- return () => this.flushCallbacks.delete(callback);
135
- }
136
- hasPending() {
137
- return this.pending.size > 0;
138
- }
139
- matches(pattern, path) {
140
- if (pattern === '**') return true;
141
- if (pattern === path) return true;
142
- if (pattern.endsWith('.*')) {
143
- const prefix = pattern.slice(0, -2);
144
- return path.startsWith(prefix + '.');
145
- }
146
- return false;
147
- }
148
- clear() {
149
- this.subscribers.clear();
150
- this.interceptors.clear();
151
- this.pending.clear();
152
- this.firstValues.clear();
153
- this.pendingFlush = false;
154
- }
155
- getSubscriberCount() {
156
- let count = 0;
157
- for (const handlers of this.subscribers.values()) {
158
- count += handlers.size;
159
- }
160
- return count;
161
- }
162
- getInterceptorCount() {
163
- let count = 0;
164
- for (const interceptors of this.interceptors.values()) {
165
- count += interceptors.size;
166
- }
167
- return count;
168
- }
169
- }
170
- let globalPathNotifier = null;
171
- function getPathNotifier() {
172
- if (!globalPathNotifier) {
173
- globalPathNotifier = new PathNotifier();
174
- }
175
- return globalPathNotifier;
176
- }
177
-
178
- export { PathNotifier, getPathNotifier };
@@ -1,21 +0,0 @@
1
- import { signalTree } from './signal-tree.js';
2
- import { effects } from '../enhancers/effects/effects.js';
3
- import { batching } from '../enhancers/batching/batching.js';
4
- import { memoization } from '../enhancers/memoization/memoization.js';
5
- import { entities } from '../enhancers/entities/entities.js';
6
- import { timeTravel } from '../enhancers/time-travel/time-travel.js';
7
- import { devTools } from '../enhancers/devtools/devtools.js';
8
-
9
- function createDevTree(initialState, config = {}) {
10
- if (arguments.length === 0) {
11
- const enhancer = tree => tree.with(effects()).with(batching()).with(memoization()).with(entities()).with(timeTravel()).with(devTools());
12
- return {
13
- enhancer
14
- };
15
- }
16
- const base = signalTree(initialState, config);
17
- const enhanced = base.with(effects()).with(batching(config.batching)).with(memoization(config.memoization)).with(entities()).with(timeTravel(config.timeTravel)).with(devTools(config.devTools));
18
- return enhanced;
19
- }
20
-
21
- export { createDevTree };
@@ -1,121 +0,0 @@
1
- const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
2
- const HTML_TAG_PATTERN = /<[^>]*>/g;
3
- const DANGEROUS_HTML_PATTERNS = [/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, /on\w+\s*=/gi, /javascript:/gi, /<iframe\b/gi, /<object\b/gi, /<embed\b/gi];
4
- class SecurityValidator {
5
- config;
6
- dangerousKeys;
7
- constructor(config = {}) {
8
- this.config = {
9
- preventPrototypePollution: config.preventPrototypePollution ?? true,
10
- preventXSS: config.preventXSS ?? false,
11
- preventFunctions: config.preventFunctions ?? true,
12
- customDangerousKeys: config.customDangerousKeys ?? [],
13
- onSecurityEvent: config.onSecurityEvent ?? (() => {}),
14
- sanitizationMode: config.sanitizationMode ?? 'strict'
15
- };
16
- this.dangerousKeys = new Set([...DANGEROUS_KEYS, ...this.config.customDangerousKeys]);
17
- }
18
- validateKey(key) {
19
- if (!this.config.preventPrototypePollution) {
20
- return;
21
- }
22
- if (this.dangerousKeys.has(key)) {
23
- const event = {
24
- type: 'dangerous-key-blocked',
25
- key,
26
- reason: `Dangerous key "${key}" blocked to prevent prototype pollution`,
27
- timestamp: Date.now()
28
- };
29
- this.config.onSecurityEvent(event);
30
- throw new Error(`[SignalTree Security] Dangerous key "${key}" is not allowed. ` + `This key can lead to prototype pollution attacks. ` + `Blocked keys: ${Array.from(this.dangerousKeys).join(', ')}`);
31
- }
32
- }
33
- validateValue(value) {
34
- if (this.config.preventFunctions && typeof value === 'function') {
35
- const event = {
36
- type: 'function-value-blocked',
37
- value,
38
- reason: 'Function values are not allowed - state must be serializable',
39
- timestamp: Date.now()
40
- };
41
- this.config.onSecurityEvent(event);
42
- throw new Error(`[SignalTree Security] Function values are not allowed in state trees. ` + `Functions cannot be serialized, breaking features like time-travel, ` + `persistence, debugging, and SSR. ` + `\n\nTo fix this:` + `\n - Store function references outside the tree` + `\n - Use method names (strings) and a function registry` + `\n - Use computed signals for derived values` + `\n\nBlocked value: ${value.toString().substring(0, 100)}...`);
43
- }
44
- if (!this.config.preventXSS || typeof value !== 'string') {
45
- return value;
46
- }
47
- let hasDangerousPattern = false;
48
- for (const pattern of DANGEROUS_HTML_PATTERNS) {
49
- pattern.lastIndex = 0;
50
- if (pattern.test(value)) {
51
- hasDangerousPattern = true;
52
- break;
53
- }
54
- }
55
- if (hasDangerousPattern) {
56
- const event = {
57
- type: 'xss-attempt-blocked',
58
- value,
59
- reason: 'Dangerous HTML pattern detected and sanitized',
60
- timestamp: Date.now()
61
- };
62
- this.config.onSecurityEvent(event);
63
- return this.sanitize(value);
64
- }
65
- return this.sanitize(value);
66
- }
67
- sanitize(value) {
68
- if (this.config.sanitizationMode === 'strict') {
69
- let sanitized = value;
70
- for (const pattern of DANGEROUS_HTML_PATTERNS) {
71
- pattern.lastIndex = 0;
72
- sanitized = sanitized.replace(pattern, '');
73
- }
74
- return sanitized.replace(HTML_TAG_PATTERN, '');
75
- } else {
76
- let sanitized = value;
77
- for (const pattern of DANGEROUS_HTML_PATTERNS) {
78
- pattern.lastIndex = 0;
79
- sanitized = sanitized.replace(pattern, '');
80
- }
81
- return sanitized;
82
- }
83
- }
84
- validateKeyValue(key, value) {
85
- this.validateKey(key);
86
- return this.validateValue(value);
87
- }
88
- isDangerousKey(key) {
89
- return this.dangerousKeys.has(key);
90
- }
91
- getConfig() {
92
- return {
93
- ...this.config
94
- };
95
- }
96
- }
97
- const SecurityPresets = {
98
- strict: () => new SecurityValidator({
99
- preventPrototypePollution: true,
100
- preventXSS: true,
101
- preventFunctions: true,
102
- sanitizationMode: 'strict'
103
- }),
104
- standard: () => new SecurityValidator({
105
- preventPrototypePollution: true,
106
- preventXSS: false,
107
- preventFunctions: true
108
- }),
109
- permissive: () => new SecurityValidator({
110
- preventPrototypePollution: true,
111
- preventXSS: false,
112
- preventFunctions: false
113
- }),
114
- disabled: () => new SecurityValidator({
115
- preventPrototypePollution: false,
116
- preventXSS: false,
117
- preventFunctions: false
118
- })
119
- };
120
-
121
- export { SecurityPresets, SecurityValidator };