@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 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":[]}
@@ -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":";AAIO,IAAM,UAAN,MAAiB;AAAA,EAGtB,YAA6B,YAAoC;AAApC;AAAA,EAAqC;AAAA,EAF1D,OAAY,CAAC;AAAA,EAIrB,IAAI,OAAe;AACjB,WAAO,KAAK,KAAK;AAAA,EACnB;AAAA,EAEA,OAAsB;AACpB,WAAO,KAAK,KAAK,CAAC;AAAA,EACpB;AAAA,EAEA,KAAK,OAAgB;AACnB,SAAK,KAAK,KAAK,KAAK;AACpB,SAAK,SAAS,KAAK,KAAK,SAAS,CAAC;AAAA,EACpC;AAAA,EAEA,MAAqB;AACnB,UAAM,IAAI,KAAK,KAAK;AACpB,QAAI,MAAM,EAAG,QAAO;AACpB,UAAM,MAAM,KAAK,KAAK,CAAC;AACvB,UAAM,OAAO,KAAK,KAAK,IAAI;AAC3B,QAAI,KAAK,KAAK,SAAS,GAAG;AACxB,WAAK,KAAK,CAAC,IAAI;AACf,WAAK,SAAS,CAAC;AAAA,IACjB;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,OAAO,WAAuC;AAC5C,UAAM,MAAM,KAAK,KAAK,UAAU,SAAS;AACzC,QAAI,QAAQ,GAAI,QAAO;AACvB,UAAM,OAAO,KAAK,KAAK,IAAI;AAC3B,QAAI,MAAM,KAAK,KAAK,QAAQ;AAC1B,WAAK,KAAK,GAAG,IAAI;AACjB,WAAK,SAAS,GAAG;AACjB,WAAK,SAAS,GAAG;AAAA,IACnB;AACA,WAAO;AAAA,EACT;AAAA,EAEA,UAAe;AACb,WAAO,CAAC,GAAG,KAAK,IAAI;AAAA,EACtB;AAAA,EAEQ,SAAS,GAAiB;AAChC,WAAO,IAAI,GAAG;AACZ,YAAM,SAAU,IAAI,KAAM;AAC1B,UAAI,KAAK,WAAW,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,MAAM,CAAC,KAAK,EAAG;AAC3D,OAAC,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,MAAM,CAAC,IAAI,CAAC,KAAK,KAAK,MAAM,GAAG,KAAK,KAAK,CAAC,CAAC;AACpE,UAAI;AAAA,IACN;AAAA,EACF;AAAA,EAEQ,SAAS,GAAiB;AAChC,UAAM,IAAI,KAAK,KAAK;AACpB,WAAO,MAAM;AACX,UAAI,WAAW;AACf,YAAM,OAAO,IAAI,IAAI;AACrB,YAAM,QAAQ,IAAI,IAAI;AACtB,UAAI,OAAO,KAAK,KAAK,WAAW,KAAK,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,CAAC,IAAI;AACtE,mBAAW;AACb,UAAI,QAAQ,KAAK,KAAK,WAAW,KAAK,KAAK,KAAK,GAAG,KAAK,KAAK,QAAQ,CAAC,IAAI;AACxE,mBAAW;AACb,UAAI,aAAa,EAAG;AACpB,OAAC,KAAK,KAAK,CAAC,GAAG,KAAK,KAAK,QAAQ,CAAC,IAAI,CAAC,KAAK,KAAK,QAAQ,GAAG,KAAK,KAAK,CAAC,CAAC;AACxE,UAAI;AAAA,IACN;AAAA,EACF;AACF;;;ACxDO,IAAM,eAAN,MAAmB;AAAA,EAChB;AAAA,EACA,QAAgB;AAAA,EAChB,UAAU;AAAA,EACV,UAAU;AAAA,EACV;AAAA,EACS,gBAAgB,oBAAI,IAAY;AAAA,EAEhC,wBAA6D,CAAC;AAAA,EAC9D,yBAA0H,CAAC;AAAA,EACpI,mBAAmB;AAAA,EACV,uBAAuB,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxD;AAAA,EAEA,YAAY,YAAoB,GAAG;AACjC,SAAK,OAAO;AACZ,SAAK,UAAU,IAAI;AAAA,MAAoB,CAAC,GAAG,MACzC,EAAE,kBAAkB,EAAE,gBAClB,EAAE,gBAAgB,EAAE,gBACpB,EAAE,KAAK,EAAE;AAAA;AAAA,IACf;AAAA,EACF;AAAA;AAAA,EAIA,MAAc;AACZ,WAAO,KAAK,OAAO,KAAK;AAAA,EAC1B;AAAA,EAEA,UAAwD;AACtD,WAAO,KAAK,QACT,QAAQ,EACR,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,eAAe,EAAE,cAAc,EAAE,EACzD,KAAK,CAAC,GAAG,MAAM,EAAE,gBAAgB,EAAE,aAAa;AAAA,EACrD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,QAAQ,UAAiC;AAC7C,UAAM,KAAK,UAAU,KAAK,OAAO,QAAQ;AAAA,EAC3C;AAAA;AAAA;AAAA;AAAA,EAKA,MAAM,UAAU,WAAkC;AAChD,QAAI,KAAK,QAAS;AAClB,QAAI,YAAY,KAAK,KAAM;AAE3B,WAAO,KAAK,QAAQ,OAAO,GAAG;AAC5B,YAAM,OAAO,KAAK,QAAQ,KAAK;AAC/B,UAAI,KAAK,gBAAgB,UAAW;AAGpC,UAAI,KAAK,cAAc,IAAI,KAAK,EAAE,GAAG;AACnC,aAAK,QAAQ,IAAI;AACjB,aAAK,cAAc,OAAO,KAAK,EAAE;AACjC;AAAA,MACF;AAGA,WAAK,QAAQ,IAAI;AACjB,UAAI,KAAK,cAAc,IAAI,KAAK,EAAE,GAAG;AAClC,aAAK,cAAc,OAAO,KAAK,EAAE;AACjC;AAAA,MACH;AAEA,WAAK,OAAO,KAAK;AAEjB,UAAI;AAIF,cAAM,eAAe,KAAK,SAAS,GAAG,KAAK,IAAI;AAC/C,cAAM,KAAK,iBAAiB;AAC5B,YAAI,wBAAwB,QAAS,OAAM;AAAA,MAC7C,SAAS,KAAK;AACZ,cAAM,IAAI,MAAM,uCAAuC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,MAC3H;AAEA,YAAM,KAAK,iBAAiB;AAG5B,UAAI,KAAK,QAAQ;AACf,cAAM,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK;AACxC,cAAM,KAAK,iBAAiB;AAAA,MAC9B;AAIA,UAAI,KAAK,aAAa,UAAa,CAAC,KAAK,cAAc,IAAI,KAAK,EAAE,GAAG;AACnE,aAAK,QAAQ,KAAK;AAAA,UAChB,GAAG;AAAA,UACH,eAAe,KAAK,gBAAgB,KAAK;AAAA,QAC3C,CAAC;AAAA,MACH;AACA,WAAK,cAAc,OAAO,KAAK,EAAE;AAEjC,YAAM,KAAK,iBAAiB;AAAA,IAC9B;AACA,SAAK,OAAO;AAGZ,UAAM,KAAK,iBAAiB;AAC5B,QAAI,KAAK,QAAQ;AACf,YAAM,KAAK,OAAO,KAAK,OAAO,KAAK,KAAK;AACxC,YAAM,KAAK,iBAAiB;AAAA,IAC9B;AACA,UAAM,KAAK,iBAAiB;AAAA,EAC9B;AAAA,EAEA,MAAc,mBAAkC;AAC9C,QAAI,WAAW;AACf,WAAO,UAAU;AAEf,aAAO,KAAK,sBAAsB,SAAS,GAAG;AAC5C,cAAM,KAAK,KAAK,sBAAsB,MAAM;AAC5C,YAAI;AACF,aAAG;AAAA,QACL,SAAS,KAAK;AACZ,gBAAM,IAAI,MAAM,iCAAiC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,QACrH;AAAA,MACF;AAEA,YAAM,QAAQ,QAAQ;AAGtB,UAAI,KAAK,sBAAsB,WAAW,GAAG;AAC3C,mBAAW;AAAA,MACb;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,mBAAkC;AAC9C,UAAM,aAAa,CAAC,GAAG,KAAK,sBAAsB;AAClD,SAAK,uBAAuB,SAAS;AAErC,eAAW,OAAO,YAAY;AAC5B,UAAI,KAAK,qBAAqB,IAAI,IAAI,EAAE,GAAG;AACzC,aAAK,qBAAqB,OAAO,IAAI,EAAE;AACvC;AAAA,MACF;AACA,UAAI;AACF,cAAM,QAAQ,QAAQ,IAAI,SAAS,GAAG,IAAI,IAAI,CAAC;AAAA,MACjD,SAAS,KAAK;AACZ,cAAM,IAAI,MAAM,kCAAkC,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC;AAAA,MACtH;AACA,YAAM,KAAK,iBAAiB;AAC5B,WAAK,qBAAqB,OAAO,IAAI,EAAE;AAAA,IACzC;AAAA,EACF;AAAA,EAEA,SAAe;AACb,SAAK,UAAU;AAAA,EACjB;AAAA,EAEA,WAAiB;AACf,SAAK,UAAU;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,KAAK,QAAsB;AACzB,SAAK,SAAS;AAAA,EAChB;AAAA;AAAA,EAIA,WACE,UACA,QAAgB,MACb,MACK;AACR,UAAM,KAAK,KAAK;AAChB,SAAK,QAAQ,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,eAAe,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK;AAAA,MAC5C;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,YACE,UACA,UACG,MACK;AACR,UAAM,KAAK,KAAK;AAChB,SAAK,QAAQ,KAAK;AAAA,MAChB;AAAA,MACA;AAAA,MACA,eAAe,KAAK,OAAO,KAAK,IAAI,GAAG,KAAK;AAAA,MAC5C,UAAU,KAAK,IAAI,GAAG,KAAK;AAAA,MAC3B;AAAA,IACF,CAAC;AACD,WAAO;AAAA,EACT;AAAA,EAEA,aAAa,IAAkB;AAC7B,SAAK,cAAc,IAAI,EAAE;AACzB,SAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EACxC;AAAA,EAEA,cAAc,IAAkB;AAC9B,SAAK,cAAc,IAAI,EAAE;AACzB,SAAK,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AAAA,EACxC;AAAA,EAEA,aAAa,aAA2D,MAAyB;AAC/F,UAAM,KAAK,KAAK;AAChB,SAAK,uBAAuB,KAAK,EAAE,IAAI,UAAU,KAAK,CAAC;AACvD,WAAO;AAAA,EACT;AAAA,EAEA,eAAe,IAAkB;AAC/B,SAAK,qBAAqB,IAAI,EAAE;AAAA,EAClC;AAAA,EAEA,SAAS,aAA2C,MAAuB;AACzE,SAAK,sBAAsB,KAAK,MAAM,SAAS,GAAG,IAAI,CAAC;AAAA,EACzD;AAAA;AAAA,EAIA,MAAM,YAAoB,GAAS;AACjC,SAAK,OAAO;AACZ,SAAK,QAAQ;AACb,SAAK,UAAU;AACf,SAAK,UAAU;AACf,SAAK,UAAU,IAAI;AAAA,MAAoB,CAAC,GAAG,MACzC,EAAE,kBAAkB,EAAE,gBAClB,EAAE,gBAAgB,EAAE,gBACpB,EAAE,KAAK,EAAE;AAAA,IACf;AACA,SAAK,cAAc,MAAM;AACzB,SAAK,sBAAsB,SAAS;AACpC,SAAK,uBAAuB,SAAS;AACrC,SAAK,qBAAqB,MAAM;AAChC,SAAK,mBAAmB;AACxB,SAAK,SAAS;AAAA,EAChB;AACF;;;AC1QO,SAAS,kBAAkB,OAAsC;AACtE,QAAM,WAAW;AAAA,EAEjB,MAAM,oBAAoB,SAAS;AAAA,IACjC,eAAe,MAAiB;AAC9B,UAAI,KAAK,WAAW,GAAG;AACrB,cAAM,MAAM,IAAI,CAAC;AAAA,MACnB,WAAW,KAAK,WAAW,GAAG;AAC5B,cAAM,KAAK,CAAC,CAAoB;AAAA,MAClC,OAAO;AAGL,cAAM,MAAM,IAAI;AAAA,UACd,KAAK,CAAC;AAAA,UACL,KAAK,CAAC,KAAgB;AAAA,UACtB,KAAK,CAAC,KAAgB;AAAA,UACtB,KAAK,CAAC,KAAgB;AAAA,UACtB,KAAK,CAAC,KAAgB;AAAA,UACtB,KAAK,CAAC,KAAgB;AAAA,UACtB,KAAK,CAAC,KAAgB;AAAA,QACzB;AACA,cAAM,IAAI,QAAQ,CAAC;AAAA,MACrB;AAAA,IACF;AAAA,IAEA,OAAgB,MAAc;AAC5B,aAAO,MAAM,IAAI;AAAA,IACnB;AAAA,IAEA,QAAQ,OAAO,WAAW,EAAE,UAA4B;AACtD,aAAO,oBAAoB;AAAA,IAC7B;AAAA,EACF;AAEA,SAAO;AACT;;;ACZO,SAAS,QAAQ,cAAsC,MAAgD;AAC5G,QAAM,QACJ,wBAAwB,eACpB,eACA,IAAI,aAAa,YAAY;AAEnC,QAAM,YAAY;AAAA,IAChB,MAAM,WAAW;AAAA,IACjB,YAAY,WAAW;AAAA,IACvB,cAAc,WAAW;AAAA,IACzB,aAAa,WAAW;AAAA,IACxB,eAAe,WAAW;AAAA,IAC1B,cAAc,WAAW;AAAA,IACzB,gBAAgB,WAAW;AAAA,IAC3B,UAAU,WAAW,SAAS;AAAA,IAC9B,gBAAgB,WAAW,YAAY,IAAI,KAAK,WAAW,WAAW;AAAA,EACxE;AAEA,aAAW,OAAO,kBAAkB,KAAK;AAGzC,EAAC,WAAmB,aAAa,CAC/B,IACA,UACG,SACA,MAAM,WAAW,IAAI,SAAS,GAAG,GAAG,IAAI;AAE7C,EAAC,WAAmB,eAAe,CAAC,OAAe,MAAM,aAAa,EAAE;AAExE,EAAC,WAAmB,cAAc,CAChC,IACA,UACG,SACA,MAAM,YAAY,IAAI,OAAO,GAAG,IAAI;AAEzC,EAAC,WAAmB,gBAAgB,CAAC,OAAe,MAAM,cAAc,EAAE;AAE1E,MAAI,OAAO,WAAW,iBAAiB,aAAa;AAClD,IAAC,WAAmB,eAAe,CAAC,OAAkC,SAAoB,MAAM,aAAa,IAAI,GAAG,IAAI;AACxH,IAAC,WAAmB,iBAAiB,CAAC,OAAe,MAAM,eAAe,EAAE;AAAA,EAC9E;AAEA,MAAI,MAAM,kBAAkB,SAAS,WAAW,WAAW,OAAO,WAAW,QAAQ,aAAa,YAAY;AAC5G,eAAW,QAAQ,WAAW,CAAC,OAAkC,SAAoB,MAAM,SAAS,IAAI,GAAG,IAAI;AAAA,EACjH;AAEA,aAAW,YAAY,MAAM,MAAM,MAAM,IAAI;AAE7C,WAAS,YAAkB;AACzB,eAAW,OAAO,UAAU;AAC5B,eAAW,aAAa,UAAU;AAClC,eAAW,eAAe,UAAU;AACpC,eAAW,cAAc,UAAU;AACnC,eAAW,gBAAgB,UAAU;AACrC,QAAI,UAAU,aAAc,YAAW,eAAe,UAAU;AAChE,QAAI,UAAU,eAAgB,YAAW,iBAAiB,UAAU;AACpE,QAAI,UAAU,YAAY,WAAW,QAAS,YAAW,QAAQ,WAAW,UAAU;AACtF,eAAW,YAAY,MAAM,UAAU;AAAA,EACzC;AAEA,SAAO,EAAE,OAAO,UAAU;AAC5B;","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
+ }