Hone logo
Hone
Problems

Implement Time-Travel Debugging in an Angular Application

This challenge focuses on building a core component of "time-travel debugging" within an Angular application. Time-travel debugging allows developers to step back through a series of application states, observe how data and UI have changed over time, and identify bugs by pinpointing the exact moment an issue occurred. You will implement a service that records state changes and provides mechanisms to navigate through these historical states.

Problem Description

Your task is to create an Angular service that can track the state of a specific observable and allow users to "rewind" and "fast-forward" through past states of that observable. This service will act as a simplified time-travel debugger for a particular piece of application data.

Key Requirements:

  1. State Recording: The service must record every new value emitted by a target observable.
  2. History Storage: Store a limited history of emitted values. The number of states to store should be configurable.
  3. Current State Access: Provide a way to access the current state being displayed.
  4. Rewind Functionality: Implement a method to move the displayed state to a previous recorded state.
  5. Fast-Forward Functionality: Implement a method to move the displayed state to a future recorded state (after rewinding).
  6. Replayability: The service should emit the currently selected state, allowing the UI to react to historical data.
  7. Configuration: Allow configuration of the maximum number of states to store in the history.

Expected Behavior:

When an observable connected to the service emits a new value, it should be added to the history. The "current" position in the history should advance.

  • Calling rewind() should move the current position one step back in the history, emitting the state at that new position.
  • Calling fastForward() should move the current position one step forward in the history, emitting the state at that new position.
  • If rewind() is called when at the earliest state, no change should occur.
  • If fastForward() is called when at the latest state, no change should occur.
  • If the history is full, the oldest state should be discarded when a new state is added.

Edge Cases:

  • Initial state: What happens when the service is initialized before any values are emitted?
  • Empty history: What happens when rewind() or fastForward() are called on an empty history?
  • History limit reached: Ensure older states are correctly discarded.
  • Concurrent updates: While this challenge doesn't explicitly focus on complex concurrency, assume single-threaded updates for simplicity.

Examples

Let's assume we have a simple counter observable that emits values 0, 1, 2, 3, 4.

Example 1: Basic Recording and Rewind

  • Input to Service Setup: maxHistory: 5

  • Observable Emissions: 0, 1, 2, 3, 4

  • Service State After Emissions:

    • History: [0, 1, 2, 3, 4]
    • Current Index: 4 (pointing to 4)
    • Output Observable emits: 4
  • Action: Call rewind() once.

  • Output Observable emits: 3

  • Explanation: The current index moves from 4 to 3, and the service emits the value at index 3, which is 3.

  • Action: Call rewind() again.

  • Output Observable emits: 2

  • Explanation: The current index moves from 3 to 2, and the service emits the value at index 2, which is 2.

Example 2: Fast-Forwarding After Rewind

  • Scenario: Starting from the state in Example 1, after rewinding twice (current index is 2, output is 2).

  • Action: Call fastForward() once.

  • Output Observable emits: 3

  • Explanation: The current index moves from 2 to 3, and the service emits the value at index 3, which is 3.

  • Action: Call fastForward() again.

  • Output Observable emits: 4

  • Explanation: The current index moves from 3 to 4, and the service emits the value at index 4, which is 4.

  • Action: Call fastForward() again.

  • Output Observable emits: 4 (no change)

  • Explanation: The current index is already at the latest state (4), so fastForward() has no effect.

Example 3: History Limit

  • Input to Service Setup: maxHistory: 3

  • Observable Emissions: A, B, C, D, E

  • Service State After Emissions:

    • History: [C, D, E] (A and B have been discarded)
    • Current Index: 2 (pointing to E)
    • Output Observable emits: E
  • Action: Call rewind() once.

  • Output Observable emits: D

  • Explanation: The current index moves from 2 to 1, and the service emits the value at index 1, which is D.

Constraints

  • The maxHistory configuration parameter will be an integer between 1 and 100.
  • The observable being tracked can emit values of any type (e.g., number, string, object).
  • The solution should be implemented as an Angular service using TypeScript.
  • The service should be efficient enough to handle frequent state updates without significant performance degradation.

Notes

  • Consider using RxJS operators to manage the observable streams.
  • Think about how to represent the "current" position within the history array.
  • The service should ideally expose an observable that emits the currently active state.
  • This challenge is a simplified version of state management found in libraries like Redux with redux-undo or Zustand's time travel middleware.
  • Focus on the core logic of state recording and navigation. UI rendering is outside the scope of this challenge.
Loading editor...
typescript