@buoy-gg/redux 7.0.32 → 7.0.34
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/lib/commonjs/preset.js +14 -22
- package/lib/commonjs/redux/components/ReduxActionInfoView.js +8 -60
- package/lib/commonjs/redux/components/ReduxActionItem.js +64 -118
- package/lib/commonjs/redux/components/ReduxDetailViewToggle.js +15 -109
- package/lib/commonjs/redux/components/ReduxModal.js +46 -103
- package/lib/commonjs/redux/hooks/useReduxActions.js +16 -33
- package/lib/commonjs/redux/sync/reduxSyncAdapter.js +11 -29
- package/lib/commonjs/redux/utils/reduxActionStore.js +24 -94
- package/lib/commonjs/redux/utils/snapshotProvider.js +17 -80
- package/lib/module/preset.js +13 -20
- package/lib/module/redux/components/ReduxActionInfoView.js +1 -53
- package/lib/module/redux/components/ReduxActionItem.js +65 -119
- package/lib/module/redux/components/ReduxDetailViewToggle.js +17 -111
- package/lib/module/redux/components/ReduxModal.js +48 -105
- package/lib/module/redux/hooks/useReduxActions.js +17 -34
- package/lib/module/redux/sync/reduxSyncAdapter.js +12 -30
- package/lib/module/redux/utils/reduxActionStore.js +25 -95
- package/lib/module/redux/utils/snapshotProvider.js +18 -81
- package/lib/typescript/preset.d.ts +3 -9
- package/lib/typescript/preset.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionInfoView.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxActionItem.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts +2 -1
- package/lib/typescript/redux/components/ReduxDetailViewToggle.d.ts.map +1 -1
- package/lib/typescript/redux/components/ReduxModal.d.ts.map +1 -1
- package/lib/typescript/redux/hooks/useReduxActions.d.ts +3 -0
- package/lib/typescript/redux/hooks/useReduxActions.d.ts.map +1 -1
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts +68 -82
- package/lib/typescript/redux/sync/reduxSyncAdapter.d.ts.map +1 -1
- package/lib/typescript/redux/utils/reduxActionStore.d.ts +2 -29
- package/lib/typescript/redux/utils/reduxActionStore.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -38,24 +38,27 @@ const FREE_TIER_ACTION_LIMIT = exports.FREE_TIER_ACTION_LIMIT = _sharedUi.TIER_L
|
|
|
38
38
|
*/
|
|
39
39
|
function EmptyState({
|
|
40
40
|
isEnabled,
|
|
41
|
-
query
|
|
41
|
+
query,
|
|
42
|
+
totalActions
|
|
42
43
|
}) {
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
44
|
+
const content = (0, _sharedUi.resolveListEmptyState)({
|
|
45
|
+
subject: "actions",
|
|
46
|
+
search: query,
|
|
47
|
+
total: totalActions,
|
|
48
|
+
capturing: isEnabled,
|
|
49
|
+
waiting: {
|
|
50
|
+
otto: "waiting",
|
|
51
|
+
title: "No Redux actions",
|
|
52
|
+
message: "Actions will appear here as they are dispatched"
|
|
53
|
+
},
|
|
54
|
+
paused: {
|
|
55
|
+
title: "No Redux actions",
|
|
56
|
+
message: "Enable capture to start recording actions"
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ToolEmptyState, {
|
|
60
|
+
...content,
|
|
61
|
+
testID: `${_testIDs.REDUX_TEST_ID}-empty`
|
|
59
62
|
});
|
|
60
63
|
}
|
|
61
64
|
function ReduxModal({
|
|
@@ -268,49 +271,29 @@ function ReduxModal({
|
|
|
268
271
|
onBack: onBack
|
|
269
272
|
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Content, {
|
|
270
273
|
title: "",
|
|
271
|
-
children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
// still armed.
|
|
295
|
-
,
|
|
296
|
-
onBlur: () => {
|
|
297
|
-
if (searchText.length === 0) setIsSearchActive(false);
|
|
298
|
-
},
|
|
299
|
-
accessibilityLabel: "Search action types",
|
|
300
|
-
autoCapitalize: "none",
|
|
301
|
-
autoCorrect: false,
|
|
302
|
-
returnKeyType: "search"
|
|
303
|
-
}), /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.TouchableOpacity, {
|
|
304
|
-
onPress: closeSearch,
|
|
305
|
-
style: styles.headerSearchClear,
|
|
306
|
-
testID: `${_testIDs.REDUX_TEST_ID}-search-clear`,
|
|
307
|
-
accessibilityRole: "button",
|
|
308
|
-
accessibilityLabel: "Close search",
|
|
309
|
-
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.X, {
|
|
310
|
-
size: 14,
|
|
311
|
-
color: _sharedUi.night.color.textSecondary
|
|
312
|
-
})
|
|
313
|
-
})]
|
|
274
|
+
children: isSearchActive ? /*#__PURE__*/(0, _jsxRuntime.jsx)(_sharedUi.ModalHeader.Search, {
|
|
275
|
+
value: searchText,
|
|
276
|
+
onChangeText: handleSearch,
|
|
277
|
+
onCancel: closeSearch,
|
|
278
|
+
placeholder: "Search action types...",
|
|
279
|
+
accessibilityLabel: "Search action types",
|
|
280
|
+
iconSize: 14,
|
|
281
|
+
testID: `${_testIDs.REDUX_TEST_ID}-search-field`,
|
|
282
|
+
containerTestID: `${_testIDs.REDUX_TEST_ID}-search`,
|
|
283
|
+
clearTestID: `${_testIDs.REDUX_TEST_ID}-search-clear`
|
|
284
|
+
// Stay open while the query still filters the list.
|
|
285
|
+
//
|
|
286
|
+
// Collapsing it on blur hid the reason rows were missing: the
|
|
287
|
+
// header went back to its chips — which count the WHOLE
|
|
288
|
+
// capture, not the filtered list — while the list stayed
|
|
289
|
+
// narrowed, so the tool showed "4 actions" above a single row
|
|
290
|
+
// and offered nothing to explain the gap. The only way back
|
|
291
|
+
// was to reopen a field the user had no reason to think was
|
|
292
|
+
// still armed.
|
|
293
|
+
,
|
|
294
|
+
onBlur: () => {
|
|
295
|
+
if (searchText.length === 0) setIsSearchActive(false);
|
|
296
|
+
}
|
|
314
297
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.View, {
|
|
315
298
|
style: styles.headerChipRow,
|
|
316
299
|
children: [/*#__PURE__*/(0, _jsxRuntime.jsxs)(_reactNative.TouchableOpacity, {
|
|
@@ -505,14 +488,11 @@ function ReduxModal({
|
|
|
505
488
|
keyExtractor: keyExtractor,
|
|
506
489
|
contentContainerStyle: styles.listContent,
|
|
507
490
|
showsVerticalScrollIndicator: true,
|
|
508
|
-
|
|
509
|
-
initialNumToRender: 12,
|
|
510
|
-
maxToRenderPerBatch: 8,
|
|
511
|
-
windowSize: 5,
|
|
512
|
-
updateCellsBatchingPeriod: 50
|
|
491
|
+
..._sharedUi.EVENT_LIST_PRESET
|
|
513
492
|
}) : /*#__PURE__*/(0, _jsxRuntime.jsx)(EmptyState, {
|
|
514
493
|
isEnabled: isEnabled,
|
|
515
|
-
query: searchText
|
|
494
|
+
query: searchText,
|
|
495
|
+
totalActions: stats.totalActions
|
|
516
496
|
})]
|
|
517
497
|
})
|
|
518
498
|
});
|
|
@@ -594,27 +574,6 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
594
574
|
// Transparent so JsModal's ToolBackground shows through (the modal paints opaque base beneath it).
|
|
595
575
|
backgroundColor: "transparent"
|
|
596
576
|
},
|
|
597
|
-
headerSearchContainer: {
|
|
598
|
-
flexDirection: "row",
|
|
599
|
-
alignItems: "center",
|
|
600
|
-
backgroundColor: _sharedUi.night.color.surfaceElevated,
|
|
601
|
-
borderRadius: 10,
|
|
602
|
-
borderWidth: 1,
|
|
603
|
-
borderColor: _sharedUi.night.color.border,
|
|
604
|
-
paddingHorizontal: 12,
|
|
605
|
-
paddingVertical: 5
|
|
606
|
-
},
|
|
607
|
-
headerSearchInput: {
|
|
608
|
-
flex: 1,
|
|
609
|
-
color: _sharedUi.night.color.text,
|
|
610
|
-
fontSize: 13,
|
|
611
|
-
marginLeft: 6,
|
|
612
|
-
paddingVertical: 2
|
|
613
|
-
},
|
|
614
|
-
headerSearchClear: {
|
|
615
|
-
marginLeft: 6,
|
|
616
|
-
padding: 4
|
|
617
|
-
},
|
|
618
577
|
headerChipRow: {
|
|
619
578
|
flexDirection: "row",
|
|
620
579
|
alignItems: "center",
|
|
@@ -709,22 +668,6 @@ const styles = _reactNative.StyleSheet.create({
|
|
|
709
668
|
paddingTop: 8,
|
|
710
669
|
paddingBottom: 20
|
|
711
670
|
},
|
|
712
|
-
emptyState: {
|
|
713
|
-
alignItems: "center",
|
|
714
|
-
paddingVertical: 40
|
|
715
|
-
},
|
|
716
|
-
emptyTitle: {
|
|
717
|
-
color: _sharedUi.night.color.text,
|
|
718
|
-
fontSize: 14,
|
|
719
|
-
fontWeight: "600",
|
|
720
|
-
marginTop: 12,
|
|
721
|
-
marginBottom: 6
|
|
722
|
-
},
|
|
723
|
-
emptyText: {
|
|
724
|
-
color: _sharedUi.night.color.textTertiary,
|
|
725
|
-
fontSize: 12,
|
|
726
|
-
textAlign: "center"
|
|
727
|
-
},
|
|
728
671
|
// Locked actions banner styles
|
|
729
672
|
lockedBanner: {
|
|
730
673
|
flexDirection: "row",
|
|
@@ -5,41 +5,31 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
});
|
|
6
6
|
exports.useReduxActions = useReduxActions;
|
|
7
7
|
var _react = require("react");
|
|
8
|
+
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
8
9
|
var _reduxActionStore = require("../utils/reduxActionStore");
|
|
9
10
|
/**
|
|
10
11
|
* Hook for consuming Redux actions from the store
|
|
12
|
+
*
|
|
13
|
+
* The subscribe / filter / pause / clear plumbing is shared `useChangeLog`;
|
|
14
|
+
* this file owns the redux-specific stats, action types and selection.
|
|
11
15
|
*/
|
|
12
16
|
|
|
17
|
+
const filterActions = (_actions, filter) => _reduxActionStore.reduxActionStore.filterActions(filter);
|
|
13
18
|
function useReduxActions() {
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
const log = (0, _sharedUi.useChangeLog)(_reduxActionStore.reduxActionStore, {
|
|
20
|
+
filterItems: filterActions
|
|
21
|
+
});
|
|
17
22
|
const [selectedAction, setSelectedAction] = (0, _react.useState)(null);
|
|
18
23
|
|
|
19
|
-
// Subscribe to store changes
|
|
20
|
-
(0, _react.useEffect)(() => {
|
|
21
|
-
const unsubscribe = _reduxActionStore.reduxActionStore.subscribe(newActions => {
|
|
22
|
-
setActions(newActions);
|
|
23
|
-
});
|
|
24
|
-
return () => {
|
|
25
|
-
unsubscribe();
|
|
26
|
-
};
|
|
27
|
-
}, []);
|
|
28
|
-
|
|
29
|
-
// Filter actions based on current filter
|
|
30
|
-
const filteredActions = (0, _react.useMemo)(() => {
|
|
31
|
-
return _reduxActionStore.reduxActionStore.filterActions(filter);
|
|
32
|
-
}, [actions, filter]);
|
|
33
|
-
|
|
34
24
|
// Get stats
|
|
35
25
|
const stats = (0, _react.useMemo)(() => {
|
|
36
26
|
return _reduxActionStore.reduxActionStore.getStats();
|
|
37
|
-
}, [
|
|
27
|
+
}, [log.items]);
|
|
38
28
|
|
|
39
29
|
// Get unique action types
|
|
40
30
|
const actionTypes = (0, _react.useMemo)(() => {
|
|
41
31
|
return _reduxActionStore.reduxActionStore.getUniqueActionTypes();
|
|
42
|
-
}, [
|
|
32
|
+
}, [log.items]);
|
|
43
33
|
|
|
44
34
|
// Clear actions
|
|
45
35
|
const clearActions = (0, _react.useCallback)(() => {
|
|
@@ -47,26 +37,19 @@ function useReduxActions() {
|
|
|
47
37
|
setSelectedAction(null);
|
|
48
38
|
}, []);
|
|
49
39
|
|
|
50
|
-
// Toggle capture
|
|
51
|
-
const toggleCapture = (0, _react.useCallback)(() => {
|
|
52
|
-
const newEnabled = !isEnabled;
|
|
53
|
-
_reduxActionStore.reduxActionStore.setEnabled(newEnabled);
|
|
54
|
-
setIsEnabled(newEnabled);
|
|
55
|
-
}, [isEnabled]);
|
|
56
|
-
|
|
57
40
|
// Get action by ID
|
|
58
41
|
const getActionById = (0, _react.useCallback)(id => {
|
|
59
42
|
return _reduxActionStore.reduxActionStore.getActionById(id);
|
|
60
43
|
}, []);
|
|
61
44
|
return {
|
|
62
|
-
actions,
|
|
63
|
-
filteredActions,
|
|
64
|
-
filter,
|
|
65
|
-
setFilter,
|
|
45
|
+
actions: log.items,
|
|
46
|
+
filteredActions: log.filteredItems,
|
|
47
|
+
filter: log.filter,
|
|
48
|
+
setFilter: log.setFilter,
|
|
66
49
|
stats,
|
|
67
50
|
clearActions,
|
|
68
|
-
isEnabled,
|
|
69
|
-
toggleCapture,
|
|
51
|
+
isEnabled: log.isEnabled,
|
|
52
|
+
toggleCapture: log.toggleCapture,
|
|
70
53
|
actionTypes,
|
|
71
54
|
getActionById,
|
|
72
55
|
selectedAction,
|
|
@@ -7,19 +7,8 @@ exports.reduxSyncAdapter = void 0;
|
|
|
7
7
|
var _reduxActionStore = require("../utils/reduxActionStore");
|
|
8
8
|
var _sharedUi = require("@buoy-gg/shared-ui");
|
|
9
9
|
var _autoInstrument = require("../utils/autoInstrument");
|
|
10
|
-
/**
|
|
11
|
-
* Upper bound for one on-demand action-detail response. Generous — this is a
|
|
12
|
-
* single explicit fetch, not a per-action stream — but still guarded so a
|
|
13
|
-
* pathological tree can't OOM the device through the reply emit.
|
|
14
|
-
*/
|
|
15
|
-
const MAX_ACTION_DETAIL_BYTES = 8 * 1024 * 1024;
|
|
16
|
-
|
|
17
10
|
/** Per-action budget for `payload`/`action`/`meta.arg`/`error` on the snapshot wire. */
|
|
18
|
-
const
|
|
19
|
-
function toWireValue(value) {
|
|
20
|
-
if (value === undefined) return value;
|
|
21
|
-
return (0, _sharedUi.isOverWireBudget)(value, MAX_WIRE_PAYLOAD_BYTES) ? _reduxActionStore.PAYLOAD_ON_DEVICE : value;
|
|
22
|
-
}
|
|
11
|
+
const toWireValue = value => (0, _sharedUi.toWire)(value, _reduxActionStore.PAYLOAD_ON_DEVICE);
|
|
23
12
|
|
|
24
13
|
/**
|
|
25
14
|
* Per-action wire cache. `reduxActionStore` never mutates an action in place —
|
|
@@ -28,10 +17,7 @@ function toWireValue(value) {
|
|
|
28
17
|
* `action`, `meta.arg` and `error` in the list is re-measured on all ~5
|
|
29
18
|
* snapshots per second for as long as it stays in the list.
|
|
30
19
|
*/
|
|
31
|
-
const
|
|
32
|
-
function toWireAction(a) {
|
|
33
|
-
const cached = wireCache.get(a);
|
|
34
|
-
if (cached) return cached;
|
|
20
|
+
const toWireAction = (0, _sharedUi.memoByIdentity)(a => {
|
|
35
21
|
const wire = (0, _reduxActionStore.isEvictedState)(a.prevState) ? {
|
|
36
22
|
...a
|
|
37
23
|
} : {
|
|
@@ -54,9 +40,8 @@ function toWireAction(a) {
|
|
|
54
40
|
arg
|
|
55
41
|
};
|
|
56
42
|
}
|
|
57
|
-
wireCache.set(a, wire);
|
|
58
43
|
return wire;
|
|
59
|
-
}
|
|
44
|
+
});
|
|
60
45
|
/** Top-level slice names of the state tree, for the compact (no-values) view. */
|
|
61
46
|
function topLevelKeys(state) {
|
|
62
47
|
return state && typeof state === "object" && !Array.isArray(state) ? Object.keys(state) : [];
|
|
@@ -93,7 +78,7 @@ function unavailable() {
|
|
|
93
78
|
* dispatch actions — redux is action-based, so the *current state* isn't in the
|
|
94
79
|
* action-log snapshot.
|
|
95
80
|
*/
|
|
96
|
-
const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
81
|
+
const reduxSyncAdapter = exports.reduxSyncAdapter = (0, _sharedUi.createSyncAdapter)({
|
|
97
82
|
version: 2,
|
|
98
83
|
/**
|
|
99
84
|
* WIRE-FORM snapshot: actions WITHOUT raw prevState/nextState trees.
|
|
@@ -118,7 +103,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
118
103
|
getActionDetail: params => {
|
|
119
104
|
const {
|
|
120
105
|
id
|
|
121
|
-
} = params
|
|
106
|
+
} = (0, _sharedUi.readParams)(params);
|
|
122
107
|
if (!id) return {
|
|
123
108
|
found: false,
|
|
124
109
|
reason: "missing id"
|
|
@@ -138,11 +123,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
138
123
|
}
|
|
139
124
|
// Per-field cap: oversized fields degrade individually instead of
|
|
140
125
|
// failing the whole detail (a giant payload shouldn't hide the trees).
|
|
141
|
-
const
|
|
142
|
-
__buoyTruncated: true,
|
|
143
|
-
note: `Exceeds the ${MAX_ACTION_DETAIL_BYTES / (1024 * 1024)}MB detail limit — inspect it on-device.`
|
|
144
|
-
};
|
|
145
|
-
const cap = value => (0, _sharedUi.isOverWireBudget)(value, MAX_ACTION_DETAIL_BYTES) ? cappedNote : value;
|
|
126
|
+
const cap = value => (0, _sharedUi.capDetail)(value, _sharedUi.WIRE_DETAIL_LIMIT_BYTES);
|
|
146
127
|
const capMeta = meta => {
|
|
147
128
|
if (!meta) return meta;
|
|
148
129
|
const arg = cap(meta.arg);
|
|
@@ -169,7 +150,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
169
150
|
getState: params => {
|
|
170
151
|
const {
|
|
171
152
|
includeValues = false
|
|
172
|
-
} = params
|
|
153
|
+
} = (0, _sharedUi.readParams)(params);
|
|
173
154
|
const store = (0, _autoInstrument.getActiveStore)();
|
|
174
155
|
if (!store) {
|
|
175
156
|
return unavailable();
|
|
@@ -191,7 +172,7 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
191
172
|
dispatch: params => {
|
|
192
173
|
const {
|
|
193
174
|
action
|
|
194
|
-
} = params;
|
|
175
|
+
} = (0, _sharedUi.readParams)(params);
|
|
195
176
|
const store = (0, _autoInstrument.getActiveStore)();
|
|
196
177
|
if (!store) {
|
|
197
178
|
return {
|
|
@@ -199,11 +180,12 @@ const reduxSyncAdapter = exports.reduxSyncAdapter = {
|
|
|
199
180
|
...unavailable()
|
|
200
181
|
};
|
|
201
182
|
}
|
|
183
|
+
if (!action) throw new Error("dispatch requires an `action`");
|
|
202
184
|
store.dispatch(action);
|
|
203
185
|
return {
|
|
204
186
|
dispatched: true,
|
|
205
|
-
type: action
|
|
187
|
+
type: action.type
|
|
206
188
|
};
|
|
207
189
|
}
|
|
208
190
|
}
|
|
209
|
-
};
|
|
191
|
+
});
|
|
@@ -238,16 +238,9 @@ function extractMeta(action) {
|
|
|
238
238
|
// Redux Action Store
|
|
239
239
|
// ============================================
|
|
240
240
|
|
|
241
|
-
class ReduxActionStore {
|
|
242
|
-
actions = [];
|
|
243
|
-
listeners = new Set();
|
|
244
|
-
clearListeners = new Set();
|
|
245
|
-
maxActions = 200;
|
|
246
|
-
idCounter = 0;
|
|
247
|
-
isEnabled = true;
|
|
248
|
-
captureSuppressed = false;
|
|
241
|
+
class ReduxActionStore extends _sharedUi.ChangeLog {
|
|
249
242
|
addAction(action, prevState, nextState, duration) {
|
|
250
|
-
if (!this.
|
|
243
|
+
if (!this.canRecord()) {
|
|
251
244
|
return;
|
|
252
245
|
}
|
|
253
246
|
const actionType = action.type || "";
|
|
@@ -273,7 +266,7 @@ class ReduxActionStore {
|
|
|
273
266
|
const isSlowAction = (duration ?? 0) > 16; // Frame budget
|
|
274
267
|
|
|
275
268
|
const reduxAction = {
|
|
276
|
-
id:
|
|
269
|
+
id: this.nextId(),
|
|
277
270
|
type: actionType,
|
|
278
271
|
payload: action.payload,
|
|
279
272
|
action,
|
|
@@ -293,7 +286,7 @@ class ReduxActionStore {
|
|
|
293
286
|
changedKeysCount,
|
|
294
287
|
isSlowAction
|
|
295
288
|
};
|
|
296
|
-
this.
|
|
289
|
+
this.push(reduxAction);
|
|
297
290
|
|
|
298
291
|
// Bound raw-state retention. Every action holds prevState/nextState by
|
|
299
292
|
// reference; with structural sharing that's normally cheap, but when a
|
|
@@ -302,8 +295,8 @@ class ReduxActionStore {
|
|
|
302
295
|
// pinned by 50 slice-replacing actions, enough to OOM-kill enterprise
|
|
303
296
|
// apps. Detail view + time travel realistically use recent actions, so
|
|
304
297
|
// older ones keep their diff metadata but drop the raw trees.
|
|
305
|
-
for (let i = STATE_RETENTION_LIMIT; i < this.
|
|
306
|
-
const a = this.
|
|
298
|
+
for (let i = STATE_RETENTION_LIMIT; i < this.items.length; i++) {
|
|
299
|
+
const a = this.items[i];
|
|
307
300
|
if (a.prevState !== STATE_EVICTED) {
|
|
308
301
|
a.prevState = STATE_EVICTED;
|
|
309
302
|
a.nextState = STATE_EVICTED;
|
|
@@ -314,63 +307,26 @@ class ReduxActionStore {
|
|
|
314
307
|
this.notifyListeners();
|
|
315
308
|
}
|
|
316
309
|
getActions() {
|
|
317
|
-
return
|
|
310
|
+
return this.getAll();
|
|
318
311
|
}
|
|
319
312
|
getActionById(id) {
|
|
320
|
-
return this.
|
|
313
|
+
return this.getById(id);
|
|
321
314
|
}
|
|
322
315
|
clearActions() {
|
|
323
|
-
this.
|
|
324
|
-
this.notifyListeners();
|
|
325
|
-
this.clearListeners.forEach(listener => {
|
|
326
|
-
try {
|
|
327
|
-
listener();
|
|
328
|
-
} catch {
|
|
329
|
-
// Ignore listener errors
|
|
330
|
-
}
|
|
331
|
-
});
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
/**
|
|
335
|
-
* Listen for clearActions() calls. Used in remote mirror mode to forward a
|
|
336
|
-
* clear performed in the dashboard UI to the synced device.
|
|
337
|
-
*/
|
|
338
|
-
onClear(listener) {
|
|
339
|
-
this.clearListeners.add(listener);
|
|
340
|
-
return () => {
|
|
341
|
-
this.clearListeners.delete(listener);
|
|
342
|
-
};
|
|
316
|
+
this.clear();
|
|
343
317
|
}
|
|
344
318
|
|
|
345
319
|
// ============================================
|
|
346
320
|
// Remote Mirror Mode
|
|
347
321
|
// ============================================
|
|
348
322
|
|
|
349
|
-
/**
|
|
350
|
-
* Permanently suppress local capture. Use when this store acts as a mirror
|
|
351
|
-
* of a remote device's actions (e.g. the desktop dashboard): actions arrive
|
|
352
|
-
* via replaceActions() and local instrumentation must never record.
|
|
353
|
-
*/
|
|
354
|
-
disableCapture() {
|
|
355
|
-
this.captureSuppressed = true;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
/**
|
|
359
|
-
* Whether the store is in remote mirror mode (capture suppressed). The UI
|
|
360
|
-
* uses this to skip local-instrumentation affordances like the
|
|
361
|
-
* "react-redux not installed" banner.
|
|
362
|
-
*/
|
|
363
|
-
isCaptureSuppressed() {
|
|
364
|
-
return this.captureSuppressed;
|
|
365
|
-
}
|
|
366
|
-
|
|
367
323
|
/**
|
|
368
324
|
* Replace the entire action list and notify listeners. Used in remote
|
|
369
325
|
* mirror mode where full snapshots arrive from a synced device. Respects
|
|
370
326
|
* the enabled flag so the UI's pause button freezes the mirror.
|
|
371
327
|
*/
|
|
372
328
|
replaceActions(actions) {
|
|
373
|
-
if (!this.
|
|
329
|
+
if (!this.getEnabled()) {
|
|
374
330
|
return;
|
|
375
331
|
}
|
|
376
332
|
// Preserve any trees a remote mirror already hydrated via
|
|
@@ -378,12 +334,12 @@ class ReduxActionStore {
|
|
|
378
334
|
// full snapshot arriving after a detail fetch must not blank the detail
|
|
379
335
|
// pane back out.
|
|
380
336
|
const hydrated = new Map();
|
|
381
|
-
for (const existing of this.
|
|
337
|
+
for (const existing of this.items) {
|
|
382
338
|
if (existing.prevState !== undefined && !isEvictedState(existing.prevState)) {
|
|
383
339
|
hydrated.set(existing.id, existing);
|
|
384
340
|
}
|
|
385
341
|
}
|
|
386
|
-
|
|
342
|
+
const merged = actions.slice(0, this.max).map(a => {
|
|
387
343
|
const h = hydrated.get(a.id);
|
|
388
344
|
if (!h || !isEvictedState(a.prevState)) return a;
|
|
389
345
|
return {
|
|
@@ -396,7 +352,7 @@ class ReduxActionStore {
|
|
|
396
352
|
error: isPayloadOnDevice(a.error) ? h.error : a.error
|
|
397
353
|
};
|
|
398
354
|
});
|
|
399
|
-
this.
|
|
355
|
+
this.setItems(merged);
|
|
400
356
|
}
|
|
401
357
|
|
|
402
358
|
/**
|
|
@@ -405,44 +361,18 @@ class ReduxActionStore {
|
|
|
405
361
|
* `getActionDetail` action. Remote-mirror (desktop) use only.
|
|
406
362
|
*/
|
|
407
363
|
hydrateAction(id, patch) {
|
|
408
|
-
|
|
409
|
-
if (index === -1) return;
|
|
410
|
-
this.actions = [...this.actions.slice(0, index), {
|
|
411
|
-
...this.actions[index],
|
|
412
|
-
...patch
|
|
413
|
-
}, ...this.actions.slice(index + 1)];
|
|
414
|
-
this.notifyListeners();
|
|
415
|
-
}
|
|
416
|
-
setEnabled(enabled) {
|
|
417
|
-
this.isEnabled = enabled;
|
|
418
|
-
}
|
|
419
|
-
getEnabled() {
|
|
420
|
-
return this.isEnabled;
|
|
421
|
-
}
|
|
422
|
-
subscribe(listener) {
|
|
423
|
-
this.listeners.add(listener);
|
|
424
|
-
return () => {
|
|
425
|
-
this.listeners.delete(listener);
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
notifyListeners() {
|
|
429
|
-
const actions = this.getActions();
|
|
430
|
-
this.listeners.forEach(listener => listener(actions));
|
|
364
|
+
this.hydrateById(id, patch);
|
|
431
365
|
}
|
|
432
366
|
setMaxActions(max) {
|
|
433
|
-
this.
|
|
434
|
-
if (this.actions.length > max) {
|
|
435
|
-
this.actions = this.actions.slice(0, max);
|
|
436
|
-
this.notifyListeners();
|
|
437
|
-
}
|
|
367
|
+
this.setMax(max);
|
|
438
368
|
}
|
|
439
369
|
getStats() {
|
|
440
|
-
const total = this.
|
|
441
|
-
const withChanges = this.
|
|
442
|
-
const actionTypes = new Set(this.
|
|
370
|
+
const total = this.items.length;
|
|
371
|
+
const withChanges = this.items.filter(a => a.hasStateChange).length;
|
|
372
|
+
const actionTypes = new Set(this.items.map(a => a.type));
|
|
443
373
|
|
|
444
374
|
// Calculate average duration
|
|
445
|
-
const durations = this.
|
|
375
|
+
const durations = this.items.filter(a => a.duration !== undefined).map(a => a.duration);
|
|
446
376
|
const avgDuration = durations.length > 0 ? durations.reduce((sum, d) => sum + d, 0) / durations.length : 0;
|
|
447
377
|
return {
|
|
448
378
|
totalActions: total,
|
|
@@ -453,7 +383,7 @@ class ReduxActionStore {
|
|
|
453
383
|
};
|
|
454
384
|
}
|
|
455
385
|
filterActions(filter) {
|
|
456
|
-
let filtered = [...this.
|
|
386
|
+
let filtered = [...this.items];
|
|
457
387
|
if (filter.searchText) {
|
|
458
388
|
const search = filter.searchText.toLowerCase();
|
|
459
389
|
filtered = filtered.filter(a => a.type.toLowerCase().includes(search));
|
|
@@ -467,14 +397,14 @@ class ReduxActionStore {
|
|
|
467
397
|
return filtered;
|
|
468
398
|
}
|
|
469
399
|
getUniqueActionTypes() {
|
|
470
|
-
return Array.from(new Set(this.
|
|
400
|
+
return Array.from(new Set(this.items.map(a => a.type))).sort();
|
|
471
401
|
}
|
|
472
402
|
|
|
473
403
|
/**
|
|
474
404
|
* Find related async actions by requestId
|
|
475
405
|
*/
|
|
476
406
|
getRelatedActions(requestId) {
|
|
477
|
-
return this.
|
|
407
|
+
return this.items.filter(a => a.meta?.requestId === requestId).sort((a, b) => a.timestamp - b.timestamp); // Sort chronologically
|
|
478
408
|
}
|
|
479
409
|
|
|
480
410
|
/**
|
|
@@ -490,7 +420,7 @@ class ReduxActionStore {
|
|
|
490
420
|
*/
|
|
491
421
|
getLinkedActionsCount(action) {
|
|
492
422
|
if (!action.meta?.requestId) return 0;
|
|
493
|
-
return this.
|
|
423
|
+
return this.items.filter(a => a.meta?.requestId === action.meta?.requestId).length;
|
|
494
424
|
}
|
|
495
425
|
|
|
496
426
|
/**
|
|
@@ -528,7 +458,7 @@ class ReduxActionStore {
|
|
|
528
458
|
getAsyncOperationIndex(requestId) {
|
|
529
459
|
// Get all unique requestIds with their first timestamp
|
|
530
460
|
const requestIdTimestamps = new Map();
|
|
531
|
-
for (const action of this.
|
|
461
|
+
for (const action of this.items) {
|
|
532
462
|
if (action.meta?.requestId && !requestIdTimestamps.has(action.meta.requestId)) {
|
|
533
463
|
requestIdTimestamps.set(action.meta.requestId, action.timestamp);
|
|
534
464
|
}
|