@amadeus-it-group/ngrx-devtool 0.1.3 → 0.2.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.
@@ -6,6 +6,8 @@ import { takeUntil, tap as tap$1 } from 'rxjs/operators';
6
6
  import { isPlatformBrowser } from '@angular/common';
7
7
  import { createSelector } from '@ngrx/store';
8
8
 
9
+ const DEFAULT_WS_URL = 'ws://localhost:4000';
10
+
9
11
  const REPLAY_BUFFER_SIZE$1 = 100;
10
12
  const TIMELINE_MAX_SIZE = 1000;
11
13
  const TIMELINE_TRIM_SIZE = 500;
@@ -17,7 +19,7 @@ class EffectTrackerService {
17
19
  actionTimeline = [];
18
20
  effectTimeline = [];
19
21
  effectActionPatterns = new Set();
20
- pendingCorrelations = new Map();
22
+ pendingCorrelations = [];
21
23
  correlationCounter = 0;
22
24
  lastTriggerAction = null;
23
25
  effectEvents$ = new ReplaySubject(REPLAY_BUFFER_SIZE$1);
@@ -80,8 +82,12 @@ class EffectTrackerService {
80
82
  if (!isEffect) {
81
83
  this.lastTriggerAction = action;
82
84
  }
85
+ // Resolve trigger action type to match the correct correlation.
86
+ const triggerActionType = isEffect
87
+ ? this.findTriggerActionType(action.type)
88
+ : undefined;
83
89
  const correlationId = isEffect
84
- ? this.findCorrelation()
90
+ ? this.consumeCorrelation(triggerActionType)
85
91
  : this.createCorrelation(action);
86
92
  const effectName = this.findEffectNameForAction(action.type);
87
93
  const tracked = {
@@ -103,22 +109,48 @@ class EffectTrackerService {
103
109
  }
104
110
  clearTimeline() {
105
111
  this.actionTimeline = [];
106
- this.pendingCorrelations.clear();
112
+ this.clearPendingCorrelations();
107
113
  }
108
114
  createCorrelation(action) {
109
115
  const correlationId = `corr_${++this.correlationCounter}_${Date.now()}`;
110
- this.pendingCorrelations.set(correlationId, {
111
- action,
116
+ const timeoutId = setTimeout(() => {
117
+ this.pendingCorrelations = this.pendingCorrelations.filter(c => c.correlationId !== correlationId);
118
+ }, CORRELATION_TIMEOUT_MS);
119
+ this.pendingCorrelations.push({
120
+ correlationId,
121
+ actionType: action.type,
112
122
  timestamp: Date.now(),
123
+ timeoutId,
113
124
  });
114
- setTimeout(() => {
115
- this.pendingCorrelations.delete(correlationId);
116
- }, CORRELATION_TIMEOUT_MS);
117
125
  return correlationId;
118
126
  }
119
- findCorrelation() {
120
- const entries = Array.from(this.pendingCorrelations.entries());
121
- return entries.length > 0 ? entries[entries.length - 1][0] : undefined;
127
+ // Find and consume the best-matching pending correlation (by trigger type, then FIFO fallback).
128
+ consumeCorrelation(triggerActionType) {
129
+ let idx = -1;
130
+ // Prefer matching by trigger action type (oldest match first)
131
+ if (triggerActionType) {
132
+ idx = this.pendingCorrelations.findIndex(c => c.actionType === triggerActionType);
133
+ }
134
+ // Fallback: oldest pending correlation (FIFO)
135
+ if (idx === -1 && this.pendingCorrelations.length > 0) {
136
+ idx = 0;
137
+ }
138
+ if (idx === -1) {
139
+ return undefined;
140
+ }
141
+ const [entry] = this.pendingCorrelations.splice(idx, 1);
142
+ clearTimeout(entry.timeoutId);
143
+ return entry.correlationId;
144
+ }
145
+ // Resolve which user action type triggered a given effect-result action type.
146
+ findTriggerActionType(effectActionType) {
147
+ for (let i = this.effectTimeline.length - 1; i >= Math.max(0, this.effectTimeline.length - 10); i--) {
148
+ const effect = this.effectTimeline[i];
149
+ if (effect.resultAction === effectActionType && effect.triggerAction) {
150
+ return effect.triggerAction;
151
+ }
152
+ }
153
+ return undefined;
122
154
  }
123
155
  findEffectNameForAction(actionType) {
124
156
  for (let i = this.effectTimeline.length - 1; i >= Math.max(0, this.effectTimeline.length - 10); i--) {
@@ -129,10 +161,17 @@ class EffectTrackerService {
129
161
  }
130
162
  return undefined;
131
163
  }
164
+ clearPendingCorrelations() {
165
+ for (const entry of this.pendingCorrelations) {
166
+ clearTimeout(entry.timeoutId);
167
+ }
168
+ this.pendingCorrelations = [];
169
+ }
132
170
  ngOnDestroy() {
133
171
  this.destroy$.next();
134
172
  this.destroy$.complete();
135
173
  this.effectEvents$.complete();
174
+ this.clearPendingCorrelations();
136
175
  }
137
176
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: EffectTrackerService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
138
177
  static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.1.5", ngImport: i0, type: EffectTrackerService, providedIn: 'root' });
@@ -142,6 +181,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.1.5", ngImpor
142
181
  args: [{ providedIn: 'root' }]
143
182
  }], ctorParameters: () => [] });
144
183
 
184
+ const MAX_BUFFER_SIZE = 200;
145
185
  class WebSocketService {
146
186
  platformId = inject(PLATFORM_ID);
147
187
  isBrowser = isPlatformBrowser(this.platformId);
@@ -160,7 +200,7 @@ class WebSocketService {
160
200
  get messages$() {
161
201
  return this.incomingMessages$.asObservable();
162
202
  }
163
- initialize(wsUrl = 'ws://localhost:4000') {
203
+ initialize(wsUrl = DEFAULT_WS_URL) {
164
204
  if (this.initialized) {
165
205
  // If already initialized with same URL, skip
166
206
  if (this.wsUrl === wsUrl) {
@@ -174,19 +214,16 @@ class WebSocketService {
174
214
  this.setupWebSocket(wsUrl);
175
215
  }
176
216
  send(message) {
177
- const payload = JSON.stringify(message);
178
- if (this.isConnected && this.socket?.readyState === WebSocket.OPEN) {
179
- this.socket.send(payload);
180
- }
181
- else if (this.isBrowser) {
182
- this.messageBuffer.push(payload);
183
- }
217
+ this.bufferOrSend(JSON.stringify(message));
184
218
  }
185
219
  sendRaw(payload) {
220
+ this.bufferOrSend(payload);
221
+ }
222
+ bufferOrSend(payload) {
186
223
  if (this.isConnected && this.socket?.readyState === WebSocket.OPEN) {
187
224
  this.socket.send(payload);
188
225
  }
189
- else if (this.isBrowser) {
226
+ else if (this.isBrowser && this.messageBuffer.length < MAX_BUFFER_SIZE) {
190
227
  this.messageBuffer.push(payload);
191
228
  }
192
229
  }
@@ -198,6 +235,7 @@ class WebSocketService {
198
235
  close() {
199
236
  this.socket?.close();
200
237
  this.socket = null;
238
+ this.messageBuffer = [];
201
239
  this.connectionState$.next(false);
202
240
  this.initialized = false;
203
241
  this.wsUrl = null;
@@ -228,9 +266,10 @@ class WebSocketService {
228
266
  };
229
267
  }
230
268
  flushBuffer() {
231
- while (this.messageBuffer.length > 0 && this.isConnected) {
232
- const message = this.messageBuffer.shift();
233
- if (message && this.socket?.readyState === WebSocket.OPEN) {
269
+ const buffered = this.messageBuffer;
270
+ this.messageBuffer = [];
271
+ for (const message of buffered) {
272
+ if (this.isConnected && this.socket?.readyState === WebSocket.OPEN) {
234
273
  this.socket.send(message);
235
274
  }
236
275
  }
@@ -249,7 +288,7 @@ class ActionsInterceptorService {
249
288
  webSocketService = inject(WebSocketService);
250
289
  destroy$ = new Subject();
251
290
  initialized = false;
252
- initialize(wsUrl = 'ws://localhost:4000') {
291
+ initialize(wsUrl = DEFAULT_WS_URL) {
253
292
  if (this.initialized) {
254
293
  return;
255
294
  }
@@ -1269,11 +1308,11 @@ function TrackedSelector(name) {
1269
1308
  };
1270
1309
  }
1271
1310
 
1272
- function createDevToolMetaReducer(wsUrlOrConfig = 'ws://localhost:4000') {
1311
+ function createDevToolMetaReducer(wsUrlOrConfig = DEFAULT_WS_URL) {
1273
1312
  const config = typeof wsUrlOrConfig === 'string'
1274
1313
  ? { wsUrl: wsUrlOrConfig }
1275
1314
  : wsUrlOrConfig;
1276
- const wsUrl = config.wsUrl ?? 'ws://localhost:4000';
1315
+ const wsUrl = config.wsUrl ?? DEFAULT_WS_URL;
1277
1316
  const enablePerf = config.enablePerformanceTracking ?? true;
1278
1317
  return function devToolMetaReducer(reducer) {
1279
1318
  const performanceTracker = inject(PerformanceTrackerService);
@@ -1313,7 +1352,7 @@ function createDevToolMetaReducer(wsUrlOrConfig = 'ws://localhost:4000') {
1313
1352
  };
1314
1353
  }
1315
1354
  function loggerMetaReducer(reducer) {
1316
- return createDevToolMetaReducer('ws://localhost:4000')(reducer);
1355
+ return createDevToolMetaReducer(DEFAULT_WS_URL)(reducer);
1317
1356
  }
1318
1357
 
1319
1358
  function provideNgrxDevTool(config = {}) {
@@ -1321,7 +1360,7 @@ function provideNgrxDevTool(config = {}) {
1321
1360
  {
1322
1361
  provide: APP_INITIALIZER,
1323
1362
  useFactory: (interceptor) => () => {
1324
- interceptor.initialize(config.wsUrl ?? 'ws://localhost:4000');
1363
+ interceptor.initialize(config.wsUrl ?? DEFAULT_WS_URL);
1325
1364
  },
1326
1365
  deps: [ActionsInterceptorService],
1327
1366
  multi: true,
@@ -1350,5 +1389,5 @@ function provideEffectTracking() {
1350
1389
  * Generated bundle index. Do not edit.
1351
1390
  */
1352
1391
 
1353
- export { ActionsInterceptorService, DevToolsEffectSources, EffectTrackerService, PerformanceAnalyzerService, PerformanceTrackerService, PerformanceWarningType, SelectorTrackerService, TrackedSelector, WebSocketService, createDevToolMetaReducer, createTrackedSelector, estimateRenderImpact, loggerMetaReducer, provideEffectTracking, provideNgrxDevTool, setSelectorTracker, trackSelector };
1392
+ export { ActionsInterceptorService, DEFAULT_WS_URL, DevToolsEffectSources, EffectTrackerService, PerformanceAnalyzerService, PerformanceTrackerService, PerformanceWarningType, SelectorTrackerService, TrackedSelector, WebSocketService, createDevToolMetaReducer, createTrackedSelector, estimateRenderImpact, loggerMetaReducer, provideEffectTracking, provideNgrxDevTool, setSelectorTracker, trackSelector };
1354
1393
  //# sourceMappingURL=amadeus-it-group-ngrx-devtool.mjs.map