@signaltree/core 8.0.0 → 8.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +96 -44
- package/dist/enhancers/devtools/devtools.js +717 -135
- package/dist/enhancers/memoization/memoization.js +2 -2
- package/dist/index.js +3 -3
- package/dist/lib/constants.js +1 -1
- package/dist/lib/edit-session.js +1 -1
- package/dist/lib/signal-tree.js +2 -2
- package/dist/lib/utils.js +1 -1
- package/package.json +2 -6
- package/src/enhancers/devtools/devtools.d.ts +1 -1
- package/src/lib/types.d.ts +5 -1
- /package/dist/{constants.js → shared/lib/constants.js} +0 -0
- /package/dist/{deep-equal.js → shared/lib/deep-equal.js} +0 -0
- /package/dist/{is-built-in-object.js → shared/lib/is-built-in-object.js} +0 -0
- /package/dist/{lru-cache.js → shared/lib/lru-cache.js} +0 -0
- /package/dist/{parse-path.js → shared/lib/parse-path.js} +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { signal } from '@angular/core';
|
|
2
2
|
import { copyTreeProperties } from '../utils/copy-tree-properties.js';
|
|
3
|
+
import { applyState, snapshotState } from '../../lib/utils.js';
|
|
3
4
|
import { getPathNotifier } from '../../lib/path-notifier.js';
|
|
4
|
-
import { snapshotState, applyState } from '../../lib/utils.js';
|
|
5
5
|
|
|
6
|
-
function createActivityTracker() {
|
|
6
|
+
function createActivityTracker(options) {
|
|
7
7
|
const modules = new Map();
|
|
8
|
+
const enableConsole = options?.enableConsole === true;
|
|
8
9
|
return {
|
|
9
10
|
trackMethodCall: (module, method, duration) => {
|
|
10
11
|
const existing = modules.get(module);
|
|
@@ -29,13 +30,15 @@ function createActivityTracker() {
|
|
|
29
30
|
if (existing) {
|
|
30
31
|
existing.errorCount++;
|
|
31
32
|
}
|
|
32
|
-
|
|
33
|
+
if (enableConsole) {
|
|
34
|
+
console.error(`❌ [${module}] Error${context ? ` in ${context}` : ''}:`, error);
|
|
35
|
+
}
|
|
33
36
|
},
|
|
34
37
|
getModuleActivity: module => modules.get(module),
|
|
35
38
|
getAllModules: () => Array.from(modules.values())
|
|
36
39
|
};
|
|
37
40
|
}
|
|
38
|
-
function createCompositionLogger() {
|
|
41
|
+
function createCompositionLogger(options) {
|
|
39
42
|
const logs = [];
|
|
40
43
|
const addLog = (module, type, data) => {
|
|
41
44
|
logs.push({
|
|
@@ -54,7 +57,9 @@ function createCompositionLogger() {
|
|
|
54
57
|
modules,
|
|
55
58
|
action
|
|
56
59
|
});
|
|
57
|
-
|
|
60
|
+
{
|
|
61
|
+
console.log(`🔗 Composition ${action}:`, modules.join(' → '));
|
|
62
|
+
}
|
|
58
63
|
},
|
|
59
64
|
logMethodExecution: (module, method, args, result) => {
|
|
60
65
|
addLog(module, 'method', {
|
|
@@ -62,10 +67,12 @@ function createCompositionLogger() {
|
|
|
62
67
|
args,
|
|
63
68
|
result
|
|
64
69
|
});
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
70
|
+
{
|
|
71
|
+
console.debug(`🔧 [${module}] ${method}`, {
|
|
72
|
+
args,
|
|
73
|
+
result
|
|
74
|
+
});
|
|
75
|
+
}
|
|
69
76
|
},
|
|
70
77
|
logStateChange: (module, path, oldValue, newValue) => {
|
|
71
78
|
addLog(module, 'state', {
|
|
@@ -73,10 +80,12 @@ function createCompositionLogger() {
|
|
|
73
80
|
oldValue,
|
|
74
81
|
newValue
|
|
75
82
|
});
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
83
|
+
{
|
|
84
|
+
console.debug(`📝 [${module}] State change at ${path}:`, {
|
|
85
|
+
from: oldValue,
|
|
86
|
+
to: newValue
|
|
87
|
+
});
|
|
88
|
+
}
|
|
80
89
|
},
|
|
81
90
|
logPerformanceWarning: (module, operation, duration, threshold) => {
|
|
82
91
|
addLog(module, 'performance', {
|
|
@@ -84,7 +93,9 @@ function createCompositionLogger() {
|
|
|
84
93
|
duration,
|
|
85
94
|
threshold
|
|
86
95
|
});
|
|
87
|
-
|
|
96
|
+
{
|
|
97
|
+
console.warn(`⚠️ [${module}] Slow ${operation}: ${duration.toFixed(2)}ms (threshold: ${threshold}ms)`);
|
|
98
|
+
}
|
|
88
99
|
},
|
|
89
100
|
exportLogs: () => [...logs]
|
|
90
101
|
};
|
|
@@ -252,6 +263,415 @@ function sanitizeState(value, options, depth = 0, seen = new WeakSet()) {
|
|
|
252
263
|
}
|
|
253
264
|
return value;
|
|
254
265
|
}
|
|
266
|
+
function parseDevToolsState(state) {
|
|
267
|
+
if (typeof state === 'string') {
|
|
268
|
+
try {
|
|
269
|
+
return JSON.parse(state);
|
|
270
|
+
} catch {
|
|
271
|
+
return undefined;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
return state;
|
|
275
|
+
}
|
|
276
|
+
function buildDevToolsAction(type, payload, meta) {
|
|
277
|
+
return {
|
|
278
|
+
type,
|
|
279
|
+
...(payload !== undefined && {
|
|
280
|
+
payload
|
|
281
|
+
}),
|
|
282
|
+
...(meta && {
|
|
283
|
+
meta: meta
|
|
284
|
+
})
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
const GLOBAL_GROUPS_KEY = '__SIGNALTREE_DEVTOOLS_GROUPS__';
|
|
288
|
+
const GLOBAL_MARKER_KEY = '__SIGNALTREE_DEVTOOLS_GLOBAL_MARKER__';
|
|
289
|
+
function getRegistryHost() {
|
|
290
|
+
return typeof window !== 'undefined' ? window : globalThis;
|
|
291
|
+
}
|
|
292
|
+
function ensureGlobalMarker() {
|
|
293
|
+
const host = getRegistryHost();
|
|
294
|
+
if (!host[GLOBAL_MARKER_KEY]) {
|
|
295
|
+
host[GLOBAL_MARKER_KEY] = `marker_${Math.random().toString(36).slice(2)}`;
|
|
296
|
+
}
|
|
297
|
+
return host[GLOBAL_MARKER_KEY];
|
|
298
|
+
}
|
|
299
|
+
function getGlobalDevToolsGroups() {
|
|
300
|
+
const host = getRegistryHost();
|
|
301
|
+
if (!host[GLOBAL_GROUPS_KEY]) {
|
|
302
|
+
host[GLOBAL_GROUPS_KEY] = new Map();
|
|
303
|
+
}
|
|
304
|
+
return host[GLOBAL_GROUPS_KEY];
|
|
305
|
+
}
|
|
306
|
+
const devToolsGroups = getGlobalDevToolsGroups();
|
|
307
|
+
const GLOBAL_CONNECTIONS_KEY = '__SIGNALTREE_DEVTOOLS_CONNECTIONS__';
|
|
308
|
+
function getGlobalDevToolsConnections() {
|
|
309
|
+
const host = getRegistryHost();
|
|
310
|
+
ensureGlobalMarker();
|
|
311
|
+
if (!host[GLOBAL_CONNECTIONS_KEY]) {
|
|
312
|
+
host[GLOBAL_CONNECTIONS_KEY] = new Map();
|
|
313
|
+
}
|
|
314
|
+
return host[GLOBAL_CONNECTIONS_KEY];
|
|
315
|
+
}
|
|
316
|
+
const devToolsConnections = getGlobalDevToolsConnections();
|
|
317
|
+
function getOrCreateDevToolsGroup(groupId, displayName) {
|
|
318
|
+
if (devToolsGroups.has(groupId)) {
|
|
319
|
+
return devToolsGroups.get(groupId);
|
|
320
|
+
}
|
|
321
|
+
const trees = new Map();
|
|
322
|
+
const lastSnapshots = new Map();
|
|
323
|
+
const lastSerialized = new Map();
|
|
324
|
+
const pendingPathsByTree = new Map();
|
|
325
|
+
let browserDevToolsConnection = null;
|
|
326
|
+
let browserDevTools = null;
|
|
327
|
+
let devToolsExtension = null;
|
|
328
|
+
let unsubscribeDevTools = null;
|
|
329
|
+
let isConnected = false;
|
|
330
|
+
let isApplyingExternalState = false;
|
|
331
|
+
let sendScheduled = false;
|
|
332
|
+
let sendTimer = null;
|
|
333
|
+
let lastSendAt = 0;
|
|
334
|
+
let sendRateLimitMs = 0;
|
|
335
|
+
let pendingAction = null;
|
|
336
|
+
let pendingExplicitAction = false;
|
|
337
|
+
let pendingSource;
|
|
338
|
+
let pendingDuration;
|
|
339
|
+
const sendAggregated = (actionType, payload) => {
|
|
340
|
+
if (!browserDevTools) return;
|
|
341
|
+
const aggregated = buildAggregatedState();
|
|
342
|
+
browserDevTools.send(buildDevToolsAction(actionType, payload), aggregated);
|
|
343
|
+
};
|
|
344
|
+
const sendEmptyState = () => {
|
|
345
|
+
try {
|
|
346
|
+
sendAggregated('SignalTree/empty', {
|
|
347
|
+
groupId
|
|
348
|
+
});
|
|
349
|
+
} catch {}
|
|
350
|
+
pendingPathsByTree.clear();
|
|
351
|
+
lastSnapshots.clear();
|
|
352
|
+
lastSerialized.clear();
|
|
353
|
+
};
|
|
354
|
+
const updateRateLimit = () => {
|
|
355
|
+
sendRateLimitMs = 0;
|
|
356
|
+
for (const tree of trees.values()) {
|
|
357
|
+
if (typeof tree.sendRateLimitMs === 'number') {
|
|
358
|
+
sendRateLimitMs = Math.max(sendRateLimitMs, tree.sendRateLimitMs);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
const buildAggregatedState = () => {
|
|
363
|
+
const aggregated = {};
|
|
364
|
+
for (const [treeKey, tree] of trees) {
|
|
365
|
+
const snapshot = tree.readSnapshot();
|
|
366
|
+
const lastSnapshot = lastSnapshots.get(treeKey);
|
|
367
|
+
let serialized;
|
|
368
|
+
if (lastSnapshot === snapshot && lastSerialized.has(treeKey)) {
|
|
369
|
+
serialized = lastSerialized.get(treeKey);
|
|
370
|
+
} else {
|
|
371
|
+
lastSnapshots.set(treeKey, snapshot);
|
|
372
|
+
serialized = tree.buildSerializedState(snapshot);
|
|
373
|
+
lastSerialized.set(treeKey, serialized);
|
|
374
|
+
}
|
|
375
|
+
aggregated[treeKey] = serialized;
|
|
376
|
+
}
|
|
377
|
+
return aggregated;
|
|
378
|
+
};
|
|
379
|
+
const sendInit = () => {
|
|
380
|
+
if (!browserDevTools) return;
|
|
381
|
+
const aggregated = buildAggregatedState();
|
|
382
|
+
browserDevTools.send('@@INIT', aggregated);
|
|
383
|
+
};
|
|
384
|
+
const applyExternalState = state => {
|
|
385
|
+
if (state === undefined || state === null) return;
|
|
386
|
+
isApplyingExternalState = true;
|
|
387
|
+
try {
|
|
388
|
+
for (const [treeKey, tree] of trees) {
|
|
389
|
+
if (!tree.enableTimeTravel) continue;
|
|
390
|
+
const treeState = state?.[treeKey];
|
|
391
|
+
if (treeState !== undefined) {
|
|
392
|
+
tree.applyExternalState(treeState);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
} finally {
|
|
396
|
+
isApplyingExternalState = false;
|
|
397
|
+
for (const treeKey of trees.keys()) {
|
|
398
|
+
lastSnapshots.delete(treeKey);
|
|
399
|
+
pendingPathsByTree.delete(treeKey);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
};
|
|
403
|
+
const handleDevToolsMessage = message => {
|
|
404
|
+
if (!message || typeof message !== 'object') return;
|
|
405
|
+
const msg = message;
|
|
406
|
+
const messageType = typeof msg.type === 'string' ? msg.type : undefined;
|
|
407
|
+
if (messageType === 'START') {
|
|
408
|
+
if (browserDevTools && trees.size > 0) {
|
|
409
|
+
const elapsed = Date.now() - lastSendAt;
|
|
410
|
+
if (elapsed < 500) return;
|
|
411
|
+
const aggregated = buildAggregatedState();
|
|
412
|
+
browserDevTools.send({
|
|
413
|
+
type: 'SignalTree/reconnect'
|
|
414
|
+
}, aggregated);
|
|
415
|
+
lastSendAt = Date.now();
|
|
416
|
+
}
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
const anyTimeTravelEnabled = Array.from(trees.values()).some(t => t.enableTimeTravel);
|
|
420
|
+
if (!anyTimeTravelEnabled) return;
|
|
421
|
+
if (messageType !== 'DISPATCH') return;
|
|
422
|
+
const actionType = msg.payload && typeof msg.payload.type === 'string' ? msg.payload.type : undefined;
|
|
423
|
+
if (!actionType) return;
|
|
424
|
+
if (actionType === 'JUMP_TO_STATE' || actionType === 'JUMP_TO_ACTION') {
|
|
425
|
+
const nextState = parseDevToolsState(msg.state);
|
|
426
|
+
applyExternalState(nextState);
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
if (actionType === 'ROLLBACK') {
|
|
430
|
+
const nextState = parseDevToolsState(msg.state);
|
|
431
|
+
applyExternalState(nextState);
|
|
432
|
+
sendInit();
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
if (actionType === 'COMMIT') {
|
|
436
|
+
sendInit();
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (actionType === 'IMPORT_STATE') {
|
|
440
|
+
const lifted = msg.payload?.nextLiftedState;
|
|
441
|
+
const computedStates = Array.isArray(lifted?.computedStates) ? lifted.computedStates : [];
|
|
442
|
+
const indexRaw = typeof lifted?.currentStateIndex === 'number' ? lifted.currentStateIndex : computedStates.length - 1;
|
|
443
|
+
const index = Math.max(0, Math.min(indexRaw, computedStates.length - 1));
|
|
444
|
+
const entry = computedStates[index];
|
|
445
|
+
const nextState = parseDevToolsState(entry?.state);
|
|
446
|
+
applyExternalState(nextState);
|
|
447
|
+
sendInit();
|
|
448
|
+
}
|
|
449
|
+
};
|
|
450
|
+
const initBrowserDevTools = () => {
|
|
451
|
+
if (isConnected) return;
|
|
452
|
+
if (typeof window === 'undefined' || !('__REDUX_DEVTOOLS_EXTENSION__' in window)) {
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
const waiters = new Set();
|
|
456
|
+
devToolsConnections.set(groupId, {
|
|
457
|
+
status: 'connecting',
|
|
458
|
+
connection: null,
|
|
459
|
+
tools: null,
|
|
460
|
+
subscribed: false,
|
|
461
|
+
unsubscribe: null,
|
|
462
|
+
waiters
|
|
463
|
+
});
|
|
464
|
+
try {
|
|
465
|
+
const devToolsExt = window['__REDUX_DEVTOOLS_EXTENSION__'];
|
|
466
|
+
devToolsExtension = devToolsExt;
|
|
467
|
+
const connection = devToolsExt.connect({
|
|
468
|
+
name: displayName,
|
|
469
|
+
instanceId: groupId,
|
|
470
|
+
features: {
|
|
471
|
+
dispatch: true,
|
|
472
|
+
jump: true,
|
|
473
|
+
skip: true
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
browserDevToolsConnection = connection;
|
|
477
|
+
browserDevTools = {
|
|
478
|
+
send: connection.send,
|
|
479
|
+
subscribe: connection.subscribe
|
|
480
|
+
};
|
|
481
|
+
if (browserDevTools.subscribe && !unsubscribeDevTools) {
|
|
482
|
+
const maybeUnsubscribe = browserDevTools.subscribe(handleDevToolsMessage);
|
|
483
|
+
if (typeof maybeUnsubscribe === 'function') {
|
|
484
|
+
unsubscribeDevTools = maybeUnsubscribe;
|
|
485
|
+
} else {
|
|
486
|
+
unsubscribeDevTools = () => {
|
|
487
|
+
browserDevTools?.subscribe?.(() => void 0);
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
const connEntry = devToolsConnections.get(groupId);
|
|
492
|
+
if (connEntry) {
|
|
493
|
+
connEntry.status = 'connected';
|
|
494
|
+
connEntry.connection = browserDevToolsConnection;
|
|
495
|
+
connEntry.tools = browserDevTools;
|
|
496
|
+
connEntry.subscribed = !!browserDevTools?.subscribe;
|
|
497
|
+
connEntry.unsubscribe = unsubscribeDevTools;
|
|
498
|
+
for (const waiter of connEntry.waiters) {
|
|
499
|
+
try {
|
|
500
|
+
waiter(connEntry);
|
|
501
|
+
} catch {}
|
|
502
|
+
}
|
|
503
|
+
connEntry.waiters.clear();
|
|
504
|
+
}
|
|
505
|
+
sendInit();
|
|
506
|
+
isConnected = true;
|
|
507
|
+
} catch (e) {
|
|
508
|
+
devToolsConnections.delete(groupId);
|
|
509
|
+
console.warn('[SignalTree] Failed to connect to Redux DevTools:', e);
|
|
510
|
+
}
|
|
511
|
+
};
|
|
512
|
+
const flushSend = () => {
|
|
513
|
+
sendScheduled = false;
|
|
514
|
+
if (!browserDevTools || isApplyingExternalState) return;
|
|
515
|
+
const aggregatedState = {};
|
|
516
|
+
const allFormattedPaths = [];
|
|
517
|
+
for (const [treeKey, tree] of trees) {
|
|
518
|
+
const currentSnapshot = tree.readSnapshot();
|
|
519
|
+
lastSnapshots.set(treeKey, currentSnapshot);
|
|
520
|
+
const serialized = tree.buildSerializedState(currentSnapshot);
|
|
521
|
+
lastSerialized.set(treeKey, serialized);
|
|
522
|
+
aggregatedState[treeKey] = serialized;
|
|
523
|
+
const pendingRaw = pendingPathsByTree.get(treeKey) ?? [];
|
|
524
|
+
const allowedRaw = pendingRaw.filter(p => p && tree.isPathAllowed(p));
|
|
525
|
+
if (allowedRaw.length > 0) {
|
|
526
|
+
const formatted = Array.from(new Set(allowedRaw)).map(path => tree.formatPathFn(`${treeKey}.${path}`));
|
|
527
|
+
allFormattedPaths.push(...formatted);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
const hasMeta = pendingSource !== undefined || pendingDuration !== undefined;
|
|
531
|
+
if (allFormattedPaths.length === 0 && !pendingExplicitAction && !hasMeta) {
|
|
532
|
+
pendingAction = null;
|
|
533
|
+
pendingExplicitAction = false;
|
|
534
|
+
pendingSource = undefined;
|
|
535
|
+
pendingDuration = undefined;
|
|
536
|
+
pendingPathsByTree.clear();
|
|
537
|
+
lastSendAt = Date.now();
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
const effectiveAction = pendingExplicitAction && pendingAction ? pendingAction : allFormattedPaths.length === 1 ? buildDevToolsAction(`SignalTree/${allFormattedPaths[0]}`, allFormattedPaths[0]) : allFormattedPaths.length > 1 ? buildDevToolsAction('SignalTree/update', allFormattedPaths) : buildDevToolsAction('SignalTree/update');
|
|
541
|
+
const actionMeta = {
|
|
542
|
+
timestamp: Date.now(),
|
|
543
|
+
...(pendingSource && {
|
|
544
|
+
source: pendingSource
|
|
545
|
+
}),
|
|
546
|
+
...(pendingDuration !== undefined && {
|
|
547
|
+
duration: pendingDuration,
|
|
548
|
+
slow: pendingDuration > 16
|
|
549
|
+
}),
|
|
550
|
+
...(allFormattedPaths.length > 0 && {
|
|
551
|
+
paths: allFormattedPaths
|
|
552
|
+
})
|
|
553
|
+
};
|
|
554
|
+
const actionToSend = buildDevToolsAction(effectiveAction?.type ?? 'SignalTree/update', effectiveAction?.payload, actionMeta);
|
|
555
|
+
try {
|
|
556
|
+
browserDevTools.send(actionToSend, aggregatedState);
|
|
557
|
+
} catch {} finally {
|
|
558
|
+
pendingAction = null;
|
|
559
|
+
pendingExplicitAction = false;
|
|
560
|
+
pendingSource = undefined;
|
|
561
|
+
pendingDuration = undefined;
|
|
562
|
+
pendingPathsByTree.clear();
|
|
563
|
+
lastSendAt = Date.now();
|
|
564
|
+
}
|
|
565
|
+
};
|
|
566
|
+
const scheduleSend = (action, meta) => {
|
|
567
|
+
if (isApplyingExternalState) return;
|
|
568
|
+
if (action !== undefined) {
|
|
569
|
+
if (!pendingExplicitAction && pendingAction == null) {
|
|
570
|
+
pendingAction = action;
|
|
571
|
+
pendingExplicitAction = true;
|
|
572
|
+
} else {
|
|
573
|
+
pendingAction = null;
|
|
574
|
+
pendingExplicitAction = false;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
if (meta?.source) {
|
|
578
|
+
if (!pendingSource) {
|
|
579
|
+
pendingSource = meta.source;
|
|
580
|
+
} else if (pendingSource !== meta.source) {
|
|
581
|
+
pendingSource = 'mixed';
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (meta?.duration !== undefined) {
|
|
585
|
+
pendingDuration = pendingDuration === undefined ? meta.duration : Math.max(pendingDuration, meta.duration);
|
|
586
|
+
}
|
|
587
|
+
if (!browserDevTools) return;
|
|
588
|
+
if (sendScheduled) return;
|
|
589
|
+
sendScheduled = true;
|
|
590
|
+
queueMicrotask(() => {
|
|
591
|
+
if (!browserDevTools) {
|
|
592
|
+
sendScheduled = false;
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
const now = Date.now();
|
|
596
|
+
const waitMs = Math.max(0, sendRateLimitMs - (now - lastSendAt));
|
|
597
|
+
if (waitMs > 0) {
|
|
598
|
+
if (sendTimer) return;
|
|
599
|
+
sendTimer = setTimeout(() => {
|
|
600
|
+
sendTimer = null;
|
|
601
|
+
flushSend();
|
|
602
|
+
}, waitMs);
|
|
603
|
+
return;
|
|
604
|
+
}
|
|
605
|
+
flushSend();
|
|
606
|
+
});
|
|
607
|
+
};
|
|
608
|
+
const registerTree = (treeKey, tree) => {
|
|
609
|
+
const wasConnected = isConnected;
|
|
610
|
+
trees.set(treeKey, tree);
|
|
611
|
+
updateRateLimit();
|
|
612
|
+
initBrowserDevTools();
|
|
613
|
+
if (browserDevTools) {
|
|
614
|
+
const snapshot = tree.readSnapshot();
|
|
615
|
+
lastSnapshots.set(treeKey, snapshot);
|
|
616
|
+
lastSerialized.set(treeKey, tree.buildSerializedState(snapshot));
|
|
617
|
+
if (trees.size === 1 && !wasConnected) {
|
|
618
|
+
sendInit();
|
|
619
|
+
} else {
|
|
620
|
+
sendAggregated('SignalTree/register', {
|
|
621
|
+
treeKey
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
return () => {
|
|
626
|
+
trees.delete(treeKey);
|
|
627
|
+
pendingPathsByTree.delete(treeKey);
|
|
628
|
+
lastSnapshots.delete(treeKey);
|
|
629
|
+
lastSerialized.delete(treeKey);
|
|
630
|
+
updateRateLimit();
|
|
631
|
+
if (trees.size === 0) {
|
|
632
|
+
sendEmptyState();
|
|
633
|
+
try {
|
|
634
|
+
unsubscribeDevTools?.();
|
|
635
|
+
} catch {}
|
|
636
|
+
try {
|
|
637
|
+
browserDevToolsConnection?.unsubscribe?.();
|
|
638
|
+
} catch {}
|
|
639
|
+
try {
|
|
640
|
+
browserDevToolsConnection?.disconnect?.();
|
|
641
|
+
} catch {}
|
|
642
|
+
unsubscribeDevTools = null;
|
|
643
|
+
browserDevToolsConnection = null;
|
|
644
|
+
browserDevTools = null;
|
|
645
|
+
devToolsExtension = null;
|
|
646
|
+
isConnected = false;
|
|
647
|
+
devToolsConnections.delete(groupId);
|
|
648
|
+
devToolsGroups.delete(groupId);
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
651
|
+
if (browserDevTools) {
|
|
652
|
+
sendAggregated('SignalTree/unregister', {
|
|
653
|
+
treeKey
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
};
|
|
658
|
+
const enqueue = (treeKey, pendingPaths, action, meta) => {
|
|
659
|
+
if (pendingPaths && pendingPaths.length > 0) {
|
|
660
|
+
const existing = pendingPathsByTree.get(treeKey) ?? [];
|
|
661
|
+
pendingPathsByTree.set(treeKey, [...existing, ...pendingPaths]);
|
|
662
|
+
}
|
|
663
|
+
scheduleSend(action, meta);
|
|
664
|
+
};
|
|
665
|
+
const group = {
|
|
666
|
+
registerTree,
|
|
667
|
+
enqueue,
|
|
668
|
+
connect() {
|
|
669
|
+
initBrowserDevTools();
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
devToolsGroups.set(groupId, group);
|
|
673
|
+
return group;
|
|
674
|
+
}
|
|
255
675
|
function devTools(config = {}) {
|
|
256
676
|
const {
|
|
257
677
|
enabled = true,
|
|
@@ -259,7 +679,7 @@ function devTools(config = {}) {
|
|
|
259
679
|
name,
|
|
260
680
|
enableBrowserDevTools = true,
|
|
261
681
|
enableTimeTravel = true,
|
|
262
|
-
enableLogging =
|
|
682
|
+
enableLogging = false,
|
|
263
683
|
performanceThreshold = 16,
|
|
264
684
|
includePaths,
|
|
265
685
|
excludePaths,
|
|
@@ -269,9 +689,11 @@ function devTools(config = {}) {
|
|
|
269
689
|
maxDepth = 10,
|
|
270
690
|
maxArrayLength = 50,
|
|
271
691
|
maxStringLength = 2000,
|
|
272
|
-
serialize
|
|
692
|
+
serialize,
|
|
693
|
+
aggregatedReduxInstance
|
|
273
694
|
} = config;
|
|
274
695
|
const displayName = name ?? treeName;
|
|
696
|
+
const groupId = aggregatedReduxInstance?.id ?? displayName;
|
|
275
697
|
const pathInclude = toArray(includePaths);
|
|
276
698
|
const pathExclude = toArray(excludePaths);
|
|
277
699
|
const sendRateLimitMs = maxSendsPerSecond && maxSendsPerSecond > 0 ? Math.ceil(1000 / maxSendsPerSecond) : rateLimitMs ?? 0;
|
|
@@ -284,7 +706,9 @@ function devTools(config = {}) {
|
|
|
284
706
|
};
|
|
285
707
|
return Object.assign(tree, noopMethods);
|
|
286
708
|
}
|
|
287
|
-
const activityTracker = createActivityTracker(
|
|
709
|
+
const activityTracker = createActivityTracker({
|
|
710
|
+
enableConsole: enableLogging
|
|
711
|
+
});
|
|
288
712
|
const logger = enableLogging ? createCompositionLogger() : createNoopLogger();
|
|
289
713
|
const metrics = createModularMetrics();
|
|
290
714
|
const compositionHistory = [];
|
|
@@ -302,14 +726,11 @@ function devTools(config = {}) {
|
|
|
302
726
|
const activeProfiles = new Map();
|
|
303
727
|
let browserDevToolsConnection = null;
|
|
304
728
|
let browserDevTools = null;
|
|
729
|
+
let devToolsExtension = null;
|
|
305
730
|
let isConnected = false;
|
|
306
731
|
let isApplyingExternalState = false;
|
|
307
732
|
let unsubscribeDevTools = null;
|
|
308
|
-
let unsubscribeNotifier = null;
|
|
309
|
-
let unsubscribeFlush = null;
|
|
310
733
|
let pendingPaths = [];
|
|
311
|
-
let effectRef = null;
|
|
312
|
-
let effectPrimed = false;
|
|
313
734
|
let sendScheduled = false;
|
|
314
735
|
let pendingAction = null;
|
|
315
736
|
let pendingExplicitAction = false;
|
|
@@ -358,15 +779,33 @@ function devTools(config = {}) {
|
|
|
358
779
|
meta: meta
|
|
359
780
|
})
|
|
360
781
|
});
|
|
782
|
+
let lastSerializedJson;
|
|
361
783
|
const flushSend = () => {
|
|
362
784
|
sendScheduled = false;
|
|
363
785
|
if (!browserDevTools || isApplyingExternalState) return;
|
|
364
786
|
const rawSnapshot = readSnapshot();
|
|
365
787
|
const currentSnapshot = rawSnapshot ?? {};
|
|
366
788
|
const sanitized = buildSerializedState(currentSnapshot);
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
789
|
+
let currentSerializedJson;
|
|
790
|
+
try {
|
|
791
|
+
currentSerializedJson = JSON.stringify(sanitized);
|
|
792
|
+
} catch {
|
|
793
|
+
currentSerializedJson = '';
|
|
794
|
+
}
|
|
795
|
+
const stateActuallyChanged = lastSerializedJson === undefined || currentSerializedJson !== lastSerializedJson;
|
|
796
|
+
const pendingAllowedPaths = pendingPaths.filter(path => path && isPathAllowed(path));
|
|
797
|
+
if (!stateActuallyChanged && !pendingExplicitAction && pendingAllowedPaths.length === 0) {
|
|
798
|
+
pendingAction = null;
|
|
799
|
+
pendingExplicitAction = false;
|
|
800
|
+
pendingSource = undefined;
|
|
801
|
+
pendingDuration = undefined;
|
|
802
|
+
pendingPaths = [];
|
|
803
|
+
lastSnapshot = currentSnapshot;
|
|
804
|
+
lastSerializedJson = currentSerializedJson;
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
const rawPathsForNaming = pendingAllowedPaths.length > 0 ? Array.from(new Set(pendingAllowedPaths)) : lastSnapshot === undefined ? [] : computeChangedPaths(lastSnapshot, currentSnapshot, maxDepth, maxArrayLength).filter(path => path && isPathAllowed(path));
|
|
808
|
+
const formattedPaths = rawPathsForNaming.map(path => formatPathFn(path));
|
|
370
809
|
if (pathInclude.length > 0 && formattedPaths.length === 0 && !pendingExplicitAction) {
|
|
371
810
|
pendingAction = null;
|
|
372
811
|
pendingExplicitAction = false;
|
|
@@ -400,6 +839,7 @@ function devTools(config = {}) {
|
|
|
400
839
|
pendingDuration = undefined;
|
|
401
840
|
pendingPaths = [];
|
|
402
841
|
lastSnapshot = currentSnapshot;
|
|
842
|
+
lastSerializedJson = currentSerializedJson;
|
|
403
843
|
lastSendAt = Date.now();
|
|
404
844
|
}
|
|
405
845
|
};
|
|
@@ -440,15 +880,23 @@ function devTools(config = {}) {
|
|
|
440
880
|
flushSend();
|
|
441
881
|
});
|
|
442
882
|
};
|
|
443
|
-
const
|
|
444
|
-
if (
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
883
|
+
const sendInit = () => {
|
|
884
|
+
if (!browserDevTools) return;
|
|
885
|
+
const rawSnapshot = readSnapshot() ?? {};
|
|
886
|
+
const serialized = buildSerializedState(rawSnapshot);
|
|
887
|
+
browserDevTools.send('@@INIT', serialized);
|
|
888
|
+
lastSnapshot = rawSnapshot;
|
|
889
|
+
try {
|
|
890
|
+
lastSerializedJson = JSON.stringify(serialized);
|
|
891
|
+
} catch {
|
|
892
|
+
lastSerializedJson = undefined;
|
|
450
893
|
}
|
|
451
|
-
|
|
894
|
+
lastSendAt = Date.now();
|
|
895
|
+
pendingPaths = [];
|
|
896
|
+
pendingAction = null;
|
|
897
|
+
pendingExplicitAction = false;
|
|
898
|
+
pendingSource = undefined;
|
|
899
|
+
pendingDuration = undefined;
|
|
452
900
|
};
|
|
453
901
|
const applyExternalState = state => {
|
|
454
902
|
if (state === undefined || state === null) return;
|
|
@@ -466,11 +914,34 @@ function devTools(config = {}) {
|
|
|
466
914
|
}
|
|
467
915
|
};
|
|
468
916
|
const handleDevToolsMessage = message => {
|
|
469
|
-
if (!enableTimeTravel) return;
|
|
470
917
|
if (!message || typeof message !== 'object') return;
|
|
471
918
|
const msg = message;
|
|
472
|
-
|
|
473
|
-
|
|
919
|
+
const messageType = typeof msg.type === 'string' ? msg.type : undefined;
|
|
920
|
+
if (messageType === 'START') {
|
|
921
|
+
if (lastSnapshot === undefined) {
|
|
922
|
+
sendInit();
|
|
923
|
+
} else if (browserDevTools) {
|
|
924
|
+
const elapsed = Date.now() - lastSendAt;
|
|
925
|
+
if (elapsed < 500) return;
|
|
926
|
+
const rawSnapshot = readSnapshot() ?? {};
|
|
927
|
+
const serialized = buildSerializedState(rawSnapshot);
|
|
928
|
+
browserDevTools.send({
|
|
929
|
+
type: 'SignalTree/reconnect'
|
|
930
|
+
}, serialized);
|
|
931
|
+
lastSnapshot = rawSnapshot;
|
|
932
|
+
try {
|
|
933
|
+
lastSerializedJson = JSON.stringify(serialized);
|
|
934
|
+
} catch {
|
|
935
|
+
lastSerializedJson = undefined;
|
|
936
|
+
}
|
|
937
|
+
lastSendAt = Date.now();
|
|
938
|
+
}
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (!enableTimeTravel) return;
|
|
942
|
+
if (messageType !== 'DISPATCH') return;
|
|
943
|
+
const actionType = msg.payload && typeof msg.payload.type === 'string' ? msg.payload.type : undefined;
|
|
944
|
+
if (!actionType) return;
|
|
474
945
|
if (actionType === 'JUMP_TO_STATE' || actionType === 'JUMP_TO_ACTION') {
|
|
475
946
|
const nextState = parseDevToolsState(msg.state);
|
|
476
947
|
applyExternalState(nextState);
|
|
@@ -479,44 +950,63 @@ function devTools(config = {}) {
|
|
|
479
950
|
if (actionType === 'ROLLBACK') {
|
|
480
951
|
const nextState = parseDevToolsState(msg.state);
|
|
481
952
|
applyExternalState(nextState);
|
|
482
|
-
|
|
483
|
-
const rawSnapshot = readSnapshot();
|
|
484
|
-
const sanitized = buildSerializedState(rawSnapshot);
|
|
485
|
-
browserDevTools.send('@@INIT', sanitized);
|
|
486
|
-
}
|
|
953
|
+
sendInit();
|
|
487
954
|
return;
|
|
488
955
|
}
|
|
489
956
|
if (actionType === 'COMMIT') {
|
|
490
|
-
|
|
491
|
-
const rawSnapshot = readSnapshot();
|
|
492
|
-
const sanitized = buildSerializedState(rawSnapshot);
|
|
493
|
-
browserDevTools.send('@@INIT', sanitized);
|
|
494
|
-
}
|
|
957
|
+
sendInit();
|
|
495
958
|
return;
|
|
496
959
|
}
|
|
497
960
|
if (actionType === 'IMPORT_STATE') {
|
|
498
|
-
const lifted = msg.payload
|
|
499
|
-
const computedStates = lifted?.computedStates
|
|
500
|
-
const
|
|
961
|
+
const lifted = msg.payload?.nextLiftedState;
|
|
962
|
+
const computedStates = Array.isArray(lifted?.computedStates) ? lifted.computedStates : [];
|
|
963
|
+
const indexRaw = typeof lifted?.currentStateIndex === 'number' ? lifted.currentStateIndex : computedStates.length - 1;
|
|
964
|
+
const index = Math.max(0, Math.min(indexRaw, computedStates.length - 1));
|
|
501
965
|
const entry = computedStates[index];
|
|
502
966
|
const nextState = parseDevToolsState(entry?.state);
|
|
503
967
|
applyExternalState(nextState);
|
|
504
|
-
|
|
505
|
-
const rawSnapshot = readSnapshot();
|
|
506
|
-
const sanitized = buildSerializedState(rawSnapshot);
|
|
507
|
-
browserDevTools.send('@@INIT', sanitized);
|
|
508
|
-
}
|
|
968
|
+
sendInit();
|
|
509
969
|
}
|
|
510
970
|
};
|
|
971
|
+
let groupUnregister = null;
|
|
511
972
|
const initBrowserDevTools = () => {
|
|
973
|
+
if (aggregatedReduxInstance) {
|
|
974
|
+
const group = getOrCreateDevToolsGroup(aggregatedReduxInstance.id, aggregatedReduxInstance.name ?? displayName);
|
|
975
|
+
if (!groupUnregister) {
|
|
976
|
+
groupUnregister = group.registerTree(treeName, {
|
|
977
|
+
readSnapshot,
|
|
978
|
+
buildSerializedState,
|
|
979
|
+
applyExternalState,
|
|
980
|
+
formatPathFn,
|
|
981
|
+
isPathAllowed,
|
|
982
|
+
enableTimeTravel,
|
|
983
|
+
maxDepth,
|
|
984
|
+
maxArrayLength,
|
|
985
|
+
maxStringLength,
|
|
986
|
+
sendRateLimitMs
|
|
987
|
+
});
|
|
988
|
+
}
|
|
989
|
+
return;
|
|
990
|
+
}
|
|
512
991
|
if (isConnected) return;
|
|
513
992
|
if (!enableBrowserDevTools || typeof window === 'undefined' || !('__REDUX_DEVTOOLS_EXTENSION__' in window)) {
|
|
514
993
|
return;
|
|
515
994
|
}
|
|
995
|
+
const waiters = new Set();
|
|
996
|
+
devToolsConnections.set(groupId, {
|
|
997
|
+
status: 'connecting',
|
|
998
|
+
connection: null,
|
|
999
|
+
tools: null,
|
|
1000
|
+
subscribed: false,
|
|
1001
|
+
unsubscribe: null,
|
|
1002
|
+
waiters
|
|
1003
|
+
});
|
|
516
1004
|
try {
|
|
517
1005
|
const devToolsExt = window['__REDUX_DEVTOOLS_EXTENSION__'];
|
|
1006
|
+
devToolsExtension = devToolsExt;
|
|
518
1007
|
const connection = devToolsExt.connect({
|
|
519
1008
|
name: displayName,
|
|
1009
|
+
instanceId: groupId,
|
|
520
1010
|
features: {
|
|
521
1011
|
dispatch: true,
|
|
522
1012
|
jump: true,
|
|
@@ -538,13 +1028,27 @@ function devTools(config = {}) {
|
|
|
538
1028
|
};
|
|
539
1029
|
}
|
|
540
1030
|
}
|
|
541
|
-
const
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1031
|
+
const connEntry = devToolsConnections.get(groupId);
|
|
1032
|
+
if (connEntry) {
|
|
1033
|
+
connEntry.status = 'connected';
|
|
1034
|
+
connEntry.connection = browserDevToolsConnection;
|
|
1035
|
+
connEntry.tools = browserDevTools;
|
|
1036
|
+
connEntry.subscribed = !!browserDevTools?.subscribe;
|
|
1037
|
+
connEntry.unsubscribe = unsubscribeDevTools;
|
|
1038
|
+
for (const waiter of connEntry.waiters) {
|
|
1039
|
+
try {
|
|
1040
|
+
waiter(connEntry);
|
|
1041
|
+
} catch {}
|
|
1042
|
+
}
|
|
1043
|
+
connEntry.waiters.clear();
|
|
1044
|
+
}
|
|
1045
|
+
sendInit();
|
|
545
1046
|
isConnected = true;
|
|
546
|
-
|
|
1047
|
+
if (enableLogging) {
|
|
1048
|
+
console.log(`🔗 Connected to Redux DevTools as "${displayName}"`);
|
|
1049
|
+
}
|
|
547
1050
|
} catch (e) {
|
|
1051
|
+
devToolsConnections.delete(groupId);
|
|
548
1052
|
console.warn('[SignalTree] Failed to connect to Redux DevTools:', e);
|
|
549
1053
|
}
|
|
550
1054
|
};
|
|
@@ -569,7 +1073,16 @@ function devTools(config = {}) {
|
|
|
569
1073
|
if (duration > performanceThreshold) {
|
|
570
1074
|
logger.logPerformanceWarning('core', 'update', duration, performanceThreshold);
|
|
571
1075
|
}
|
|
572
|
-
if (
|
|
1076
|
+
if (aggregatedReduxInstance) {
|
|
1077
|
+
const group = devToolsGroups.get(aggregatedReduxInstance.id);
|
|
1078
|
+
if (group) {
|
|
1079
|
+
group.enqueue(treeName, [], undefined, {
|
|
1080
|
+
timestamp: Date.now(),
|
|
1081
|
+
source: 'tree.update',
|
|
1082
|
+
duration
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1085
|
+
} else if (browserDevTools) {
|
|
573
1086
|
scheduleSend(undefined, {
|
|
574
1087
|
source: 'tree.update',
|
|
575
1088
|
duration
|
|
@@ -631,58 +1144,152 @@ function devTools(config = {}) {
|
|
|
631
1144
|
const profile = activeProfiles.get(profileId);
|
|
632
1145
|
if (profile) {
|
|
633
1146
|
const duration = performance.now() - profile.startTime;
|
|
634
|
-
|
|
1147
|
+
metrics.trackModuleUpdate(profile.module, duration);
|
|
635
1148
|
activeProfiles.delete(profileId);
|
|
636
1149
|
}
|
|
637
1150
|
},
|
|
638
|
-
connectDevTools:
|
|
639
|
-
if (!browserDevTools || !isConnected) {
|
|
640
|
-
initBrowserDevTools();
|
|
641
|
-
}
|
|
642
|
-
if (browserDevTools) {
|
|
643
|
-
const rawSnapshot = readSnapshot();
|
|
644
|
-
const sanitized = buildSerializedState(rawSnapshot);
|
|
645
|
-
browserDevTools.send('@@INIT', sanitized);
|
|
646
|
-
lastSnapshot = rawSnapshot;
|
|
647
|
-
console.log(`🔗 Connected to Redux DevTools as "${name}"`);
|
|
648
|
-
}
|
|
649
|
-
},
|
|
650
|
-
exportDebugSession: () => ({
|
|
651
|
-
metrics: metrics.signal(),
|
|
652
|
-
modules: activityTracker.getAllModules(),
|
|
653
|
-
logs: logger.exportLogs(),
|
|
654
|
-
compositionHistory: [...compositionHistory]
|
|
655
|
-
})
|
|
656
|
-
};
|
|
657
|
-
const methods = {
|
|
658
|
-
connectDevTools() {
|
|
1151
|
+
connectDevTools: () => {
|
|
659
1152
|
initBrowserDevTools();
|
|
660
1153
|
},
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
1154
|
+
exportDebugSession: () => {
|
|
1155
|
+
return {
|
|
1156
|
+
metrics: metrics.signal(),
|
|
1157
|
+
modules: activityTracker.getAllModules(),
|
|
1158
|
+
logs: logger.exportLogs(),
|
|
1159
|
+
compositionHistory
|
|
1160
|
+
};
|
|
1161
|
+
}
|
|
1162
|
+
};
|
|
1163
|
+
const shouldFilterByOwnership = Boolean(aggregatedReduxInstance);
|
|
1164
|
+
const treeTopKeys = new Set();
|
|
1165
|
+
const refreshTreeTopKeys = () => {
|
|
1166
|
+
treeTopKeys.clear();
|
|
1167
|
+
try {
|
|
1168
|
+
if ('$' in tree) {
|
|
1169
|
+
for (const key of Object.keys(tree.$)) {
|
|
1170
|
+
treeTopKeys.add(key);
|
|
1171
|
+
}
|
|
677
1172
|
}
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
1173
|
+
} catch {}
|
|
1174
|
+
};
|
|
1175
|
+
refreshTreeTopKeys();
|
|
1176
|
+
const isPathOwnedByTree = path => {
|
|
1177
|
+
if (!shouldFilterByOwnership) return true;
|
|
1178
|
+
if (treeTopKeys.size === 0) return true;
|
|
1179
|
+
const root = path.split('.')[0];
|
|
1180
|
+
if (treeTopKeys.has(root)) return true;
|
|
1181
|
+
refreshTreeTopKeys();
|
|
1182
|
+
return treeTopKeys.has(root);
|
|
1183
|
+
};
|
|
1184
|
+
const notifier = getPathNotifier();
|
|
1185
|
+
const restoreInterceptors = [];
|
|
1186
|
+
try {
|
|
1187
|
+
if ('$' in tree) {
|
|
1188
|
+
const treeNode = tree.$;
|
|
1189
|
+
for (const key of treeTopKeys) {
|
|
1190
|
+
const sig = treeNode[key];
|
|
1191
|
+
if (typeof sig === 'function' && 'set' in sig && 'update' in sig && typeof sig.set === 'function' && typeof sig.update === 'function' && !('add' in sig) && !('remove' in sig)) {
|
|
1192
|
+
const original = sig;
|
|
1193
|
+
const originalSet = original.set.bind(original);
|
|
1194
|
+
const originalUpdate = original.update.bind(original);
|
|
1195
|
+
restoreInterceptors.push(() => {
|
|
1196
|
+
original.set = originalSet;
|
|
1197
|
+
original.update = originalUpdate;
|
|
1198
|
+
});
|
|
1199
|
+
original.set = value => {
|
|
1200
|
+
const prev = original();
|
|
1201
|
+
originalSet(value);
|
|
1202
|
+
const next = original();
|
|
1203
|
+
if (next !== prev) {
|
|
1204
|
+
notifier.notify(key, next, prev);
|
|
1205
|
+
}
|
|
1206
|
+
};
|
|
1207
|
+
original.update = updater => {
|
|
1208
|
+
const prev = original();
|
|
1209
|
+
originalUpdate(updater);
|
|
1210
|
+
const next = original();
|
|
1211
|
+
if (next !== prev) {
|
|
1212
|
+
notifier.notify(key, next, prev);
|
|
1213
|
+
}
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
681
1216
|
}
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
1217
|
+
}
|
|
1218
|
+
} catch {}
|
|
1219
|
+
let unsubscribePathNotifier = null;
|
|
1220
|
+
let unsubscribePathFlush = null;
|
|
1221
|
+
unsubscribePathNotifier = notifier.subscribe('**', (_value, _prev, path) => {
|
|
1222
|
+
if (isApplyingExternalState) return;
|
|
1223
|
+
if (!isPathOwnedByTree(path)) return;
|
|
1224
|
+
if (!isPathAllowed(path)) return;
|
|
1225
|
+
pendingPaths.push(path);
|
|
1226
|
+
});
|
|
1227
|
+
unsubscribePathFlush = notifier.onFlush(() => {
|
|
1228
|
+
if (isApplyingExternalState) return;
|
|
1229
|
+
if (pendingPaths.length === 0) return;
|
|
1230
|
+
if (aggregatedReduxInstance) {
|
|
1231
|
+
const group = devToolsGroups.get(aggregatedReduxInstance.id);
|
|
1232
|
+
if (group) {
|
|
1233
|
+
group.enqueue(treeName, pendingPaths, undefined, {
|
|
1234
|
+
timestamp: Date.now(),
|
|
1235
|
+
source: 'path-notifier'
|
|
1236
|
+
});
|
|
1237
|
+
pendingPaths = [];
|
|
1238
|
+
}
|
|
1239
|
+
} else {
|
|
1240
|
+
if (!browserDevTools) {
|
|
1241
|
+
pendingPaths = [];
|
|
1242
|
+
return;
|
|
1243
|
+
}
|
|
1244
|
+
scheduleSend(undefined, {
|
|
1245
|
+
source: 'path-notifier'
|
|
1246
|
+
});
|
|
1247
|
+
}
|
|
1248
|
+
});
|
|
1249
|
+
const result = Object.assign(enhancedTree, {
|
|
1250
|
+
__devTools: devToolsInterface,
|
|
1251
|
+
connectDevTools: devToolsInterface.connectDevTools,
|
|
1252
|
+
exportDebugSession: devToolsInterface.exportDebugSession,
|
|
1253
|
+
disconnectDevTools: () => {
|
|
1254
|
+
if (unsubscribePathNotifier) {
|
|
1255
|
+
unsubscribePathNotifier();
|
|
1256
|
+
unsubscribePathNotifier = null;
|
|
1257
|
+
}
|
|
1258
|
+
if (unsubscribePathFlush) {
|
|
1259
|
+
unsubscribePathFlush();
|
|
1260
|
+
unsubscribePathFlush = null;
|
|
1261
|
+
}
|
|
1262
|
+
for (const restore of restoreInterceptors) {
|
|
1263
|
+
try {
|
|
1264
|
+
restore();
|
|
1265
|
+
} catch {}
|
|
1266
|
+
}
|
|
1267
|
+
if (aggregatedReduxInstance) {
|
|
1268
|
+
if (groupUnregister) {
|
|
1269
|
+
groupUnregister();
|
|
1270
|
+
groupUnregister = null;
|
|
1271
|
+
}
|
|
1272
|
+
} else {
|
|
1273
|
+
if (browserDevToolsConnection) {
|
|
1274
|
+
try {
|
|
1275
|
+
browserDevToolsConnection.init({});
|
|
1276
|
+
} catch {}
|
|
1277
|
+
}
|
|
1278
|
+
try {
|
|
1279
|
+
unsubscribeDevTools?.();
|
|
1280
|
+
} catch {}
|
|
1281
|
+
try {
|
|
1282
|
+
browserDevToolsConnection?.unsubscribe?.();
|
|
1283
|
+
} catch {}
|
|
1284
|
+
try {
|
|
1285
|
+
browserDevToolsConnection?.disconnect?.();
|
|
1286
|
+
} catch {}
|
|
1287
|
+
browserDevToolsConnection = null;
|
|
1288
|
+
browserDevTools = null;
|
|
1289
|
+
devToolsExtension = null;
|
|
1290
|
+
isConnected = false;
|
|
1291
|
+
unsubscribeDevTools = null;
|
|
1292
|
+
devToolsConnections.delete(groupId);
|
|
686
1293
|
}
|
|
687
1294
|
if (sendTimer) {
|
|
688
1295
|
clearTimeout(sendTimer);
|
|
@@ -695,38 +1302,13 @@ function devTools(config = {}) {
|
|
|
695
1302
|
pendingSource = undefined;
|
|
696
1303
|
pendingDuration = undefined;
|
|
697
1304
|
lastSnapshot = undefined;
|
|
1305
|
+
lastSerializedJson = undefined;
|
|
698
1306
|
}
|
|
699
|
-
};
|
|
700
|
-
|
|
701
|
-
try {
|
|
1307
|
+
});
|
|
1308
|
+
if (enableBrowserDevTools) {
|
|
702
1309
|
initBrowserDevTools();
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (!isPathAllowed(path)) return;
|
|
706
|
-
pendingPaths.push(path);
|
|
707
|
-
});
|
|
708
|
-
unsubscribeFlush = notifier.onFlush(() => {
|
|
709
|
-
if (!browserDevTools) {
|
|
710
|
-
pendingPaths = [];
|
|
711
|
-
return;
|
|
712
|
-
}
|
|
713
|
-
if (pendingPaths.length === 0) return;
|
|
714
|
-
scheduleSend(undefined, {
|
|
715
|
-
source: 'path-notifier'
|
|
716
|
-
});
|
|
717
|
-
});
|
|
718
|
-
effectRef = effect(() => {
|
|
719
|
-
void originalTreeCall();
|
|
720
|
-
if (!effectPrimed) {
|
|
721
|
-
effectPrimed = true;
|
|
722
|
-
return;
|
|
723
|
-
}
|
|
724
|
-
scheduleSend(undefined, {
|
|
725
|
-
source: 'signal'
|
|
726
|
-
});
|
|
727
|
-
});
|
|
728
|
-
} catch {}
|
|
729
|
-
return Object.assign(enhancedTree, methods);
|
|
1310
|
+
}
|
|
1311
|
+
return result;
|
|
730
1312
|
};
|
|
731
1313
|
}
|
|
732
1314
|
function enableDevTools(treeName = 'SignalTree') {
|