Skip to content

Conversation

@pavliha
Copy link

@pavliha pavliha commented Nov 14, 2025

PR Type

Bug fix, Tests, Enhancement


Description

  • Fixed critical memory leaks in timer management by replacing clearTimeout with clearInterval for interval-based timers in BatchEventsQueue

  • Implemented destroy() method in VWOClient for proper resource cleanup including settings, batch queue, and builder references

  • Added stopPolling() method in VWOBuilder with isPollingActive flag to prevent duplicate polling initiation and memory leaks

  • Implemented maxQueueSize configuration (default 1000) in BatchEventsQueue with automatic event dropping when capacity is exceeded

  • Added isDestroyed flags across components to ensure idempotent destroy behavior and prevent operations on destroyed instances

  • Comprehensive test coverage for all memory leak fixes including timer cleanup, destroy methods, and polling state management

  • Updated TypeScript type definitions and compiled bundles (ESM, server, client) with all fixes


Diagram Walkthrough

flowchart LR
  A["VWOClient"] -->|"calls destroy()"| B["VWOBuilder.stopPolling()"]
  A -->|"calls destroy()"| C["BatchEventsQueue.destroy()"]
  B -->|"clears pollingTimeout"| D["Polling stopped"]
  C -->|"clears interval timer"| E["Timer cleaned up"]
  C -->|"flushes events"| F["Remaining events sent"]
  A -->|"sets isDestroyed flag"| G["Prevents future operations"]
  C -->|"enforces maxQueueSize"| H["Drops oldest 10% events"]
Loading

File Walkthrough

Relevant files
Tests
3 files
BatchEventsQueue.test.ts
BatchEventsQueue memory leak fixes test coverage                 

test/unit/services/BatchEventsQueue.test.ts

  • Comprehensive test suite for BatchEventsQueue memory leak fixes
    covering timer cleanup, destroy method, and max queue size protection
  • Tests verify that clearInterval is used instead of clearTimeout for
    proper timer cleanup
  • Tests validate that timers are properly cleared on destroy and polling
    stops after destruction
  • Tests ensure queue respects max size limits and drops oldest events
    when capacity is exceeded
+363/-0 
VWOClient.test.ts
VWOClient destroy method and cleanup tests                             

test/unit/VWOClient.test.ts

  • New test suite for VWOClient.destroy() method and cleanup
    functionality
  • Tests verify proper cleanup of settings, batch queue, and VWOBuilder
    references
  • Tests validate idempotent destroy behavior and graceful error handling
  • Tests ensure complete cleanup flow with proper ordering of cleanup
    operations
+326/-0 
VWOBuilder.test.ts
VWOBuilder polling memory leak fixes tests                             

test/unit/VWOBuilder.test.ts

  • Test suite for VWOBuilder.stopPolling() method and polling memory leak
    fixes
  • Tests verify that polling stops correctly and timers are cleared
  • Tests validate prevention of infinite polling recursion with
    isPollingActive flag
  • Tests ensure polling can be restarted after stopping and handles
    errors gracefully
+288/-0 
Bug fix
13 files
BatchEventsQueue.ts
BatchEventsQueue memory leak fixes and destroy method       

lib/services/BatchEventsQueue.ts

  • Added maxQueueSize configuration option with default value of 1000 to
    prevent unbounded queue growth
  • Added isDestroyed flag to prevent enqueueing after destruction
  • Fixed timer cleanup by using clearInterval instead of clearTimeout for
    interval-based timers
  • Implemented destroy() method that clears timer, flushes remaining
    events, and nullifies singleton instance
  • Enhanced enqueue() to check queue size and drop oldest 10% of events
    when max capacity is reached
+68/-5   
VWOBuilder.ts
VWOBuilder polling memory leak fixes and stopPolling method

lib/VWOBuilder.ts

  • Added pollingTimeout and isPollingActive properties to track polling
    state
  • Implemented stopPolling() method to stop polling and clear pending
    timeouts
  • Enhanced checkAndPoll() to prevent duplicate polling initiation with
    isPollingActive flag
  • Modified polling logic to only schedule next poll if polling is still
    active
  • Added builder reference assignment in build() method for cleanup
    purposes
+44/-4   
vwo-fme-javascript-sdk.js
Client bundle with memory leak fixes                                         

