time-machine-js 1.0.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 +83 -0
- package/dist/index.d.mts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +98 -0
- package/dist/index.mjs +68 -0
- package/package.json +38 -0
package/README.md
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
# time-machine-js
|
|
2
|
+
|
|
3
|
+
A zero-dependency, framework-agnostic utility that monkey-patches `Date.now()` globally to simulate a different point in time. Works identically in browser and Node.js environments.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Zero dependencies**: No external dependencies required.
|
|
8
|
+
- **Framework agnostic**: Use it with React, Vue, Vanilla JS, or Node.js.
|
|
9
|
+
- **Global patch**: Affects `Date.now()` globally, ensuring `new Date()` and other time-dependent utilities work as expected.
|
|
10
|
+
- **Dual Exports**: Ships with both ESM and CJS support.
|
|
11
|
+
- **TypeScript**: Fully typed with included definitions.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install time-machine-js
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## API
|
|
20
|
+
|
|
21
|
+
The core API consists of four main functions:
|
|
22
|
+
|
|
23
|
+
### `travel(timestamp: number, mode: 'flowing' | 'frozen'): void`
|
|
24
|
+
Moves the environment time to the specified Unix timestamp.
|
|
25
|
+
- `flowing`: Time continues to advance normally from the specified point.
|
|
26
|
+
- `frozen`: Time remains fixed at the specify timestamp.
|
|
27
|
+
|
|
28
|
+
### `returnToPresent(): void`
|
|
29
|
+
Restores the original `Date.now` and clears internal state.
|
|
30
|
+
|
|
31
|
+
### `getOffset(): number | null`
|
|
32
|
+
Returns the current offset in ms (flowing mode) or the frozen timestamp (frozen mode). Returns `null` if inactive.
|
|
33
|
+
|
|
34
|
+
### `isActive(): boolean`
|
|
35
|
+
Returns `true` if the time machine is currently active.
|
|
36
|
+
|
|
37
|
+
## Persistence (Browser Only)
|
|
38
|
+
|
|
39
|
+
For environments with `localStorage`, you can manually save and restore the time machine state across page reloads:
|
|
40
|
+
|
|
41
|
+
### `save(storageKey?: string): void`
|
|
42
|
+
Writes the current mode and offset/timestamp to `localStorage`. Defaults to `__timeMachine__`.
|
|
43
|
+
|
|
44
|
+
### `restore(storageKey?: string): boolean`
|
|
45
|
+
Reads the state from `localStorage` and activates it. Returns `true` if a state was found and applied.
|
|
46
|
+
|
|
47
|
+
## Usage
|
|
48
|
+
|
|
49
|
+
### Basic Example
|
|
50
|
+
```javascript
|
|
51
|
+
import { travel, returnToPresent } from 'time-machine-js';
|
|
52
|
+
|
|
53
|
+
// Travel to Jan 1st, 2025
|
|
54
|
+
travel(new Date('2025-01-01').getTime(), 'frozen');
|
|
55
|
+
console.log(new Date().toISOString()); // 2025-01-01T00:00:00.000Z
|
|
56
|
+
|
|
57
|
+
returnToPresent();
|
|
58
|
+
console.log(new Date().getFullYear()); // Current year
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Persistence
|
|
62
|
+
```javascript
|
|
63
|
+
import { restore, save, travel } from 'time-machine-js';
|
|
64
|
+
|
|
65
|
+
// On app initialization
|
|
66
|
+
restore();
|
|
67
|
+
|
|
68
|
+
// Later
|
|
69
|
+
travel(Date.now() + 3600000, 'flowing');
|
|
70
|
+
save();
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Caveats
|
|
74
|
+
|
|
75
|
+
### `new Date()`
|
|
76
|
+
In most modern JavaScript environments (V8, Node.js), the `Date` constructor uses `Date.now()` internally to determine the current time. This means monkey-patching `Date.now()` is sufficient to "travel" for both `Date.now()` and `new Date()`.
|
|
77
|
+
|
|
78
|
+
### `performance.now()`
|
|
79
|
+
`performance.now()` is **not** patched by this utility, as it represents a monotonic clock used for high-resolution profiling rather than wall-clock time.
|
|
80
|
+
|
|
81
|
+
## License
|
|
82
|
+
|
|
83
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid modes for the time machine.
|
|
3
|
+
* - 'flowing': Time continues to advance normally but from a specified offset.
|
|
4
|
+
* - 'frozen': Time remains fixed at the specify timestamp.
|
|
5
|
+
*/
|
|
6
|
+
type TimeMachineMode = 'flowing' | 'frozen';
|
|
7
|
+
/**
|
|
8
|
+
* Monkey-patches Date.now() to simulate a different point in time.
|
|
9
|
+
*
|
|
10
|
+
* @param timestamp - The Unix timestamp in milliseconds to travel to.
|
|
11
|
+
* @param mode - The mode of travel: 'flowing' or 'frozen'.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* travel(new Date('2025-01-01').getTime(), 'frozen');
|
|
15
|
+
*/
|
|
16
|
+
declare function travel(timestamp: number, mode: TimeMachineMode): void;
|
|
17
|
+
/**
|
|
18
|
+
* Restores the original Date.now and clears the internal state.
|
|
19
|
+
*/
|
|
20
|
+
declare function returnToPresent(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the current offset in milliseconds when in 'flowing' mode,
|
|
23
|
+
* the frozen timestamp when in 'frozen' mode, or null if inactive.
|
|
24
|
+
*
|
|
25
|
+
* @returns The offset, frozen timestamp, or null.
|
|
26
|
+
*/
|
|
27
|
+
declare function getOffset(): number | null;
|
|
28
|
+
/**
|
|
29
|
+
* Returns whether the time machine is currently active.
|
|
30
|
+
*
|
|
31
|
+
* @returns True if active, false otherwise.
|
|
32
|
+
*/
|
|
33
|
+
declare function isActive(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Saves the current time machine state to localStorage (browser only).
|
|
36
|
+
*
|
|
37
|
+
* @param storageKey - The key to use in localStorage. Defaults to '__timeMachine__'.
|
|
38
|
+
*/
|
|
39
|
+
declare function save(storageKey?: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Restores the time machine state from localStorage (browser only).
|
|
42
|
+
*
|
|
43
|
+
* @param storageKey - The key to search for in localStorage. Defaults to '__timeMachine__'.
|
|
44
|
+
* @returns True if state was found and restored, false otherwise.
|
|
45
|
+
*/
|
|
46
|
+
declare function restore(storageKey?: string): boolean;
|
|
47
|
+
|
|
48
|
+
export { type TimeMachineMode, getOffset, isActive, restore, returnToPresent, save, travel };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Valid modes for the time machine.
|
|
3
|
+
* - 'flowing': Time continues to advance normally but from a specified offset.
|
|
4
|
+
* - 'frozen': Time remains fixed at the specify timestamp.
|
|
5
|
+
*/
|
|
6
|
+
type TimeMachineMode = 'flowing' | 'frozen';
|
|
7
|
+
/**
|
|
8
|
+
* Monkey-patches Date.now() to simulate a different point in time.
|
|
9
|
+
*
|
|
10
|
+
* @param timestamp - The Unix timestamp in milliseconds to travel to.
|
|
11
|
+
* @param mode - The mode of travel: 'flowing' or 'frozen'.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* travel(new Date('2025-01-01').getTime(), 'frozen');
|
|
15
|
+
*/
|
|
16
|
+
declare function travel(timestamp: number, mode: TimeMachineMode): void;
|
|
17
|
+
/**
|
|
18
|
+
* Restores the original Date.now and clears the internal state.
|
|
19
|
+
*/
|
|
20
|
+
declare function returnToPresent(): void;
|
|
21
|
+
/**
|
|
22
|
+
* Returns the current offset in milliseconds when in 'flowing' mode,
|
|
23
|
+
* the frozen timestamp when in 'frozen' mode, or null if inactive.
|
|
24
|
+
*
|
|
25
|
+
* @returns The offset, frozen timestamp, or null.
|
|
26
|
+
*/
|
|
27
|
+
declare function getOffset(): number | null;
|
|
28
|
+
/**
|
|
29
|
+
* Returns whether the time machine is currently active.
|
|
30
|
+
*
|
|
31
|
+
* @returns True if active, false otherwise.
|
|
32
|
+
*/
|
|
33
|
+
declare function isActive(): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* Saves the current time machine state to localStorage (browser only).
|
|
36
|
+
*
|
|
37
|
+
* @param storageKey - The key to use in localStorage. Defaults to '__timeMachine__'.
|
|
38
|
+
*/
|
|
39
|
+
declare function save(storageKey?: string): void;
|
|
40
|
+
/**
|
|
41
|
+
* Restores the time machine state from localStorage (browser only).
|
|
42
|
+
*
|
|
43
|
+
* @param storageKey - The key to search for in localStorage. Defaults to '__timeMachine__'.
|
|
44
|
+
* @returns True if state was found and restored, false otherwise.
|
|
45
|
+
*/
|
|
46
|
+
declare function restore(storageKey?: string): boolean;
|
|
47
|
+
|
|
48
|
+
export { type TimeMachineMode, getOffset, isActive, restore, returnToPresent, save, travel };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
getOffset: () => getOffset,
|
|
24
|
+
isActive: () => isActive,
|
|
25
|
+
restore: () => restore,
|
|
26
|
+
returnToPresent: () => returnToPresent,
|
|
27
|
+
save: () => save,
|
|
28
|
+
travel: () => travel
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(index_exports);
|
|
31
|
+
var _realDateNow = Date.now.bind(Date);
|
|
32
|
+
var state = {
|
|
33
|
+
isActive: false,
|
|
34
|
+
mode: null,
|
|
35
|
+
offset: null,
|
|
36
|
+
frozenTimestamp: null
|
|
37
|
+
};
|
|
38
|
+
function travel(timestamp, mode) {
|
|
39
|
+
state.isActive = true;
|
|
40
|
+
state.mode = mode;
|
|
41
|
+
if (mode === "frozen") {
|
|
42
|
+
state.frozenTimestamp = timestamp;
|
|
43
|
+
state.offset = null;
|
|
44
|
+
Date.now = () => state.frozenTimestamp;
|
|
45
|
+
} else {
|
|
46
|
+
state.frozenTimestamp = null;
|
|
47
|
+
state.offset = timestamp - _realDateNow();
|
|
48
|
+
Date.now = () => _realDateNow() + state.offset;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function returnToPresent() {
|
|
52
|
+
state.isActive = false;
|
|
53
|
+
state.mode = null;
|
|
54
|
+
state.offset = null;
|
|
55
|
+
state.frozenTimestamp = null;
|
|
56
|
+
Date.now = _realDateNow;
|
|
57
|
+
}
|
|
58
|
+
function getOffset() {
|
|
59
|
+
if (!state.isActive) return null;
|
|
60
|
+
return state.mode === "frozen" ? state.frozenTimestamp : state.offset;
|
|
61
|
+
}
|
|
62
|
+
function isActive() {
|
|
63
|
+
return state.isActive;
|
|
64
|
+
}
|
|
65
|
+
function save(storageKey = "__timeMachine__") {
|
|
66
|
+
if (typeof localStorage === "undefined" || !state.isActive) return;
|
|
67
|
+
const savedState = {
|
|
68
|
+
mode: state.mode,
|
|
69
|
+
value: state.mode === "frozen" ? state.frozenTimestamp : state.offset
|
|
70
|
+
};
|
|
71
|
+
localStorage.setItem(storageKey, JSON.stringify(savedState));
|
|
72
|
+
}
|
|
73
|
+
function restore(storageKey = "__timeMachine__") {
|
|
74
|
+
if (typeof localStorage === "undefined") return false;
|
|
75
|
+
const data = localStorage.getItem(storageKey);
|
|
76
|
+
if (!data) return false;
|
|
77
|
+
try {
|
|
78
|
+
const savedState = JSON.parse(data);
|
|
79
|
+
if (savedState.mode === "frozen") {
|
|
80
|
+
travel(savedState.value, "frozen");
|
|
81
|
+
} else {
|
|
82
|
+
const targetTimestamp = _realDateNow() + savedState.value;
|
|
83
|
+
travel(targetTimestamp, "flowing");
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
} catch {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
91
|
+
0 && (module.exports = {
|
|
92
|
+
getOffset,
|
|
93
|
+
isActive,
|
|
94
|
+
restore,
|
|
95
|
+
returnToPresent,
|
|
96
|
+
save,
|
|
97
|
+
travel
|
|
98
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
var _realDateNow = Date.now.bind(Date);
|
|
3
|
+
var state = {
|
|
4
|
+
isActive: false,
|
|
5
|
+
mode: null,
|
|
6
|
+
offset: null,
|
|
7
|
+
frozenTimestamp: null
|
|
8
|
+
};
|
|
9
|
+
function travel(timestamp, mode) {
|
|
10
|
+
state.isActive = true;
|
|
11
|
+
state.mode = mode;
|
|
12
|
+
if (mode === "frozen") {
|
|
13
|
+
state.frozenTimestamp = timestamp;
|
|
14
|
+
state.offset = null;
|
|
15
|
+
Date.now = () => state.frozenTimestamp;
|
|
16
|
+
} else {
|
|
17
|
+
state.frozenTimestamp = null;
|
|
18
|
+
state.offset = timestamp - _realDateNow();
|
|
19
|
+
Date.now = () => _realDateNow() + state.offset;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function returnToPresent() {
|
|
23
|
+
state.isActive = false;
|
|
24
|
+
state.mode = null;
|
|
25
|
+
state.offset = null;
|
|
26
|
+
state.frozenTimestamp = null;
|
|
27
|
+
Date.now = _realDateNow;
|
|
28
|
+
}
|
|
29
|
+
function getOffset() {
|
|
30
|
+
if (!state.isActive) return null;
|
|
31
|
+
return state.mode === "frozen" ? state.frozenTimestamp : state.offset;
|
|
32
|
+
}
|
|
33
|
+
function isActive() {
|
|
34
|
+
return state.isActive;
|
|
35
|
+
}
|
|
36
|
+
function save(storageKey = "__timeMachine__") {
|
|
37
|
+
if (typeof localStorage === "undefined" || !state.isActive) return;
|
|
38
|
+
const savedState = {
|
|
39
|
+
mode: state.mode,
|
|
40
|
+
value: state.mode === "frozen" ? state.frozenTimestamp : state.offset
|
|
41
|
+
};
|
|
42
|
+
localStorage.setItem(storageKey, JSON.stringify(savedState));
|
|
43
|
+
}
|
|
44
|
+
function restore(storageKey = "__timeMachine__") {
|
|
45
|
+
if (typeof localStorage === "undefined") return false;
|
|
46
|
+
const data = localStorage.getItem(storageKey);
|
|
47
|
+
if (!data) return false;
|
|
48
|
+
try {
|
|
49
|
+
const savedState = JSON.parse(data);
|
|
50
|
+
if (savedState.mode === "frozen") {
|
|
51
|
+
travel(savedState.value, "frozen");
|
|
52
|
+
} else {
|
|
53
|
+
const targetTimestamp = _realDateNow() + savedState.value;
|
|
54
|
+
travel(targetTimestamp, "flowing");
|
|
55
|
+
}
|
|
56
|
+
return true;
|
|
57
|
+
} catch {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
export {
|
|
62
|
+
getOffset,
|
|
63
|
+
isActive,
|
|
64
|
+
restore,
|
|
65
|
+
returnToPresent,
|
|
66
|
+
save,
|
|
67
|
+
travel
|
|
68
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "time-machine-js",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A zero-dependency utility to monkey-patch Date.now() globally.",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.mjs",
|
|
12
|
+
"require": "./dist/index.js"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"dist",
|
|
17
|
+
"README.md"
|
|
18
|
+
],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
21
|
+
"test": "vitest run",
|
|
22
|
+
"test:watch": "vitest",
|
|
23
|
+
"lint": "tsc --noEmit"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"time",
|
|
27
|
+
"travel",
|
|
28
|
+
"date",
|
|
29
|
+
"manipulation"
|
|
30
|
+
],
|
|
31
|
+
"author": "",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"tsup": "^8.0.2",
|
|
35
|
+
"typescript": "^5.3.3",
|
|
36
|
+
"vitest": "^1.3.1"
|
|
37
|
+
}
|
|
38
|
+
}
|