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