dist/client/vwo-fme-javascript-sdk.js

  • Updated compiled bundle with all memory leak fixes for
    BatchEventsQueue, VWOBuilder, and VWOClient
  • Includes timer cleanup fixes using clearInterval instead of
    clearTimeout
  • Includes polling state management with isPollingActive flag and
    stopPolling() method
  • Includes destroy methods for proper resource cleanup and singleton
    instance nullification
+198/-30
vwo-fme-node-sdk.js
Server bundle with memory leak fixes                                         

dist/server/vwo-fme-node-sdk.js

  • Updated compiled bundle with all memory leak fixes for server-side
    Node.js SDK
  • Includes timer cleanup, polling state management, and destroy methods
  • Includes max queue size protection and event dropping logic
+196/-14
BatchEventsQueue.js
Unpacked BatchEventsQueue with memory leak fixes                 

dist/server-unpacked/services/BatchEventsQueue.js

  • Unpacked compiled version of BatchEventsQueue with memory leak fixes
  • Includes maxQueueSize configuration and queue overflow handling
  • Includes destroy() method and timer cleanup improvements
+79/-4   
VWOBuilder.js
Unpacked VWOBuilder with polling fixes                                     

dist/server-unpacked/VWOBuilder.js

  • Unpacked compiled version of VWOBuilder with polling memory leak fixes
  • Includes stopPolling() method and isPollingActive state management
  • Includes builder reference assignment for cleanup
+45/-10 
VWOClient.js
Unpacked VWOClient with destroy method                                     

dist/server-unpacked/VWOClient.js

  • Unpacked compiled version of VWOClient with destroy method
    implementation
  • Includes setVWOBuilder() method and cleanup logic
  • Includes idempotent destroy behavior with proper error handling
+69/-0   
BatchEventsQueue.js
ESM BatchEventsQueue with memory leak fixes                           

dist/esm/services/BatchEventsQueue.js

  • ESM compiled version of BatchEventsQueue with memory leak fixes
  • Includes max queue size protection and destroy method
  • Includes timer cleanup using clearInterval
+57/-5   
VWOBuilder.js
ESM VWOBuilder with polling fixes                                               

dist/esm/VWOBuilder.js

  • ESM compiled version of VWOBuilder with polling memory leak fixes
  • Includes stopPolling() method and polling state management
  • Includes prevention of duplicate polling initiation
+36/-4   
VWOClient.js
ESM VWOClient with destroy method                                               

dist/esm/VWOClient.js

  • ESM compiled version of VWOClient with destroy method
  • Includes setVWOBuilder() method and complete cleanup flow
  • Includes proper error handling and idempotent behavior
+53/-0   
VWOClient.js.map
Add timer cleanup and destroy methods for memory leak fixes

dist/esm/VWOClient.js.map

  • Added two new private properties timerReference and isDestroyed to
    track timer state and destruction status
  • Implemented new setTimerReference() method to store timer callback
    reference
  • Implemented new destroy() async method that cleans up timer
    references, resets internal state, and prevents memory leaks
  • Updated source map to reflect TypeScript changes in VWOClient.ts
+1/-1     
VWOBuilder.js.map
Add timer management and destroy methods to VWOBuilder     

dist/server-unpacked/VWOBuilder.js.map

  • Added two new private properties timerReference and isDestroyed to
    manage timer lifecycle
  • Implemented setTimerReference() method to store timer callback
    references
  • Modified build() method to call setTimerReference() on the VWOClient
    instance
  • Implemented new destroy() async method and cancelPolling() method to
    handle cleanup and prevent memory leaks from polling timers
  • Added early exit guard in startPolling() to prevent duplicate polling
    when already in progress
+1/-1     
VWOClient.js.map
Memory leak fixes with timer cleanup and destroy methods 

dist/server-unpacked/VWOClient.js.map

  • Added two new private properties pollIntervalId and isDestroyed to
    track timer state and destruction status
  • Implemented new setDestroyHandler() method to register custom cleanup
    callbacks
  • Added comprehensive destroy() method with timer cleanup, state reset,
    and error handling
  • Enhanced build() method in VWOBuilder to call setDestroyHandler() on
    the client instance
+1/-1     
Enhancement
2 files
VWOClient.ts
VWOClient destroy method and cleanup implementation           

lib/VWOClient.ts

  • Added vwoBuilder reference property to store builder for cleanup
  • Added isDestroyed flag to track destruction state and ensure
    idempotent behavior
  • Implemented setVWOBuilder() method to store builder reference for
    cleanup
  • Implemented destroy() method that stops polling, flushes batch queue,
    and clears all settings
  • Updated interface IVWOClient to include new setVWOBuilder() and
    destroy() methods
