hoangsonww / hoangsonww/Time-Warp-Manipulation-Library

Virtual Clock & Timer Interception (setTimeout / setInterval / performance.now / requestAnimationFrame) + Time-Travel Controls

Open
#2 0 comments 0 reactions 1 assignee Claimed by @hoangsonww View on GitHub
bug documentation duplicate enhancement good first issue help wanted question
Dominant language
TypeScript
Stars
11
Forks
7
PR merge metrics
No merged PRs in 30d

Description

**Summary**
Introduce a deterministic **Virtual Clock** that intercepts and controls common time sources and schedulers—`setTimeout`, `setInterval`, `performance.now`, and `requestAnimationFrame`—in addition to the existing `Date` manipulation. Provide an explicit **time-travel API** (`advanceBy`, `advanceTo`, `tick`, `flush`) for precise, fast-forwardable simulations and tests in both Node and browsers.

## Motivation

* **Deterministic tests without real waiting**: Today, changing `Date.now()` or speed helps, but timeouts/intervals/RAF still run on real wall clock. A Virtual Clock lets tests jump forward instantly and flush pending timers.
* **Parity with ecosystem expectations**: Tools like Jest’s modern fake timers and sinon’s fake timers are popular. Having first-class support in this library creates a one-stop solution for *both* logical time (`Date`) and scheduled tasks.
* **Browser + Node coverage**: Frontend apps rely on `requestAnimationFrame` and `performance.now`; Node apps rely on precise timer control. A unified abstraction simplifies usage across environments.
* **Faster feedback loops**: Simulations (e.g., “simulate 24 hours of app activity”) become milliseconds.

## Goals

1. Optional **interception (monkey-patch)** for:

* `setTimeout`, `clearTimeout`
* `setInterval`, `clearInterval`
* `performance.now`
* `requestAnimationFrame`, `cancelAnimationFrame` (browser only)
* (Node) `setImmediate`, `clearImmediate`
2. **Virtual Clock API**:

* `advanceBy(ms)`, `advanceTo(timestampMs)`, `tick()` (run next scheduled task), `flush()` (run all due tasks)
* `now()` returns the virtual, monotonic clock time
3. **Determinism & order guarantees** for same-time tasks (stable FIFO by scheduling order).
4. **Zero-wait testing**: advancing time triggers due callbacks synchronously without real delays.
5. **Backward compatible**: current `enableTimeWarp` usage continues to work; timer interception is opt-in.

## Non-Goals

* Full event-loop phase emulation (we’ll keep a pragmatic, deterministic queue model).
* Complex cron parsing (could be a follow-up).

## Proposed API

```ts
type TimeWarpOptions = {
freezeAt?: number | null; // existing
speed?: number; // existing
monkeyPatch?: boolean; // existing Date patch

// NEW: timer interception & virtual clock
timers?: {
intercept?: boolean; // default: false
rAF?: boolean; // default: true when intercept=true and window exists
performanceNow?: boolean; // default: true when intercept=true
immediate?: boolean; // Node setImmediate, default: true when intercept=true in Node
};
};

// New Virtual Clock interface
interface VirtualClock {
// time
now(): number; // virtual monotonic time (maps to performance.now if patched)
dateNow(): number; // virtual epoch ms (maps to Date.now if patched)

// time travel
advanceBy(ms: number): void;
advanceTo(targetEpochMs: number): void; // jumps Date epoch and perf base accordingly
tick(): boolean; // run next due task; returns whether something ran
flush(limit?: number): number; // run all due tasks (or up to 'limit'); returns count

// inspection
getPendingTimers(): Array<{ id: number; type: 'timeout'|'interval'|'raf'|'immediate'; at: number }>;
}

// New exports
export function getVirtualClock(): VirtualClock;
export function isTimerInterceptionEnabled(): boolean;
```

### Request/Runtime Behavior

* When `timers.intercept` is **enabled**:

