time-machine-js-plugin-express 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 ADDED
@@ -0,0 +1,71 @@
1
+ # time-machine-js-plugin-express
2
+
3
+ An Express.js middleware plugin for [`time-machine-js`](https://www.npmjs.com/package/time-machine-js). It allows you to easily manipulate the global server time globally via environment variables or on a per-request basis using HTTP headers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install time-machine-js-plugin-express
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ### 1. Global Initialization via Environment Variable
14
+
15
+ You can automatically set the server's time upon initialization by reading from the `TIME_MACHINE_TRAVEL_TO` environment variable.
16
+
17
+ Make sure your environment variable is set before your app starts:
18
+
19
+ ```bash
20
+ # .env
21
+ TIME_MACHINE_TRAVEL_TO="2020-01-01T00:00:00Z"
22
+ ```
23
+
24
+ Then initialize it in your code:
25
+
26
+ ```typescript
27
+ import { initTimeMachineFromEnv } from "time-machine-js-plugin-express";
28
+
29
+ // Initialize with a preferred mode ('flowing' or 'frozen')
30
+ initTimeMachineFromEnv({ mode: "flowing" });
31
+ ```
32
+
33
+ ### 2. Per-Request Time Travel via Middleware
34
+
35
+ You can allow clients to travel to a specific time for the duration of a single HTTP request using a custom header. The middleware automatically handles reverting the time back to its previous state after the request finishes.
36
+
37
+ ```typescript
38
+ import express from "express";
39
+ import { timeMachineMiddleware } from "time-machine-js-plugin-express";
40
+
41
+ const app = express();
42
+
43
+ // Use the middleware globally or on specific routes
44
+ app.use(
45
+ timeMachineMiddleware({
46
+ // Optional: customize the header name (default is 'x-time-traveled-to')
47
+ headerName: "x-time-traveled-to",
48
+ }),
49
+ );
50
+
51
+ app.get("/current-time", (req, res) => {
52
+ res.json({ time: new Date().toISOString() });
53
+ });
54
+
55
+ app.listen(3000, () => console.log("Server running on port 3000"));
56
+ ```
57
+
58
+ **Testing the request:**
59
+
60
+ ```bash
61
+ curl -H "x-time-traveled-to: 1999-12-31T23:59:59Z" http://localhost:3000/current-time
62
+ # Output will reflect the traveled time
63
+ ```
64
+
65
+ ## Concurrency Note for Node.js
66
+
67
+ Because `time-machine-js` patches `Date.now()` globally, Node.js applications that process multiple requests concurrently may experience side-effects where one request's modified time bleeds into another request running at the exact same time. This plugin is most optimal for development, testing, and staging environments.
68
+
69
+ ## License
70
+
71
+ MIT
@@ -0,0 +1,17 @@
1
+ import { TimeMachineMode } from 'time-machine-js';
2
+ import { Request, Response, NextFunction } from 'express';
3
+
4
+ interface TimeMachineMiddlewareOptions {
5
+ /**
6
+ * Header name to look for to trigger time travel.
7
+ * @default 'x-time-traveled-to'
8
+ */
9
+ headerName?: string;
10
+ }
11
+ interface TimeMachineOptions {
12
+ mode: TimeMachineMode;
13
+ }
14
+ declare function initTimeMachineFromEnv(config: TimeMachineOptions): void;
15
+ declare function timeMachineMiddleware(options?: TimeMachineMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
16
+
17
+ export { type TimeMachineMiddlewareOptions, type TimeMachineOptions, initTimeMachineFromEnv, timeMachineMiddleware };
@@ -0,0 +1,17 @@
1
+ import { TimeMachineMode } from 'time-machine-js';
2
+ import { Request, Response, NextFunction } from 'express';
3
+
4
+ interface TimeMachineMiddlewareOptions {
5
+ /**
6
+ * Header name to look for to trigger time travel.
7
+ * @default 'x-time-traveled-to'
8
+ */
9
+ headerName?: string;
10
+ }
11
+ interface TimeMachineOptions {
12
+ mode: TimeMachineMode;
13
+ }
14
+ declare function initTimeMachineFromEnv(config: TimeMachineOptions): void;
15
+ declare function timeMachineMiddleware(options?: TimeMachineMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => void;
16
+
17
+ export { type TimeMachineMiddlewareOptions, type TimeMachineOptions, initTimeMachineFromEnv, timeMachineMiddleware };
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
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
+ initTimeMachineFromEnv: () => initTimeMachineFromEnv,
24
+ timeMachineMiddleware: () => timeMachineMiddleware
25
+ });
26
+ module.exports = __toCommonJS(index_exports);
27
+ var import_time_machine_js = require("time-machine-js");
28
+ var envTimeSet = false;
29
+ function initTimeMachineFromEnv(config) {
30
+ if (envTimeSet) return;
31
+ const envDate = process.env.TIME_MACHINE_TRAVEL_TO;
32
+ if (envDate) {
33
+ const timestamp = new Date(envDate).getTime();
34
+ if (!isNaN(timestamp)) {
35
+ (0, import_time_machine_js.travel)(timestamp, config.mode);
36
+ envTimeSet = true;
37
+ } else {
38
+ console.warn(
39
+ `[time-machine-js-plugin-express] Invalid date format in TIME_MACHINE_TRAVEL_TO: ${envDate}`
40
+ );
41
+ }
42
+ }
43
+ }
44
+ function timeMachineMiddleware(options = {}) {
45
+ const headerName = (options.headerName || "x-time-traveled-to").toLowerCase();
46
+ return (req, res, next) => {
47
+ const headerValue = req.headers[headerName];
48
+ const resolvedHeaderValue = Array.isArray(headerValue) ? headerValue[0] : headerValue;
49
+ if (!resolvedHeaderValue || typeof resolvedHeaderValue !== "string") {
50
+ return next();
51
+ }
52
+ const targetDate = new Date(resolvedHeaderValue);
53
+ const targetTimestamp = targetDate.getTime();
54
+ if (isNaN(targetTimestamp)) {
55
+ console.warn(
56
+ `[time-machine-js-plugin-express] Invalid date received in header ${headerName}: ${resolvedHeaderValue}`
57
+ );
58
+ return next();
59
+ }
60
+ const wasActive = (0, import_time_machine_js.isActive)();
61
+ const currentMode = (0, import_time_machine_js.getMode)();
62
+ const currentOffset = (0, import_time_machine_js.getOffset)();
63
+ let originalTimestamp = null;
64
+ if (wasActive && currentMode === "flowing" && currentOffset !== null) {
65
+ originalTimestamp = Date.now();
66
+ } else if (wasActive && currentMode === "frozen" && currentOffset !== null) {
67
+ originalTimestamp = currentOffset;
68
+ }
69
+ (0, import_time_machine_js.travel)(targetTimestamp, "flowing");
70
+ let reverted = false;
71
+ const revertTime = () => {
72
+ if (reverted) return;
73
+ if (wasActive && originalTimestamp !== null && currentMode !== null) {
74
+ (0, import_time_machine_js.travel)(originalTimestamp, currentMode);
75
+ } else if (!wasActive) {
76
+ (0, import_time_machine_js.returnToPresent)();
77
+ }
78
+ reverted = true;
79
+ };
80
+ res.on("finish", revertTime);
81
+ res.on("close", revertTime);
82
+ next();
83
+ };
84
+ }
85
+ // Annotate the CommonJS export names for ESM import in node:
86
+ 0 && (module.exports = {
87
+ initTimeMachineFromEnv,
88
+ timeMachineMiddleware
89
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,69 @@
1
+ // src/index.ts
2
+ import {
3
+ travel,
4
+ returnToPresent,
5
+ getMode,
6
+ getOffset,
7
+ isActive
8
+ } from "time-machine-js";
9
+ var envTimeSet = false;
10
+ function initTimeMachineFromEnv(config) {
11
+ if (envTimeSet) return;
12
+ const envDate = process.env.TIME_MACHINE_TRAVEL_TO;
13
+ if (envDate) {
14
+ const timestamp = new Date(envDate).getTime();
15
+ if (!isNaN(timestamp)) {
16
+ travel(timestamp, config.mode);
17
+ envTimeSet = true;
18
+ } else {
19
+ console.warn(
20
+ `[time-machine-js-plugin-express] Invalid date format in TIME_MACHINE_TRAVEL_TO: ${envDate}`
21
+ );
22
+ }
23
+ }
24
+ }
25
+ function timeMachineMiddleware(options = {}) {
26
+ const headerName = (options.headerName || "x-time-traveled-to").toLowerCase();
27
+ return (req, res, next) => {
28
+ const headerValue = req.headers[headerName];
29
+ const resolvedHeaderValue = Array.isArray(headerValue) ? headerValue[0] : headerValue;
30
+ if (!resolvedHeaderValue || typeof resolvedHeaderValue !== "string") {
31
+ return next();
32
+ }
33
+ const targetDate = new Date(resolvedHeaderValue);
34
+ const targetTimestamp = targetDate.getTime();
35
+ if (isNaN(targetTimestamp)) {
36
+ console.warn(
37
+ `[time-machine-js-plugin-express] Invalid date received in header ${headerName}: ${resolvedHeaderValue}`
38
+ );
39
+ return next();
40
+ }
41
+ const wasActive = isActive();
42
+ const currentMode = getMode();
43
+ const currentOffset = getOffset();
44
+ let originalTimestamp = null;
45
+ if (wasActive && currentMode === "flowing" && currentOffset !== null) {
46
+ originalTimestamp = Date.now();
47
+ } else if (wasActive && currentMode === "frozen" && currentOffset !== null) {
48
+ originalTimestamp = currentOffset;
49
+ }
50
+ travel(targetTimestamp, "flowing");
51
+ let reverted = false;
52
+ const revertTime = () => {
53
+ if (reverted) return;
54
+ if (wasActive && originalTimestamp !== null && currentMode !== null) {
55
+ travel(originalTimestamp, currentMode);
56
+ } else if (!wasActive) {
57
+ returnToPresent();
58
+ }
59
+ reverted = true;
60
+ };
61
+ res.on("finish", revertTime);
62
+ res.on("close", revertTime);
63
+ next();
64
+ };
65
+ }
66
+ export {
67
+ initTimeMachineFromEnv,
68
+ timeMachineMiddleware
69
+ };
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "time-machine-js-plugin-express",
3
+ "version": "1.0.0",
4
+ "main": "./dist/index.js",
5
+ "scripts": {
6
+ "test": "vitest run",
7
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
8
+ "test:watch": "vitest start"
9
+ },
10
+ "keywords": [],
11
+ "author": "williamp29 <williamp.olmos@gmail.com>",
12
+ "license": "MIT",
13
+ "description": "Express middleware for time-machine-js",
14
+ "dependencies": {
15
+ "time-machine-js": "^1.1.0"
16
+ },
17
+ "devDependencies": {
18
+ "@types/express": "^5.0.6",
19
+ "@types/node": "^25.3.0",
20
+ "@types/supertest": "^6.0.3",
21
+ "express": "^5.2.1",
22
+ "supertest": "^7.2.2",
23
+ "tsup": "^8.5.1",
24
+ "typescript": "^5.9.3",
25
+ "vitest": "^4.0.18"
26
+ },
27
+ "module": "./dist/index.mjs",
28
+ "types": "./dist/index.d.ts",
29
+ "peerDependencies": {
30
+ "express": "^4.17.1 || ^5.0.0",
31
+ "time-machine-js": "^1.1.0"
32
+ },
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "git+https://github.com/WilliamPinto-Olmos/time-machine-js-plugin-express.git"
36
+ },
37
+ "bugs": {
38
+ "url": "https://github.com/WilliamPinto-Olmos/time-machine-js-plugin-express/issues"
39
+ },
40
+ "homepage": "https://github.com/WilliamPinto-Olmos/time-machine-js-plugin-express#readme",
41
+ "files": [
42
+ "dist",
43
+ "README.md"
44
+ ]
45
+ }