+59/-0   
VWOBuilder.js.map
Polling lifecycle management with destroy handler integration

dist/esm/VWOBuilder.js.map

  • Added destroyHandler and isPolling private properties to VWOBuilder
    class
  • Modified build() method to register destroy handler with the VWOClient
    instance
  • Implemented startPolling() method with polling state management and
    timer cleanup on destroy
  • Added stopPolling() method to halt polling and clear associated timers
+1/-1     
Documentation
3 files
VWOClient.d.ts
VWOClient TypeScript type definitions update                         

dist/types/VWOClient.d.ts

  • Added type definitions for setVWOBuilder() method
  • Added type definition for destroy() async method
  • Added private property declarations for vwoBuilder and isDestroyed
+15/-0   
BatchEventsQueue.d.ts
BatchEventsQueue TypeScript type definitions update           

dist/types/services/BatchEventsQueue.d.ts

  • Added maxQueueSize property to BatchConfig interface
  • Added private property declarations for maxQueueSize and isDestroyed
  • Added type definition for destroy() async method
+8/-0     
VWOBuilder.d.ts
VWOBuilder TypeScript type definitions update                       

dist/types/VWOBuilder.d.ts

  • Added private property declarations for pollingTimeout and
    isPollingActive
  • Added type definition for stopPolling() method
+6/-0     
Additional files
8 files
vwo-fme-javascript-sdk.js.map +1/-1     
vwo-fme-javascript-sdk.min.js +1/-1     
vwo-fme-javascript-sdk.min.js.map +1/-1     
BatchEventsQueue.js.map +1/-1     
BatchEventsQueue.js.map +1/-1     
vwo-fme-node-sdk.js.map +1/-1     
vwo-fme-node-sdk.min.js +1/-1     
vwo-fme-node-sdk.min.js.map +1/-1     

Summary by CodeRabbit

  • New Features

    • Added lifecycle management capabilities for proper resource cleanup
    • Introduced configurable queue capacity limits for better performance control
  • Bug Fixes

    • Enhanced resource management to prevent accumulation of pending operations
    • Improved cleanup behavior for better application stability

@coderabbitai
Copy link

coderabbitai bot commented Nov 14, 2025

Walkthrough

The changes introduce comprehensive lifecycle management and resource cleanup patterns across the SDK. VWOBuilder gains polling control with a stopPolling() method, VWOClient and BatchEventsQueue implement destroy() methods for cleanup, and new tests validate these behaviors to prevent memory leaks and ensure proper shutdown.

Changes

Cohort / File(s) Summary
Polling and Client Lifecycle
lib/VWOBuilder.ts, lib/VWOClient.ts
VWOBuilder introduces polling state fields (pollingTimeout, isPollingActive) and a stopPolling() method to halt recurring polls. VWOClient adds setVWOBuilder() for builder reference storage and destroy() for resource cleanup, including stopping polls via the builder, destroying BatchEventsQueue, and resetting state.
Event Queue Lifecycle
lib/services/BatchEventsQueue.ts
BatchEventsQueue adds maxQueueSize configuration (default 1000), capacity enforcement with event eviction, and a destroy() method for idempotent cleanup. Timer handling changed from clearTimeout to clearInterval, and flushAndClearTimer() now operates asynchronously.
Lifecycle and Memory Leak Prevention Tests
test/unit/VWOBuilder.test.ts, test/unit/VWOClient.test.ts, test/unit/services/BatchEventsQueue.test.ts
Comprehensive test suites validate polling control and state management, destroy idempotence and cleanup order, BatchEventsQueue capacity and lifecycle, timer cleanup, builder integration, and memory leak prevention across all three components.

Sequence Diagram(s)