* `setTimeout`/`setInterval` schedule into the Virtual Clock queue at `virtualEpoch + delay`.
* `performance.now()` derives from the Virtual Clock’s monotonic origin.
* In browsers, `requestAnimationFrame` schedules at the **next frame boundary**; advancing time beyond ~16.67ms triggers RAF callbacks in order, with a synthetic high-res timestamp (from `performance.now()`).
* In Node, `setImmediate` queues into a micro-slot that runs before timers scheduled at the same virtual timestamp.
* `advanceBy` / `advanceTo` move virtual time and **synchronously run** all now-due callbacks.
* Intervals re-schedule based on their registered period using the **virtual time**, not wall clock.
* If both `speed` (real-time acceleration) **and** interception are enabled, time still flows automatically; `advanceBy`/`advanceTo` are *additive* manual controls. (Docs will clarify tradeoffs; most tests will prefer `freezeAt` or `speed=0` + manual `advanceBy`.)

### Error Handling & Edges

* **Reentrancy**: If a timer schedules another timer at the same timestamp, execution is FIFO by enqueue order.
* **Long jumps**: `advanceBy(1e9)` executes due tasks in timestamp order; intervals iterate predictably without drift.
* **Cancel semantics**: `clear*` removes pending entries before execution.

## Examples

```ts
import { enableTimeWarp, getVirtualClock, disableTimeWarp } from 'time-warp-manipulation';

enableTimeWarp({
freezeAt: Date.UTC(2030, 0, 1),
monkeyPatch: true,
timers: { intercept: true }
});

const clock = getVirtualClock();
let fired = 0;

setTimeout(() => fired++, 1000);
setInterval(() => fired += 10, 500);

clock.advanceBy(500); // runs interval once -> fired = 10
clock.advanceBy(500); // runs timeout -> fired = 11; interval again -> fired = 21
clock.flush(); // nothing else due -> returns 0

disableTimeWarp();
```

**Browser RAF**

```ts
enableTimeWarp({ timers: { intercept: true, rAF: true } });
const clock = getVirtualClock();

let frames = 0;
function loop(ts: number) {
frames++;
if (frames < 3) requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

clock.advanceBy(50); // ~3 frames worth -> frames === 3
```

## Acceptance Criteria

* [ ] **Timer interception** works for `setTimeout`, `setInterval`, `clear*` across Node & browser.
* [ ] **performance.now** and **Date.now** reflect virtual time when enabled.
* [ ] **requestAnimationFrame** and **cancelAnimationFrame** supported in browser envs.
* [ ] **Virtual Clock API** (`advanceBy`, `advanceTo`, `tick`, `flush`, `now`) exposed and documented.
* [ ] Deterministic execution order for timers scheduled at the same timestamp (stable FIFO).
* [ ] Intervals reschedule based on original cadence without cumulative drift.
* [ ] Works with existing options (`freezeAt`, `speed`, `monkeyPatch`) and remains backward-compatible when interception is off.
* [ ] Comprehensive tests:

* [ ] Basic scheduling, canceling, and re-scheduling
* [ ] Intervals under long `advanceBy` jumps
* [ ] `performance.now` and RAF timestamps
* [ ] Node `setImmediate` ordering (if enabled)
* [ ] Concurrency/reentrancy within callbacks
* [ ] README updated with usage, examples, and caveats.

## Implementation Notes

* Maintain a min-heap priority queue keyed by **dueTime** and **sequence** for stable ordering.
* Represent virtual time as `{ epochBaseMs, perfBaseMs }` so `Date.now()` and `performance.now()` remain coherent.
* For RAF, simulate frame cadence using a configurable `frameDurationMs` (default 16.67ms) and batch callbacks per “frame”.
* Provide a no-op shim when APIs don’t exist (e.g., RAF in Node); only patch what’s present.

## Potential Follow-Ups (separate issues)

* **Jest integration helper**: tiny adapter to sync with `jest.useFakeTimers('modern')` semantics.
* **Temporal API support** (TC39): `Temporal.Now` hooks.
* **Clock domains**: multiple independent virtual clocks with scoped patches.
* **Devtools overlay** to visualize scheduled tasks and time jumps.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.