@ngrx/store-devtools 12.5.1 → 13.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.
Files changed (58) hide show
  1. package/{esm2015/index.js → esm2020/index.mjs} +0 -0
  2. package/esm2020/ngrx-store-devtools.mjs +5 -0
  3. package/{esm2015/public_api.js → esm2020/public_api.mjs} +0 -0
  4. package/{esm2015/src/actions.js → esm2020/src/actions.mjs} +0 -0
  5. package/{esm2015/src/config.js → esm2020/src/config.mjs} +0 -0
  6. package/esm2020/src/devtools-dispatcher.mjs +11 -0
  7. package/esm2020/src/devtools.mjs +108 -0
  8. package/esm2020/src/extension.mjs +161 -0
  9. package/{esm2015/src/index.js → esm2020/src/index.mjs} +0 -0
  10. package/esm2020/src/instrument.mjs +71 -0
  11. package/esm2020/src/reducer.mjs +368 -0
  12. package/esm2020/src/utils.mjs +114 -0
  13. package/fesm2015/{ngrx-store-devtools.js → ngrx-store-devtools.mjs} +182 -188
  14. package/fesm2015/ngrx-store-devtools.mjs.map +1 -0
  15. package/fesm2020/ngrx-store-devtools.mjs +969 -0
  16. package/fesm2020/ngrx-store-devtools.mjs.map +1 -0
  17. package/migrations/6_0_0/index.js +1 -1
  18. package/migrations/6_0_0/index.js.map +1 -1
  19. package/ngrx-store-devtools.d.ts +1 -4
  20. package/package.json +21 -9
  21. package/schematics/ng-add/index.js +12 -12
  22. package/schematics/ng-add/index.js.map +1 -1
  23. package/schematics-core/index.js +1 -3
  24. package/schematics-core/index.js.map +1 -1
  25. package/schematics-core/utility/ast-utils.js +12 -8
  26. package/schematics-core/utility/ast-utils.js.map +1 -1
  27. package/schematics-core/utility/find-component.js +12 -12
  28. package/schematics-core/utility/find-component.js.map +1 -1
  29. package/schematics-core/utility/find-module.js +12 -12
  30. package/schematics-core/utility/find-module.js.map +1 -1
  31. package/schematics-core/utility/json-utilts.js.map +1 -1
  32. package/schematics-core/utility/libs-version.js +1 -1
  33. package/schematics-core/utility/libs-version.js.map +1 -1
  34. package/schematics-core/utility/ngrx-utils.js +16 -12
  35. package/schematics-core/utility/ngrx-utils.js.map +1 -1
  36. package/schematics-core/utility/parse-name.js +3 -3
  37. package/schematics-core/utility/parse-name.js.map +1 -1
  38. package/schematics-core/utility/project.js +1 -1
  39. package/schematics-core/utility/project.js.map +1 -1
  40. package/schematics-core/utility/visitors.js +2 -2
  41. package/schematics-core/utility/visitors.js.map +1 -1
  42. package/src/devtools-dispatcher.d.ts +3 -0
  43. package/src/devtools.d.ts +3 -0
  44. package/src/extension.d.ts +3 -0
  45. package/src/instrument.d.ts +4 -0
  46. package/bundles/ngrx-store-devtools.umd.js +0 -1318
  47. package/bundles/ngrx-store-devtools.umd.js.map +0 -1
  48. package/esm2015/ngrx-store-devtools.js +0 -9
  49. package/esm2015/src/devtools-dispatcher.js +0 -9
  50. package/esm2015/src/devtools.js +0 -115
  51. package/esm2015/src/extension.js +0 -160
  52. package/esm2015/src/instrument.js +0 -67
  53. package/esm2015/src/reducer.js +0 -365
  54. package/esm2015/src/utils.js +0 -106
  55. package/fesm2015/ngrx-store-devtools.js.map +0 -1
  56. package/ngrx-store-devtools.metadata.json +0 -1
  57. package/schematics-core/utility/angular-utils.js +0 -33
  58. package/schematics-core/utility/angular-utils.js.map +0 -1
