scope-state 0.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.
package/dist/index.js ADDED
@@ -0,0 +1,2073 @@
1
+ 'use strict';
2
+
3
+ var react = require('react');
4
+ var localforage = require('localforage');
5
+ var memoryDriver = require('localforage-driver-memory');
6
+
7
+ function _interopNamespaceDefault(e) {
8
+ var n = Object.create(null);
9
+ if (e) {
10
+ Object.keys(e).forEach(function (k) {
11
+ if (k !== 'default') {
12
+ var d = Object.getOwnPropertyDescriptor(e, k);
13
+ Object.defineProperty(n, k, d.get ? d : {
14
+ enumerable: true,
15
+ get: function () { return e[k]; }
16
+ });
17
+ }
18
+ });
19
+ }
20
+ n.default = e;
21
+ return Object.freeze(n);
22
+ }
23
+
24
+ var memoryDriver__namespace = /*#__PURE__*/_interopNamespaceDefault(memoryDriver);
25
+
26
+ // Default configurations
27
+ const defaultProxyConfig = {
28
+ enabled: true,
29
+ maxDepth: 5,
30
+ selectiveProxying: false,
31
+ trackPathUsage: true,
32
+ lazyProxyDeepObjects: false,
33
+ preProxyPaths: [],
34
+ maxPathLength: 20,
35
+ smartArrayTracking: true,
36
+ nonBlockingProxyCreation: true,
37
+ batchSize: 1,
38
+ prioritizeUIObjects: true,
39
+ maxQueueSize: 1000,
40
+ memoryLimit: false,
41
+ memoryThreshold: 1,
42
+ targetMemoryUsage: 3000,
43
+ proxyEvictionStrategy: 'lru',
44
+ disableProxyingUnderPressure: false,
45
+ maxProxyCacheSize: 5000,
46
+ ultraSelectiveProxying: false,
47
+ proxySelectorPaths: true,
48
+ forceMemoryCheck: true,
49
+ aggressiveMemoryManagement: false,
50
+ priorityPaths: [],
51
+ };
52
+ const defaultMonitoringConfig = {
53
+ enabled: true,
54
+ verboseLogging: false,
55
+ logSubscriptions: false,
56
+ logProxyCreation: false,
57
+ logStateChanges: false,
58
+ logPersistence: false,
59
+ logTimings: false,
60
+ autoLeakDetection: false,
61
+ leakDetectionInterval: 30000,
62
+ leakAlertThreshold: 0,
63
+ leakScanMinimumRenderCycles: 3,
64
+ };
65
+ const defaultPersistenceConfig = {
66
+ enabled: true,
67
+ paths: [],
68
+ blacklist: [],
69
+ batchDelay: 300,
70
+ };
71
+ // Current active configurations (will be modified by configure())
72
+ let proxyConfig = { ...defaultProxyConfig };
73
+ let monitoringConfig = { ...defaultMonitoringConfig };
74
+ let persistenceConfig = { ...defaultPersistenceConfig };
75
+ // Note: The main configure function is now in src/index.ts to avoid circular dependencies
76
+ // These configuration objects are still exported for internal use
77
+ /**
78
+ * Get current configuration
79
+ */
80
+ function getConfig() {
81
+ return {
82
+ proxy: { ...proxyConfig },
83
+ monitoring: { ...monitoringConfig },
84
+ persistence: { ...persistenceConfig },
85
+ };
86
+ }
87
+ /**
88
+ * Reset all configuration to defaults
89
+ */
90
+ function resetConfig() {
91
+ proxyConfig = { ...defaultProxyConfig };
92
+ monitoringConfig = { ...defaultMonitoringConfig };
93
+ persistenceConfig = { ...defaultPersistenceConfig };
94
+ }
95
+ /**
96
+ * Quick presets for common use cases
97
+ */
98
+ const presets = {
99
+ /**
100
+ * Development preset: Enhanced debugging and monitoring
101
+ */
102
+ development: () => ({
103
+ monitoring: {
104
+ enabled: true,
105
+ verboseLogging: true,
106
+ logSubscriptions: true,
107
+ logStateChanges: true,
108
+ autoLeakDetection: true,
109
+ },
110
+ proxy: {
111
+ maxDepth: 3,
112
+ smartArrayTracking: true,
113
+ },
114
+ }),
115
+ /**
116
+ * Production preset: Optimized for performance
117
+ */
118
+ production: () => ({
119
+ monitoring: {
120
+ enabled: false,
121
+ verboseLogging: false,
122
+ logSubscriptions: false,
123
+ logStateChanges: false,
124
+ autoLeakDetection: false,
125
+ },
126
+ proxy: {
127
+ maxDepth: 6,
128
+ ultraSelectiveProxying: false,
129
+ aggressiveMemoryManagement: false,
130
+ },
131
+ }),
132
+ /**
133
+ * Memory-constrained preset: Minimal memory usage
134
+ */
135
+ minimal: () => ({
136
+ monitoring: {
137
+ enabled: false,
138
+ },
139
+ proxy: {
140
+ maxDepth: 1,
141
+ ultraSelectiveProxying: true,
142
+ aggressiveMemoryManagement: true,
143
+ maxProxyCacheSize: 1000,
144
+ maxQueueSize: 100,
145
+ },
146
+ persistence: {
147
+ enabled: false,
148
+ },
149
+ }),
150
+ /**
151
+ * Full-featured preset: All features enabled
152
+ */
153
+ full: () => ({
154
+ monitoring: {
155
+ enabled: true,
156
+ verboseLogging: true,
157
+ autoLeakDetection: true,
158
+ },
159
+ proxy: {
160
+ maxDepth: 5,
161
+ selectiveProxying: false,
162
+ },
163
+ persistence: {
164
+ enabled: true,
165
+ },
166
+ }),
167
+ };
168
+
169
+ // Path-specific listeners
170
+ const pathListeners = new Map();
171
+ // Statistics for monitoring
172
+ let monitoringStats = {
173
+ proxyCount: 0,
174
+ totalSubscriptionsCreated: 0,
175
+ totalSubscriptionsRemoved: 0,
176
+ activeSubscriptions: 0,
177
+ pathSubscriptionCounts: {},
178
+ timings: {
179
+ lastNotifyTime: 0,
180
+ lastPersistTime: 0,
181
+ averageNotifyTime: 0,
182
+ averagePersistTime: 0,
183
+ notifyTimeTotal: 0,
184
+ persistTimeTotal: 0,
185
+ notifyCount: 0,
186
+ persistCount: 0,
187
+ },
188
+ leakDetection: {
189
+ lastCheckTime: 0,
190
+ totalChecks: 0,
191
+ leaksDetected: 0,
192
+ renderCyclesSinceCheck: 0,
193
+ isLeakDetectionRunning: false,
194
+ leakDetectionTimer: null,
195
+ }
196
+ };
197
+ /**
198
+ * Subscribe to changes on a specific path
199
+ */
200
+ function subscribe(path, listener) {
201
+ var _a;
202
+ if (!pathListeners.has(path)) {
203
+ pathListeners.set(path, new Set());
204
+ }
205
+ (_a = pathListeners.get(path)) === null || _a === void 0 ? void 0 : _a.add(listener);
206
+ logSubscriptionAdded(path);
207
+ // Return unsubscribe function
208
+ return () => {
209
+ var _a, _b;
210
+ (_a = pathListeners.get(path)) === null || _a === void 0 ? void 0 : _a.delete(listener);
211
+ logSubscriptionRemoved(path);
212
+ // Clean up empty listener sets
213
+ if (((_b = pathListeners.get(path)) === null || _b === void 0 ? void 0 : _b.size) === 0) {
214
+ pathListeners.delete(path);
215
+ if (monitoringConfig.enabled && monitoringConfig.logSubscriptions) {
216
+ console.log(`🧹 Removed empty listener set for ${path}`);
217
+ }
218
+ }
219
+ };
220
+ }
221
+ /**
222
+ * Notify all listeners for a given path and its parents/children
223
+ */
224
+ function notifyListeners(path) {
225
+ if (typeof window === 'undefined')
226
+ return;
227
+ const pathKey = path.join('.');
228
+ let startTime = 0;
229
+ if (monitoringConfig.enabled && monitoringConfig.logStateChanges) {
230
+ startTime = logTimestamp$1(`⚡️ Notifying path: ${pathKey}`);
231
+ }
232
+ // Track render cycle for leak detection
233
+ if (monitoringConfig.enabled && monitoringConfig.autoLeakDetection) {
234
+ monitoringStats.leakDetection.renderCyclesSinceCheck++;
235
+ }
236
+ // Notify exact path listeners
237
+ if (pathListeners.has(pathKey)) {
238
+ const listeners = pathListeners.get(pathKey);
239
+ if (monitoringConfig.enabled && monitoringConfig.verboseLogging) {
240
+ console.log(`🔔 Notifying ${listeners === null || listeners === void 0 ? void 0 : listeners.size} exact listeners for ${pathKey}`);
241
+ }
242
+ listeners === null || listeners === void 0 ? void 0 : listeners.forEach(listener => listener());
243
+ }
244
+ // Special handling for array index changes - notify the array itself too
245
+ const lastSegment = path.length > 0 ? path[path.length - 1] : '';
246
+ if (lastSegment && !isNaN(Number(lastSegment))) {
247
+ // This is an array index update, also notify about the array
248
+ const arrayPath = path.slice(0, -1).join('.');
249
+ if (pathListeners.has(arrayPath)) {
250
+ const listeners = pathListeners.get(arrayPath);
251
+ if (monitoringConfig.enabled && monitoringConfig.verboseLogging) {
252
+ console.log(`🔔 Notifying ${listeners === null || listeners === void 0 ? void 0 : listeners.size} array listeners for ${arrayPath} (index change)`);
253
+ }
254
+ listeners === null || listeners === void 0 ? void 0 : listeners.forEach(listener => listener());
255
+ }
256
+ }
257
+ // Also notify parent paths (more granular update notifications)
258
+ for (let i = path.length - 1; i >= 0; i--) {
259
+ const parentPath = path.slice(0, i).join('.');
260
+ if (pathListeners.has(parentPath)) {
261
+ const listeners = pathListeners.get(parentPath);
262
+ if (monitoringConfig.enabled && monitoringConfig.verboseLogging) {
263
+ console.log(`🔔 Notifying ${listeners === null || listeners === void 0 ? void 0 : listeners.size} parent listeners for ${parentPath}`);
264
+ }
265
+ listeners === null || listeners === void 0 ? void 0 : listeners.forEach(listener => listener());
266
+ }
267
+ }
268
+ // Notify child paths (if an object was completely replaced)
269
+ const prefix = pathKey + '.';
270
+ let childListenerCount = 0;
271
+ pathListeners.forEach((listeners, key) => {
272
+ if (key.startsWith(prefix)) {
273
+ childListenerCount += listeners.size;
274
+ listeners.forEach(listener => listener());
275
+ }
276
+ });
277
+ if (monitoringConfig.enabled && monitoringConfig.verboseLogging && childListenerCount > 0) {
278
+ console.log(`🔔 Notified ${childListenerCount} child listeners for paths starting with ${prefix}`);
279
+ }
280
+ if (monitoringConfig.enabled && monitoringConfig.logStateChanges && startTime > 0) {
281
+ const duration = logTimingEnd$1('Notification cycle', startTime);
282
+ updateTimingStat('notify', duration);
283
+ }
284
+ }
285
+ // Logging helper functions
286
+ function logTimestamp$1(action) {
287
+ if (!monitoringConfig.enabled || !monitoringConfig.logTimings)
288
+ return 0;
289
+ const now = performance.now();
290
+ console.log(`⏱️ [${now.toFixed(2)}ms] ${action}`);
291
+ return now;
292
+ }
293
+ function logTimingEnd$1(action, startTime) {
294
+ if (!monitoringConfig.enabled || !monitoringConfig.logTimings)
295
+ return 0;
296
+ const now = performance.now();
297
+ const duration = now - startTime;
298
+ console.log(`⏱️ [${now.toFixed(2)}ms] ${action} completed in ${duration.toFixed(2)}ms`);
299
+ return duration;
300
+ }
301
+ function updateTimingStat(type, duration) {
302
+ if (!monitoringConfig.enabled)
303
+ return;
304
+ if (type === 'notify') {
305
+ monitoringStats.timings.lastNotifyTime = duration;
306
+ monitoringStats.timings.notifyTimeTotal += duration;
307
+ monitoringStats.timings.notifyCount++;
308
+ monitoringStats.timings.averageNotifyTime =
309
+ monitoringStats.timings.notifyTimeTotal / monitoringStats.timings.notifyCount;
310
+ }
311
+ else {
312
+ monitoringStats.timings.lastPersistTime = duration;
313
+ monitoringStats.timings.persistTimeTotal += duration;
314
+ monitoringStats.timings.persistCount++;
315
+ monitoringStats.timings.averagePersistTime =
316
+ monitoringStats.timings.persistTimeTotal / monitoringStats.timings.persistCount;
317
+ }
318
+ }
319
+ function logSubscriptionAdded(path) {
320
+ if (!monitoringConfig.enabled || !monitoringConfig.logSubscriptions)
321
+ return;
322
+ monitoringStats.totalSubscriptionsCreated++;
323
+ monitoringStats.activeSubscriptions++;
324
+ monitoringStats.pathSubscriptionCounts[path] = (monitoringStats.pathSubscriptionCounts[path] || 0) + 1;
325
+ console.log(`📈 Subscription ADDED to ${path}. Total: ${monitoringStats.activeSubscriptions}, Path count: ${monitoringStats.pathSubscriptionCounts[path]}`);
326
+ }
327
+ function logSubscriptionRemoved(path) {
328
+ var _a;
329
+ if (!monitoringConfig.enabled || !monitoringConfig.logSubscriptions)
330
+ return;
331
+ monitoringStats.totalSubscriptionsRemoved++;
332
+ monitoringStats.activeSubscriptions--;
333
+ monitoringStats.pathSubscriptionCounts[path] = (monitoringStats.pathSubscriptionCounts[path] || 0) - 1;
334
+ console.log(`📉 Subscription REMOVED from ${path}. Total: ${monitoringStats.activeSubscriptions}, Path count: ${monitoringStats.pathSubscriptionCounts[path]}`);
335
+ // Alert if a path has no subscribers but still exists in the map
336
+ if (monitoringStats.pathSubscriptionCounts[path] <= 0) {
337
+ const actualListeners = ((_a = pathListeners.get(path)) === null || _a === void 0 ? void 0 : _a.size) || 0;
338
+ if (actualListeners > 0) {
339
+ console.warn(`⚠️ Path ${path} shows ${actualListeners} listeners but tracking shows ${monitoringStats.pathSubscriptionCounts[path]}`);
340
+ }
341
+ }
342
+ }
343
+
344
+ // Track proxy to path mapping for type-safe activation
345
+ const proxyPathMap = new WeakMap();
346
+ // Store a deep clone of the initial store state for use with $reset
347
+ let initialStoreState$1 = {};
348
+ // Path usage tracking
349
+ const pathUsageStats = {
350
+ accessedPaths: new Set(),
351
+ modifiedPaths: new Set(),
352
+ subscribedPaths: new Set(),
353
+ };
354
+ // Selector paths for ultra-selective proxying
355
+ const selectorPaths = new Set();
356
+ // Proxy cache using WeakMap for garbage collection
357
+ let proxyCache = new WeakMap();
358
+ // Proxy cache statistics
359
+ const proxyCacheStats = {
360
+ cacheHits: 0,
361
+ cacheMisses: 0,
362
+ totalProxiesCreated: 0,
363
+ activeCachedProxies: 0
364
+ };
365
+ // LRU tracking for proxy cache
366
+ const proxyCacheLRU = {
367
+ keys: [],
368
+ timestamp: [],
369
+ maxSize: proxyConfig.maxProxyCacheSize,
370
+ add(key, proxy) {
371
+ if (key && typeof key === 'object') {
372
+ const index = this.keys.findIndex(k => k === key);
373
+ if (index !== -1) {
374
+ this.timestamp[index] = Date.now();
375
+ }
376
+ else {
377
+ this.keys.push(key);
378
+ this.timestamp.push(Date.now());
379
+ if (this.keys.length > this.maxSize) {
380
+ this.evictOldest();
381
+ }
382
+ }
383
+ }
384
+ },
385
+ touch(key) {
386
+ const index = this.keys.findIndex(k => k === key);
387
+ if (index !== -1) {
388
+ this.timestamp[index] = Date.now();
389
+ }
390
+ },
391
+ evictOldest() {
392
+ if (this.keys.length === 0)
393
+ return;
394
+ let oldestIndex = 0;
395
+ let oldestTime = this.timestamp[0];
396
+ for (let i = 1; i < this.timestamp.length; i++) {
397
+ if (this.timestamp[i] < oldestTime) {
398
+ oldestTime = this.timestamp[i];
399
+ oldestIndex = i;
400
+ }
401
+ }
402
+ const oldestKey = this.keys[oldestIndex];
403
+ if (oldestKey && proxyCache.has(oldestKey)) {
404
+ proxyCache.delete(oldestKey);
405
+ proxyCacheStats.activeCachedProxies--;
406
+ }
407
+ this.keys.splice(oldestIndex, 1);
408
+ this.timestamp.splice(oldestIndex, 1);
409
+ },
410
+ evictPercentage(percent) {
411
+ const count = Math.ceil(this.keys.length * percent);
412
+ if (count <= 0)
413
+ return 0;
414
+ const indices = Array.from({ length: this.keys.length }, (_, i) => i)
415
+ .sort((a, b) => this.timestamp[a] - this.timestamp[b]);
416
+ let evicted = 0;
417
+ for (let i = 0; i < count && i < indices.length; i++) {
418
+ const key = this.keys[indices[i]];
419
+ if (key && proxyCache.has(key)) {
420
+ proxyCache.delete(key);
421
+ evicted++;
422
+ }
423
+ }
424
+ const newKeys = [];
425
+ const newTimestamps = [];
426
+ for (let i = count; i < indices.length; i++) {
427
+ const idx = indices[i];
428
+ newKeys.push(this.keys[idx]);
429
+ newTimestamps.push(this.timestamp[idx]);
430
+ }
431
+ this.keys = newKeys;
432
+ this.timestamp = newTimestamps;
433
+ proxyCacheStats.activeCachedProxies -= evicted;
434
+ return evicted;
435
+ },
436
+ size() {
437
+ return this.keys.length;
438
+ },
439
+ clear() {
440
+ this.keys = [];
441
+ this.timestamp = [];
442
+ }
443
+ };
444
+ // Memory pressure detection
445
+ const memoryPressure = {
446
+ isUnderPressure: false,
447
+ lastCheckTime: 0,
448
+ checkInterval: 5000,
449
+ highUsageCount: 0,
450
+ check() {
451
+ const now = Date.now();
452
+ if (!proxyConfig.forceMemoryCheck && now - this.lastCheckTime < this.checkInterval) {
453
+ return this.isUnderPressure;
454
+ }
455
+ this.lastCheckTime = now;
456
+ try {
457
+ const estimatedUsageMB = this.estimateMemoryUsage();
458
+ const thresholdMB = proxyConfig.targetMemoryUsage * proxyConfig.memoryThreshold;
459
+ const wasUnderPressure = this.isUnderPressure;
460
+ this.isUnderPressure = estimatedUsageMB > thresholdMB;
461
+ if (proxyConfig.aggressiveMemoryManagement && estimatedUsageMB > thresholdMB * 0.8) {
462
+ this.highUsageCount++;
463
+ if (this.highUsageCount > 2) {
464
+ this.isUnderPressure = true;
465
+ }
466
+ }
467
+ else {
468
+ this.highUsageCount = 0;
469
+ }
470
+ if (this.isUnderPressure !== wasUnderPressure && monitoringConfig.enabled) {
471
+ if (this.isUnderPressure) {
472
+ console.warn(`⚠️ High memory pressure detected: ${estimatedUsageMB.toFixed(1)}MB > ${thresholdMB.toFixed(1)}MB threshold`);
473
+ }
474
+ else {
475
+ console.log(`✅ Memory pressure relieved: ${estimatedUsageMB.toFixed(1)}MB < ${thresholdMB.toFixed(1)}MB threshold`);
476
+ }
477
+ }
478
+ }
479
+ catch (e) {
480
+ console.error('Error checking memory pressure:', e);
481
+ }
482
+ return this.isUnderPressure;
483
+ },
484
+ estimateMemoryUsage() {
485
+ const proxyCacheSize = proxyCacheLRU.size() * 2;
486
+ const pathStatsSize = (pathUsageStats.accessedPaths.size +
487
+ pathUsageStats.modifiedPaths.size +
488
+ pathUsageStats.subscribedPaths.size) * 0.2;
489
+ const baseMB = 400;
490
+ return baseMB + proxyCacheSize + pathStatsSize;
491
+ }
492
+ };
493
+ /**
494
+ * Set the initial store state for reset functionality
495
+ */
496
+ function setInitialStoreState(state) {
497
+ initialStoreState$1 = JSON.parse(JSON.stringify(state));
498
+ }
499
+ /**
500
+ * Check if a path is high priority
501
+ */
502
+ function isHighPriorityPath(path) {
503
+ if (!proxyConfig.prioritizeUIObjects)
504
+ return false;
505
+ const pathStr = path.join('.');
506
+ return proxyConfig.priorityPaths.some(p => pathStr === p || pathStr.startsWith(`${p}.`));
507
+ }
508
+ /**
509
+ * Create an advanced proxy with all the features from the original code
510
+ */
511
+ function createAdvancedProxy(target, path = [], depth = 0) {
512
+ if (target === null || typeof target !== 'object') {
513
+ return target;
514
+ }
515
+ if (!proxyConfig.enabled) {
516
+ return target;
517
+ }
518
+ const pathKey = path.join('.');
519
+ // Check memory pressure
520
+ const isUnderPressure = memoryPressure.check();
521
+ if (isUnderPressure && proxyConfig.disableProxyingUnderPressure && depth > 0 && !isHighPriorityPath(path)) {
522
+ return target;
523
+ }
524
+ // Ultra-selective proxying
525
+ if (proxyConfig.ultraSelectiveProxying && depth > 0) {
526
+ if (!selectorPaths.has(pathKey) &&
527
+ !isHighPriorityPath(path) &&
528
+ !proxyConfig.preProxyPaths.some(p => pathKey === p || pathKey.startsWith(`${p}.`))) {
529
+ return target;
530
+ }
531
+ }
532
+ // Check for cached proxy
533
+ if (proxyCache.has(target)) {
534
+ proxyCacheStats.cacheHits++;
535
+ proxyCacheLRU.touch(target);
536
+ return proxyCache.get(target);
537
+ }
538
+ // Check depth limit
539
+ if (proxyConfig.selectiveProxying && depth > proxyConfig.maxDepth) {
540
+ return target;
541
+ }
542
+ // Create new proxy
543
+ proxyCacheStats.cacheMisses++;
544
+ proxyCacheStats.totalProxiesCreated++;
545
+ proxyCacheStats.activeCachedProxies++;
546
+ // If target is not extensible, clone it
547
+ if (!Object.isExtensible(target)) {
548
+ target = Array.isArray(target) ? [...target] : { ...target };
549
+ }
550
+ // Add custom methods for objects
551
+ if (typeof target === 'object' && !Array.isArray(target)) {
552
+ addObjectMethods(target, path);
553
+ }
554
+ // Add custom methods for arrays
555
+ if (Array.isArray(target)) {
556
+ addArrayMethods(target, path);
557
+ }
558
+ const proxy = new Proxy(target, {
559
+ get(obj, prop, receiver) {
560
+ if (typeof prop === 'symbol') {
561
+ return Reflect.get(obj, prop, receiver);
562
+ }
563
+ const currentPropPath = [...path, prop.toString()];
564
+ const propPathKey = currentPropPath.join('.');
565
+ // Track path access during dependency tracking
566
+ if (proxyConfig.trackPathUsage && propPathKey.split('.').length <= proxyConfig.maxPathLength) {
567
+ pathUsageStats.accessedPaths.add(propPathKey);
568
+ // Also add to selector paths for ultra-selective proxying
569
+ selectorPaths.add(propPathKey);
570
+ }
571
+ // Track path access for dependency tracking (inline to avoid circular dependency)
572
+ if (typeof require !== 'undefined') {
573
+ try {
574
+ const { trackPathAccess } = require('./tracking');
575
+ trackPathAccess(currentPropPath);
576
+ }
577
+ catch (e) {
578
+ // Skip tracking if module not available
579
+ }
580
+ }
581
+ const value = obj[prop];
582
+ // For objects, create proxies for nested values
583
+ if (value && typeof value === 'object' && path.length < proxyConfig.maxDepth) {
584
+ const shouldProxy = !proxyConfig.lazyProxyDeepObjects ||
585
+ pathUsageStats.accessedPaths.has(propPathKey) ||
586
+ pathUsageStats.modifiedPaths.has(propPathKey) ||
587
+ pathUsageStats.subscribedPaths.has(propPathKey) ||
588
+ proxyConfig.preProxyPaths.some(p => propPathKey === p || propPathKey.startsWith(`${p}.`));
589
+ if (shouldProxy) {
590
+ return createAdvancedProxy(value, currentPropPath, depth + 1);
591
+ }
592
+ }
593
+ return value;
594
+ },
595
+ set(obj, prop, value, receiver) {
596
+ if (typeof prop === 'symbol') {
597
+ return Reflect.set(obj, prop, value, receiver);
598
+ }
599
+ const propPath = [...path, prop.toString()];
600
+ const propPathKey = propPath.join('.');
601
+ // Track modification
602
+ if (proxyConfig.trackPathUsage) {
603
+ pathUsageStats.modifiedPaths.add(propPathKey);
604
+ }
605
+ // Handle object assignment with proxying
606
+ if (value && typeof value === 'object' && !proxyCache.has(value)) {
607
+ const newPath = [...path, prop.toString()];
608
+ const proxiedValue = createAdvancedProxy(value, newPath, 0);
609
+ const result = Reflect.set(obj, prop, proxiedValue, receiver);
610
+ // Notify the specific property path and all parent/child paths
611
+ notifyListeners(propPath);
612
+ return result;
613
+ }
614
+ const result = Reflect.set(obj, prop, value, receiver);
615
+ // Notify the specific property path (this will also notify parent/child paths)
616
+ notifyListeners(propPath);
617
+ return result;
618
+ },
619
+ deleteProperty(obj, prop) {
620
+ if (typeof prop === 'string') {
621
+ const propPath = [...path, prop];
622
+ const propPathKey = propPath.join('.');
623
+ if (proxyConfig.trackPathUsage) {
624
+ pathUsageStats.modifiedPaths.add(propPathKey);
625
+ }
626
+ const result = Reflect.deleteProperty(obj, prop);
627
+ if (result) {
628
+ // Notify the deleted property path (this will also notify parent/child paths)
629
+ notifyListeners(propPath);
630
+ }
631
+ return result;
632
+ }
633
+ return Reflect.deleteProperty(obj, prop);
634
+ }
635
+ });
636
+ // Cache the proxy
637
+ proxyCache.set(target, proxy);
638
+ proxyCacheLRU.add(target, proxy);
639
+ // Track the path for this proxy
640
+ proxyPathMap.set(proxy, [...path]);
641
+ return proxy;
642
+ }
643
+ /**
644
+ * Add custom methods to object targets
645
+ */
646
+ function addObjectMethods(target, path) {
647
+ const methodsToDefine = {};
648
+ if (!('$merge' in target)) {
649
+ methodsToDefine.$merge = {
650
+ value: function (newProps) {
651
+ const currentPath = proxyPathMap.get(this) || path;
652
+ Object.keys(newProps).forEach(key => {
653
+ if (proxyConfig.trackPathUsage) {
654
+ const propPath = [...currentPath, key].join('.');
655
+ pathUsageStats.modifiedPaths.add(propPath);
656
+ }
657
+ const newValue = newProps[key];
658
+ if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
659
+ const newPath = [...currentPath, key];
660
+ // Use Reflect.set with this proxy as the receiver to trigger the set handler
661
+ Reflect.set(this, key, createAdvancedProxy(newValue, newPath, 0), this);
662
+ }
663
+ else {
664
+ // Use Reflect.set with this proxy as the receiver to trigger the set handler
665
+ Reflect.set(this, key, newValue, this);
666
+ }
667
+ });
668
+ return this;
669
+ },
670
+ enumerable: false
671
+ };
672
+ }
673
+ if (!('$set' in target)) {
674
+ methodsToDefine.$set = {
675
+ value: function (newProps) {
676
+ const currentPath = proxyPathMap.get(this) || path;
677
+ // Clear existing properties
678
+ Object.keys(this).forEach(key => {
679
+ if (typeof this[key] !== 'function') {
680
+ Reflect.deleteProperty(this, key);
681
+ }
682
+ });
683
+ // Set new properties
684
+ Object.keys(newProps || {}).forEach(key => {
685
+ if (proxyConfig.trackPathUsage) {
686
+ const propPath = [...currentPath, key].join('.');
687
+ pathUsageStats.modifiedPaths.add(propPath);
688
+ }
689
+ const newValue = newProps[key];
690
+ if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
691
+ const newPath = [...currentPath, key];
692
+ // Use Reflect.set with this proxy as the receiver to trigger the set handler
693
+ Reflect.set(this, key, createAdvancedProxy(newValue, newPath, 0), this);
694
+ }
695
+ else {
696
+ // Use Reflect.set with this proxy as the receiver to trigger the set handler
697
+ Reflect.set(this, key, newValue, this);
698
+ }
699
+ });
700
+ return this;
701
+ },
702
+ enumerable: false
703
+ };
704
+ }
705
+ if (!('$delete' in target)) {
706
+ methodsToDefine.$delete = {
707
+ value: function (keys) {
708
+ proxyPathMap.get(this) || path;
709
+ const keysToDelete = Array.isArray(keys) ? keys : [keys];
710
+ keysToDelete.forEach(key => {
711
+ // Use Reflect.deleteProperty to trigger the deleteProperty handler
712
+ Reflect.deleteProperty(this, key);
713
+ });
714
+ return this;
715
+ },
716
+ enumerable: false
717
+ };
718
+ }
719
+ if (!('$update' in target)) {
720
+ methodsToDefine.$update = {
721
+ value: function (key, updater) {
722
+ const currentPath = proxyPathMap.get(this) || path;
723
+ if (proxyConfig.trackPathUsage && currentPath.length > 0) {
724
+ const propPath = [...currentPath, key].join('.');
725
+ pathUsageStats.modifiedPaths.add(propPath);
726
+ }
727
+ const currentValue = this[key];
728
+ let newValue = updater(currentValue);
729
+ if (newValue && typeof newValue === 'object' && !proxyCache.has(newValue)) {
730
+ const newPath = [...currentPath, key];
731
+ newValue = createAdvancedProxy(newValue, newPath, 0);
732
+ }
733
+ // Use Reflect.set with this proxy as the receiver to trigger the set handler
734
+ Reflect.set(this, key, newValue, this);
735
+ return this;
736
+ },
737
+ enumerable: false
738
+ };
739
+ }
740
+ if (!('$reset' in target)) {
741
+ methodsToDefine.$reset = {
742
+ value: function () {
743
+ const currentPath = proxyPathMap.get(this) || path;
744
+ let initialValue = initialStoreState$1;
745
+ for (const segment of currentPath) {
746
+ if (initialValue && typeof initialValue === 'object' && segment in initialValue) {
747
+ initialValue = initialValue[segment];
748
+ }
749
+ else {
750
+ initialValue = undefined;
751
+ break;
752
+ }
753
+ }
754
+ if (initialValue !== undefined) {
755
+ // Clear existing properties
756
+ Object.keys(this).forEach((key) => {
757
+ if (typeof this[key] !== 'function') {
758
+ Reflect.deleteProperty(this, key);
759
+ }
760
+ });
761
+ // Set new properties
762
+ if (initialValue && typeof initialValue === 'object') {
763
+ Object.entries(initialValue).forEach(([key, value]) => {
764
+ // Use Reflect.set with this proxy as the receiver to trigger the set handler
765
+ Reflect.set(this, key, JSON.parse(JSON.stringify(value)), this);
766
+ });
767
+ }
768
+ }
769
+ return this;
770
+ },
771
+ enumerable: false
772
+ };
773
+ }
774
+ if (!('raw' in target)) {
775
+ methodsToDefine.raw = {
776
+ value: function () {
777
+ return JSON.parse(JSON.stringify(this));
778
+ },
779
+ enumerable: false,
780
+ configurable: true
781
+ };
782
+ }
783
+ if (Object.keys(methodsToDefine).length > 0) {
784
+ Object.defineProperties(target, methodsToDefine);
785
+ }
786
+ }
787
+ /**
788
+ * Add custom methods to array targets
789
+ */
790
+ function addArrayMethods(target, path) {
791
+ const originalPush = target.push;
792
+ const originalSplice = target.splice;
793
+ // Override push
794
+ Object.defineProperty(target, 'push', {
795
+ value: function (...items) {
796
+ const currentPath = proxyPathMap.get(this) || path;
797
+ const processedItems = items.map(item => {
798
+ if (item && typeof item === 'object' && !proxyCache.has(item)) {
799
+ const itemPath = [...currentPath, '_item'];
800
+ return createAdvancedProxy(item, itemPath, 0);
801
+ }
802
+ return item;
803
+ });
804
+ const result = originalPush.apply(this, processedItems);
805
+ if (currentPath.length > 0) {
806
+ if (proxyConfig.trackPathUsage) {
807
+ pathUsageStats.modifiedPaths.add(currentPath.join('.'));
808
+ }
809
+ notifyListeners(currentPath);
810
+ }
811
+ return result;
812
+ },
813
+ writable: true,
814
+ configurable: true
815
+ });
816
+ // Override splice
817
+ Object.defineProperty(target, 'splice', {
818
+ value: function (start, deleteCount, ...items) {
819
+ const currentPath = proxyPathMap.get(this) || path;
820
+ const arrayLength = this.length;
821
+ const processedItems = items.map((item, index) => {
822
+ if (item && typeof item === 'object' && !proxyCache.has(item)) {
823
+ const itemPath = [...currentPath, (start + index).toString()];
824
+ return createAdvancedProxy(item, itemPath, 0);
825
+ }
826
+ return item;
827
+ });
828
+ const actualDeleteCount = deleteCount === undefined ? (arrayLength - start) : deleteCount;
829
+ const result = originalSplice.apply(this, [start, actualDeleteCount, ...processedItems]);
830
+ if (currentPath.length > 0) {
831
+ if (proxyConfig.trackPathUsage) {
832
+ pathUsageStats.modifiedPaths.add(currentPath.join('.'));
833
+ }
834
+ // Notify the array itself
835
+ notifyListeners(currentPath);
836
+ // Also notify about each index that was affected
837
+ for (let i = start; i < arrayLength; i++) {
838
+ const indexPath = [...currentPath, i.toString()];
839
+ notifyListeners(indexPath);
840
+ }
841
+ }
842
+ return result;
843
+ },
844
+ writable: true,
845
+ configurable: true
846
+ });
847
+ // Add $set method for arrays
848
+ if (!('$set' in target)) {
849
+ Object.defineProperty(target, '$set', {
850
+ value: function (newArray) {
851
+ if (!Array.isArray(newArray)) {
852
+ console.error('$set on array must be called with an array');
853
+ return this;
854
+ }
855
+ const currentPath = proxyPathMap.get(this) || path;
856
+ this.length = 0;
857
+ const processedItems = newArray.map((item, index) => {
858
+ if (item && typeof item === 'object' && !proxyCache.has(item)) {
859
+ const itemPath = [...currentPath, index.toString()];
860
+ return createAdvancedProxy(item, itemPath, 0);
861
+ }
862
+ return item;
863
+ });
864
+ originalPush.apply(this, processedItems);
865
+ if (currentPath.length > 0) {
866
+ if (proxyConfig.trackPathUsage) {
867
+ pathUsageStats.modifiedPaths.add(currentPath.join('.'));
868
+ }
869
+ notifyListeners(currentPath);
870
+ }
871
+ return this;
872
+ },
873
+ enumerable: false,
874
+ configurable: true
875
+ });
876
+ }
877
+ // Add $reset method for arrays
878
+ if (!('$reset' in target)) {
879
+ Object.defineProperty(target, '$reset', {
880
+ value: function () {
881
+ const currentPath = proxyPathMap.get(this) || path;
882
+ let initialValue = initialStoreState$1;
883
+ for (const segment of currentPath) {
884
+ if (initialValue && typeof initialValue === 'object' && segment in initialValue) {
885
+ initialValue = initialValue[segment];
886
+ }
887
+ else {
888
+ initialValue = [];
889
+ break;
890
+ }
891
+ }
892
+ if (!Array.isArray(initialValue)) {
893
+ initialValue = [];
894
+ }
895
+ this.length = 0;
896
+ const processedItems = initialValue.map((item, index) => {
897
+ if (item && typeof item === 'object' && !proxyCache.has(item)) {
898
+ const itemPath = [...currentPath, index.toString()];
899
+ return createAdvancedProxy(item, itemPath, 0);
900
+ }
901
+ return item;
902
+ });
903
+ if (processedItems.length > 0) {
904
+ originalPush.apply(this, processedItems);
905
+ }
906
+ if (currentPath.length > 0) {
907
+ notifyListeners(currentPath);
908
+ }
909
+ return this;
910
+ },
911
+ enumerable: false,
912
+ configurable: true
913
+ });
914
+ }
915
+ // Add raw method
916
+ if (!('raw' in target)) {
917
+ Object.defineProperty(target, 'raw', {
918
+ value: function () {
919
+ return JSON.parse(JSON.stringify(this));
920
+ },
921
+ enumerable: false,
922
+ configurable: true
923
+ });
924
+ }
925
+ }
926
+ /**
927
+ * Clear proxy cache
928
+ */
929
+ function clearProxyCache() {
930
+ proxyCache = new WeakMap();
931
+ proxyCacheLRU.clear();
932
+ proxyCacheStats.cacheHits = 0;
933
+ proxyCacheStats.cacheMisses = 0;
934
+ proxyCacheStats.activeCachedProxies = 0;
935
+ if (monitoringConfig.enabled) {
936
+ console.log('🧹 Proxy cache cleared');
937
+ }
938
+ }
939
+ /**
940
+ * Get proxy cache statistics
941
+ */
942
+ function getProxyCacheStats() {
943
+ return {
944
+ ...proxyCacheStats,
945
+ hitRatio: proxyCacheStats.cacheHits / (proxyCacheStats.cacheHits + proxyCacheStats.cacheMisses || 1),
946
+ estimatedCacheSize: proxyCacheLRU.size()
947
+ };
948
+ }
949
+ /**
950
+ * Optimize memory usage
951
+ */
952
+ function optimizeMemoryUsage(aggressive = false) {
953
+ pathUsageStats.accessedPaths.clear();
954
+ pathUsageStats.modifiedPaths.clear();
955
+ const evictedCount = aggressive ?
956
+ proxyCacheLRU.evictPercentage(0.8) :
957
+ proxyCacheLRU.evictPercentage(0.4);
958
+ if (aggressive && typeof global.gc === 'function') {
959
+ try {
960
+ global.gc();
961
+ if (monitoringConfig.enabled) {
962
+ console.log('🧹 Forced garbage collection');
963
+ }
964
+ }
965
+ catch (e) {
966
+ console.error('Failed to force garbage collection:', e);
967
+ }
968
+ }
969
+ if (monitoringConfig.enabled) {
970
+ console.log(`🧹 Memory optimization complete. Evicted ${evictedCount} cached proxies`);
971
+ }
972
+ return {
973
+ proxiesEvicted: evictedCount,
974
+ currentMemoryEstimate: memoryPressure.estimateMemoryUsage(),
975
+ selectorPathsRemaining: selectorPaths.size
976
+ };
977
+ }
978
+
979
+ // Track the current path we're accessing during a selector function
980
+ let currentPath = [];
981
+ /**
982
+ * Track dependencies during selector execution - tracks individual path segments
983
+ */
984
+ function trackDependencies(selector) {
985
+ currentPath = [];
986
+ // Execute selector to track dependencies
987
+ const value = selector();
988
+ // Clean up and return individual path segments (not full paths)
989
+ const cleanedPaths = [...currentPath].filter(segment => {
990
+ // Filter out segments that would create overly long paths
991
+ return segment && segment.length < 100; // Basic length check for individual segments
992
+ });
993
+ currentPath = [];
994
+ return { value, paths: cleanedPaths };
995
+ }
996
+
997
+ /**
998
+ * Hook to subscribe to the global store and re-render when specific data changes.
999
+ *
1000
+ * The hook tracks which store paths your selector function accesses and only
1001
+ * re-renders the component when those specific paths change. This provides
1002
+ * fine-grained reactivity without unnecessary renders.
1003
+ *
1004
+ * For objects, the returned value includes custom methods ($merge, $set, $delete, $update)
1005
+ * that allow you to modify the data directly and trigger reactive updates.
1006
+ *
1007
+ * @example
1008
+ * // Subscribe to user data
1009
+ * const user = useScope(() => $.user);
1010
+ *
1011
+ * // Update user data directly (triggers re-render only for components using $.user)
1012
+ * user.$merge({ name: 'New Name' });
1013
+ *
1014
+ * // Subscribe to a specific property
1015
+ * const userName = useScope(() => $.user.name);
1016
+ *
1017
+ * // Subscribe to a computed value
1018
+ * const isAdmin = useScope(() => $.user.role === 'admin');
1019
+ *
1020
+ * @param selector - Function that returns the data you want to subscribe to
1021
+ * @returns The selected data, with custom methods attached if it's an object
1022
+ */
1023
+ function useScope(selector) {
1024
+ // Use ref to store the latest selector to avoid stale closures
1025
+ const selectorRef = react.useRef(selector);
1026
+ selectorRef.current = selector;
1027
+ // Track dependencies and get initial value with advanced tracking
1028
+ const { value: initialValue, paths: trackedPaths } = trackDependencies(selector);
1029
+ // Add tracked paths to selector paths for ultra-selective proxying
1030
+ trackedPaths.forEach(path => {
1031
+ selectorPaths.add(path);
1032
+ pathUsageStats.subscribedPaths.add(path);
1033
+ });
1034
+ // Use a counter to force re-renders instead of storing the value
1035
+ // This way we always return the fresh proxy object from the selector
1036
+ const [, forceUpdate] = react.useState(0);
1037
+ // Create stable update handler that forces re-render
1038
+ const handleChange = react.useCallback(() => {
1039
+ try {
1040
+ forceUpdate(count => count + 1);
1041
+ }
1042
+ catch (error) {
1043
+ console.error('Error in useScope update:', error);
1044
+ }
1045
+ }, []);
1046
+ react.useEffect(() => {
1047
+ // Create path keys for subscription using the original approach
1048
+ // If trackedPaths is ['user', 'name'], create subscriptions for ['user', 'user.name']
1049
+ const pathKeys = trackedPaths.length > 0
1050
+ ? trackedPaths.map((_, index, array) => array.slice(0, index + 1).join('.'))
1051
+ : [''];
1052
+ // Subscribe to all relevant paths
1053
+ const unsubscribeFunctions = pathKeys.map(pathKey => {
1054
+ // Mark this path as subscribed for usage tracking
1055
+ pathUsageStats.subscribedPaths.add(pathKey);
1056
+ return subscribe(pathKey, handleChange);
1057
+ });
1058
+ // Clean up subscriptions on unmount or dependency change
1059
+ return () => {
1060
+ unsubscribeFunctions.forEach(unsubscribe => unsubscribe());
1061
+ };
1062
+ }, [trackedPaths.join(','), handleChange]); // Stable dependencies
1063
+ // Always return the fresh result from the selector (preserves proxy and methods)
1064
+ return selectorRef.current();
1065
+ }
1066
+
1067
+ /**
1068
+ * Hook to create reactive local state that persists across component re-renders.
1069
+ *
1070
+ * This creates a reactive proxy object with custom methods ($merge, $set, $update, etc.)
1071
+ * that can be used to update local component state. Unlike the global store,
1072
+ * this state is isolated to the component instance.
1073
+ *
1074
+ * The state persists across re-renders but is reset when the component unmounts.
1075
+ *
1076
+ * @example
1077
+ * function MyComponent() {
1078
+ * const localState = useLocal({ count: 0, name: 'John' });
1079
+ *
1080
+ * return (
1081
+ * <div>
1082
+ * <p>Count: {localState.count}</p>
1083
+ * <button onClick={() => localState.$merge({ count: localState.count + 1 })}>
1084
+ * Increment
1085
+ * </button>
1086
+ * <button onClick={() => localState.$update('name', name => name.toUpperCase())}>
1087
+ * Uppercase Name
1088
+ * </button>
1089
+ * </div>
1090
+ * );
1091
+ * }
1092
+ *
1093
+ * @param initialState - The initial state object
1094
+ * @returns A reactive proxy object with custom methods for state updates
1095
+ */
1096
+ function useLocal(initialState) {
1097
+ // Create the reactive state only once using lazy initialization
1098
+ const [localState] = react.useState(() => createAdvancedProxy(initialState));
1099
+ return localState;
1100
+ }
1101
+
1102
+ // Default store structure - can be overridden via configuration
1103
+ const defaultStore = {
1104
+ user: {
1105
+ name: 'John Doe',
1106
+ age: 30,
1107
+ },
1108
+ };
1109
+ // The actual store that will be used - start with default but allow override
1110
+ let store = defaultStore;
1111
+ // Store a deep clone of the initial store state for use with $reset
1112
+ let initialStoreState = JSON.parse(JSON.stringify(store));
1113
+ /**
1114
+ * Initialize the store with custom initial state
1115
+ * This function updates both the store and its type
1116
+ */
1117
+ function initializeStore(config = {}) {
1118
+ if (config.initialState) {
1119
+ // Update the actual store with the new state
1120
+ store = { ...config.initialState };
1121
+ initialStoreState = JSON.parse(JSON.stringify(store));
1122
+ if (typeof window !== 'undefined') {
1123
+ console.log('🏪 Store initialized with custom state');
1124
+ }
1125
+ return store;
1126
+ }
1127
+ else {
1128
+ // Use default store
1129
+ store = { ...defaultStore };
1130
+ initialStoreState = JSON.parse(JSON.stringify(store));
1131
+ return store;
1132
+ }
1133
+ }
1134
+ /**
1135
+ * Get the current store (for debugging)
1136
+ */
1137
+ function getStore() {
1138
+ return store;
1139
+ }
1140
+ /**
1141
+ * Reset the entire store to its initial state
1142
+ */
1143
+ function resetStore() {
1144
+ // Clear current store
1145
+ Object.keys(store).forEach(key => {
1146
+ delete store[key];
1147
+ });
1148
+ // Restore initial state
1149
+ Object.assign(store, JSON.parse(JSON.stringify(initialStoreState)));
1150
+ if (typeof window !== 'undefined') {
1151
+ console.log('🔄 Store reset to initial state');
1152
+ }
1153
+ }
1154
+
1155
+ /**
1156
+ * Perform automatic leak check
1157
+ */
1158
+ function performAutoLeakCheck() {
1159
+ if (typeof window === 'undefined')
1160
+ return;
1161
+ if (!monitoringConfig.enabled || !monitoringConfig.autoLeakDetection ||
1162
+ monitoringStats.leakDetection.isLeakDetectionRunning) {
1163
+ return;
1164
+ }
1165
+ monitoringStats.leakDetection.isLeakDetectionRunning = true;
1166
+ try {
1167
+ console.log(`Running automatic leak detection (check #${monitoringStats.leakDetection.totalChecks + 1})`);
1168
+ const leakReport = monitorAPI.checkForLeaks(false);
1169
+ if (!leakReport)
1170
+ return;
1171
+ // Only show detailed report if threshold is exceeded
1172
+ if (leakReport.orphanedListeners >= monitoringConfig.leakAlertThreshold) {
1173
+ console.warn(`MEMORY LEAK ALERT: ${leakReport.orphanedListeners} orphaned listeners detected!`);
1174
+ console.log('Paths with potential leaks:');
1175
+ leakReport.mismatchedCounts.forEach(msg => console.log(`- ${msg}`));
1176
+ monitoringStats.leakDetection.leaksDetected++;
1177
+ // Auto-optimize if many leaks found
1178
+ if (leakReport.orphanedListeners > monitoringConfig.leakAlertThreshold * 2) {
1179
+ console.log('Auto-optimizing memory due to high leak count...');
1180
+ optimizeMemoryUsage(false);
1181
+ }
1182
+ }
1183
+ monitoringStats.leakDetection.lastCheckTime = Date.now();
1184
+ monitoringStats.leakDetection.totalChecks++;
1185
+ monitoringStats.leakDetection.renderCyclesSinceCheck = 0;
1186
+ }
1187
+ catch (e) {
1188
+ console.error('Error during automatic leak check:', e);
1189
+ }
1190
+ finally {
1191
+ monitoringStats.leakDetection.isLeakDetectionRunning = false;
1192
+ }
1193
+ }
1194
+ /**
1195
+ * Monitor API for debugging and memory leak detection
1196
+ */
1197
+ const monitorAPI = {
1198
+ // Enable or disable monitoring
1199
+ setEnabled: (enabled) => {
1200
+ if (typeof window === 'undefined')
1201
+ return;
1202
+ monitoringConfig.enabled = enabled;
1203
+ },
1204
+ // Set verbose logging
1205
+ setVerboseLogging: (enabled) => {
1206
+ if (typeof window === 'undefined')
1207
+ return;
1208
+ monitoringConfig.verboseLogging = enabled;
1209
+ },
1210
+ // Configure specific monitoring features
1211
+ configure: (config) => {
1212
+ Object.assign(monitoringConfig, config);
1213
+ },
1214
+ // Get current stats
1215
+ getStats: () => ({ ...monitoringStats }),
1216
+ // Get detailed listener info
1217
+ getListenerInfo: () => {
1218
+ const info = {};
1219
+ pathListeners.forEach((listeners, path) => {
1220
+ info[path] = listeners.size;
1221
+ });
1222
+ return info;
1223
+ },
1224
+ // Check for potential memory leaks
1225
+ checkForLeaks: (logReport = true) => {
1226
+ if (typeof window === 'undefined')
1227
+ return null;
1228
+ const leakReport = {
1229
+ orphanedListeners: 0,
1230
+ mismatchedCounts: [],
1231
+ emptyPaths: [],
1232
+ totalListeners: 0,
1233
+ summary: ''
1234
+ };
1235
+ // Check for paths with listeners but no subscribers in our tracking
1236
+ pathListeners.forEach((listeners, path) => {
1237
+ leakReport.totalListeners += listeners.size;
1238
+ const trackedCount = monitoringStats.pathSubscriptionCounts[path] || 0;
1239
+ if (listeners.size > 0 && trackedCount <= 0) {
1240
+ leakReport.orphanedListeners += listeners.size;
1241
+ leakReport.mismatchedCounts.push(`Path ${path}: actual=${listeners.size}, tracked=${trackedCount}`);
1242
+ }
1243
+ if (listeners.size === 0) {
1244
+ leakReport.emptyPaths.push(path);
1245
+ }
1246
+ });
1247
+ leakReport.summary = `Found ${leakReport.orphanedListeners} potential leaked listeners across ${leakReport.mismatchedCounts.length} paths. Total listeners: ${leakReport.totalListeners}`;
1248
+ if (logReport) {
1249
+ console.log('LEAK CHECK REPORT:');
1250
+ console.log(leakReport.summary);
1251
+ if (leakReport.mismatchedCounts.length > 0) {
1252
+ console.log('Paths with potential leaks:');
1253
+ leakReport.mismatchedCounts.forEach(msg => console.log(`- ${msg}`));
1254
+ }
1255
+ }
1256
+ return leakReport;
1257
+ },
1258
+ // Start automatic leak detection
1259
+ startAutoLeakDetection: (interval = 30000) => {
1260
+ if (typeof window === 'undefined')
1261
+ return false;
1262
+ monitoringConfig.autoLeakDetection = true;
1263
+ monitoringConfig.leakDetectionInterval = interval;
1264
+ // Clear any existing timer
1265
+ if (monitoringStats.leakDetection.leakDetectionTimer) {
1266
+ clearInterval(monitoringStats.leakDetection.leakDetectionTimer);
1267
+ }
1268
+ // Set up periodic leak detection
1269
+ monitoringStats.leakDetection.leakDetectionTimer = setInterval(() => {
1270
+ if (monitoringStats.leakDetection.renderCyclesSinceCheck >= monitoringConfig.leakScanMinimumRenderCycles) {
1271
+ performAutoLeakCheck();
1272
+ }
1273
+ }, interval);
1274
+ console.log(`Automatic leak detection started (interval: ${interval}ms)`);
1275
+ return true;
1276
+ },
1277
+ // Stop automatic leak detection
1278
+ stopAutoLeakDetection: () => {
1279
+ if (typeof window === 'undefined')
1280
+ return false;
1281
+ monitoringConfig.autoLeakDetection = false;
1282
+ if (monitoringStats.leakDetection.leakDetectionTimer) {
1283
+ clearInterval(monitoringStats.leakDetection.leakDetectionTimer);
1284
+ monitoringStats.leakDetection.leakDetectionTimer = null;
1285
+ }
1286
+ console.log('Automatic leak detection stopped');
1287
+ return true;
1288
+ },
1289
+ // Configure leak detection
1290
+ configureLeakDetection: (config) => {
1291
+ if (typeof window === 'undefined')
1292
+ return;
1293
+ if (config.enabled !== undefined) {
1294
+ monitoringConfig.autoLeakDetection = config.enabled;
1295
+ }
1296
+ if (config.interval !== undefined) {
1297
+ monitoringConfig.leakDetectionInterval = config.interval;
1298
+ }
1299
+ if (config.alertThreshold !== undefined) {
1300
+ monitoringConfig.leakAlertThreshold = config.alertThreshold;
1301
+ }
1302
+ if (config.minimumRenderCycles !== undefined) {
1303
+ monitoringConfig.leakScanMinimumRenderCycles = config.minimumRenderCycles;
1304
+ }
1305
+ // Restart if enabled
1306
+ if (monitoringConfig.autoLeakDetection) {
1307
+ monitorAPI.startAutoLeakDetection(monitoringConfig.leakDetectionInterval);
1308
+ }
1309
+ else {
1310
+ monitorAPI.stopAutoLeakDetection();
1311
+ }
1312
+ console.log('Leak detection configuration updated:', {
1313
+ enabled: monitoringConfig.autoLeakDetection,
1314
+ interval: monitoringConfig.leakDetectionInterval,
1315
+ alertThreshold: monitoringConfig.leakAlertThreshold,
1316
+ minimumRenderCycles: monitoringConfig.leakScanMinimumRenderCycles
1317
+ });
1318
+ },
1319
+ // Get leak detection stats
1320
+ getLeakDetectionStats: () => ({
1321
+ isEnabled: monitoringConfig.autoLeakDetection,
1322
+ interval: monitoringConfig.leakDetectionInterval,
1323
+ checksPerformed: monitoringStats.leakDetection.totalChecks,
1324
+ leaksDetected: monitoringStats.leakDetection.leaksDetected,
1325
+ alertThreshold: monitoringConfig.leakAlertThreshold
1326
+ }),
1327
+ // Reset statistics
1328
+ resetStats: () => {
1329
+ monitoringStats.proxyCount = 0;
1330
+ monitoringStats.totalSubscriptionsCreated = 0;
1331
+ monitoringStats.totalSubscriptionsRemoved = 0;
1332
+ monitoringStats.activeSubscriptions = 0;
1333
+ monitoringStats.pathSubscriptionCounts = {};
1334
+ monitoringStats.timings = {
1335
+ lastNotifyTime: 0,
1336
+ lastPersistTime: 0,
1337
+ averageNotifyTime: 0,
1338
+ averagePersistTime: 0,
1339
+ notifyTimeTotal: 0,
1340
+ persistTimeTotal: 0,
1341
+ notifyCount: 0,
1342
+ persistCount: 0,
1343
+ };
1344
+ console.log('Monitoring statistics reset');
1345
+ },
1346
+ // Force cleanup of empty listener sets
1347
+ cleanupEmptyListeners: () => {
1348
+ let cleanedCount = 0;
1349
+ pathListeners.forEach((listeners, path) => {
1350
+ if (listeners.size === 0) {
1351
+ pathListeners.delete(path);
1352
+ cleanedCount++;
1353
+ }
1354
+ });
1355
+ console.log(`Cleaned up ${cleanedCount} empty listener sets`);
1356
+ return cleanedCount;
1357
+ },
1358
+ // Get proxy cache stats (delegated to proxy module)
1359
+ getProxyCacheStats,
1360
+ // Get proxy usage stats
1361
+ getProxyUsageStats: () => ({
1362
+ accessedPaths: Array.from(pathUsageStats.accessedPaths),
1363
+ modifiedPaths: Array.from(pathUsageStats.modifiedPaths),
1364
+ subscribedPaths: Array.from(pathUsageStats.subscribedPaths),
1365
+ totalAccessedPaths: pathUsageStats.accessedPaths.size,
1366
+ totalModifiedPaths: pathUsageStats.modifiedPaths.size,
1367
+ totalSubscribedPaths: pathUsageStats.subscribedPaths.size,
1368
+ }),
1369
+ // Reset usage stats
1370
+ resetProxyUsageStats: () => {
1371
+ pathUsageStats.accessedPaths.clear();
1372
+ pathUsageStats.modifiedPaths.clear();
1373
+ // Don't clear subscribedPaths as they're used for optimization
1374
+ console.log('Proxy usage statistics reset (except subscriptions)');
1375
+ },
1376
+ // Generate report of most accessed/modified paths for optimization
1377
+ generatePathUsageReport: () => {
1378
+ const report = {
1379
+ mostAccessedPaths: Array.from(pathUsageStats.accessedPaths).slice(0, 20),
1380
+ mostModifiedPaths: Array.from(pathUsageStats.modifiedPaths).slice(0, 20),
1381
+ subscribedPaths: Array.from(pathUsageStats.subscribedPaths),
1382
+ recommendations: []
1383
+ };
1384
+ // Generate recommendations
1385
+ if (pathUsageStats.accessedPaths.size > 100) {
1386
+ report.recommendations.push(`Consider adding frequently accessed paths to preProxyPaths: ${report.mostAccessedPaths.slice(0, 5).join(', ')}`);
1387
+ }
1388
+ return report;
1389
+ },
1390
+ // Memory-optimized leak detection
1391
+ optimizeMemoryUsage,
1392
+ // Get selector path statistics
1393
+ getSelectorPathStats: () => {
1394
+ return {
1395
+ totalSelectorPaths: selectorPaths.size,
1396
+ paths: Array.from(selectorPaths),
1397
+ mostAccessedSelectorPaths: Array.from(selectorPaths).slice(0, 20),
1398
+ };
1399
+ },
1400
+ // Clear selector paths (force rebuilding)
1401
+ clearSelectorPaths: () => {
1402
+ const count = selectorPaths.size;
1403
+ selectorPaths.clear();
1404
+ console.log(`Cleared ${count} selector paths. They will be rebuilt as components render.`);
1405
+ return count;
1406
+ },
1407
+ // Get comprehensive system status
1408
+ getSystemStatus: () => {
1409
+ const proxyCacheStats = getProxyCacheStats();
1410
+ const leakStats = monitorAPI.getLeakDetectionStats();
1411
+ return {
1412
+ monitoring: {
1413
+ enabled: monitoringConfig.enabled,
1414
+ verboseLogging: monitoringConfig.verboseLogging,
1415
+ autoLeakDetection: monitoringConfig.autoLeakDetection,
1416
+ },
1417
+ listeners: {
1418
+ total: monitoringStats.activeSubscriptions,
1419
+ pathCount: pathListeners.size,
1420
+ },
1421
+ proxies: {
1422
+ cacheSize: proxyCacheStats.estimatedCacheSize,
1423
+ hitRatio: proxyCacheStats.hitRatio,
1424
+ totalCreated: proxyCacheStats.totalProxiesCreated,
1425
+ },
1426
+ paths: {
1427
+ accessed: pathUsageStats.accessedPaths.size,
1428
+ modified: pathUsageStats.modifiedPaths.size,
1429
+ subscribed: pathUsageStats.subscribedPaths.size,
1430
+ selectors: selectorPaths.size,
1431
+ },
1432
+ leakDetection: leakStats,
1433
+ performance: {
1434
+ averageNotifyTime: monitoringStats.timings.averageNotifyTime,
1435
+ averagePersistTime: monitoringStats.timings.averagePersistTime,
1436
+ totalNotifications: monitoringStats.timings.notifyCount,
1437
+ totalPersistOperations: monitoringStats.timings.persistCount,
1438
+ }
1439
+ };
1440
+ },
1441
+ // Force a comprehensive health check
1442
+ performHealthCheck: () => {
1443
+ console.log('Performing comprehensive health check...');
1444
+ const leakReport = monitorAPI.checkForLeaks(true);
1445
+ const systemStatus = monitorAPI.getSystemStatus();
1446
+ const cleanedListeners = monitorAPI.cleanupEmptyListeners();
1447
+ const recommendations = [];
1448
+ if (leakReport && leakReport.orphanedListeners > 5) {
1449
+ recommendations.push('🚨 High number of orphaned listeners detected - consider running optimizeMemoryUsage()');
1450
+ }
1451
+ if (systemStatus.proxies.hitRatio < 0.8) {
1452
+ recommendations.push('📈 Low proxy cache hit ratio - consider reviewing proxy configuration');
1453
+ }
1454
+ if (systemStatus.paths.accessed > 1000) {
1455
+ recommendations.push('📊 High number of accessed paths - consider enabling ultraSelectiveProxying');
1456
+ }
1457
+ const healthScore = Math.max(0, 100 -
1458
+ (leakReport ? leakReport.orphanedListeners * 5 : 0) -
1459
+ (systemStatus.proxies.hitRatio < 0.8 ? 20 : 0) -
1460
+ (systemStatus.paths.accessed > 1000 ? 15 : 0));
1461
+ console.log(`Health Check Complete - Score: ${healthScore}/100`);
1462
+ if (recommendations.length > 0) {
1463
+ console.log('Recommendations:');
1464
+ recommendations.forEach(rec => console.log(` ${rec}`));
1465
+ }
1466
+ return {
1467
+ healthScore,
1468
+ recommendations,
1469
+ systemStatus,
1470
+ leakReport,
1471
+ cleanedListeners
1472
+ };
1473
+ }
1474
+ };
1475
+ // Initialize auto leak detection if configured
1476
+ if (typeof window !== 'undefined' && monitoringConfig.enabled && monitoringConfig.autoLeakDetection) {
1477
+ setTimeout(() => monitorAPI.startAutoLeakDetection(), 5000); // Start after 5 seconds
1478
+ }
1479
+
1480
+ let storage = null;
1481
+ /**
1482
+ * Initialize storage for persistence
1483
+ */
1484
+ function initializeStorage() {
1485
+ try {
1486
+ if (typeof window !== 'undefined') {
1487
+ storage = localforage.createInstance({
1488
+ name: 'SCOPE_STATE',
1489
+ description: 'Scope state management storage'
1490
+ });
1491
+ // Add memory driver as fallback
1492
+ localforage.defineDriver(memoryDriver__namespace);
1493
+ localforage.setDriver([
1494
+ localforage.INDEXEDDB,
1495
+ localforage.LOCALSTORAGE,
1496
+ localforage.WEBSQL,
1497
+ memoryDriver__namespace._driver
1498
+ ]);
1499
+ console.log('💾 Storage initialized successfully');
1500
+ }
1501
+ }
1502
+ catch (error) {
1503
+ console.error('❌ Error creating storage instance:', error);
1504
+ storage = null;
1505
+ }
1506
+ }
1507
+ /**
1508
+ * Get the current storage instance
1509
+ */
1510
+ function getStorage() {
1511
+ return storage;
1512
+ }
1513
+ // Initialize storage by default
1514
+ if (typeof window !== 'undefined') {
1515
+ initializeStorage();
1516
+ }
1517
+
1518
+ // Storage constants
1519
+ const PERSISTED_STATE_KEY = 'persisted_state';
1520
+ const PERSISTENCE_CONFIG_KEY = 'persistence_config';
1521
+ // Batch persistence state
1522
+ const persistenceBatch = {
1523
+ paths: new Set(),
1524
+ timeoutId: null,
1525
+ isPersisting: false,
1526
+ };
1527
+ // Performance timing helpers
1528
+ function logTimestamp(action) {
1529
+ if (!monitoringConfig.enabled || !monitoringConfig.logTimings)
1530
+ return 0;
1531
+ const now = performance.now();
1532
+ console.log(`⏱️ [${now.toFixed(2)}ms] ${action}`);
1533
+ return now;
1534
+ }
1535
+ function logTimingEnd(action, startTime) {
1536
+ if (!monitoringConfig.enabled || !monitoringConfig.logTimings)
1537
+ return 0;
1538
+ const now = performance.now();
1539
+ const duration = now - startTime;
1540
+ console.log(`⏱️ [${now.toFixed(2)}ms] ${action} completed in ${duration.toFixed(2)}ms`);
1541
+ return duration;
1542
+ }
1543
+ /**
1544
+ * Process all paths in the batch
1545
+ */
1546
+ function processPersistenceBatch() {
1547
+ if (typeof window === 'undefined')
1548
+ return;
1549
+ if (persistenceBatch.isPersisting || persistenceBatch.paths.size === 0) {
1550
+ return;
1551
+ }
1552
+ persistenceBatch.isPersisting = true;
1553
+ let startTime = 0;
1554
+ if (monitoringConfig.enabled && monitoringConfig.logPersistence) {
1555
+ startTime = logTimestamp(`💾 Batch persisting ${persistenceBatch.paths.size} paths`);
1556
+ }
1557
+ try {
1558
+ // Convert set to array of path arrays
1559
+ const pathArrays = Array.from(persistenceBatch.paths).map(path => path.split('.'));
1560
+ // Persist entire state if we're persisting many paths
1561
+ if (pathArrays.length > 10) {
1562
+ persistState();
1563
+ }
1564
+ else {
1565
+ // Otherwise persist individual paths
1566
+ pathArrays.forEach(pathArray => {
1567
+ persistState(pathArray);
1568
+ });
1569
+ }
1570
+ // Clear the batch
1571
+ persistenceBatch.paths.clear();
1572
+ }
1573
+ catch (e) {
1574
+ console.error('Error during batch persistence:', e);
1575
+ }
1576
+ finally {
1577
+ persistenceBatch.isPersisting = false;
1578
+ // If new paths were added during processing, schedule another batch
1579
+ if (persistenceBatch.paths.size > 0) {
1580
+ schedulePersistenceBatch();
1581
+ }
1582
+ if (monitoringConfig.enabled && monitoringConfig.logPersistence && startTime > 0) {
1583
+ logTimingEnd('Batch persistence', startTime);
1584
+ }
1585
+ }
1586
+ }
1587
+ /**
1588
+ * Schedule batch persistence with debounce
1589
+ */
1590
+ function schedulePersistenceBatch() {
1591
+ if (persistenceBatch.timeoutId) {
1592
+ clearTimeout(persistenceBatch.timeoutId);
1593
+ }
1594
+ persistenceBatch.timeoutId = setTimeout(() => {
1595
+ persistenceBatch.timeoutId = null;
1596
+ processPersistenceBatch();
1597
+ }, persistenceConfig.batchDelay);
1598
+ }
1599
+ /**
1600
+ * Add a path to the persistence batch
1601
+ */
1602
+ function addToPersistenceBatch(path) {
1603
+ if (!persistenceConfig.enabled)
1604
+ return;
1605
+ const pathKey = path.join('.');
1606
+ if (shouldPersistPath(pathKey)) {
1607
+ persistenceBatch.paths.add(pathKey);
1608
+ schedulePersistenceBatch();
1609
+ }
1610
+ }
1611
+ /**
1612
+ * Function to determine if a path should be persisted
1613
+ */
1614
+ function shouldPersistPath(path) {
1615
+ if (!persistenceConfig.enabled)
1616
+ return false;
1617
+ // Check if path is blacklisted
1618
+ if (persistenceConfig.blacklist.some(blacklistedPath => path === blacklistedPath || path.startsWith(`${blacklistedPath}.`))) {
1619
+ return false;
1620
+ }
1621
+ // If paths array is empty, persist everything not blacklisted
1622
+ if (!persistenceConfig.paths || persistenceConfig.paths.length === 0)
1623
+ return true;
1624
+ // Check if path is in the persistence paths list
1625
+ return persistenceConfig.paths.some(persistedPath => path === persistedPath || path.startsWith(`${persistedPath}.`));
1626
+ }
1627
+ /**
1628
+ * Save state to storage
1629
+ */
1630
+ function persistState(path = []) {
1631
+ if (typeof window === 'undefined')
1632
+ return;
1633
+ if (!persistenceConfig.enabled)
1634
+ return;
1635
+ const currentStorage = getStorage();
1636
+ if (!currentStorage)
1637
+ return;
1638
+ const pathKey = path.join('.');
1639
+ if (path.length === 0) {
1640
+ // We need access to the store to persist entire state
1641
+ // For now, we'll skip this and only support path-specific persistence
1642
+ console.warn('Cannot persist entire state without store reference');
1643
+ return;
1644
+ }
1645
+ if (shouldPersistPath(pathKey)) {
1646
+ // For individual path persistence, we'd need access to the store
1647
+ // This will be implemented when integrating with the main store
1648
+ try {
1649
+ currentStorage.setItem(`${PERSISTED_STATE_KEY}_${pathKey}`, JSON.stringify({}));
1650
+ }
1651
+ catch (e) {
1652
+ console.error(`Error persisting path ${pathKey}:`, e);
1653
+ }
1654
+ }
1655
+ }
1656
+ /**
1657
+ * Hydrate state from storage
1658
+ */
1659
+ async function hydrateState(store) {
1660
+ if (typeof window === 'undefined')
1661
+ return false;
1662
+ if (!store) {
1663
+ console.warn('Cannot hydrate state without store reference');
1664
+ return false;
1665
+ }
1666
+ const currentStorage = getStorage();
1667
+ if (!currentStorage)
1668
+ return false;
1669
+ try {
1670
+ // First try to load entire state
1671
+ const savedState = await currentStorage.getItem(PERSISTED_STATE_KEY);
1672
+ if (savedState) {
1673
+ const parsedState = JSON.parse(savedState);
1674
+ // Direct replacement instead of merging
1675
+ Object.keys(parsedState).forEach(key => {
1676
+ if (key in store) {
1677
+ store[key] = parsedState[key];
1678
+ }
1679
+ });
1680
+ if (monitoringConfig.enabled) {
1681
+ console.log('🔄 State hydrated from storage');
1682
+ }
1683
+ }
1684
+ // Then try to load individual persisted paths
1685
+ const keys = await currentStorage.keys();
1686
+ if (!keys)
1687
+ return true;
1688
+ for (const key of keys) {
1689
+ if (key.startsWith(`${PERSISTED_STATE_KEY}_`)) {
1690
+ const path = key.replace(`${PERSISTED_STATE_KEY}_`, '').split('.');
1691
+ const value = await currentStorage.getItem(key);
1692
+ if (value) {
1693
+ try {
1694
+ const parsedValue = JSON.parse(value);
1695
+ // Set the value in the store
1696
+ let current = store;
1697
+ for (let i = 0; i < path.length - 1; i++) {
1698
+ if (!(path[i] in current)) {
1699
+ current[path[i]] = {};
1700
+ }
1701
+ current = current[path[i]];
1702
+ }
1703
+ const lastKey = path[path.length - 1];
1704
+ current[lastKey] = parsedValue;
1705
+ }
1706
+ catch (e) {
1707
+ console.error(`Error hydrating path ${path.join('.')}:`, e);
1708
+ }
1709
+ }
1710
+ }
1711
+ }
1712
+ if (monitoringConfig.enabled) {
1713
+ console.log(`🔄 Hydrated ${keys.length} individual paths`);
1714
+ }
1715
+ }
1716
+ catch (e) {
1717
+ console.error('Error hydrating state:', e);
1718
+ return false;
1719
+ }
1720
+ return true;
1721
+ }
1722
+ /**
1723
+ * Load persistence configuration
1724
+ */
1725
+ async function loadPersistenceConfig() {
1726
+ if (typeof window === 'undefined')
1727
+ return;
1728
+ const currentStorage = getStorage();
1729
+ if (!currentStorage)
1730
+ return;
1731
+ try {
1732
+ const config = await currentStorage.getItem(PERSISTENCE_CONFIG_KEY);
1733
+ if (config) {
1734
+ const parsedConfig = JSON.parse(config);
1735
+ Object.assign(persistenceConfig, parsedConfig);
1736
+ }
1737
+ }
1738
+ catch (e) {
1739
+ console.error('Error loading persistence configuration:', e);
1740
+ }
1741
+ }
1742
+ /**
1743
+ * Save persistence configuration
1744
+ */
1745
+ function savePersistenceConfig() {
1746
+ if (typeof window === 'undefined')
1747
+ return;
1748
+ const currentStorage = getStorage();
1749
+ if (!currentStorage)
1750
+ return;
1751
+ try {
1752
+ currentStorage.setItem(PERSISTENCE_CONFIG_KEY, JSON.stringify(persistenceConfig));
1753
+ }
1754
+ catch (e) {
1755
+ console.error('Error saving persistence configuration:', e);
1756
+ }
1757
+ }
1758
+ /**
1759
+ * Persistence API - matches the original implementation
1760
+ */
1761
+ const persistenceAPI = {
1762
+ // Enable or disable persistence
1763
+ setEnabled: (enabled) => {
1764
+ persistenceConfig.enabled = enabled;
1765
+ savePersistenceConfig();
1766
+ },
1767
+ // Add paths to be persisted
1768
+ persistPaths: (paths) => {
1769
+ if (!persistenceConfig.paths) {
1770
+ persistenceConfig.paths = [];
1771
+ }
1772
+ persistenceConfig.paths = Array.from(new Set([...persistenceConfig.paths, ...paths]));
1773
+ savePersistenceConfig();
1774
+ // Force persist current state for these paths
1775
+ paths.forEach(path => addToPersistenceBatch(path.split('.')));
1776
+ },
1777
+ // Remove paths from persistence
1778
+ unpersistPaths: (paths) => {
1779
+ if (persistenceConfig.paths) {
1780
+ persistenceConfig.paths = persistenceConfig.paths.filter(p => !paths.includes(p));
1781
+ savePersistenceConfig();
1782
+ }
1783
+ // Remove from storage
1784
+ const currentStorage = getStorage();
1785
+ if (currentStorage) {
1786
+ paths.forEach(path => {
1787
+ currentStorage.removeItem(`${PERSISTED_STATE_KEY}_${path}`);
1788
+ });
1789
+ }
1790
+ },
1791
+ // Add paths to blacklist
1792
+ blacklistPaths: (paths) => {
1793
+ persistenceConfig.blacklist = Array.from(new Set([...persistenceConfig.blacklist, ...paths]));
1794
+ savePersistenceConfig();
1795
+ // Remove from storage
1796
+ const currentStorage = getStorage();
1797
+ if (currentStorage) {
1798
+ paths.forEach(path => {
1799
+ currentStorage.removeItem(`${PERSISTED_STATE_KEY}_${path}`);
1800
+ });
1801
+ }
1802
+ },
1803
+ // Remove paths from blacklist
1804
+ unblacklistPaths: (paths) => {
1805
+ persistenceConfig.blacklist = persistenceConfig.blacklist.filter(p => !paths.includes(p));
1806
+ savePersistenceConfig();
1807
+ },
1808
+ // Get current persistence configuration
1809
+ getConfig: () => ({ ...persistenceConfig }),
1810
+ // Set the batch delay (in ms)
1811
+ setBatchDelay: (delay) => {
1812
+ persistenceConfig.batchDelay = delay;
1813
+ savePersistenceConfig();
1814
+ },
1815
+ // Reset persistence
1816
+ reset: async () => {
1817
+ const currentStorage = getStorage();
1818
+ if (!currentStorage)
1819
+ return;
1820
+ // Clear all persisted state
1821
+ const keys = await currentStorage.keys();
1822
+ if (keys) {
1823
+ for (const key of keys) {
1824
+ if (key.startsWith(PERSISTED_STATE_KEY)) {
1825
+ await currentStorage.removeItem(key);
1826
+ }
1827
+ }
1828
+ }
1829
+ // Reset configuration to defaults
1830
+ Object.assign(persistenceConfig, {
1831
+ enabled: true,
1832
+ paths: [],
1833
+ blacklist: [],
1834
+ batchDelay: 300,
1835
+ });
1836
+ savePersistenceConfig();
1837
+ },
1838
+ // Force persist current state
1839
+ persist: () => persistState(),
1840
+ // Force persist current batch immediately
1841
+ flushBatch: () => {
1842
+ if (persistenceBatch.timeoutId) {
1843
+ clearTimeout(persistenceBatch.timeoutId);
1844
+ persistenceBatch.timeoutId = null;
1845
+ }
1846
+ processPersistenceBatch();
1847
+ },
1848
+ // Force rehydrate state
1849
+ rehydrate: (store) => hydrateState(store),
1850
+ // Get batch status
1851
+ getBatchStatus: () => ({
1852
+ pendingPaths: Array.from(persistenceBatch.paths),
1853
+ isPersisting: persistenceBatch.isPersisting,
1854
+ batchSize: persistenceBatch.paths.size,
1855
+ }),
1856
+ };
1857
+ /**
1858
+ * Initialize persistence system
1859
+ */
1860
+ function initializePersistence() {
1861
+ if (typeof window !== 'undefined') {
1862
+ loadPersistenceConfig();
1863
+ console.log('💾 Advanced persistence system initialized');
1864
+ }
1865
+ }
1866
+ // Initialize automatically
1867
+ initializePersistence();
1868
+
1869
+ // Main exports for the library
1870
+ // Global state
1871
+ let globalStore = {
1872
+ user: {
1873
+ name: 'John Doe',
1874
+ age: 30,
1875
+ },
1876
+ };
1877
+ let globalStoreProxy = null;
1878
+ /**
1879
+ * Configure Scope with custom settings and return a properly typed store
1880
+ * This is the main way to set up Scope with TypeScript support
1881
+ */
1882
+ function configure(config) {
1883
+ // Update configurations
1884
+ if (config.proxy) {
1885
+ Object.assign(proxyConfig, config.proxy);
1886
+ }
1887
+ if (config.monitoring) {
1888
+ Object.assign(monitoringConfig, config.monitoring);
1889
+ }
1890
+ if (config.persistence) {
1891
+ Object.assign(persistenceConfig, config.persistence);
1892
+ }
1893
+ // Update the global store
1894
+ if (config.initialState) {
1895
+ globalStore = { ...config.initialState };
1896
+ // Store initial state for reset functionality
1897
+ setInitialStoreState(globalStore);
1898
+ if (typeof window !== 'undefined' && monitoringConfig.enabled) {
1899
+ console.log('🏪 Store configured with custom state');
1900
+ }
1901
+ }
1902
+ // Create and cache the advanced proxy
1903
+ globalStoreProxy = createAdvancedProxy(globalStore);
1904
+ // Hydrate persisted state if persistence is enabled
1905
+ if (typeof window !== 'undefined' && persistenceConfig.enabled) {
1906
+ hydrateState(globalStore).then((success) => {
1907
+ if (success && monitoringConfig.enabled) {
1908
+ console.log('🔄 State hydrated from persistence');
1909
+ }
1910
+ });
1911
+ }
1912
+ if (typeof window !== 'undefined' && monitoringConfig.enabled) {
1913
+ console.log('🔧 Scope configured with advanced features');
1914
+ }
1915
+ return globalStoreProxy;
1916
+ }
1917
+ // Enhanced tracking that integrates with the advanced proxy system
1918
+ function trackDependenciesAdvanced(selector) {
1919
+ try {
1920
+ // Execute selector and track paths
1921
+ const result = trackDependencies(selector);
1922
+ // Add tracked paths to selector paths for ultra-selective proxying
1923
+ result.paths.forEach(path => {
1924
+ selectorPaths.add(path);
1925
+ pathUsageStats.subscribedPaths.add(path);
1926
+ });
1927
+ return result;
1928
+ }
1929
+ finally {
1930
+ // Cleanup any temporary tracking state
1931
+ }
1932
+ }
1933
+ // Default store for when configure() isn't called
1934
+ if (!globalStoreProxy) {
1935
+ globalStoreProxy = createAdvancedProxy(globalStore);
1936
+ setInitialStoreState(globalStore);
1937
+ }
1938
+ // Export the main $ proxy - will be properly typed if configure() is called first
1939
+ const $ = globalStoreProxy;
1940
+ // Utility functions from the original code
1941
+ function createReactive(obj) {
1942
+ return createAdvancedProxy(obj);
1943
+ }
1944
+ // Legacy alias - use useLocal() hook instead for proper React integration
1945
+ const $local = createReactive;
1946
+ // Development helper to check if an object is reactive
1947
+ function isReactive(obj) {
1948
+ return obj && typeof obj === 'object' && typeof obj.$merge === 'function';
1949
+ }
1950
+ // Type-safe activation functions from the original
1951
+ function activate(obj) {
1952
+ if (obj === null || typeof obj !== 'object') {
1953
+ return obj;
1954
+ }
1955
+ const path = proxyPathMap.get(obj);
1956
+ if (!path) {
1957
+ console.warn('⚠️ Could not determine path for object. Make sure it\'s from the $ tree.');
1958
+ return obj;
1959
+ }
1960
+ if (monitoringConfig.enabled && monitoringConfig.verboseLogging) {
1961
+ console.log(`🔄 Type-safe activation of path: ${path.join('.')}`);
1962
+ }
1963
+ // Add to selector paths
1964
+ selectorPaths.add(path.join('.'));
1965
+ // Add parent paths
1966
+ for (let i = 1; i < path.length; i++) {
1967
+ const parentPath = path.slice(0, i).join('.');
1968
+ selectorPaths.add(parentPath);
1969
+ }
1970
+ return obj;
1971
+ }
1972
+ function $activate(obj) {
1973
+ return activate(obj);
1974
+ }
1975
+ function getProxy(path) {
1976
+ // Handle case where an object from $ is passed directly
1977
+ if (path !== null && typeof path === 'object') {
1978
+ return activate(path);
1979
+ }
1980
+ const pathArray = typeof path === 'string' ? path.split('.') : path;
1981
+ const pathKey = pathArray.join('.');
1982
+ // Add to selector paths
1983
+ selectorPaths.add(pathKey);
1984
+ if (monitoringConfig.enabled && monitoringConfig.verboseLogging) {
1985
+ console.log(`🔄 Explicitly activating proxy for path: ${pathKey}`);
1986
+ }
1987
+ // Navigate to the target
1988
+ let target = globalStore;
1989
+ for (let i = 0; i < pathArray.length; i++) {
1990
+ if (target === undefined || target === null)
1991
+ break;
1992
+ target = target[pathArray[i]];
1993
+ }
1994
+ if (target === undefined || target === null) {
1995
+ console.warn(`⚠️ Path not found: ${pathKey}`);
1996
+ return null;
1997
+ }
1998
+ // Force create an immediate proxy for this specific path
1999
+ const immediateProxy = createAdvancedProxy(target, pathArray, 0);
2000
+ // Add parent paths
2001
+ for (let i = 1; i < pathArray.length; i++) {
2002
+ const parentPath = pathArray.slice(0, i).join('.');
2003
+ selectorPaths.add(parentPath);
2004
+ }
2005
+ return immediateProxy;
2006
+ }
2007
+ function $get(path) {
2008
+ if (path !== null && typeof path === 'object') {
2009
+ return activate(path);
2010
+ }
2011
+ return getProxy(path);
2012
+ }
2013
+ // For debugging - matches original API
2014
+ const debugInfo = {
2015
+ getListenerCount: () => {
2016
+ const { getListenerCount } = require('./core/listeners');
2017
+ return getListenerCount();
2018
+ },
2019
+ getPathCount: () => {
2020
+ const { pathListeners } = require('./core/listeners');
2021
+ return pathListeners.size;
2022
+ },
2023
+ getActivePaths: () => {
2024
+ const { getActivePaths } = require('./core/listeners');
2025
+ return getActivePaths();
2026
+ }
2027
+ };
2028
+ // Expose raw store for debugging
2029
+ const rawStore = globalStore;
2030
+ // Initialize library with enhanced features
2031
+ if (typeof window !== 'undefined') {
2032
+ console.log('🎯 Scope State initialized with advanced features - ready for reactive state management');
2033
+ console.log('💡 Tip: Call configure() with your initialState for full TypeScript support');
2034
+ console.log('🔧 Advanced features: WeakMap caching, leak detection, batch persistence, memory management');
2035
+ // Initialize auto-hydration if persistence is enabled
2036
+ if (persistenceConfig.enabled) {
2037
+ setTimeout(() => {
2038
+ hydrateState(globalStore);
2039
+ }, 100);
2040
+ }
2041
+ }
2042
+
2043
+ exports.$ = $;
2044
+ exports.$activate = $activate;
2045
+ exports.$get = $get;
2046
+ exports.$local = $local;
2047
+ exports.activate = activate;
2048
+ exports.clearProxyCache = clearProxyCache;
2049
+ exports.configure = configure;
2050
+ exports.createAdvancedProxy = createAdvancedProxy;
2051
+ exports.createReactive = createReactive;
2052
+ exports.debugInfo = debugInfo;
2053
+ exports.getConfig = getConfig;
2054
+ exports.getProxy = getProxy;
2055
+ exports.getProxyCacheStats = getProxyCacheStats;
2056
+ exports.getStore = getStore;
2057
+ exports.initializeStore = initializeStore;
2058
+ exports.isReactive = isReactive;
2059
+ exports.monitorAPI = monitorAPI;
2060
+ exports.optimizeMemoryUsage = optimizeMemoryUsage;
2061
+ exports.pathUsageStats = pathUsageStats;
2062
+ exports.persistenceAPI = persistenceAPI;
2063
+ exports.presets = presets;
2064
+ exports.proxyPathMap = proxyPathMap;
2065
+ exports.rawStore = rawStore;
2066
+ exports.resetConfig = resetConfig;
2067
+ exports.resetStore = resetStore;
2068
+ exports.selectorPaths = selectorPaths;
2069
+ exports.setInitialStoreState = setInitialStoreState;
2070
+ exports.trackDependenciesAdvanced = trackDependenciesAdvanced;
2071
+ exports.useLocal = useLocal;
2072
+ exports.useScope = useScope;
2073
+ //# sourceMappingURL=index.js.map