@crashlab/clock 0.1.0
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.
- package/README.md +32 -0
- package/dist/MinHeap.d.ts +19 -0
- package/dist/MinHeap.d.ts.map +1 -0
- package/dist/VirtualClock.d.ts +66 -0
- package/dist/VirtualClock.d.ts.map +1 -0
- package/dist/VirtualDate.d.ts +7 -0
- package/dist/VirtualDate.d.ts.map +1 -0
- package/dist/index.cjs +370 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +340 -0
- package/dist/index.js.map +1 -0
- package/dist/install.d.ts +27 -0
- package/dist/install.d.ts.map +1 -0
- package/package.json +25 -0
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# @crashlab/clock
|
|
2
|
+
|
|
3
|
+
Deterministic virtual clock for Node.js. Replaces `Date`, `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, and `performance.now()` with manually-controllable fakes.
|
|
4
|
+
|
|
5
|
+
## Usage
|
|
6
|
+
|
|
7
|
+
```ts
|
|
8
|
+
import { VirtualClock, install } from '@crashlab/clock';
|
|
9
|
+
|
|
10
|
+
// Standalone (no global patching)
|
|
11
|
+
const clock = new VirtualClock(0);
|
|
12
|
+
const order: number[] = [];
|
|
13
|
+
clock.setTimeout(() => order.push(1), 100);
|
|
14
|
+
clock.setTimeout(() => order.push(2), 200);
|
|
15
|
+
clock.advance(200);
|
|
16
|
+
// order = [1, 2]
|
|
17
|
+
|
|
18
|
+
// With global patching
|
|
19
|
+
const { clock: c, uninstall } = install(0);
|
|
20
|
+
console.log(Date.now()); // 0
|
|
21
|
+
c.advance(5000);
|
|
22
|
+
console.log(Date.now()); // 5000
|
|
23
|
+
uninstall();
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Key Features
|
|
27
|
+
|
|
28
|
+
- **Min-heap timer queue** — O(log n) insertion and removal
|
|
29
|
+
- **Deterministic ordering** — same-time timers fire in FIFO order
|
|
30
|
+
- **Cascading timers** — timers that schedule sub-timers within the same `advance()` window are picked up and executed in order
|
|
31
|
+
- **`freeze()` / `unfreeze()`** — pause time progression
|
|
32
|
+
- **`pending()`** — inspect all scheduled timers
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic min-heap priority queue.
|
|
3
|
+
* O(log n) push/pop, used as the timer scheduling backbone.
|
|
4
|
+
*/
|
|
5
|
+
export declare class MinHeap<T> {
|
|
6
|
+
private readonly comparator;
|
|
7
|
+
private heap;
|
|
8
|
+
constructor(comparator: (a: T, b: T) => number);
|
|
9
|
+
get size(): number;
|
|
10
|
+
peek(): T | undefined;
|
|
11
|
+
push(value: T): void;
|
|
12
|
+
pop(): T | undefined;
|
|
13
|
+
/** Remove first entry matching predicate. Returns true if found. */
|
|
14
|
+
remove(predicate: (v: T) => boolean): boolean;
|
|
15
|
+
toArray(): T[];
|
|
16
|
+
private bubbleUp;
|
|
17
|
+
private sinkDown;
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=MinHeap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MinHeap.d.ts","sourceRoot":"","sources":["../src/MinHeap.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,OAAO,CAAC,CAAC;IAGR,OAAO,CAAC,QAAQ,CAAC,UAAU;IAFvC,OAAO,CAAC,IAAI,CAAW;gBAEM,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,KAAK,MAAM;IAE/D,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,IAAI,IAAI,CAAC,GAAG,SAAS;IAIrB,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAKpB,GAAG,IAAI,CAAC,GAAG,SAAS;IAYpB,oEAAoE;IACpE,MAAM,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,OAAO,GAAG,OAAO;IAY7C,OAAO,IAAI,CAAC,EAAE;IAId,OAAO,CAAC,QAAQ;IAShB,OAAO,CAAC,QAAQ;CAejB"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/** Internal timer entry stored in the heap. */
|
|
2
|
+
export interface TimerEntry {
|
|
3
|
+
id: number;
|
|
4
|
+
callback: (...args: unknown[]) => void | Promise<void>;
|
|
5
|
+
scheduledTime: number;
|
|
6
|
+
interval?: number;
|
|
7
|
+
args: unknown[];
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* A manually-controllable virtual clock.
|
|
11
|
+
*
|
|
12
|
+
* Replaces all time primitives so that `advance(duration)` fires timers
|
|
13
|
+
* deterministically, in scheduled-time order. Timers that schedule new
|
|
14
|
+
* timers within the same advance window ARE picked up and executed in
|
|
15
|
+
* the correct order (the heap is re-checked after every callback).
|
|
16
|
+
*/
|
|
17
|
+
export declare class VirtualClock {
|
|
18
|
+
private _now;
|
|
19
|
+
private _skew;
|
|
20
|
+
private _nextId;
|
|
21
|
+
private _frozen;
|
|
22
|
+
private _timers;
|
|
23
|
+
private readonly _cancelledIds;
|
|
24
|
+
private readonly _virtualNextTickQueue;
|
|
25
|
+
private readonly _virtualImmediateQueue;
|
|
26
|
+
private _nextImmediateId;
|
|
27
|
+
private readonly _cancelledImmediates;
|
|
28
|
+
/**
|
|
29
|
+
* Optional hook called after each timer fires (and after microtask flush).
|
|
30
|
+
* The clock attaches the scheduler here so that advancing time drives I/O.
|
|
31
|
+
*/
|
|
32
|
+
onTick?: (time: number) => Promise<void>;
|
|
33
|
+
constructor(startTime?: number);
|
|
34
|
+
now(): number;
|
|
35
|
+
pending(): Array<{
|
|
36
|
+
id: number;
|
|
37
|
+
scheduledTime: number;
|
|
38
|
+
}>;
|
|
39
|
+
/**
|
|
40
|
+
* Advance the clock by `duration` ms, firing every timer whose
|
|
41
|
+
* scheduled time falls within `[now, now + duration]` in order.
|
|
42
|
+
*/
|
|
43
|
+
advance(duration: number): Promise<void>;
|
|
44
|
+
/**
|
|
45
|
+
* Jump to an absolute virtual timestamp, draining timers along the way.
|
|
46
|
+
*/
|
|
47
|
+
advanceTo(timestamp: number): Promise<void>;
|
|
48
|
+
private _flushMicrotasks;
|
|
49
|
+
private _flushImmediates;
|
|
50
|
+
freeze(): void;
|
|
51
|
+
unfreeze(): void;
|
|
52
|
+
/**
|
|
53
|
+
* Apply a clock skew offset (ms). Does NOT fire timers.
|
|
54
|
+
* `now()` returns `_now + _skew`.
|
|
55
|
+
*/
|
|
56
|
+
skew(amount: number): void;
|
|
57
|
+
setTimeout(callback: (...args: unknown[]) => void | Promise<void>, delay?: number, ...args: unknown[]): number;
|
|
58
|
+
setInterval(callback: (...args: unknown[]) => void | Promise<void>, delay: number, ...args: unknown[]): number;
|
|
59
|
+
clearTimeout(id: number): void;
|
|
60
|
+
clearInterval(id: number): void;
|
|
61
|
+
setImmediate(callback: (...args: unknown[]) => void | Promise<void>, ...args: unknown[]): number;
|
|
62
|
+
clearImmediate(id: number): void;
|
|
63
|
+
nextTick(callback: (...args: unknown[]) => void, ...args: unknown[]): void;
|
|
64
|
+
reset(startTime?: number): void;
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=VirtualClock.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VirtualClock.d.ts","sourceRoot":"","sources":["../src/VirtualClock.ts"],"names":[],"mappings":"AAEA,+CAA+C;AAC/C,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvD,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED;;;;;;;GAOG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,KAAK,CAAa;IAC1B,OAAO,CAAC,OAAO,CAAK;IACpB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,OAAO,CAAsB;IACrC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAqB;IAEnD,OAAO,CAAC,QAAQ,CAAC,qBAAqB,CAA2C;IACjF,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAuG;IAC9I,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAE1D;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;gBAE7B,SAAS,GAAE,MAAU;IAWjC,GAAG,IAAI,MAAM;IAIb,OAAO,IAAI,KAAK,CAAC;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAA;KAAE,CAAC;IASvD;;;OAGG;IACG,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C;;OAEG;IACG,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAkEnC,gBAAgB;YAsBhB,gBAAgB;IAmB9B,MAAM,IAAI,IAAI;IAId,QAAQ,IAAI,IAAI;IAIhB;;;OAGG;IACH,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAM1B,UAAU,CACR,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACtD,KAAK,GAAE,MAAU,EACjB,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,MAAM;IAWT,WAAW,CACT,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EACtD,KAAK,EAAE,MAAM,EACb,GAAG,IAAI,EAAE,OAAO,EAAE,GACjB,MAAM;IAYT,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAK9B,aAAa,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAK/B,YAAY,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,MAAM;IAMhG,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAIhC,QAAQ,CAAC,QAAQ,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,GAAG,IAAI;IAM1E,KAAK,CAAC,SAAS,GAAE,MAAU,GAAG,IAAI;CAiBnC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { VirtualClock } from './VirtualClock.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build a `DateConstructor` whose `now()` and zero-arg `new Date()`
|
|
4
|
+
* read from the supplied VirtualClock.
|
|
5
|
+
*/
|
|
6
|
+
export declare function createVirtualDate(clock: VirtualClock): DateConstructor;
|
|
7
|
+
//# sourceMappingURL=VirtualDate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"VirtualDate.d.ts","sourceRoot":"","sources":["../src/VirtualDate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEtD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,YAAY,GAAG,eAAe,CAmCtE"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
MinHeap: () => MinHeap,
|
|
24
|
+
VirtualClock: () => VirtualClock,
|
|
25
|
+
createVirtualDate: () => createVirtualDate,
|
|
26
|
+
install: () => install
|
|
27
|
+
});
|
|
28
|
+
module.exports = __toCommonJS(index_exports);
|
|
29
|
+
|
|
30
|
+
// src/MinHeap.ts
|
|
31
|
+
var MinHeap = class {
|
|
32
|
+
constructor(comparator) {
|
|
33
|
+
this.comparator = comparator;
|
|
34
|
+
}
|
|
35
|
+
heap = [];
|
|
36
|
+
get size() {
|
|
37
|
+
return this.heap.length;
|
|
38
|
+
}
|
|
39
|
+
peek() {
|
|
40
|
+
return this.heap[0];
|
|
41
|
+
}
|
|
42
|
+
push(value) {
|
|
43
|
+
this.heap.push(value);
|
|
44
|
+
this.bubbleUp(this.heap.length - 1);
|
|
45
|
+
}
|
|
46
|
+
pop() {
|
|
47
|
+
const n = this.heap.length;
|
|
48
|
+
if (n === 0) return void 0;
|
|
49
|
+
const top = this.heap[0];
|
|
50
|
+
const last = this.heap.pop();
|
|
51
|
+
if (this.heap.length > 0) {
|
|
52
|
+
this.heap[0] = last;
|
|
53
|
+
this.sinkDown(0);
|
|
54
|
+
}
|
|
55
|
+
return top;
|
|
56
|
+
}
|
|
57
|
+
/** Remove first entry matching predicate. Returns true if found. */
|
|
58
|
+
remove(predicate) {
|
|
59
|
+
const idx = this.heap.findIndex(predicate);
|
|
60
|
+
if (idx === -1) return false;
|
|
61
|
+
const last = this.heap.pop();
|
|
62
|
+
if (idx < this.heap.length) {
|
|
63
|
+
this.heap[idx] = last;
|
|
64
|
+
this.bubbleUp(idx);
|
|
65
|
+
this.sinkDown(idx);
|
|
66
|
+
}
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
toArray() {
|
|
70
|
+
return [...this.heap];
|
|
71
|
+
}
|
|
72
|
+
bubbleUp(i) {
|
|
73
|
+
while (i > 0) {
|
|
74
|
+
const parent = i - 1 >> 1;
|
|
75
|
+
if (this.comparator(this.heap[i], this.heap[parent]) >= 0) break;
|
|
76
|
+
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
|
|
77
|
+
i = parent;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
sinkDown(i) {
|
|
81
|
+
const n = this.heap.length;
|
|
82
|
+
while (true) {
|
|
83
|
+
let smallest = i;
|
|
84
|
+
const left = 2 * i + 1;
|
|
85
|
+
const right = 2 * i + 2;
|
|
86
|
+
if (left < n && this.comparator(this.heap[left], this.heap[smallest]) < 0)
|
|
87
|
+
smallest = left;
|
|
88
|
+
if (right < n && this.comparator(this.heap[right], this.heap[smallest]) < 0)
|
|
89
|
+
smallest = right;
|
|
90
|
+
if (smallest === i) break;
|
|
91
|
+
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
|
|
92
|
+
i = smallest;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/VirtualClock.ts
|
|
98
|
+
var VirtualClock = class {
|
|
99
|
+
_now;
|
|
100
|
+
_skew = 0;
|
|
101
|
+
_nextId = 1;
|
|
102
|
+
_frozen = false;
|
|
103
|
+
_timers;
|
|
104
|
+
_cancelledIds = /* @__PURE__ */ new Set();
|
|
105
|
+
_virtualNextTickQueue = [];
|
|
106
|
+
_virtualImmediateQueue = [];
|
|
107
|
+
_nextImmediateId = 1;
|
|
108
|
+
_cancelledImmediates = /* @__PURE__ */ new Set();
|
|
109
|
+
/**
|
|
110
|
+
* Optional hook called after each timer fires (and after microtask flush).
|
|
111
|
+
* The clock attaches the scheduler here so that advancing time drives I/O.
|
|
112
|
+
*/
|
|
113
|
+
onTick;
|
|
114
|
+
constructor(startTime = 0) {
|
|
115
|
+
this._now = startTime;
|
|
116
|
+
this._timers = new MinHeap(
|
|
117
|
+
(a, b) => a.scheduledTime !== b.scheduledTime ? a.scheduledTime - b.scheduledTime : a.id - b.id
|
|
118
|
+
// FIFO tie-break
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
// queries
|
|
122
|
+
now() {
|
|
123
|
+
return this._now + this._skew;
|
|
124
|
+
}
|
|
125
|
+
pending() {
|
|
126
|
+
return this._timers.toArray().map((t) => ({ id: t.id, scheduledTime: t.scheduledTime })).sort((a, b) => a.scheduledTime - b.scheduledTime);
|
|
127
|
+
}
|
|
128
|
+
// time control
|
|
129
|
+
/**
|
|
130
|
+
* Advance the clock by `duration` ms, firing every timer whose
|
|
131
|
+
* scheduled time falls within `[now, now + duration]` in order.
|
|
132
|
+
*/
|
|
133
|
+
async advance(duration) {
|
|
134
|
+
await this.advanceTo(this._now + duration);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Jump to an absolute virtual timestamp, draining timers along the way.
|
|
138
|
+
*/
|
|
139
|
+
async advanceTo(timestamp) {
|
|
140
|
+
if (this._frozen) return;
|
|
141
|
+
if (timestamp < this._now) return;
|
|
142
|
+
while (this._timers.size > 0) {
|
|
143
|
+
const next = this._timers.peek();
|
|
144
|
+
if (next.scheduledTime > timestamp) break;
|
|
145
|
+
if (this._cancelledIds.has(next.id)) {
|
|
146
|
+
this._timers.pop();
|
|
147
|
+
this._cancelledIds.delete(next.id);
|
|
148
|
+
continue;
|
|
149
|
+
}
|
|
150
|
+
this._timers.pop();
|
|
151
|
+
if (this._cancelledIds.has(next.id)) {
|
|
152
|
+
this._cancelledIds.delete(next.id);
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
this._now = next.scheduledTime;
|
|
156
|
+
try {
|
|
157
|
+
const maybePromise = next.callback(...next.args);
|
|
158
|
+
await this._flushMicrotasks();
|
|
159
|
+
if (maybePromise instanceof Promise) await maybePromise;
|
|
160
|
+
} catch (err) {
|
|
161
|
+
throw new Error(`VirtualClock timer callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
162
|
+
}
|
|
163
|
+
await this._flushMicrotasks();
|
|
164
|
+
if (this.onTick) {
|
|
165
|
+
await this.onTick(this._now + this._skew);
|
|
166
|
+
await this._flushMicrotasks();
|
|
167
|
+
}
|
|
168
|
+
if (next.interval !== void 0 && !this._cancelledIds.has(next.id)) {
|
|
169
|
+
this._timers.push({
|
|
170
|
+
...next,
|
|
171
|
+
scheduledTime: next.scheduledTime + next.interval
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
this._cancelledIds.delete(next.id);
|
|
175
|
+
await this._flushImmediates();
|
|
176
|
+
}
|
|
177
|
+
this._now = timestamp;
|
|
178
|
+
await this._flushMicrotasks();
|
|
179
|
+
if (this.onTick) {
|
|
180
|
+
await this.onTick(this._now + this._skew);
|
|
181
|
+
await this._flushMicrotasks();
|
|
182
|
+
}
|
|
183
|
+
await this._flushImmediates();
|
|
184
|
+
}
|
|
185
|
+
async _flushMicrotasks() {
|
|
186
|
+
let draining = true;
|
|
187
|
+
while (draining) {
|
|
188
|
+
while (this._virtualNextTickQueue.length > 0) {
|
|
189
|
+
const cb = this._virtualNextTickQueue.shift();
|
|
190
|
+
try {
|
|
191
|
+
cb();
|
|
192
|
+
} catch (err) {
|
|
193
|
+
throw new Error(`VirtualClock nextTick failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
await Promise.resolve();
|
|
197
|
+
if (this._virtualNextTickQueue.length === 0) {
|
|
198
|
+
draining = false;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
async _flushImmediates() {
|
|
203
|
+
const immediates = [...this._virtualImmediateQueue];
|
|
204
|
+
this._virtualImmediateQueue.length = 0;
|
|
205
|
+
for (const imm of immediates) {
|
|
206
|
+
if (this._cancelledImmediates.has(imm.id)) {
|
|
207
|
+
this._cancelledImmediates.delete(imm.id);
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
try {
|
|
211
|
+
await Promise.resolve(imm.callback(...imm.args));
|
|
212
|
+
} catch (err) {
|
|
213
|
+
throw new Error(`VirtualClock immediate failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
214
|
+
}
|
|
215
|
+
await this._flushMicrotasks();
|
|
216
|
+
this._cancelledImmediates.delete(imm.id);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
freeze() {
|
|
220
|
+
this._frozen = true;
|
|
221
|
+
}
|
|
222
|
+
unfreeze() {
|
|
223
|
+
this._frozen = false;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Apply a clock skew offset (ms). Does NOT fire timers.
|
|
227
|
+
* `now()` returns `_now + _skew`.
|
|
228
|
+
*/
|
|
229
|
+
skew(amount) {
|
|
230
|
+
this._skew += amount;
|
|
231
|
+
}
|
|
232
|
+
// timer primitives
|
|
233
|
+
setTimeout(callback, delay = 0, ...args) {
|
|
234
|
+
const id = this._nextId++;
|
|
235
|
+
this._timers.push({
|
|
236
|
+
id,
|
|
237
|
+
callback,
|
|
238
|
+
scheduledTime: this._now + Math.max(0, delay),
|
|
239
|
+
args
|
|
240
|
+
});
|
|
241
|
+
return id;
|
|
242
|
+
}
|
|
243
|
+
setInterval(callback, delay, ...args) {
|
|
244
|
+
const id = this._nextId++;
|
|
245
|
+
this._timers.push({
|
|
246
|
+
id,
|
|
247
|
+
callback,
|
|
248
|
+
scheduledTime: this._now + Math.max(0, delay),
|
|
249
|
+
interval: Math.max(1, delay),
|
|
250
|
+
args
|
|
251
|
+
});
|
|
252
|
+
return id;
|
|
253
|
+
}
|
|
254
|
+
clearTimeout(id) {
|
|
255
|
+
this._cancelledIds.add(id);
|
|
256
|
+
this._timers.remove((t) => t.id === id);
|
|
257
|
+
}
|
|
258
|
+
clearInterval(id) {
|
|
259
|
+
this._cancelledIds.add(id);
|
|
260
|
+
this._timers.remove((t) => t.id === id);
|
|
261
|
+
}
|
|
262
|
+
setImmediate(callback, ...args) {
|
|
263
|
+
const id = this._nextImmediateId++;
|
|
264
|
+
this._virtualImmediateQueue.push({ id, callback, args });
|
|
265
|
+
return id;
|
|
266
|
+
}
|
|
267
|
+
clearImmediate(id) {
|
|
268
|
+
this._cancelledImmediates.add(id);
|
|
269
|
+
}
|
|
270
|
+
nextTick(callback, ...args) {
|
|
271
|
+
this._virtualNextTickQueue.push(() => callback(...args));
|
|
272
|
+
}
|
|
273
|
+
// lifecycle
|
|
274
|
+
reset(startTime = 0) {
|
|
275
|
+
this._now = startTime;
|
|
276
|
+
this._skew = 0;
|
|
277
|
+
this._nextId = 1;
|
|
278
|
+
this._frozen = false;
|
|
279
|
+
this._timers = new MinHeap(
|
|
280
|
+
(a, b) => a.scheduledTime !== b.scheduledTime ? a.scheduledTime - b.scheduledTime : a.id - b.id
|
|
281
|
+
);
|
|
282
|
+
this._cancelledIds.clear();
|
|
283
|
+
this._virtualNextTickQueue.length = 0;
|
|
284
|
+
this._virtualImmediateQueue.length = 0;
|
|
285
|
+
this._cancelledImmediates.clear();
|
|
286
|
+
this._nextImmediateId = 1;
|
|
287
|
+
this.onTick = void 0;
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// src/VirtualDate.ts
|
|
292
|
+
function createVirtualDate(clock) {
|
|
293
|
+
const OrigDate = Date;
|
|
294
|
+
class VirtualDate extends OrigDate {
|
|
295
|
+
constructor(...args) {
|
|
296
|
+
if (args.length === 0) {
|
|
297
|
+
super(clock.now());
|
|
298
|
+
} else if (args.length === 1) {
|
|
299
|
+
super(args[0]);
|
|
300
|
+
} else {
|
|
301
|
+
const tmp = new OrigDate(
|
|
302
|
+
args[0],
|
|
303
|
+
args[1] ?? 0,
|
|
304
|
+
args[2] ?? 1,
|
|
305
|
+
args[3] ?? 0,
|
|
306
|
+
args[4] ?? 0,
|
|
307
|
+
args[5] ?? 0,
|
|
308
|
+
args[6] ?? 0
|
|
309
|
+
);
|
|
310
|
+
super(tmp.getTime());
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
static now() {
|
|
314
|
+
return clock.now();
|
|
315
|
+
}
|
|
316
|
+
static [Symbol.hasInstance](instance) {
|
|
317
|
+
return instance instanceof OrigDate;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return VirtualDate;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// src/install.ts
|
|
324
|
+
function install(clockOrStart, opts) {
|
|
325
|
+
const clock = clockOrStart instanceof VirtualClock ? clockOrStart : new VirtualClock(clockOrStart);
|
|
326
|
+
const originals = {
|
|
327
|
+
Date: globalThis.Date,
|
|
328
|
+
setTimeout: globalThis.setTimeout,
|
|
329
|
+
clearTimeout: globalThis.clearTimeout,
|
|
330
|
+
setInterval: globalThis.setInterval,
|
|
331
|
+
clearInterval: globalThis.clearInterval,
|
|
332
|
+
setImmediate: globalThis.setImmediate,
|
|
333
|
+
clearImmediate: globalThis.clearImmediate,
|
|
334
|
+
nextTick: globalThis.process?.nextTick,
|
|
335
|
+
performanceNow: globalThis.performance.now.bind(globalThis.performance)
|
|
336
|
+
};
|
|
337
|
+
globalThis.Date = createVirtualDate(clock);
|
|
338
|
+
globalThis.setTimeout = (cb, delay, ...args) => clock.setTimeout(cb, delay ?? 0, ...args);
|
|
339
|
+
globalThis.clearTimeout = (id) => clock.clearTimeout(id);
|
|
340
|
+
globalThis.setInterval = (cb, delay, ...args) => clock.setInterval(cb, delay, ...args);
|
|
341
|
+
globalThis.clearInterval = (id) => clock.clearInterval(id);
|
|
342
|
+
if (typeof globalThis.setImmediate !== "undefined") {
|
|
343
|
+
globalThis.setImmediate = (cb, ...args) => clock.setImmediate(cb, ...args);
|
|
344
|
+
globalThis.clearImmediate = (id) => clock.clearImmediate(id);
|
|
345
|
+
}
|
|
346
|
+
if (opts?.patchNextTick !== false && globalThis.process && typeof globalThis.process.nextTick === "function") {
|
|
347
|
+
globalThis.process.nextTick = (cb, ...args) => clock.nextTick(cb, ...args);
|
|
348
|
+
}
|
|
349
|
+
globalThis.performance.now = () => clock.now();
|
|
350
|
+
function uninstall() {
|
|
351
|
+
globalThis.Date = originals.Date;
|
|
352
|
+
globalThis.setTimeout = originals.setTimeout;
|
|
353
|
+
globalThis.clearTimeout = originals.clearTimeout;
|
|
354
|
+
globalThis.setInterval = originals.setInterval;
|
|
355
|
+
globalThis.clearInterval = originals.clearInterval;
|
|
356
|
+
if (originals.setImmediate) globalThis.setImmediate = originals.setImmediate;
|
|
357
|
+
if (originals.clearImmediate) globalThis.clearImmediate = originals.clearImmediate;
|
|
358
|
+
if (originals.nextTick && globalThis.process) globalThis.process.nextTick = originals.nextTick;
|
|
359
|
+
globalThis.performance.now = originals.performanceNow;
|
|
360
|
+
}
|
|
361
|
+
return { clock, uninstall };
|
|
362
|
+
}
|
|
363
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
364
|
+
0 && (module.exports = {
|
|
365
|
+
MinHeap,
|
|
366
|
+
VirtualClock,
|
|
367
|
+
createVirtualDate,
|
|
368
|
+
install
|
|
369
|
+
});
|
|
370
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/MinHeap.ts","../src/VirtualClock.ts","../src/VirtualDate.ts","../src/install.ts"],"sourcesContent":["export { VirtualClock } from './VirtualClock.js';\nexport { createVirtualDate } from './VirtualDate.js';\nexport { install } from './install.js';\nexport type { ClockInstallResult, ClockInstallOptions } from './install.js';\nexport type { TimerEntry } from './VirtualClock.js';\nexport { MinHeap } from './MinHeap.js';\n","/**\n * Generic min-heap priority queue.\n * O(log n) push/pop, used as the timer scheduling backbone.\n */\nexport class MinHeap<T> {\n private heap: T[] = [];\n\n constructor(private readonly comparator: (a: T, b: T) => number) {}\n\n get size(): number {\n return this.heap.length;\n }\n\n peek(): T | undefined {\n return this.heap[0];\n }\n\n push(value: T): void {\n this.heap.push(value);\n this.bubbleUp(this.heap.length - 1);\n }\n\n pop(): T | undefined {\n const n = this.heap.length;\n if (n === 0) return undefined;\n const top = this.heap[0];\n const last = this.heap.pop()!;\n if (this.heap.length > 0) {\n this.heap[0] = last;\n this.sinkDown(0);\n }\n return top;\n }\n\n /** Remove first entry matching predicate. Returns true if found. */\n remove(predicate: (v: T) => boolean): boolean {\n const idx = this.heap.findIndex(predicate);\n if (idx === -1) return false;\n const last = this.heap.pop()!;\n if (idx < this.heap.length) {\n this.heap[idx] = last;\n this.bubbleUp(idx);\n this.sinkDown(idx);\n }\n return true;\n }\n\n toArray(): T[] {\n return [...this.heap];\n }\n\n private bubbleUp(i: number): void {\n while (i > 0) {\n const parent = (i - 1) >> 1;\n if (this.comparator(this.heap[i], this.heap[parent]) >= 0) break;\n [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];\n i = parent;\n }\n }\n\n private sinkDown(i: number): void {\n const n = this.heap.length;\n while (true) {\n let smallest = i;\n const left = 2 * i + 1;\n const right = 2 * i + 2;\n if (left < n && this.comparator(this.heap[left], this.heap[smallest]) < 0)\n smallest = left;\n if (right < n && this.comparator(this.heap[right], this.heap[smallest]) < 0)\n smallest = right;\n if (smallest === i) break;\n [this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];\n i = smallest;\n }\n }\n}\n","import { MinHeap } from './MinHeap.js';\n\n/** Internal timer entry stored in the heap. */\nexport interface TimerEntry {\n id: number;\n callback: (...args: unknown[]) => void | Promise<void>;\n scheduledTime: number;\n interval?: number;\n args: unknown[];\n}\n\n/**\n * A manually-controllable virtual clock.\n *\n * Replaces all time primitives so that `advance(duration)` fires timers\n * deterministically, in scheduled-time order. Timers that schedule new\n * timers within the same advance window ARE picked up and executed in\n * the correct order (the heap is re-checked after every callback).\n */\nexport class VirtualClock {\n private _now: number;\n private _skew: number = 0;\n private _nextId = 1;\n private _frozen = false;\n private _timers: MinHeap<TimerEntry>;\n private readonly _cancelledIds = new Set<number>();\n\n private readonly _virtualNextTickQueue: Array<(...args: unknown[]) => void> = [];\n private readonly _virtualImmediateQueue: Array<{ id: number; callback: (...args: unknown[]) => void | Promise<void>; args: unknown[]; }> = [];\n private _nextImmediateId = 1;\n private readonly _cancelledImmediates = new Set<number>();\n\n /**\n * Optional hook called after each timer fires (and after microtask flush).\n * The clock attaches the scheduler here so that advancing time drives I/O.\n */\n onTick?: (time: number) => Promise<void>;\n\n constructor(startTime: number = 0) {\n this._now = startTime;\n this._timers = new MinHeap<TimerEntry>((a, b) =>\n a.scheduledTime !== b.scheduledTime\n ? a.scheduledTime - b.scheduledTime\n : a.id - b.id, // FIFO tie-break\n );\n }\n\n // queries\n\n now(): number {\n return this._now + this._skew;\n }\n\n pending(): Array<{ id: number; scheduledTime: number }> {\n return this._timers\n .toArray()\n .map((t) => ({ id: t.id, scheduledTime: t.scheduledTime }))\n .sort((a, b) => a.scheduledTime - b.scheduledTime);\n }\n\n // time control\n\n /**\n * Advance the clock by `duration` ms, firing every timer whose\n * scheduled time falls within `[now, now + duration]` in order.\n */\n async advance(duration: number): Promise<void> {\n await this.advanceTo(this._now + duration);\n }\n\n /**\n * Jump to an absolute virtual timestamp, draining timers along the way.\n */\n async advanceTo(timestamp: number): Promise<void> {\n if (this._frozen) return;\n if (timestamp < this._now) return; // never go backward\n\n while (this._timers.size > 0) {\n const next = this._timers.peek()!;\n if (next.scheduledTime > timestamp) break;\n\n // If cancelled while it was in queue\n if (this._cancelledIds.has(next.id)) {\n this._timers.pop();\n this._cancelledIds.delete(next.id);\n continue;\n }\n\n // Pop right before execution\n this._timers.pop();\n if (this._cancelledIds.has(next.id)) {\n this._cancelledIds.delete(next.id);\n continue;\n }\n\n this._now = next.scheduledTime;\n\n try {\n // Call synchronously first so that process.nextTick callbacks\n // registered inside the timer run before native Promise microtasks.\n // If the callback is async, await its returned Promise afterward.\n const maybePromise = next.callback(...next.args);\n await this._flushMicrotasks();\n if (maybePromise instanceof Promise) await maybePromise;\n } catch (err) {\n throw new Error(`VirtualClock timer callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });\n }\n\n await this._flushMicrotasks();\n\n // Call onTick hook so the scheduler can run I/O completions for this tick\n if (this.onTick) {\n await this.onTick(this._now + this._skew);\n await this._flushMicrotasks();\n }\n\n // If this was an interval AND not cancelled during the callback or microtasks,\n // reschedule for the next tick.\n if (next.interval !== undefined && !this._cancelledIds.has(next.id)) {\n this._timers.push({\n ...next,\n scheduledTime: next.scheduledTime + next.interval,\n });\n }\n this._cancelledIds.delete(next.id);\n\n await this._flushImmediates();\n }\n this._now = timestamp;\n\n // End of advance flush\n await this._flushMicrotasks();\n if (this.onTick) {\n await this.onTick(this._now + this._skew);\n await this._flushMicrotasks();\n }\n await this._flushImmediates();\n }\n\n private async _flushMicrotasks(): Promise<void> {\n let draining = true;\n while (draining) {\n // Phase 1: drain the virtual nextTick queue synchronously (nextTick runs before native microtasks)\n while (this._virtualNextTickQueue.length > 0) {\n const cb = this._virtualNextTickQueue.shift()!;\n try {\n cb();\n } catch (err) {\n throw new Error(`VirtualClock nextTick failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });\n }\n }\n // Phase 2: one V8 microtask checkpoint (lets native Promise chains resolve)\n await Promise.resolve();\n // Phase 3: if new nextTick callbacks were enqueued by those microtasks, loop again;\n // otherwise we're stable.\n if (this._virtualNextTickQueue.length === 0) {\n draining = false;\n }\n }\n }\n\n private async _flushImmediates(): Promise<void> {\n const immediates = [...this._virtualImmediateQueue];\n this._virtualImmediateQueue.length = 0;\n\n for (const imm of immediates) {\n if (this._cancelledImmediates.has(imm.id)) {\n this._cancelledImmediates.delete(imm.id);\n continue;\n }\n try {\n await Promise.resolve(imm.callback(...imm.args));\n } catch (err) {\n throw new Error(`VirtualClock immediate failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });\n }\n await this._flushMicrotasks();\n this._cancelledImmediates.delete(imm.id);\n }\n }\n\n freeze(): void {\n this._frozen = true;\n }\n\n unfreeze(): void {\n this._frozen = false;\n }\n\n /**\n * Apply a clock skew offset (ms). Does NOT fire timers.\n * `now()` returns `_now + _skew`.\n */\n skew(amount: number): void {\n this._skew += amount;\n }\n\n // timer primitives\n\n setTimeout(\n callback: (...args: unknown[]) => void | Promise<void>,\n delay: number = 0,\n ...args: unknown[]\n ): number {\n const id = this._nextId++;\n this._timers.push({\n id,\n callback,\n scheduledTime: this._now + Math.max(0, delay),\n args,\n });\n return id;\n }\n\n setInterval(\n callback: (...args: unknown[]) => void | Promise<void>,\n delay: number,\n ...args: unknown[]\n ): number {\n const id = this._nextId++;\n this._timers.push({\n id,\n callback,\n scheduledTime: this._now + Math.max(0, delay),\n interval: Math.max(1, delay),\n args,\n });\n return id;\n }\n\n clearTimeout(id: number): void {\n this._cancelledIds.add(id);\n this._timers.remove((t) => t.id === id);\n }\n\n clearInterval(id: number): void {\n this._cancelledIds.add(id);\n this._timers.remove((t) => t.id === id);\n }\n\n setImmediate(callback: (...args: unknown[]) => void | Promise<void>, ...args: unknown[]): number {\n const id = this._nextImmediateId++;\n this._virtualImmediateQueue.push({ id, callback, args });\n return id;\n }\n\n clearImmediate(id: number): void {\n this._cancelledImmediates.add(id);\n }\n\n nextTick(callback: (...args: unknown[]) => void, ...args: unknown[]): void {\n this._virtualNextTickQueue.push(() => callback(...args));\n }\n\n // lifecycle\n\n reset(startTime: number = 0): void {\n this._now = startTime;\n this._skew = 0;\n this._nextId = 1;\n this._frozen = false;\n this._timers = new MinHeap<TimerEntry>((a, b) =>\n a.scheduledTime !== b.scheduledTime\n ? a.scheduledTime - b.scheduledTime\n : a.id - b.id,\n );\n this._cancelledIds.clear();\n this._virtualNextTickQueue.length = 0;\n this._virtualImmediateQueue.length = 0;\n this._cancelledImmediates.clear();\n this._nextImmediateId = 1;\n this.onTick = undefined;\n }\n}\n","import type { VirtualClock } from './VirtualClock.js';\n\n/**\n * Build a `DateConstructor` whose `now()` and zero-arg `new Date()`\n * read from the supplied VirtualClock.\n */\nexport function createVirtualDate(clock: VirtualClock): DateConstructor {\n const OrigDate = Date;\n\n class VirtualDate extends OrigDate {\n constructor(...args: unknown[]) {\n if (args.length === 0) {\n super(clock.now());\n } else if (args.length === 1) {\n super(args[0] as string | number);\n } else {\n // Multi-arg (year, month, …): delegate to native Date for\n // local-time semantics, then feed the resulting timestamp to super.\n const tmp = new OrigDate(\n args[0] as number,\n (args[1] as number) ?? 0,\n (args[2] as number) ?? 1,\n (args[3] as number) ?? 0,\n (args[4] as number) ?? 0,\n (args[5] as number) ?? 0,\n (args[6] as number) ?? 0,\n );\n super(tmp.getTime());\n }\n }\n\n static override now(): number {\n return clock.now();\n }\n\n static [Symbol.hasInstance](instance: unknown): boolean {\n return instance instanceof OrigDate;\n }\n }\n\n return VirtualDate as unknown as DateConstructor;\n}\n","import { VirtualClock } from './VirtualClock.js';\nimport { createVirtualDate } from './VirtualDate.js';\n\nexport interface ClockInstallResult {\n clock: VirtualClock;\n uninstall: () => void;\n}\n\nexport interface ClockInstallOptions {\n /**\n * If true (the default), patch `process.nextTick` to route through the virtual clock.\n * This ensures deterministic ordering of nextTick callbacks relative to timers.\n *\n * Set to false only when your scenario uses dynamic `import()`, undici, or other\n * Node.js internals that rely on `process.nextTick` for initialization, and those\n * operations run outside of any `advance()` call (which would otherwise drain the\n * virtual nextTick queue via `_flushMicrotasks`).\n *\n * @default true\n */\n patchNextTick?: boolean;\n}\n\n/**\n * Patch all global time primitives to use a VirtualClock.\n *\n * Returns the clock instance and an `uninstall` function that restores\n * every original global.\n */\nexport function install(clockOrStart?: VirtualClock | number, opts?: ClockInstallOptions): ClockInstallResult {\n const clock =\n clockOrStart instanceof VirtualClock\n ? clockOrStart\n : new VirtualClock(clockOrStart);\n\n const originals = {\n Date: globalThis.Date,\n setTimeout: globalThis.setTimeout,\n clearTimeout: globalThis.clearTimeout,\n setInterval: globalThis.setInterval,\n clearInterval: globalThis.clearInterval,\n setImmediate: globalThis.setImmediate,\n clearImmediate: globalThis.clearImmediate,\n nextTick: globalThis.process?.nextTick,\n performanceNow: globalThis.performance.now.bind(globalThis.performance),\n };\n\n globalThis.Date = createVirtualDate(clock);\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n (globalThis as any).setTimeout = (\n cb: (...a: unknown[]) => void,\n delay?: number,\n ...args: unknown[]\n ) => clock.setTimeout(cb, delay ?? 0, ...args);\n\n (globalThis as any).clearTimeout = (id: number) => clock.clearTimeout(id);\n\n (globalThis as any).setInterval = (\n cb: (...a: unknown[]) => void,\n delay: number,\n ...args: unknown[]\n ) => clock.setInterval(cb, delay, ...args);\n\n (globalThis as any).clearInterval = (id: number) => clock.clearInterval(id);\n\n if (typeof globalThis.setImmediate !== 'undefined') {\n (globalThis as any).setImmediate = (cb: (...a: unknown[]) => void, ...args: unknown[]) => clock.setImmediate(cb, ...args);\n (globalThis as any).clearImmediate = (id: number) => clock.clearImmediate(id);\n }\n\n if (opts?.patchNextTick !== false && globalThis.process && typeof globalThis.process.nextTick === 'function') {\n globalThis.process.nextTick = (cb: (...a: unknown[]) => void, ...args: unknown[]) => clock.nextTick(cb, ...args);\n }\n\n globalThis.performance.now = () => clock.now();\n\n function uninstall(): void {\n globalThis.Date = originals.Date;\n globalThis.setTimeout = originals.setTimeout;\n globalThis.clearTimeout = originals.clearTimeout;\n globalThis.setInterval = originals.setInterval;\n globalThis.clearInterval = originals.clearInterval;\n if (originals.setImmediate) globalThis.setImmediate = originals.setImmediate;\n if (originals.clearImmediate) globalThis.clearImmediate = originals.clearImmediate;\n if (originals.nextTick && globalThis.process) globalThis.process.nextTick = originals.nextTick;\n globalThis.performance.now = originals.performanceNow;\n }\n\n return { clock, uninstall };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { VirtualClock } from './VirtualClock.js';
|
|
2
|
+
export { createVirtualDate } from './VirtualDate.js';
|
|
3
|
+
export { install } from './install.js';
|
|
4
|
+
export type { ClockInstallResult, ClockInstallOptions } from './install.js';
|
|
5
|
+
export type { TimerEntry } from './VirtualClock.js';
|
|
6
|
+
export { MinHeap } from './MinHeap.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAC5E,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
// src/MinHeap.ts
|
|
2
|
+
var MinHeap = class {
|
|
3
|
+
constructor(comparator) {
|
|
4
|
+
this.comparator = comparator;
|
|
5
|
+
}
|
|
6
|
+
heap = [];
|
|
7
|
+
get size() {
|
|
8
|
+
return this.heap.length;
|
|
9
|
+
}
|
|
10
|
+
peek() {
|
|
11
|
+
return this.heap[0];
|
|
12
|
+
}
|
|
13
|
+
push(value) {
|
|
14
|
+
this.heap.push(value);
|
|
15
|
+
this.bubbleUp(this.heap.length - 1);
|
|
16
|
+
}
|
|
17
|
+
pop() {
|
|
18
|
+
const n = this.heap.length;
|
|
19
|
+
if (n === 0) return void 0;
|
|
20
|
+
const top = this.heap[0];
|
|
21
|
+
const last = this.heap.pop();
|
|
22
|
+
if (this.heap.length > 0) {
|
|
23
|
+
this.heap[0] = last;
|
|
24
|
+
this.sinkDown(0);
|
|
25
|
+
}
|
|
26
|
+
return top;
|
|
27
|
+
}
|
|
28
|
+
/** Remove first entry matching predicate. Returns true if found. */
|
|
29
|
+
remove(predicate) {
|
|
30
|
+
const idx = this.heap.findIndex(predicate);
|
|
31
|
+
if (idx === -1) return false;
|
|
32
|
+
const last = this.heap.pop();
|
|
33
|
+
if (idx < this.heap.length) {
|
|
34
|
+
this.heap[idx] = last;
|
|
35
|
+
this.bubbleUp(idx);
|
|
36
|
+
this.sinkDown(idx);
|
|
37
|
+
}
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
toArray() {
|
|
41
|
+
return [...this.heap];
|
|
42
|
+
}
|
|
43
|
+
bubbleUp(i) {
|
|
44
|
+
while (i > 0) {
|
|
45
|
+
const parent = i - 1 >> 1;
|
|
46
|
+
if (this.comparator(this.heap[i], this.heap[parent]) >= 0) break;
|
|
47
|
+
[this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];
|
|
48
|
+
i = parent;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
sinkDown(i) {
|
|
52
|
+
const n = this.heap.length;
|
|
53
|
+
while (true) {
|
|
54
|
+
let smallest = i;
|
|
55
|
+
const left = 2 * i + 1;
|
|
56
|
+
const right = 2 * i + 2;
|
|
57
|
+
if (left < n && this.comparator(this.heap[left], this.heap[smallest]) < 0)
|
|
58
|
+
smallest = left;
|
|
59
|
+
if (right < n && this.comparator(this.heap[right], this.heap[smallest]) < 0)
|
|
60
|
+
smallest = right;
|
|
61
|
+
if (smallest === i) break;
|
|
62
|
+
[this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];
|
|
63
|
+
i = smallest;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// src/VirtualClock.ts
|
|
69
|
+
var VirtualClock = class {
|
|
70
|
+
_now;
|
|
71
|
+
_skew = 0;
|
|
72
|
+
_nextId = 1;
|
|
73
|
+
_frozen = false;
|
|
74
|
+
_timers;
|
|
75
|
+
_cancelledIds = /* @__PURE__ */ new Set();
|
|
76
|
+
_virtualNextTickQueue = [];
|
|
77
|
+
_virtualImmediateQueue = [];
|
|
78
|
+
_nextImmediateId = 1;
|
|
79
|
+
_cancelledImmediates = /* @__PURE__ */ new Set();
|
|
80
|
+
/**
|
|
81
|
+
* Optional hook called after each timer fires (and after microtask flush).
|
|
82
|
+
* The clock attaches the scheduler here so that advancing time drives I/O.
|
|
83
|
+
*/
|
|
84
|
+
onTick;
|
|
85
|
+
constructor(startTime = 0) {
|
|
86
|
+
this._now = startTime;
|
|
87
|
+
this._timers = new MinHeap(
|
|
88
|
+
(a, b) => a.scheduledTime !== b.scheduledTime ? a.scheduledTime - b.scheduledTime : a.id - b.id
|
|
89
|
+
// FIFO tie-break
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
// queries
|
|
93
|
+
now() {
|
|
94
|
+
return this._now + this._skew;
|
|
95
|
+
}
|
|
96
|
+
pending() {
|
|
97
|
+
return this._timers.toArray().map((t) => ({ id: t.id, scheduledTime: t.scheduledTime })).sort((a, b) => a.scheduledTime - b.scheduledTime);
|
|
98
|
+
}
|
|
99
|
+
// time control
|
|
100
|
+
/**
|
|
101
|
+
* Advance the clock by `duration` ms, firing every timer whose
|
|
102
|
+
* scheduled time falls within `[now, now + duration]` in order.
|
|
103
|
+
*/
|
|
104
|
+
async advance(duration) {
|
|
105
|
+
await this.advanceTo(this._now + duration);
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Jump to an absolute virtual timestamp, draining timers along the way.
|
|
109
|
+
*/
|
|
110
|
+
async advanceTo(timestamp) {
|
|
111
|
+
if (this._frozen) return;
|
|
112
|
+
if (timestamp < this._now) return;
|
|
113
|
+
while (this._timers.size > 0) {
|
|
114
|
+
const next = this._timers.peek();
|
|
115
|
+
if (next.scheduledTime > timestamp) break;
|
|
116
|
+
if (this._cancelledIds.has(next.id)) {
|
|
117
|
+
this._timers.pop();
|
|
118
|
+
this._cancelledIds.delete(next.id);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
this._timers.pop();
|
|
122
|
+
if (this._cancelledIds.has(next.id)) {
|
|
123
|
+
this._cancelledIds.delete(next.id);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
this._now = next.scheduledTime;
|
|
127
|
+
try {
|
|
128
|
+
const maybePromise = next.callback(...next.args);
|
|
129
|
+
await this._flushMicrotasks();
|
|
130
|
+
if (maybePromise instanceof Promise) await maybePromise;
|
|
131
|
+
} catch (err) {
|
|
132
|
+
throw new Error(`VirtualClock timer callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
133
|
+
}
|
|
134
|
+
await this._flushMicrotasks();
|
|
135
|
+
if (this.onTick) {
|
|
136
|
+
await this.onTick(this._now + this._skew);
|
|
137
|
+
await this._flushMicrotasks();
|
|
138
|
+
}
|
|
139
|
+
if (next.interval !== void 0 && !this._cancelledIds.has(next.id)) {
|
|
140
|
+
this._timers.push({
|
|
141
|
+
...next,
|
|
142
|
+
scheduledTime: next.scheduledTime + next.interval
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
this._cancelledIds.delete(next.id);
|
|
146
|
+
await this._flushImmediates();
|
|
147
|
+
}
|
|
148
|
+
this._now = timestamp;
|
|
149
|
+
await this._flushMicrotasks();
|
|
150
|
+
if (this.onTick) {
|
|
151
|
+
await this.onTick(this._now + this._skew);
|
|
152
|
+
await this._flushMicrotasks();
|
|
153
|
+
}
|
|
154
|
+
await this._flushImmediates();
|
|
155
|
+
}
|
|
156
|
+
async _flushMicrotasks() {
|
|
157
|
+
let draining = true;
|
|
158
|
+
while (draining) {
|
|
159
|
+
while (this._virtualNextTickQueue.length > 0) {
|
|
160
|
+
const cb = this._virtualNextTickQueue.shift();
|
|
161
|
+
try {
|
|
162
|
+
cb();
|
|
163
|
+
} catch (err) {
|
|
164
|
+
throw new Error(`VirtualClock nextTick failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
await Promise.resolve();
|
|
168
|
+
if (this._virtualNextTickQueue.length === 0) {
|
|
169
|
+
draining = false;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async _flushImmediates() {
|
|
174
|
+
const immediates = [...this._virtualImmediateQueue];
|
|
175
|
+
this._virtualImmediateQueue.length = 0;
|
|
176
|
+
for (const imm of immediates) {
|
|
177
|
+
if (this._cancelledImmediates.has(imm.id)) {
|
|
178
|
+
this._cancelledImmediates.delete(imm.id);
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
try {
|
|
182
|
+
await Promise.resolve(imm.callback(...imm.args));
|
|
183
|
+
} catch (err) {
|
|
184
|
+
throw new Error(`VirtualClock immediate failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
185
|
+
}
|
|
186
|
+
await this._flushMicrotasks();
|
|
187
|
+
this._cancelledImmediates.delete(imm.id);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
freeze() {
|
|
191
|
+
this._frozen = true;
|
|
192
|
+
}
|
|
193
|
+
unfreeze() {
|
|
194
|
+
this._frozen = false;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Apply a clock skew offset (ms). Does NOT fire timers.
|
|
198
|
+
* `now()` returns `_now + _skew`.
|
|
199
|
+
*/
|
|
200
|
+
skew(amount) {
|
|
201
|
+
this._skew += amount;
|
|
202
|
+
}
|
|
203
|
+
// timer primitives
|
|
204
|
+
setTimeout(callback, delay = 0, ...args) {
|
|
205
|
+
const id = this._nextId++;
|
|
206
|
+
this._timers.push({
|
|
207
|
+
id,
|
|
208
|
+
callback,
|
|
209
|
+
scheduledTime: this._now + Math.max(0, delay),
|
|
210
|
+
args
|
|
211
|
+
});
|
|
212
|
+
return id;
|
|
213
|
+
}
|
|
214
|
+
setInterval(callback, delay, ...args) {
|
|
215
|
+
const id = this._nextId++;
|
|
216
|
+
this._timers.push({
|
|
217
|
+
id,
|
|
218
|
+
callback,
|
|
219
|
+
scheduledTime: this._now + Math.max(0, delay),
|
|
220
|
+
interval: Math.max(1, delay),
|
|
221
|
+
args
|
|
222
|
+
});
|
|
223
|
+
return id;
|
|
224
|
+
}
|
|
225
|
+
clearTimeout(id) {
|
|
226
|
+
this._cancelledIds.add(id);
|
|
227
|
+
this._timers.remove((t) => t.id === id);
|
|
228
|
+
}
|
|
229
|
+
clearInterval(id) {
|
|
230
|
+
this._cancelledIds.add(id);
|
|
231
|
+
this._timers.remove((t) => t.id === id);
|
|
232
|
+
}
|
|
233
|
+
setImmediate(callback, ...args) {
|
|
234
|
+
const id = this._nextImmediateId++;
|
|
235
|
+
this._virtualImmediateQueue.push({ id, callback, args });
|
|
236
|
+
return id;
|
|
237
|
+
}
|
|
238
|
+
clearImmediate(id) {
|
|
239
|
+
this._cancelledImmediates.add(id);
|
|
240
|
+
}
|
|
241
|
+
nextTick(callback, ...args) {
|
|
242
|
+
this._virtualNextTickQueue.push(() => callback(...args));
|
|
243
|
+
}
|
|
244
|
+
// lifecycle
|
|
245
|
+
reset(startTime = 0) {
|
|
246
|
+
this._now = startTime;
|
|
247
|
+
this._skew = 0;
|
|
248
|
+
this._nextId = 1;
|
|
249
|
+
this._frozen = false;
|
|
250
|
+
this._timers = new MinHeap(
|
|
251
|
+
(a, b) => a.scheduledTime !== b.scheduledTime ? a.scheduledTime - b.scheduledTime : a.id - b.id
|
|
252
|
+
);
|
|
253
|
+
this._cancelledIds.clear();
|
|
254
|
+
this._virtualNextTickQueue.length = 0;
|
|
255
|
+
this._virtualImmediateQueue.length = 0;
|
|
256
|
+
this._cancelledImmediates.clear();
|
|
257
|
+
this._nextImmediateId = 1;
|
|
258
|
+
this.onTick = void 0;
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
// src/VirtualDate.ts
|
|
263
|
+
function createVirtualDate(clock) {
|
|
264
|
+
const OrigDate = Date;
|
|
265
|
+
class VirtualDate extends OrigDate {
|
|
266
|
+
constructor(...args) {
|
|
267
|
+
if (args.length === 0) {
|
|
268
|
+
super(clock.now());
|
|
269
|
+
} else if (args.length === 1) {
|
|
270
|
+
super(args[0]);
|
|
271
|
+
} else {
|
|
272
|
+
const tmp = new OrigDate(
|
|
273
|
+
args[0],
|
|
274
|
+
args[1] ?? 0,
|
|
275
|
+
args[2] ?? 1,
|
|
276
|
+
args[3] ?? 0,
|
|
277
|
+
args[4] ?? 0,
|
|
278
|
+
args[5] ?? 0,
|
|
279
|
+
args[6] ?? 0
|
|
280
|
+
);
|
|
281
|
+
super(tmp.getTime());
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
static now() {
|
|
285
|
+
return clock.now();
|
|
286
|
+
}
|
|
287
|
+
static [Symbol.hasInstance](instance) {
|
|
288
|
+
return instance instanceof OrigDate;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
return VirtualDate;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// src/install.ts
|
|
295
|
+
function install(clockOrStart, opts) {
|
|
296
|
+
const clock = clockOrStart instanceof VirtualClock ? clockOrStart : new VirtualClock(clockOrStart);
|
|
297
|
+
const originals = {
|
|
298
|
+
Date: globalThis.Date,
|
|
299
|
+
setTimeout: globalThis.setTimeout,
|
|
300
|
+
clearTimeout: globalThis.clearTimeout,
|
|
301
|
+
setInterval: globalThis.setInterval,
|
|
302
|
+
clearInterval: globalThis.clearInterval,
|
|
303
|
+
setImmediate: globalThis.setImmediate,
|
|
304
|
+
clearImmediate: globalThis.clearImmediate,
|
|
305
|
+
nextTick: globalThis.process?.nextTick,
|
|
306
|
+
performanceNow: globalThis.performance.now.bind(globalThis.performance)
|
|
307
|
+
};
|
|
308
|
+
globalThis.Date = createVirtualDate(clock);
|
|
309
|
+
globalThis.setTimeout = (cb, delay, ...args) => clock.setTimeout(cb, delay ?? 0, ...args);
|
|
310
|
+
globalThis.clearTimeout = (id) => clock.clearTimeout(id);
|
|
311
|
+
globalThis.setInterval = (cb, delay, ...args) => clock.setInterval(cb, delay, ...args);
|
|
312
|
+
globalThis.clearInterval = (id) => clock.clearInterval(id);
|
|
313
|
+
if (typeof globalThis.setImmediate !== "undefined") {
|
|
314
|
+
globalThis.setImmediate = (cb, ...args) => clock.setImmediate(cb, ...args);
|
|
315
|
+
globalThis.clearImmediate = (id) => clock.clearImmediate(id);
|
|
316
|
+
}
|
|
317
|
+
if (opts?.patchNextTick !== false && globalThis.process && typeof globalThis.process.nextTick === "function") {
|
|
318
|
+
globalThis.process.nextTick = (cb, ...args) => clock.nextTick(cb, ...args);
|
|
319
|
+
}
|
|
320
|
+
globalThis.performance.now = () => clock.now();
|
|
321
|
+
function uninstall() {
|
|
322
|
+
globalThis.Date = originals.Date;
|
|
323
|
+
globalThis.setTimeout = originals.setTimeout;
|
|
324
|
+
globalThis.clearTimeout = originals.clearTimeout;
|
|
325
|
+
globalThis.setInterval = originals.setInterval;
|
|
326
|
+
globalThis.clearInterval = originals.clearInterval;
|
|
327
|
+
if (originals.setImmediate) globalThis.setImmediate = originals.setImmediate;
|
|
328
|
+
if (originals.clearImmediate) globalThis.clearImmediate = originals.clearImmediate;
|
|
329
|
+
if (originals.nextTick && globalThis.process) globalThis.process.nextTick = originals.nextTick;
|
|
330
|
+
globalThis.performance.now = originals.performanceNow;
|
|
331
|
+
}
|
|
332
|
+
return { clock, uninstall };
|
|
333
|
+
}
|
|
334
|
+
export {
|
|
335
|
+
MinHeap,
|
|
336
|
+
VirtualClock,
|
|
337
|
+
createVirtualDate,
|
|
338
|
+
install
|
|
339
|
+
};
|
|
340
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/MinHeap.ts","../src/VirtualClock.ts","../src/VirtualDate.ts","../src/install.ts"],"sourcesContent":["/**\n * Generic min-heap priority queue.\n * O(log n) push/pop, used as the timer scheduling backbone.\n */\nexport class MinHeap<T> {\n private heap: T[] = [];\n\n constructor(private readonly comparator: (a: T, b: T) => number) {}\n\n get size(): number {\n return this.heap.length;\n }\n\n peek(): T | undefined {\n return this.heap[0];\n }\n\n push(value: T): void {\n this.heap.push(value);\n this.bubbleUp(this.heap.length - 1);\n }\n\n pop(): T | undefined {\n const n = this.heap.length;\n if (n === 0) return undefined;\n const top = this.heap[0];\n const last = this.heap.pop()!;\n if (this.heap.length > 0) {\n this.heap[0] = last;\n this.sinkDown(0);\n }\n return top;\n }\n\n /** Remove first entry matching predicate. Returns true if found. */\n remove(predicate: (v: T) => boolean): boolean {\n const idx = this.heap.findIndex(predicate);\n if (idx === -1) return false;\n const last = this.heap.pop()!;\n if (idx < this.heap.length) {\n this.heap[idx] = last;\n this.bubbleUp(idx);\n this.sinkDown(idx);\n }\n return true;\n }\n\n toArray(): T[] {\n return [...this.heap];\n }\n\n private bubbleUp(i: number): void {\n while (i > 0) {\n const parent = (i - 1) >> 1;\n if (this.comparator(this.heap[i], this.heap[parent]) >= 0) break;\n [this.heap[i], this.heap[parent]] = [this.heap[parent], this.heap[i]];\n i = parent;\n }\n }\n\n private sinkDown(i: number): void {\n const n = this.heap.length;\n while (true) {\n let smallest = i;\n const left = 2 * i + 1;\n const right = 2 * i + 2;\n if (left < n && this.comparator(this.heap[left], this.heap[smallest]) < 0)\n smallest = left;\n if (right < n && this.comparator(this.heap[right], this.heap[smallest]) < 0)\n smallest = right;\n if (smallest === i) break;\n [this.heap[i], this.heap[smallest]] = [this.heap[smallest], this.heap[i]];\n i = smallest;\n }\n }\n}\n","import { MinHeap } from './MinHeap.js';\n\n/** Internal timer entry stored in the heap. */\nexport interface TimerEntry {\n id: number;\n callback: (...args: unknown[]) => void | Promise<void>;\n scheduledTime: number;\n interval?: number;\n args: unknown[];\n}\n\n/**\n * A manually-controllable virtual clock.\n *\n * Replaces all time primitives so that `advance(duration)` fires timers\n * deterministically, in scheduled-time order. Timers that schedule new\n * timers within the same advance window ARE picked up and executed in\n * the correct order (the heap is re-checked after every callback).\n */\nexport class VirtualClock {\n private _now: number;\n private _skew: number = 0;\n private _nextId = 1;\n private _frozen = false;\n private _timers: MinHeap<TimerEntry>;\n private readonly _cancelledIds = new Set<number>();\n\n private readonly _virtualNextTickQueue: Array<(...args: unknown[]) => void> = [];\n private readonly _virtualImmediateQueue: Array<{ id: number; callback: (...args: unknown[]) => void | Promise<void>; args: unknown[]; }> = [];\n private _nextImmediateId = 1;\n private readonly _cancelledImmediates = new Set<number>();\n\n /**\n * Optional hook called after each timer fires (and after microtask flush).\n * The clock attaches the scheduler here so that advancing time drives I/O.\n */\n onTick?: (time: number) => Promise<void>;\n\n constructor(startTime: number = 0) {\n this._now = startTime;\n this._timers = new MinHeap<TimerEntry>((a, b) =>\n a.scheduledTime !== b.scheduledTime\n ? a.scheduledTime - b.scheduledTime\n : a.id - b.id, // FIFO tie-break\n );\n }\n\n // queries\n\n now(): number {\n return this._now + this._skew;\n }\n\n pending(): Array<{ id: number; scheduledTime: number }> {\n return this._timers\n .toArray()\n .map((t) => ({ id: t.id, scheduledTime: t.scheduledTime }))\n .sort((a, b) => a.scheduledTime - b.scheduledTime);\n }\n\n // time control\n\n /**\n * Advance the clock by `duration` ms, firing every timer whose\n * scheduled time falls within `[now, now + duration]` in order.\n */\n async advance(duration: number): Promise<void> {\n await this.advanceTo(this._now + duration);\n }\n\n /**\n * Jump to an absolute virtual timestamp, draining timers along the way.\n */\n async advanceTo(timestamp: number): Promise<void> {\n if (this._frozen) return;\n if (timestamp < this._now) return; // never go backward\n\n while (this._timers.size > 0) {\n const next = this._timers.peek()!;\n if (next.scheduledTime > timestamp) break;\n\n // If cancelled while it was in queue\n if (this._cancelledIds.has(next.id)) {\n this._timers.pop();\n this._cancelledIds.delete(next.id);\n continue;\n }\n\n // Pop right before execution\n this._timers.pop();\n if (this._cancelledIds.has(next.id)) {\n this._cancelledIds.delete(next.id);\n continue;\n }\n\n this._now = next.scheduledTime;\n\n try {\n // Call synchronously first so that process.nextTick callbacks\n // registered inside the timer run before native Promise microtasks.\n // If the callback is async, await its returned Promise afterward.\n const maybePromise = next.callback(...next.args);\n await this._flushMicrotasks();\n if (maybePromise instanceof Promise) await maybePromise;\n } catch (err) {\n throw new Error(`VirtualClock timer callback failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });\n }\n\n await this._flushMicrotasks();\n\n // Call onTick hook so the scheduler can run I/O completions for this tick\n if (this.onTick) {\n await this.onTick(this._now + this._skew);\n await this._flushMicrotasks();\n }\n\n // If this was an interval AND not cancelled during the callback or microtasks,\n // reschedule for the next tick.\n if (next.interval !== undefined && !this._cancelledIds.has(next.id)) {\n this._timers.push({\n ...next,\n scheduledTime: next.scheduledTime + next.interval,\n });\n }\n this._cancelledIds.delete(next.id);\n\n await this._flushImmediates();\n }\n this._now = timestamp;\n\n // End of advance flush\n await this._flushMicrotasks();\n if (this.onTick) {\n await this.onTick(this._now + this._skew);\n await this._flushMicrotasks();\n }\n await this._flushImmediates();\n }\n\n private async _flushMicrotasks(): Promise<void> {\n let draining = true;\n while (draining) {\n // Phase 1: drain the virtual nextTick queue synchronously (nextTick runs before native microtasks)\n while (this._virtualNextTickQueue.length > 0) {\n const cb = this._virtualNextTickQueue.shift()!;\n try {\n cb();\n } catch (err) {\n throw new Error(`VirtualClock nextTick failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });\n }\n }\n // Phase 2: one V8 microtask checkpoint (lets native Promise chains resolve)\n await Promise.resolve();\n // Phase 3: if new nextTick callbacks were enqueued by those microtasks, loop again;\n // otherwise we're stable.\n if (this._virtualNextTickQueue.length === 0) {\n draining = false;\n }\n }\n }\n\n private async _flushImmediates(): Promise<void> {\n const immediates = [...this._virtualImmediateQueue];\n this._virtualImmediateQueue.length = 0;\n\n for (const imm of immediates) {\n if (this._cancelledImmediates.has(imm.id)) {\n this._cancelledImmediates.delete(imm.id);\n continue;\n }\n try {\n await Promise.resolve(imm.callback(...imm.args));\n } catch (err) {\n throw new Error(`VirtualClock immediate failed: ${err instanceof Error ? err.message : String(err)}`, { cause: err });\n }\n await this._flushMicrotasks();\n this._cancelledImmediates.delete(imm.id);\n }\n }\n\n freeze(): void {\n this._frozen = true;\n }\n\n unfreeze(): void {\n this._frozen = false;\n }\n\n /**\n * Apply a clock skew offset (ms). Does NOT fire timers.\n * `now()` returns `_now + _skew`.\n */\n skew(amount: number): void {\n this._skew += amount;\n }\n\n // timer primitives\n\n setTimeout(\n callback: (...args: unknown[]) => void | Promise<void>,\n delay: number = 0,\n ...args: unknown[]\n ): number {\n const id = this._nextId++;\n this._timers.push({\n id,\n callback,\n scheduledTime: this._now + Math.max(0, delay),\n args,\n });\n return id;\n }\n\n setInterval(\n callback: (...args: unknown[]) => void | Promise<void>,\n delay: number,\n ...args: unknown[]\n ): number {\n const id = this._nextId++;\n this._timers.push({\n id,\n callback,\n scheduledTime: this._now + Math.max(0, delay),\n interval: Math.max(1, delay),\n args,\n });\n return id;\n }\n\n clearTimeout(id: number): void {\n this._cancelledIds.add(id);\n this._timers.remove((t) => t.id === id);\n }\n\n clearInterval(id: number): void {\n this._cancelledIds.add(id);\n this._timers.remove((t) => t.id === id);\n }\n\n setImmediate(callback: (...args: unknown[]) => void | Promise<void>, ...args: unknown[]): number {\n const id = this._nextImmediateId++;\n this._virtualImmediateQueue.push({ id, callback, args });\n return id;\n }\n\n clearImmediate(id: number): void {\n this._cancelledImmediates.add(id);\n }\n\n nextTick(callback: (...args: unknown[]) => void, ...args: unknown[]): void {\n this._virtualNextTickQueue.push(() => callback(...args));\n }\n\n // lifecycle\n\n reset(startTime: number = 0): void {\n this._now = startTime;\n this._skew = 0;\n this._nextId = 1;\n this._frozen = false;\n this._timers = new MinHeap<TimerEntry>((a, b) =>\n a.scheduledTime !== b.scheduledTime\n ? a.scheduledTime - b.scheduledTime\n : a.id - b.id,\n );\n this._cancelledIds.clear();\n this._virtualNextTickQueue.length = 0;\n this._virtualImmediateQueue.length = 0;\n this._cancelledImmediates.clear();\n this._nextImmediateId = 1;\n this.onTick = undefined;\n }\n}\n","import type { VirtualClock } from './VirtualClock.js';\n\n/**\n * Build a `DateConstructor` whose `now()` and zero-arg `new Date()`\n * read from the supplied VirtualClock.\n */\nexport function createVirtualDate(clock: VirtualClock): DateConstructor {\n const OrigDate = Date;\n\n class VirtualDate extends OrigDate {\n constructor(...args: unknown[]) {\n if (args.length === 0) {\n super(clock.now());\n } else if (args.length === 1) {\n super(args[0] as string | number);\n } else {\n // Multi-arg (year, month, …): delegate to native Date for\n // local-time semantics, then feed the resulting timestamp to super.\n const tmp = new OrigDate(\n args[0] as number,\n (args[1] as number) ?? 0,\n (args[2] as number) ?? 1,\n (args[3] as number) ?? 0,\n (args[4] as number) ?? 0,\n (args[5] as number) ?? 0,\n (args[6] as number) ?? 0,\n );\n super(tmp.getTime());\n }\n }\n\n static override now(): number {\n return clock.now();\n }\n\n static [Symbol.hasInstance](instance: unknown): boolean {\n return instance instanceof OrigDate;\n }\n }\n\n return VirtualDate as unknown as DateConstructor;\n}\n","import { VirtualClock } from './VirtualClock.js';\nimport { createVirtualDate } from './VirtualDate.js';\n\nexport interface ClockInstallResult {\n clock: VirtualClock;\n uninstall: () => void;\n}\n\nexport interface ClockInstallOptions {\n /**\n * If true (the default), patch `process.nextTick` to route through the virtual clock.\n * This ensures deterministic ordering of nextTick callbacks relative to timers.\n *\n * Set to false only when your scenario uses dynamic `import()`, undici, or other\n * Node.js internals that rely on `process.nextTick` for initialization, and those\n * operations run outside of any `advance()` call (which would otherwise drain the\n * virtual nextTick queue via `_flushMicrotasks`).\n *\n * @default true\n */\n patchNextTick?: boolean;\n}\n\n/**\n * Patch all global time primitives to use a VirtualClock.\n *\n * Returns the clock instance and an `uninstall` function that restores\n * every original global.\n */\nexport function install(clockOrStart?: VirtualClock | number, opts?: ClockInstallOptions): ClockInstallResult {\n const clock =\n clockOrStart instanceof VirtualClock\n ? clockOrStart\n : new VirtualClock(clockOrStart);\n\n const originals = {\n Date: globalThis.Date,\n setTimeout: globalThis.setTimeout,\n clearTimeout: globalThis.clearTimeout,\n setInterval: globalThis.setInterval,\n clearInterval: globalThis.clearInterval,\n setImmediate: globalThis.setImmediate,\n clearImmediate: globalThis.clearImmediate,\n nextTick: globalThis.process?.nextTick,\n performanceNow: globalThis.performance.now.bind(globalThis.performance),\n };\n\n globalThis.Date = createVirtualDate(clock);\n\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment\n (globalThis as any).setTimeout = (\n cb: (...a: unknown[]) => void,\n delay?: number,\n ...args: unknown[]\n ) => clock.setTimeout(cb, delay ?? 0, ...args);\n\n (globalThis as any).clearTimeout = (id: number) => clock.clearTimeout(id);\n\n (globalThis as any).setInterval = (\n cb: (...a: unknown[]) => void,\n delay: number,\n ...args: unknown[]\n ) => clock.setInterval(cb, delay, ...args);\n\n (globalThis as any).clearInterval = (id: number) => clock.clearInterval(id);\n\n if (typeof globalThis.setImmediate !== 'undefined') {\n (globalThis as any).setImmediate = (cb: (...a: unknown[]) => void, ...args: unknown[]) => clock.setImmediate(cb, ...args);\n (globalThis as any).clearImmediate = (id: number) => clock.clearImmediate(id);\n }\n\n if (opts?.patchNextTick !== false && globalThis.process && typeof globalThis.process.nextTick === 'function') {\n globalThis.process.nextTick = (cb: (...a: unknown[]) => void, ...args: unknown[]) => clock.nextTick(cb, ...args);\n }\n\n globalThis.performance.now = () => clock.now();\n\n function uninstall(): void {\n globalThis.Date = originals.Date;\n globalThis.setTimeout = originals.setTimeout;\n globalThis.clearTimeout = originals.clearTimeout;\n globalThis.setInterval = originals.setInterval;\n globalThis.clearInterval = originals.clearInterval;\n if (originals.setImmediate) globalThis.setImmediate = originals.setImmediate;\n if (originals.clearImmediate) globalThis.clearImmediate = originals.clearImmediate;\n if (originals.nextTick && globalThis.process) globalThis.process.nextTick = originals.nextTick;\n globalThis.performance.now = originals.performanceNow;\n }\n\n return { clock, uninstall };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { VirtualClock } from './VirtualClock.js';
|
|
2
|
+
export interface ClockInstallResult {
|
|
3
|
+
clock: VirtualClock;
|
|
4
|
+
uninstall: () => void;
|
|
5
|
+
}
|
|
6
|
+
export interface ClockInstallOptions {
|
|
7
|
+
/**
|
|
8
|
+
* If true (the default), patch `process.nextTick` to route through the virtual clock.
|
|
9
|
+
* This ensures deterministic ordering of nextTick callbacks relative to timers.
|
|
10
|
+
*
|
|
11
|
+
* Set to false only when your scenario uses dynamic `import()`, undici, or other
|
|
12
|
+
* Node.js internals that rely on `process.nextTick` for initialization, and those
|
|
13
|
+
* operations run outside of any `advance()` call (which would otherwise drain the
|
|
14
|
+
* virtual nextTick queue via `_flushMicrotasks`).
|
|
15
|
+
*
|
|
16
|
+
* @default true
|
|
17
|
+
*/
|
|
18
|
+
patchNextTick?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Patch all global time primitives to use a VirtualClock.
|
|
22
|
+
*
|
|
23
|
+
* Returns the clock instance and an `uninstall` function that restores
|
|
24
|
+
* every original global.
|
|
25
|
+
*/
|
|
26
|
+
export declare function install(clockOrStart?: VirtualClock | number, opts?: ClockInstallOptions): ClockInstallResult;
|
|
27
|
+
//# sourceMappingURL=install.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"install.d.ts","sourceRoot":"","sources":["../src/install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAGjD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,YAAY,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;OAUG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,YAAY,CAAC,EAAE,YAAY,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,mBAAmB,GAAG,kBAAkB,CA6D5G"}
|
package/package.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crashlab/clock",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.cjs",
|
|
7
|
+
"module": "dist/index.js",
|
|
8
|
+
"types": "dist/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./dist/index.d.ts",
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"require": "./dist/index.cjs"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup && tsc --build tsconfig.json --force",
|
|
21
|
+
"clean": "rimraf dist",
|
|
22
|
+
"prepack": "npm run build",
|
|
23
|
+
"build:pkg": "tsup && tsc --build tsconfig.json --force"
|
|
24
|
+
}
|
|
25
|
+
}
|