sequenceDiagram
    participant App
    participant VWOClient
    participant VWOBuilder
    participant BatchEventsQueue
    
    Note over App,BatchEventsQueue: Normal Operation
    App->>VWOClient: build()
    VWOClient->>VWOBuilder: setVWOBuilder()
    Note over VWOBuilder: Polling active, timer scheduled
    
    Note over App,BatchEventsQueue: Destroy Flow (Cleanup)
    App->>VWOClient: destroy()
    activate VWOClient
    VWOClient->>VWOBuilder: stopPolling()
    activate VWOBuilder
    Note over VWOBuilder: Cancel pending timer,<br/>set isPollingActive = false
    deactivate VWOBuilder
    VWOClient->>BatchEventsQueue: destroy()
    activate BatchEventsQueue
    Note over BatchEventsQueue: Flush events, clear queue,<br/>set isDestroyed = true
    deactivate BatchEventsQueue
    Note over VWOClient: Clear settings & state
    deactivate VWOClient
    Note over App,BatchEventsQueue: Resources cleaned up
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Areas requiring extra attention:
    • The builder-to-client bidirectional reference pattern in setVWOBuilder() and destroy flow; verify no circular reference leaks occur
    • Idempotence guarantees for both VWOClient.destroy() and BatchEventsQueue.destroy() under concurrent or repeated calls
    • Timer/interval clearance semantics in BatchEventsQueue; confirm clearInterval is correct replacement for clearTimeout
    • Queue eviction logic when maxQueueSize limit is hit; ensure oldest 10% calculation doesn't drop events unexpectedly
    • Error handling in destroy paths, particularly try/catch blocks around stopPolling() and BatchEventsQueue destroy

Poem

🐰 When timers tick too long, we hop to stop,
Polls pause, queues purge, and leaks drop.
With destroy's dance, we clean the slate—
No dangling refs, just a tidy state!

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main changes: fixing memory leaks through timer cleanup and implementing destroy methods.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@qodo-code-review
Copy link

PR Compliance Guide 🔍

Below is a summary of compliance checks for this PR:

Security Compliance
🟢
No security concerns identified No security vulnerabilities detected by AI analysis. Human verification advised for critical code.
Ticket Compliance
🎫 No ticket provided
  • Create ticket/issue
Codebase Duplication Compliance
Codebase context is not defined

Follow the guide to enable codebase context checks.

Custom Compliance
🟢
Generic: Meaningful Naming and Self-Documenting Code

Objective: Ensure all identifiers clearly express their purpose and intent, making code
self-documenting

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Error Handling

Objective: To prevent the leakage of sensitive system information through error messages while
providing sufficient detail for internal debugging.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Secure Logging Practices

Objective: To ensure logs are useful for debugging and auditing without exposing sensitive
information like PII, PHI, or cardholder data.

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Security-First Input Validation and Data Handling

Objective: Ensure all data inputs are validated, sanitized, and handled securely to prevent
vulnerabilities

Status: Passed

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Comprehensive Audit Trails

Objective: To create a detailed and reliable record of critical system actions for security analysis
and compliance.

Status:
Action Logging: New critical lifecycle actions like client and queue destruction are logged, but the logs
lack explicit user identifiers or structured context to qualify as comprehensive audit
trails for security analysis.

Referred Code
/**
 * Destroys the VWO client instance and cleans up all resources
 * This includes flushing pending events and stopping polling
 */