@@ -0,0 +1,969 @@
1
+ import * as i0 from '@angular/core';
2
+ import { InjectionToken, Injectable, Inject, NgModule } from '@angular/core';
3
+ import * as i2 from '@ngrx/store';
4
+ import { INIT, UPDATE, ActionsSubject, INITIAL_STATE, StateObservable, ReducerManagerDispatcher } from '@ngrx/store';
5
+ import { EMPTY, Observable, of, merge, queueScheduler, ReplaySubject } from 'rxjs';
6
+ import { share, filter, map, concatMap, timeout, debounceTime, catchError, take, takeUntil, switchMap, skip, observeOn, withLatestFrom, scan } from 'rxjs/operators';
7
+
8
+ /**
9
+ * Chrome extension documentation
10
+ * @see https://github.com/reduxjs/redux-devtools/blob/main/extension/docs/API/Arguments.md
11
+ * Firefox extension documentation
12
+ * @see https://github.com/zalmoxisus/redux-devtools-extension/blob/master/docs/API/Arguments.md
13
+ */
14
+ class StoreDevtoolsConfig {
15
+ constructor() {
16
+ /**
17
+ * Maximum allowed actions to be stored in the history tree (default: `false`)
18
+ */
19
+ this.maxAge = false;
20
+ }
21
+ }
22
+ const STORE_DEVTOOLS_CONFIG = new InjectionToken('@ngrx/store-devtools Options');
23
+ /**
24
+ * Used to provide a `StoreDevtoolsConfig` for the store-devtools.
25
+ */
26
+ const INITIAL_OPTIONS = new InjectionToken('@ngrx/store-devtools Initial Config');
27
+ function noMonitor() {
28
+ return null;
29
+ }
30
+ const DEFAULT_NAME = 'NgRx Store DevTools';
31
+ function createConfig(optionsInput) {
32
+ const DEFAULT_OPTIONS = {
33
+ maxAge: false,
34
+ monitor: noMonitor,
35
+ actionSanitizer: undefined,
36
+ stateSanitizer: undefined,
37
+ name: DEFAULT_NAME,
38
+ serialize: false,
39
+ logOnly: false,
40
+ autoPause: false,
41
+ // Add all features explicitly. This prevent buggy behavior for
42
+ // options like "lock" which might otherwise not show up.
43
+ features: {
44
+ pause: true,
45
+ lock: true,
46
+ persist: true,
47
+ export: true,
48
+ import: 'custom',
49
+ jump: true,
50
+ skip: true,
51
+ reorder: true,
52
+ dispatch: true,
53
+ test: true, // Generate tests for the selected actions
54
+ },
55
+ };
56
+ const options = typeof optionsInput === 'function' ? optionsInput() : optionsInput;
57
+ const logOnly = options.logOnly
58
+ ? { pause: true, export: true, test: true }
59
+ : false;
60
+ const features = options.features || logOnly || DEFAULT_OPTIONS.features;
61
+ const config = Object.assign({}, DEFAULT_OPTIONS, { features }, options);
62
+ if (config.maxAge && config.maxAge < 2) {
63
+ throw new Error(`Devtools 'maxAge' cannot be less than 2, got ${config.maxAge}`);
64
+ }
65
+ return config;
66
+ }
67
+
68
+ const PERFORM_ACTION = 'PERFORM_ACTION';
69
+ const REFRESH = 'REFRESH';
70
+ const RESET = 'RESET';
71
+ const ROLLBACK = 'ROLLBACK';
72
+ const COMMIT = 'COMMIT';
73
+ const SWEEP = 'SWEEP';
74
+ const TOGGLE_ACTION = 'TOGGLE_ACTION';
75
+ const SET_ACTIONS_ACTIVE = 'SET_ACTIONS_ACTIVE';
76
+ const JUMP_TO_STATE = 'JUMP_TO_STATE';
77
+ const JUMP_TO_ACTION = 'JUMP_TO_ACTION';
78
+ const IMPORT_STATE = 'IMPORT_STATE';
79
+ const LOCK_CHANGES = 'LOCK_CHANGES';
80
+ const PAUSE_RECORDING = 'PAUSE_RECORDING';
81
+ class PerformAction {
82
+ constructor(action, timestamp) {
83
+ this.action = action;
84
+ this.timestamp = timestamp;
85
+ this.type = PERFORM_ACTION;
86
+ if (typeof action.type === 'undefined') {
87
+ throw new Error('Actions may not have an undefined "type" property. ' +
88
+ 'Have you misspelled a constant?');
89
+ }
90
+ }
91
+ }
92
+ class Refresh {
93
+ constructor() {
94
+ this.type = REFRESH;
95
+ }
96
+ }
97
+ class Reset {
98
+ constructor(timestamp) {
99
+ this.timestamp = timestamp;
100
+ this.type = RESET;
101
+ }
102
+ }
103
+ class Rollback {
104
+ constructor(timestamp) {
105
+ this.timestamp = timestamp;
106
+ this.type = ROLLBACK;
107
+ }
108
+ }
109
+ class Commit {
110
+ constructor(timestamp) {
111
+ this.timestamp = timestamp;
112
+ this.type = COMMIT;
113
+ }
114
+ }
115
+ class Sweep {
116
+ constructor() {
117
+ this.type = SWEEP;
118
+ }
119
+ }
120
+ class ToggleAction {
121
+ constructor(id) {
122
+ this.id = id;
123
+ this.type = TOGGLE_ACTION;
124
+ }
125
+ }
126
+ class SetActionsActive {
127
+ constructor(start, end, active = true) {
128
+ this.start = start;
129
+ this.end = end;
130
+ this.active = active;
131
+ this.type = SET_ACTIONS_ACTIVE;
132
+ }
133
+ }
134
+ class JumpToState {
135
+ constructor(index) {
136
+ this.index = index;
137
+ this.type = JUMP_TO_STATE;
138
+ }
139
+ }
140
+ class JumpToAction {
141
+ constructor(actionId) {
142
+ this.actionId = actionId;
143
+ this.type = JUMP_TO_ACTION;
144
+ }
145
+ }
146
+ class ImportState {
147
+ constructor(nextLiftedState) {
148
+ this.nextLiftedState = nextLiftedState;
149
+ this.type = IMPORT_STATE;
150
+ }
151
+ }
152
+ class LockChanges {
153
+ constructor(status) {
154
+ this.status = status;
155
+ this.type = LOCK_CHANGES;
156
+ }
157
+ }
158
+ class PauseRecording {
159
+ constructor(status) {
160
+ this.status = status;
161
+ this.type = PAUSE_RECORDING;
162
+ }
163
+ }
164
+
165
+ function difference(first, second) {
166
+ return first.filter((item) => second.indexOf(item) < 0);
167
+ }
168
+ /**
169
+ * Provides an app's view into the state of the lifted store.
170
+ */
171
+ function unliftState(liftedState) {
172
+ const { computedStates, currentStateIndex } = liftedState;
173
+ // At start up NgRx dispatches init actions,
174
+ // When these init actions are being filtered out by the predicate or safe/block list options
175
+ // we don't have a complete computed states yet.
176
+ // At this point it could happen that we're out of bounds, when this happens we fall back to the last known state
177
+ if (currentStateIndex >= computedStates.length) {
178
+ const { state } = computedStates[computedStates.length - 1];
179
+ return state;
180
+ }
181
+ const { state } = computedStates[currentStateIndex];
182
+ return state;
183
+ }
184
+ function unliftAction(liftedState) {
185
+ return liftedState.actionsById[liftedState.nextActionId - 1];
186
+ }
187
+ /**
188
+ * Lifts an app's action into an action on the lifted store.
189
+ */
190
+ function liftAction(action) {
191
+ return new PerformAction(action, +Date.now());
192
+ }
193
+ /**
194
+ * Sanitizes given actions with given function.
195
+ */
196
+ function sanitizeActions(actionSanitizer, actions) {
197
+ return Object.keys(actions).reduce((sanitizedActions, actionIdx) => {
198
+ const idx = Number(actionIdx);
199
+ sanitizedActions[idx] = sanitizeAction(actionSanitizer, actions[idx], idx);
200
+ return sanitizedActions;
201
+ }, {});
202
+ }
203
+ /**
204
+ * Sanitizes given action with given function.
205
+ */
206
+ function sanitizeAction(actionSanitizer, action, actionIdx) {
207
+ return {
208
+ ...action,
209
+ action: actionSanitizer(action.action, actionIdx),
210
+ };
211
+ }
212
+ /**
213
+ * Sanitizes given states with given function.
214
+ */
215
+ function sanitizeStates(stateSanitizer, states) {
216
+ return states.map((computedState, idx) => ({
217
+ state: sanitizeState(stateSanitizer, computedState.state, idx),
218
+ error: computedState.error,
219
+ }));
220
+ }
221
+ /**
222
+ * Sanitizes given state with given function.
223
+ */
224
+ function sanitizeState(stateSanitizer, state, stateIdx) {
225
+ return stateSanitizer(state, stateIdx);
226
+ }
227
+ /**
228
+ * Read the config and tell if actions should be filtered
229
+ */
230
+ function shouldFilterActions(config) {
231
+ return config.predicate || config.actionsSafelist || config.actionsBlocklist;
232
+ }
233
+ /**
234
+ * Return a full filtered lifted state
235
+ */
236
+ function filterLiftedState(liftedState, predicate, safelist, blocklist) {
237
+ const filteredStagedActionIds = [];
238
+ const filteredActionsById = {};
239
+ const filteredComputedStates = [];
240
+ liftedState.stagedActionIds.forEach((id, idx) => {
241
+ const liftedAction = liftedState.actionsById[id];
242
+ if (!liftedAction)
243
+ return;
244
+ if (idx &&
245
+ isActionFiltered(liftedState.computedStates[idx], liftedAction, predicate, safelist, blocklist)) {
246
+ return;
247
+ }
248
+ filteredActionsById[id] = liftedAction;
249
+ filteredStagedActionIds.push(id);
250
+ filteredComputedStates.push(liftedState.computedStates[idx]);
251
+ });
252
+ return {
253
+ ...liftedState,
254
+ stagedActionIds: filteredStagedActionIds,
255
+ actionsById: filteredActionsById,
256
+ computedStates: filteredComputedStates,
257
+ };
258
+ }
259
+ /**
260
+ * Return true is the action should be ignored
261
+ */
262
+ function isActionFiltered(state, action, predicate, safelist, blockedlist) {
263
+ const predicateMatch = predicate && !predicate(state, action.action);
264
+ const safelistMatch = safelist &&
265
+ !action.action.type.match(safelist.map((s) => escapeRegExp(s)).join('|'));
266
+ const blocklistMatch = blockedlist &&
267
+ action.action.type.match(blockedlist.map((s) => escapeRegExp(s)).join('|'));
268
+ return predicateMatch || safelistMatch || blocklistMatch;
269
+ }
270
+ /**
271
+ * Return string with escaped RegExp special characters
272
+ * https://stackoverflow.com/a/6969486/1337347
273
+ */
274
+ function escapeRegExp(s) {
275
+ return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
276
+ }
277
+
278
+ const INIT_ACTION = { type: INIT };
279
+ const RECOMPUTE = '@ngrx/store-devtools/recompute';
280
+ const RECOMPUTE_ACTION = { type: RECOMPUTE };
281
+ /**
282
+ * Computes the next entry in the log by applying an action.
283
+ */
284
+ function computeNextEntry(reducer, action, state, error, errorHandler) {
285
+ if (error) {
286
+ return {
287
+ state,
288
+ error: 'Interrupted by an error up the chain',
289
+ };
290
+ }
291
+ let nextState = state;
292
+ let nextError;
293
+ try {
294
+ nextState = reducer(state, action);
295
+ }
296
+ catch (err) {
297
+ nextError = err.toString();
298
+ errorHandler.handleError(err);
299
+ }
300
+ return {
301
+ state: nextState,
302
+ error: nextError,
303
+ };
304
+ }
305
+ /**
306
+ * Runs the reducer on invalidated actions to get a fresh computation log.
307
+ */
308
+ function recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, errorHandler, isPaused) {
309
+ // Optimization: exit early and return the same reference
310
+ // if we know nothing could have changed.
311
+ if (minInvalidatedStateIndex >= computedStates.length &&
312
+ computedStates.length === stagedActionIds.length) {
313
+ return computedStates;
314
+ }
315
+ const nextComputedStates = computedStates.slice(0, minInvalidatedStateIndex);
316
+ // If the recording is paused, recompute all states up until the pause state,
317
+ // else recompute all states.
318
+ const lastIncludedActionId = stagedActionIds.length - (isPaused ? 1 : 0);
319
+ for (let i = minInvalidatedStateIndex; i < lastIncludedActionId; i++) {
320
+ const actionId = stagedActionIds[i];
321
+ const action = actionsById[actionId].action;
322
+ const previousEntry = nextComputedStates[i - 1];
323
+ const previousState = previousEntry ? previousEntry.state : committedState;
324
+ const previousError = previousEntry ? previousEntry.error : undefined;
325
+ const shouldSkip = skippedActionIds.indexOf(actionId) > -1;
326
+ const entry = shouldSkip
327
+ ? previousEntry
328
+ : computeNextEntry(reducer, action, previousState, previousError, errorHandler);
329
+ nextComputedStates.push(entry);
330
+ }
331
+ // If the recording is paused, the last state will not be recomputed,
332
+ // because it's essentially not part of the state history.
333
+ if (isPaused) {
334
+ nextComputedStates.push(computedStates[computedStates.length - 1]);
335
+ }
336
+ return nextComputedStates;
337
+ }
338
+ function liftInitialState(initialCommittedState, monitorReducer) {
339
+ return {
340
+ monitorState: monitorReducer(undefined, {}),
341
+ nextActionId: 1,
342
+ actionsById: { 0: liftAction(INIT_ACTION) },
343
+ stagedActionIds: [0],
344
+ skippedActionIds: [],
345
+ committedState: initialCommittedState,
346
+ currentStateIndex: 0,
347
+ computedStates: [],
348
+ isLocked: false,
349
+ isPaused: false,
350
+ };
351
+ }
352
+ /**
353
+ * Creates a history state reducer from an app's reducer.
354
+ */
355
+ function liftReducerWith(initialCommittedState, initialLiftedState, errorHandler, monitorReducer, options = {}) {
356
+ /**
357
+ * Manages how the history actions modify the history state.
358
+ */
359
+ return (reducer) => (liftedState, liftedAction) => {
360
+ let { monitorState, actionsById, nextActionId, stagedActionIds, skippedActionIds, committedState, currentStateIndex, computedStates, isLocked, isPaused, } = liftedState || initialLiftedState;
361
+ if (!liftedState) {
362
+ // Prevent mutating initialLiftedState
363
+ actionsById = Object.create(actionsById);
364
+ }
365
+ function commitExcessActions(n) {
366
+ // Auto-commits n-number of excess actions.
367
+ let excess = n;
368
+ let idsToDelete = stagedActionIds.slice(1, excess + 1);
369
+ for (let i = 0; i < idsToDelete.length; i++) {
370
+ if (computedStates[i + 1].error) {
371
+ // Stop if error is found. Commit actions up to error.
372
+ excess = i;
373
+ idsToDelete = stagedActionIds.slice(1, excess + 1);
374
+ break;
375
+ }
376
+ else {
377
+ delete actionsById[idsToDelete[i]];
378
+ }
379
+ }
380
+ skippedActionIds = skippedActionIds.filter((id) => idsToDelete.indexOf(id) === -1);
381
+ stagedActionIds = [0, ...stagedActionIds.slice(excess + 1)];
382
+ committedState = computedStates[excess].state;
383
+ computedStates = computedStates.slice(excess);
384
+ currentStateIndex =
385
+ currentStateIndex > excess ? currentStateIndex - excess : 0;
386
+ }
387
+ function commitChanges() {
388
+ // Consider the last committed state the new starting point.
389
+ // Squash any staged actions into a single committed state.
390
+ actionsById = { 0: liftAction(INIT_ACTION) };
391
+ nextActionId = 1;
392
+ stagedActionIds = [0];
393
+ skippedActionIds = [];
394
+ committedState = computedStates[currentStateIndex].state;
395
+ currentStateIndex = 0;
396
+ computedStates = [];
397
+ }
398
+ // By default, aggressively recompute every state whatever happens.
399
+ // This has O(n) performance, so we'll override this to a sensible
400
+ // value whenever we feel like we don't have to recompute the states.
401
+ let minInvalidatedStateIndex = 0;
402
+ switch (liftedAction.type) {
403
+ case LOCK_CHANGES: {
404
+ isLocked = liftedAction.status;
405
+ minInvalidatedStateIndex = Infinity;
406
+ break;
407
+ }
408
+ case PAUSE_RECORDING: {
409
+ isPaused = liftedAction.status;
410
+ if (isPaused) {
411
+ // Add a pause action to signal the devtools-user the recording is paused.
412
+ // The corresponding state will be overwritten on each update to always contain
413
+ // the latest state (see Actions.PERFORM_ACTION).
414
+ stagedActionIds = [...stagedActionIds, nextActionId];
415
+ actionsById[nextActionId] = new PerformAction({
416
+ type: '@ngrx/devtools/pause',
417
+ }, +Date.now());
418
+ nextActionId++;
419
+ minInvalidatedStateIndex = stagedActionIds.length - 1;
420
+ computedStates = computedStates.concat(computedStates[computedStates.length - 1]);
421
+ if (currentStateIndex === stagedActionIds.length - 2) {
422
+ currentStateIndex++;
423
+ }
424
+ minInvalidatedStateIndex = Infinity;
425
+ }
426
+ else {
427
+ commitChanges();
428
+ }
429
+ break;
430
+ }
431
+ case RESET: {
432
+ // Get back to the state the store was created with.
433
+ actionsById = { 0: liftAction(INIT_ACTION) };
434
+ nextActionId = 1;
435
+ stagedActionIds = [0];
436
+ skippedActionIds = [];
437
+ committedState = initialCommittedState;
438
+ currentStateIndex = 0;
439
+ computedStates = [];
440
+ break;
441
+ }
442
+ case COMMIT: {
443
+ commitChanges();
444
+ break;
445
+ }
446
+ case ROLLBACK: {
447
+ // Forget about any staged actions.
448
+ // Start again from the last committed state.
449
+ actionsById = { 0: liftAction(INIT_ACTION) };
450
+ nextActionId = 1;
451
+ stagedActionIds = [0];
452
+ skippedActionIds = [];
453
+ currentStateIndex = 0;
454
+ computedStates = [];
455
+ break;
456
+ }
457
+ case TOGGLE_ACTION: {
458
+ // Toggle whether an action with given ID is skipped.
459
+ // Being skipped means it is a no-op during the computation.
460
+ const { id: actionId } = liftedAction;
461
+ const index = skippedActionIds.indexOf(actionId);
462
+ if (index === -1) {
463
+ skippedActionIds = [actionId, ...skippedActionIds];
464
+ }
465
+ else {
466
+ skippedActionIds = skippedActionIds.filter((id) => id !== actionId);
467
+ }
468
+ // Optimization: we know history before this action hasn't changed
469
+ minInvalidatedStateIndex = stagedActionIds.indexOf(actionId);
470
+ break;
471
+ }
472
+ case SET_ACTIONS_ACTIVE: {
473
+ // Toggle whether an action with given ID is skipped.
474
+ // Being skipped means it is a no-op during the computation.
475
+ const { start, end, active } = liftedAction;
476
+ const actionIds = [];
477
+ for (let i = start; i < end; i++)
478
+ actionIds.push(i);
479
+ if (active) {
480
+ skippedActionIds = difference(skippedActionIds, actionIds);
481
+ }
482
+ else {
483
+ skippedActionIds = [...skippedActionIds, ...actionIds];
484
+ }
485
+ // Optimization: we know history before this action hasn't changed
486
+ minInvalidatedStateIndex = stagedActionIds.indexOf(start);
487
+ break;
488
+ }
489
+ case JUMP_TO_STATE: {
490
+ // Without recomputing anything, move the pointer that tell us
491
+ // which state is considered the current one. Useful for sliders.
492
+ currentStateIndex = liftedAction.index;
493
+ // Optimization: we know the history has not changed.
494
+ minInvalidatedStateIndex = Infinity;
495
+ break;
496
+ }
497
+ case JUMP_TO_ACTION: {
498
+ // Jumps to a corresponding state to a specific action.
499
+ // Useful when filtering actions.
500
+ const index = stagedActionIds.indexOf(liftedAction.actionId);
501
+ if (index !== -1)
502
+ currentStateIndex = index;
503
+ minInvalidatedStateIndex = Infinity;
504
+ break;
505
+ }
506
+ case SWEEP: {
507
+ // Forget any actions that are currently being skipped.
508
+ stagedActionIds = difference(stagedActionIds, skippedActionIds);
509
+ skippedActionIds = [];
510
+ currentStateIndex = Math.min(currentStateIndex, stagedActionIds.length - 1);
511
+ break;
512
+ }
513
+ case PERFORM_ACTION: {
514
+ // Ignore action and return state as is if recording is locked
515
+ if (isLocked) {
516
+ return liftedState || initialLiftedState;
517
+ }
518
+ if (isPaused ||
519
+ (liftedState &&
520
+ isActionFiltered(liftedState.computedStates[currentStateIndex], liftedAction, options.predicate, options.actionsSafelist, options.actionsBlocklist))) {
521
+ // If recording is paused or if the action should be ignored, overwrite the last state
522
+ // (corresponds to the pause action) and keep everything else as is.
523
+ // This way, the app gets the new current state while the devtools
524
+ // do not record another action.
525
+ const lastState = computedStates[computedStates.length - 1];
526
+ computedStates = [
527
+ ...computedStates.slice(0, -1),
528
+ computeNextEntry(reducer, liftedAction.action, lastState.state, lastState.error, errorHandler),
529
+ ];
530
+ minInvalidatedStateIndex = Infinity;
531
+ break;
532
+ }
533
+ // Auto-commit as new actions come in.
534
+ if (options.maxAge && stagedActionIds.length === options.maxAge) {
535
+ commitExcessActions(1);
536
+ }
537
+ if (currentStateIndex === stagedActionIds.length - 1) {
538
+ currentStateIndex++;
539
+ }
540
+ const actionId = nextActionId++;
541
+ // Mutation! This is the hottest path, and we optimize on purpose.
542
+ // It is safe because we set a new key in a cache dictionary.
543
+ actionsById[actionId] = liftedAction;
544
+ stagedActionIds = [...stagedActionIds, actionId];
545
+ // Optimization: we know that only the new action needs computing.
546
+ minInvalidatedStateIndex = stagedActionIds.length - 1;
547
+ break;
548
+ }
549
+ case IMPORT_STATE: {
550
+ // Completely replace everything.
551
+ ({
552
+ monitorState,
553
+ actionsById,
554
+ nextActionId,
555
+ stagedActionIds,
556
+ skippedActionIds,
557
+ committedState,
558
+ currentStateIndex,
559
+ computedStates,
560
+ isLocked,
561
+ isPaused,
562
+ } = liftedAction.nextLiftedState);
563
+ break;
564
+ }
565
+ case INIT: {
566
+ // Always recompute states on hot reload and init.
567
+ minInvalidatedStateIndex = 0;
568
+ if (options.maxAge && stagedActionIds.length > options.maxAge) {
569
+ // States must be recomputed before committing excess.
570
+ computedStates = recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, errorHandler, isPaused);
571
+ commitExcessActions(stagedActionIds.length - options.maxAge);
572
+ // Avoid double computation.
573
+ minInvalidatedStateIndex = Infinity;
574
+ }
575
+ break;
576
+ }
577
+ case UPDATE: {
578
+ const stateHasErrors = computedStates.filter((state) => state.error).length > 0;
579
+ if (stateHasErrors) {
580
+ // Recompute all states
581
+ minInvalidatedStateIndex = 0;
582
+ if (options.maxAge && stagedActionIds.length > options.maxAge) {
583
+ // States must be recomputed before committing excess.
584
+ computedStates = recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, errorHandler, isPaused);
585
+ commitExcessActions(stagedActionIds.length - options.maxAge);
586
+ // Avoid double computation.
587
+ minInvalidatedStateIndex = Infinity;
588
+ }
589
+ }
590
+ else {
591
+ // If not paused/locked, add a new action to signal devtools-user
592
+ // that there was a reducer update.
593
+ if (!isPaused && !isLocked) {
594
+ if (currentStateIndex === stagedActionIds.length - 1) {
595
+ currentStateIndex++;
596
+ }
597
+ // Add a new action to only recompute state
598
+ const actionId = nextActionId++;
599
+ actionsById[actionId] = new PerformAction(liftedAction, +Date.now());
600
+ stagedActionIds = [...stagedActionIds, actionId];
601
+ minInvalidatedStateIndex = stagedActionIds.length - 1;
602
+ computedStates = recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, errorHandler, isPaused);
603
+ }
604
+ // Recompute state history with latest reducer and update action
605
+ computedStates = computedStates.map((cmp) => ({
606
+ ...cmp,
607
+ state: reducer(cmp.state, RECOMPUTE_ACTION),
608
+ }));
609
+ currentStateIndex = stagedActionIds.length - 1;
610
+ if (options.maxAge && stagedActionIds.length > options.maxAge) {
611
+ commitExcessActions(stagedActionIds.length - options.maxAge);
612
+ }
613
+ // Avoid double computation.
614
+ minInvalidatedStateIndex = Infinity;
615
+ }
616
+ break;
617
+ }
618
+ default: {
619
+ // If the action is not recognized, it's a monitor action.
620
+ // Optimization: a monitor action can't change history.
621
+ minInvalidatedStateIndex = Infinity;
622
+ break;
623
+ }
624
+ }
625
+ computedStates = recomputeStates(computedStates, minInvalidatedStateIndex, reducer, committedState, actionsById, stagedActionIds, skippedActionIds, errorHandler, isPaused);
626
+ monitorState = monitorReducer(monitorState, liftedAction);
627
+ return {
628
+ monitorState,
629
+ actionsById,
630
+ nextActionId,
631
+ stagedActionIds,
632
+ skippedActionIds,
633
+ committedState,
634
+ currentStateIndex,
635
+ computedStates,
636
+ isLocked,
637
+ isPaused,
638
+ };
639
+ };
640
+ }
641
+
642
+ class DevtoolsDispatcher extends ActionsSubject {
643
+ }
644
+ /** @nocollapse */ /** @nocollapse */ DevtoolsDispatcher.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: DevtoolsDispatcher, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
645
+ /** @nocollapse */ /** @nocollapse */ DevtoolsDispatcher.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: DevtoolsDispatcher });
646
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: DevtoolsDispatcher, decorators: [{
647
+ type: Injectable
648
+ }] });
649
+
650
+ const ExtensionActionTypes = {
651
+ START: 'START',
652
+ DISPATCH: 'DISPATCH',
653
+ STOP: 'STOP',
654
+ ACTION: 'ACTION',
655
+ };
656
+ const REDUX_DEVTOOLS_EXTENSION = new InjectionToken('@ngrx/store-devtools Redux Devtools Extension');
657
+ class DevtoolsExtension {
658
+ constructor(devtoolsExtension, config, dispatcher) {
659
+ this.config = config;
660
+ this.dispatcher = dispatcher;
661
+ this.devtoolsExtension = devtoolsExtension;
662
+ this.createActionStreams();
663
+ }
664
+ notify(action, state) {
665
+ if (!this.devtoolsExtension) {
666
+ return;
667
+ }
668
+ // Check to see if the action requires a full update of the liftedState.
669
+ // If it is a simple action generated by the user's app and the recording
670
+ // is not locked/paused, only send the action and the current state (fast).
671
+ //
672
+ // A full liftedState update (slow: serializes the entire liftedState) is
673
+ // only required when:
674
+ // a) redux-devtools-extension fires the @@Init action (ignored by
675
+ // @ngrx/store-devtools)
676
+ // b) an action is generated by an @ngrx module (e.g. @ngrx/effects/init
677
+ // or @ngrx/store/update-reducers)
678
+ // c) the state has been recomputed due to time-traveling
679
+ // d) any action that is not a PerformAction to err on the side of
680
+ // caution.
681
+ if (action.type === PERFORM_ACTION) {
682
+ if (state.isLocked || state.isPaused) {
683
+ return;
684
+ }
685
+ const currentState = unliftState(state);
686
+ if (shouldFilterActions(this.config) &&
687
+ isActionFiltered(currentState, action, this.config.predicate, this.config.actionsSafelist, this.config.actionsBlocklist)) {
688
+ return;
689
+ }
690
+ const sanitizedState = this.config.stateSanitizer
691
+ ? sanitizeState(this.config.stateSanitizer, currentState, state.currentStateIndex)
692
+ : currentState;
693
+ const sanitizedAction = this.config.actionSanitizer
694
+ ? sanitizeAction(this.config.actionSanitizer, action, state.nextActionId)
695
+ : action;
696
+ this.sendToReduxDevtools(() => this.extensionConnection.send(sanitizedAction, sanitizedState));
697
+ }
698
+ else {
699
+ // Requires full state update
700
+ const sanitizedLiftedState = {
701
+ ...state,
702
+ stagedActionIds: state.stagedActionIds,
703
+ actionsById: this.config.actionSanitizer
704
+ ? sanitizeActions(this.config.actionSanitizer, state.actionsById)
705
+ : state.actionsById,
706
+ computedStates: this.config.stateSanitizer
707
+ ? sanitizeStates(this.config.stateSanitizer, state.computedStates)
708
+ : state.computedStates,
709
+ };
710
+ this.sendToReduxDevtools(() => this.devtoolsExtension.send(null, sanitizedLiftedState, this.getExtensionConfig(this.config)));
711
+ }
712
+ }
713
+ createChangesObservable() {
714
+ if (!this.devtoolsExtension) {
715
+ return EMPTY;
716
+ }
717
+ return new Observable((subscriber) => {
718
+ const connection = this.devtoolsExtension.connect(this.getExtensionConfig(this.config));
719
+ this.extensionConnection = connection;
720
+ connection.init();
721
+ connection.subscribe((change) => subscriber.next(change));
722
+ return connection.unsubscribe;
723
+ });
724
+ }
725
+ createActionStreams() {
726
+ // Listens to all changes
727
+ const changes$ = this.createChangesObservable().pipe(share());
728
+ // Listen for the start action
729
+ const start$ = changes$.pipe(filter((change) => change.type === ExtensionActionTypes.START));
730
+ // Listen for the stop action
731
+ const stop$ = changes$.pipe(filter((change) => change.type === ExtensionActionTypes.STOP));
732
+ // Listen for lifted actions
733
+ const liftedActions$ = changes$.pipe(filter((change) => change.type === ExtensionActionTypes.DISPATCH), map((change) => this.unwrapAction(change.payload)), concatMap((action) => {
734
+ if (action.type === IMPORT_STATE) {
735
+ // State imports may happen in two situations:
736
+ // 1. Explicitly by user
737
+ // 2. User activated the "persist state accross reloads" option
738
+ // and now the state is imported during reload.
739
+ // Because of option 2, we need to give possible
740
+ // lazy loaded reducers time to instantiate.
741
+ // As soon as there is no UPDATE action within 1 second,
742
+ // it is assumed that all reducers are loaded.
743
+ return this.dispatcher.pipe(filter((action) => action.type === UPDATE), timeout(1000), debounceTime(1000), map(() => action), catchError(() => of(action)), take(1));
744
+ }
745
+ else {
746
+ return of(action);
747
+ }
748
+ }));
749
+ // Listen for unlifted actions
750
+ const actions$ = changes$.pipe(filter((change) => change.type === ExtensionActionTypes.ACTION), map((change) => this.unwrapAction(change.payload)));
751
+ const actionsUntilStop$ = actions$.pipe(takeUntil(stop$));
752
+ const liftedUntilStop$ = liftedActions$.pipe(takeUntil(stop$));
753
+ this.start$ = start$.pipe(takeUntil(stop$));
754
+ // Only take the action sources between the start/stop events
755
+ this.actions$ = this.start$.pipe(switchMap(() => actionsUntilStop$));
756
+ this.liftedActions$ = this.start$.pipe(switchMap(() => liftedUntilStop$));
757
+ }
758
+ unwrapAction(action) {
759
+ return typeof action === 'string' ? eval(`(${action})`) : action;
760
+ }
761
+ getExtensionConfig(config) {
762
+ const extensionOptions = {
763
+ name: config.name,
764
+ features: config.features,
765
+ serialize: config.serialize,
766
+ autoPause: config.autoPause ?? false,
767
+ // The action/state sanitizers are not added to the config
768
+ // because sanitation is done in this class already.
769
+ // It is done before sending it to the devtools extension for consistency:
770
+ // - If we call extensionConnection.send(...),
771
+ // the extension would call the sanitizers.
772
+ // - If we call devtoolsExtension.send(...) (aka full state update),
773
+ // the extension would NOT call the sanitizers, so we have to do it ourselves.
774
+ };
775
+ if (config.maxAge !== false /* support === 0 */) {
776
+ extensionOptions.maxAge = config.maxAge;
777
+ }
778
+ return extensionOptions;
779
+ }
780
+ sendToReduxDevtools(send) {
781
+ try {
782
+ send();
783
+ }
784
+ catch (err) {
785
+ console.warn('@ngrx/store-devtools: something went wrong inside the redux devtools', err);
786
+ }
787
+ }
788
+ }
789
+ /** @nocollapse */ /** @nocollapse */ DevtoolsExtension.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: DevtoolsExtension, deps: [{ token: REDUX_DEVTOOLS_EXTENSION }, { token: STORE_DEVTOOLS_CONFIG }, { token: DevtoolsDispatcher }], target: i0.ɵɵFactoryTarget.Injectable });
790
+ /** @nocollapse */ /** @nocollapse */ DevtoolsExtension.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: DevtoolsExtension });
791
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: DevtoolsExtension, decorators: [{
792
+ type: Injectable
793
+ }], ctorParameters: function () { return [{ type: undefined, decorators: [{
794
+ type: Inject,
795
+ args: [REDUX_DEVTOOLS_EXTENSION]
796
+ }] }, { type: StoreDevtoolsConfig, decorators: [{
797
+ type: Inject,
798
+ args: [STORE_DEVTOOLS_CONFIG]
799
+ }] }, { type: DevtoolsDispatcher }]; } });
800
+
801
+ class StoreDevtools {
802
+ constructor(dispatcher, actions$, reducers$, extension, scannedActions, errorHandler, initialState, config) {
803
+ const liftedInitialState = liftInitialState(initialState, config.monitor);
804
+ const liftReducer = liftReducerWith(initialState, liftedInitialState, errorHandler, config.monitor, config);
805
+ const liftedAction$ = merge(merge(actions$.asObservable().pipe(skip(1)), extension.actions$).pipe(map(liftAction)), dispatcher, extension.liftedActions$).pipe(observeOn(queueScheduler));
806
+ const liftedReducer$ = reducers$.pipe(map(liftReducer));
807
+ const liftedStateSubject = new ReplaySubject(1);
808
+ const liftedStateSubscription = liftedAction$
809
+ .pipe(withLatestFrom(liftedReducer$), scan(({ state: liftedState }, [action, reducer]) => {
810
+ let reducedLiftedState = reducer(liftedState, action);
811
+ // On full state update
812
+ // If we have actions filters, we must filter completely our lifted state to be sync with the extension
813
+ if (action.type !== PERFORM_ACTION && shouldFilterActions(config)) {
814
+ reducedLiftedState = filterLiftedState(reducedLiftedState, config.predicate, config.actionsSafelist, config.actionsBlocklist);
815
+ }
816
+ // Extension should be sent the sanitized lifted state
817
+ extension.notify(action, reducedLiftedState);
818
+ return { state: reducedLiftedState, action };
819
+ }, { state: liftedInitialState, action: null }))
820
+ .subscribe(({ state, action }) => {
821
+ liftedStateSubject.next(state);
822
+ if (action.type === PERFORM_ACTION) {
823
+ const unliftedAction = action.action;
824
+ scannedActions.next(unliftedAction);
825
+ }
826
+ });
827
+ const extensionStartSubscription = extension.start$.subscribe(() => {
828
+ this.refresh();
829
+ });
830
+ const liftedState$ = liftedStateSubject.asObservable();
831
+ const state$ = liftedState$.pipe(map(unliftState));
832
+ this.extensionStartSubscription = extensionStartSubscription;
833
+ this.stateSubscription = liftedStateSubscription;
834
+ this.dispatcher = dispatcher;
835
+ this.liftedState = liftedState$;
836
+ this.state = state$;
837
+ }
838
+ dispatch(action) {
839
+ this.dispatcher.next(action);
840
+ }
841
+ next(action) {
842
+ this.dispatcher.next(action);
843
+ }
844
+ error(error) { }
845
+ complete() { }
846
+ performAction(action) {
847
+ this.dispatch(new PerformAction(action, +Date.now()));
848
+ }
849
+ refresh() {
850
+ this.dispatch(new Refresh());
851
+ }
852
+ reset() {
853
+ this.dispatch(new Reset(+Date.now()));
854
+ }
855
+ rollback() {
856
+ this.dispatch(new Rollback(+Date.now()));
857
+ }
858
+ commit() {
859
+ this.dispatch(new Commit(+Date.now()));
860
+ }
861
+ sweep() {
862
+ this.dispatch(new Sweep());
863
+ }
864
+ toggleAction(id) {
865
+ this.dispatch(new ToggleAction(id));
866
+ }
867
+ jumpToAction(actionId) {
868
+ this.dispatch(new JumpToAction(actionId));
869
+ }
870
+ jumpToState(index) {
871
+ this.dispatch(new JumpToState(index));
872
+ }
873
+ importState(nextLiftedState) {
874
+ this.dispatch(new ImportState(nextLiftedState));
875
+ }
876
+ lockChanges(status) {
877
+ this.dispatch(new LockChanges(status));
878
+ }
879
+ pauseRecording(status) {
880
+ this.dispatch(new PauseRecording(status));
881
+ }
882
+ }
883
+ /** @nocollapse */ /** @nocollapse */ StoreDevtools.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtools, deps: [{ token: DevtoolsDispatcher }, { token: i2.ActionsSubject }, { token: i2.ReducerObservable }, { token: DevtoolsExtension }, { token: i2.ScannedActionsSubject }, { token: i0.ErrorHandler }, { token: INITIAL_STATE }, { token: STORE_DEVTOOLS_CONFIG }], target: i0.ɵɵFactoryTarget.Injectable });
884
+ /** @nocollapse */ /** @nocollapse */ StoreDevtools.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtools });
885
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtools, decorators: [{
886
+ type: Injectable
887
+ }], ctorParameters: function () { return [{ type: DevtoolsDispatcher }, { type: i2.ActionsSubject }, { type: i2.ReducerObservable }, { type: DevtoolsExtension }, { type: i2.ScannedActionsSubject }, { type: i0.ErrorHandler }, { type: undefined, decorators: [{
888
+ type: Inject,
889
+ args: [INITIAL_STATE]
890
+ }] }, { type: StoreDevtoolsConfig, decorators: [{
891
+ type: Inject,
892
+ args: [STORE_DEVTOOLS_CONFIG]
893
+ }] }]; } });
894
+
895
+ const IS_EXTENSION_OR_MONITOR_PRESENT = new InjectionToken('@ngrx/store-devtools Is Devtools Extension or Monitor Present');
896
+ function createIsExtensionOrMonitorPresent(extension, config) {
897
+ return Boolean(extension) || config.monitor !== noMonitor;
898
+ }
899
+ function createReduxDevtoolsExtension() {
900
+ const extensionKey = '__REDUX_DEVTOOLS_EXTENSION__';
901
+ if (typeof window === 'object' &&
902
+ typeof window[extensionKey] !== 'undefined') {
903
+ return window[extensionKey];
904
+ }
905
+ else {
906
+ return null;
907
+ }
908
+ }
909
+ function createStateObservable(devtools) {
910
+ return devtools.state;
911
+ }
912
+ class StoreDevtoolsModule {
913
+ static instrument(options = {}) {
914
+ return {
915
+ ngModule: StoreDevtoolsModule,
916
+ providers: [
917
+ DevtoolsExtension,
918
+ DevtoolsDispatcher,
919
+ StoreDevtools,
920
+ {
921
+ provide: INITIAL_OPTIONS,
922
+ useValue: options,
923
+ },
924
+ {
925
+ provide: IS_EXTENSION_OR_MONITOR_PRESENT,
926
+ deps: [REDUX_DEVTOOLS_EXTENSION, STORE_DEVTOOLS_CONFIG],
927
+ useFactory: createIsExtensionOrMonitorPresent,
928
+ },
929
+ {
930
+ provide: REDUX_DEVTOOLS_EXTENSION,
931
+ useFactory: createReduxDevtoolsExtension,
932
+ },
933
+ {
934
+ provide: STORE_DEVTOOLS_CONFIG,
935
+ deps: [INITIAL_OPTIONS],
936
+ useFactory: createConfig,
937
+ },
938
+ {
939
+ provide: StateObservable,
940
+ deps: [StoreDevtools],
941
+ useFactory: createStateObservable,
942
+ },
943
+ {
944
+ provide: ReducerManagerDispatcher,
945
+ useExisting: DevtoolsDispatcher,
946
+ },
947
+ ],
948
+ };
949
+ }
950
+ }
951
+ /** @nocollapse */ /** @nocollapse */ StoreDevtoolsModule.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtoolsModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
952
+ /** @nocollapse */ /** @nocollapse */ StoreDevtoolsModule.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtoolsModule });
953
+ /** @nocollapse */ /** @nocollapse */ StoreDevtoolsModule.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtoolsModule });
954
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "13.0.0", ngImport: i0, type: StoreDevtoolsModule, decorators: [{
955
+ type: NgModule,
956
+ args: [{}]
957
+ }] });
958
+
959
+ /**
960
+ * DO NOT EDIT
961
+ *
962
+ * This file is automatically generated at build
963
+ */
964
+
965
+ /**
966
+ * Generated bundle index. Do not edit.
967
+ */
968
+
969
+ export { INITIAL_OPTIONS, RECOMPUTE, StoreDevtools, StoreDevtoolsConfig, StoreDevtoolsModule };