async destroy(): Promise<void> {
  const apiName = 'destroy';
  try {
    // Check if already destroyed (idempotent)
    if (this.isDestroyed) {
      LogManager.Instance.warn('VWO client already destroyed');
      return;
    }

    LogManager.Instance.info('Destroying VWO client instance');
    this.isDestroyed = true;

    // Stop polling if VWOBuilder reference is available
    if (this.vwoBuilder && typeof this.vwoBuilder.stopPolling === 'function') {
      try {
        this.vwoBuilder.stopPolling();
      } catch (error) {


 ... (clipped 23 lines)

Learn more about managing compliance generic rules or creating your own custom rules

Generic: Robust Error Handling and Edge Case Management

Objective: Ensure comprehensive error handling that provides meaningful context and graceful
degradation

Status:
Error Context: Errors during destroy and flush are caught and logged, but callbacks or callers are not
notified of failures which may hide operational issues depending on upstream expectations.

Referred Code
// Flush any remaining events
try {
  await this.flush(true);
  LogManager.Instance.info('BatchEventsQueue destroyed successfully');
} catch (error) {
  LogManager.Instance.error('Error flushing events during destroy: ' + error);
}

Learn more about managing compliance generic rules or creating your own custom rules

Compliance status legend 🟢 - Fully Compliant
🟡 - Partial Compliant
🔴 - Not Compliant
⚪ - Requires Further Human Verification
🏷️ - Compliance label

@qodo-code-review
Copy link

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
Possible issue
Fix incorrect method call context

Fix a TypeError in the build method by calling setDestroyHandler on the
VWOClient instance (this.vwoClient) instead of the VWOBuilder instance (this).

dist/server-unpacked/VWOBuilder.js.map [1]

 IACH,0BAAK,GAAL,UAAM,QAA0B;
 QAC9B,IAAI,CAAC,WAAW,GAAG,IAAI,qBAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;
 QAEzD,gDAAgD;
 QAChD,IAAI,OAAO,IAAI,CAAC,WAAW,CAAC,aAAa,KAAK,UAAU,EAAE,CAAC;
     YACzD,IAAI,CAAC,WAAW,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;
 QACvC,CAAC;
-QAED,IAAI,CAAC,iCAAiC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;
+QAED,IAAI,CAAC,WAAW,CAAC,iCAAiC,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;
 QACvD,OAAO,IAAI,CAAC,WAAW,CAAC;
 IAC1B,CAAC;

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 9

__

Why: The suggestion correctly identifies a TypeError where setDestroyHandler is called on the wrong object (VWOBuilder instead of VWOClient), which would cause a runtime crash.

High
General
Set destroyed flag after cleanup

In the destroy() method, set the isDestroyed flag to true only after all cleanup
operations have successfully completed to ensure a consistent state.

lib/VWOClient.ts [572-600]

 async destroy(): Promise<void> {
   const apiName = 'destroy';
   try {
     // Check if already destroyed (idempotent)
     if (this.isDestroyed) {
       LogManager.Instance.warn('VWO client already destroyed');
       return;
     }
 
     LogManager.Instance.info('Destroying VWO client instance');
-    this.isDestroyed = true;
 
     // Stop polling if VWOBuilder reference is available
     if (this.vwoBuilder && typeof this.vwoBuilder.stopPolling === 'function') {
       try {
         this.vwoBuilder.stopPolling();
       } catch (error) {
         LogManager.Instance.error('Error stopping polling: ' + error);
       }
     }
 
     // Flush any pending events in the batch queue
     if (BatchEventsQueue.Instance) {
       try {
         await BatchEventsQueue.Instance.destroy();
       } catch (error) {
         LogManager.Instance.error('Error destroying BatchEventsQueue: ' + error);
       }
     }
 
+    // Clear settings
+    this.settings = null;
+    this.originalSettings = {};
+    this.isSettingsValid = false;
+    this.vwoBuilder = null;
+
+    // Mark as destroyed only after all cleanup completes
+    this.isDestroyed = true;
+    LogManager.Instance.info('VWO client destroyed successfully');
+
  • Apply / Chat
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out a potential issue with the state management in destroy, but the proposed change could introduce a new problem where a failed destroy call can be retried, potentially causing unintended side effects.

Low
Remove unnecessary await on synchronous function

Remove the unnecessary await from the clearTimeout call in the destroy method,
as clearTimeout is a synchronous function.

dist/server-unpacked/VWOClient.js.map [1]

 ...
 6BAGG,mCAAgB,CAAC,QAAQ,EAAzB,wBAAyB;;;;
-wBAEzB,qBAAM,mCAAgB,CAAC,QAAQ,CAAC,OAAO,EAAE,EAAA;;
-wBAAzC,SAAyC,CAAC;;;;
+wBAEzB,mCAAgB,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;
 wBAE1C,mBAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,qCAAqC,GAAG,OAAK,CAAC,CAAC;;;
 ...

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 4

__

Why: The suggestion correctly points out that await is used on a synchronous function clearTimeout, which is unnecessary and can be misleading. Removing it improves code clarity.

Low
Improve queue overflow handling

Improve queue overflow handling by notifying the caller when events are dropped,
possibly via a callback, to prevent silent data loss.

lib/services/BatchEventsQueue.ts [128-137]

 if (this.queue.length >= this.maxQueueSize) {
+  const droppedCount = Math.floor(this.maxQueueSize * 0.1);
+  const droppedEvents = this.queue.splice(0, droppedCount);
+  
   LogManager.Instance.warn(
     buildMessage('Event queue has reached maximum size, dropping oldest events', {
       maxQueueSize: this.maxQueueSize,
       currentSize: this.queue.length,
+      droppedCount: droppedCount,
     }),
   );
-  // Remove oldest events to make room (FIFO)
-  this.queue.splice(0, Math.floor(this.maxQueueSize * 0.1)); // Remove oldest 10%
+  
+  // Notify via flush callback about dropped events
+  if (this.flushCallback) {
+    this.flushCallback(new Error('Queue overflow: events dropped'), { droppedEvents });
+  }
 }
  • Apply / Chat
Suggestion importance[1-10]: 2

__

Why: The suggestion incorrectly identifies a race condition in single-threaded JavaScript, and the proposed solution misuses the flushCallback which is intended for flush results, not for notifications about dropped events during enqueue.

Low
  • More

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (5)
lib/services/BatchEventsQueue.ts (1)

35-37: Consider typing BatchEventsQueue.instance as nullable for correctness

destroy() assigns BatchEventsQueue.instance = null, but the static field and Instance getter are typed as BatchEventsQueue only. Under stricter TS settings this would be a type error; consider:

  • private static instance: BatchEventsQueue | null;
  • public static get Instance(): BatchEventsQueue | null { ... }

This keeps the runtime behavior the same while making the API honest about the nullable singleton.

Also applies to: 254-256

lib/VWOClient.ts (1)

71-73: Destroy flow is well-structured; consider a couple of small cleanup/typing tweaks

The new setVWOBuilder + destroy flow is coherent: it stops polling via the builder, tears down BatchEventsQueue.Instance, and clears settings with an idempotent guard. This should address the main leak scenarios around timers and the singleton queue.

Two small follow-ups you might consider:

  • settings is typed as SettingsModel but assigned null in destroy(). If you ever enable strictNullChecks, this will need to become SettingsModel | null (and the same for the interface).
  • To further reduce dangling references after destroy, you could also clear vwoClientInstance (and possibly storage) alongside vwoBuilder, since they aren’t expected to be used post‑destroy.

Also applies to: 83-85, 559-612

test/unit/services/BatchEventsQueue.test.ts (3)

17-19: ESLint warnings are false positives, but consider type-only import for BatchConfig.

The ESLint warnings about unused imports are false positives:

  • SettingsService and LogManager are used by the mocks on lines 34 and 22 respectively
  • BatchConfig is used as a type for the configuration objects passed to BatchEventsQueue constructor throughout the tests

However, since BatchConfig is only used as a type, consider using a type-only import to clarify intent:

-import { BatchEventsQueue, type BatchConfig } from '../../../lib/services/BatchEventsQueue';
+import type { BatchConfig } from '../../../lib/services/BatchEventsQueue';
+import { BatchEventsQueue } from '../../../lib/services/BatchEventsQueue';

Alternatively, add ESLint disable comments if the current import structure is preferred.


196-216: Consider adding direct queue size verification.

The test correctly validates that the queue doesn't grow unbounded, but only indirectly (by checking that the dispatcher wasn't called). The comment on lines 208-215 acknowledges this is "internal verification."

Consider adding a more explicit assertion by exposing queue length or by testing the behavior when flushing:

// After enqueuing 1001 events with maxQueueSize 1000
// Manually flush to verify the queue contains ~901 events (not 1001)
await queue.flush();

// Verify dispatcher was called with roughly 901 events
expect(mockDispatcher).toHaveBeenCalledTimes(1);
const calledWith = mockDispatcher.mock.calls[0][0];
expect(calledWith.length).toBeLessThan(1001);
expect(calledWith.length).toBeGreaterThan(800); // Allow for 10% drop

This would make the test more explicit about what's being protected against.


344-362: Remove unnecessary timeout or add explanation.

This test sets a 10-second timeout (}, 10000);), but it uses fake timers (jest.useFakeTimers() from beforeEach). Since fake timers allow instant time progression, this extended timeout appears unnecessary.

If the timeout is needed for a specific reason (e.g., actual async network operations in CI), please add a comment explaining why. Otherwise, consider removing it:

-    }, 10000); // Increase timeout for this test
+    });

Additionally, note that jest.runOnlyPendingTimers() on line 357 triggers the async flush, but the test doesn't await its completion before calling destroy(). This is acceptable since the test only verifies that destroy() doesn't throw, but consider adding a comment clarifying this behavior.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d7f2244 and 4a204cb.

⛔ Files ignored due to path filters (23)
  • dist/client/vwo-fme-javascript-sdk.js is excluded by !**/dist/**
  • dist/client/vwo-fme-javascript-sdk.js.map is excluded by !**/dist/**, !**/*.map
  • dist/client/vwo-fme-javascript-sdk.min.js is excluded by !**/dist/**, !**/*.min.js
  • dist/client/vwo-fme-javascript-sdk.min.js.map is excluded by !**/dist/**, !**/*.map, !**/*.min.js.map
  • dist/esm/VWOBuilder.js is excluded by !**/dist/**
  • dist/esm/VWOBuilder.js.map is excluded by !**/dist/**, !**/*.map
  • dist/esm/VWOClient.js is excluded by !**/dist/**
  • dist/esm/VWOClient.js.map is excluded by !**/dist/**, !**/*.map
  • dist/esm/services/BatchEventsQueue.js is excluded by !**/dist/**
  • dist/esm/services/BatchEventsQueue.js.map is excluded by !**/dist/**, !**/*.map
  • dist/server-unpacked/VWOBuilder.js is excluded by !**/dist/**
  • dist/server-unpacked/VWOBuilder.js.map is excluded by !**/dist/**, !**/*.map
  • dist/server-unpacked/VWOClient.js is excluded by !**/dist/**
  • dist/server-unpacked/VWOClient.js.map is excluded by !**/dist/**, !**/*.map
  • dist/server-unpacked/services/BatchEventsQueue.js is excluded by !**/dist/**
  • dist/server-unpacked/services/BatchEventsQueue.js.map is excluded by !**/dist/**, !**/*.map
  • dist/server/vwo-fme-node-sdk.js is excluded by !**/dist/**
  • dist/server/vwo-fme-node-sdk.js.map is excluded by !**/dist/**, !**/*.map
  • dist/server/vwo-fme-node-sdk.min.js is excluded by !**/dist/**, !**/*.min.js
  • dist/server/vwo-fme-node-sdk.min.js.map is excluded by !**/dist/**, !**/*.map, !**/*.min.js.map
  • dist/types/VWOBuilder.d.ts is excluded by !**/dist/**
  • dist/types/VWOClient.d.ts is excluded by !**/dist/**
  • dist/types/services/BatchEventsQueue.d.ts is excluded by !**/dist/**
📒 Files selected for processing (6)
  • lib/VWOBuilder.ts (4 hunks)
  • lib/VWOClient.ts (3 hunks)
  • lib/services/BatchEventsQueue.ts (5 hunks)
  • test/unit/VWOBuilder.test.ts (1 hunks)
  • test/unit/VWOClient.test.ts (1 hunks)
  • test/unit/services/BatchEventsQueue.test.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
lib/services/BatchEventsQueue.ts (2)
lib/utils/DataTypeUtil.ts (1)
  • isNumber (64-67)
lib/utils/LogMessageUtil.ts (1)
  • buildMessage (31-53)
test/unit/VWOBuilder.test.ts (2)
lib/VWOBuilder.ts (1)
  • VWOBuilder (62-494)
lib/services/SettingsService.ts (1)
  • SettingsService (39-349)
lib/VWOClient.ts (3)
lib/packages/logger/core/LogManager.ts (2)
  • LogManager (55-183)
  • error (179-182)
lib/services/BatchEventsQueue.ts (1)
  • BatchEventsQueue (35-258)
lib/utils/LogMessageUtil.ts (1)
  • buildMessage (31-53)
test/unit/VWOClient.test.ts (2)
lib/VWOClient.ts (1)
  • VWOClient (75-613)
lib/services/BatchEventsQueue.ts (1)
  • BatchEventsQueue (35-258)
test/unit/services/BatchEventsQueue.test.ts (1)
lib/services/BatchEventsQueue.ts (1)
  • BatchEventsQueue (35-258)
lib/VWOBuilder.ts (2)
lib/packages/logger/core/LogManager.ts (1)
  • LogManager (55-183)
lib/constants/index.ts (1)
  • Constants (38-89)
🪛 ESLint
test/unit/services/BatchEventsQueue.test.ts

[error] 17-17: 'BatchConfig' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 18-18: 'SettingsService' is defined but never used.

(@typescript-eslint/no-unused-vars)


[error] 19-19: 'LogManager' is defined but never used.

(@typescript-eslint/no-unused-vars)

🔇 Additional comments (7)
test/unit/VWOBuilder.test.ts (1)

37-259: Polling tests cover the right failure modes and look solid

The suite exercises stop/start, idempotence, recursion prevention, and long‑running polling with fake timers in a way that matches the new checkAndPoll / stopPolling implementation. This should give good confidence around timer cleanup and leak prevention.

lib/VWOBuilder.ts (1)

78-80: Polling lifecycle changes look correct and align with tests

The introduction of pollingTimeout/isPollingActive, the re‑entrancy guard in checkAndPoll, and stopPolling()’s clearing of the timeout give you a single active poll loop that can be cleanly stopped and won’t reschedule once deactivated. The wiring from build() into VWOClient.setVWOBuilder fits the new destroy() flow.

Also applies to: 398-405, 413-460, 462-477

test/unit/services/BatchEventsQueue.test.ts (5)

21-40: LGTM!

The mocks are well-structured and provide all the necessary methods that BatchEventsQueue depends on.


42-58: LGTM!

Excellent test setup with proper cleanup. The use of fake timers and the conditional queue.destroy() in afterEach follows best practices and prevents test pollution.


60-116: LGTM!

These tests effectively validate the timer cleanup fixes that prevent memory leaks:

  1. Verifies clearInterval is used instead of clearTimeout (fixing the implementation bug)
  2. Confirms timers are actually cleared using jest.getTimerCount()
  3. Ensures no automatic flushing occurs after destroy

118-193: LGTM!

Comprehensive coverage of the destroy() method behavior:

  • Validates that pending events are flushed (preventing data loss)
  • Confirms post-destroy enqueues are rejected
  • Tests idempotency (safe to call multiple times)
  • Verifies singleton instance nullification for proper garbage collection

280-318: LGTM!

The tests correctly validate both aspects of flushAndClearTimer():

  • Timer is properly cleared (verified via jest.getTimerCount())
  • Events are flushed and the result is returned

Comment on lines +93 to +99
// Set max queue size with a reasonable default
if (isNumber(config.maxQueueSize) && config.maxQueueSize > 0) {
this.maxQueueSize = config.maxQueueSize;
} else {
this.maxQueueSize = 1000; // Default max queue size to prevent unbounded growth
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix eviction when maxQueueSize is small; current logic can still grow unbounded

When maxQueueSize < 10, Math.floor(this.maxQueueSize * 0.1) becomes 0, so splice removes nothing and the queue can still grow beyond maxQueueSize despite the guard. This undermines the new bound meant to prevent memory growth.

You can ensure at least one event is evicted before pushing the new one:

-      // Remove oldest events to make room (FIFO)
-      this.queue.splice(0, Math.floor(this.maxQueueSize * 0.1)); // Remove oldest 10%
+      // Remove oldest events to make room (FIFO). Always evict at least 1.
+      const removeCount = Math.max(1, Math.floor(this.maxQueueSize * 0.1));
+      this.queue.splice(0, removeCount);

Also applies to: 127-137

🤖 Prompt for AI Agents
In lib/services/BatchEventsQueue.ts around lines 93-99 and also apply same fix
to 127-137: the eviction calculation uses Math.floor(this.maxQueueSize * 0.1)
which becomes 0 for small maxQueueSize, so splice removes nothing and the queue
can exceed the bound; change the eviction logic to compute evictCount =
Math.max(1, Math.floor(this.maxQueueSize * 0.1)) (or another minimal positive
integer) and use that value when splicing before pushing a new event so at least
one event is removed when the queue is full; apply the identical change to the
other block at 127-137 to keep behavior consistent.

Comment on lines +131 to +141
test('should handle missing BatchEventsQueue.Instance gracefully', async () => {
Object.defineProperty(BatchEventsQueue, 'Instance', {
get: jest.fn(() => null),
configurable: true,
});

client = new VWOClient(mockSettings, mockOptions);

await expect(client.destroy()).resolves.not.toThrow();
});

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix Jest expectations using resolves.not.toThrow() on destroy()

Several tests assert non‑throwing behavior like:

await expect(client.destroy()).resolves.not.toThrow();

But toThrow expects a function, whereas resolves passes the resolved value (undefined), so these matchers are invalid and will fail for the wrong reason.

Prefer asserting the promise resolves successfully instead, e.g.:

- await expect(client.destroy()).resolves.not.toThrow();
+ await expect(client.destroy()).resolves.toBeUndefined();

or simply:

await client.destroy();
// implicit assertion: test will fail if the promise rejects

Apply this pattern to the tests that are only verifying that destroy() does not reject (missing BatchEventsQueue.Instance, failing destroy(), missing/invalid builder, stopPolling errors, etc.).

Also applies to: 142-158, 176-181, 183-193, 194-208

🤖 Prompt for AI Agents
In test/unit/VWOClient.test.ts around lines 131-141 (and also apply same change
to 142-158, 176-181, 183-193, 194-208), replace invalid assertions using await
expect(client.destroy()).resolves.not.toThrow() with a proper promise-resolution
check; either call await client.destroy() directly (so the test fails if it
rejects) or use await expect(client.destroy()).resolves.toBeUndefined() to
explicitly assert successful resolution.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants