@crashlab/http-proxy 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,42 @@
1
+ # @crashlab/http-proxy
2
+
3
+ Lightweight HTTP interceptor for deterministic simulation testing. Patches `http.request`/`https.request`, records calls, serves static mocks, and integrates with `@crashlab/clock` for virtual latency.
4
+
5
+ ## Usage
6
+
7
+ ```ts
8
+ import { HttpInterceptor } from '@crashlab/http-proxy';
9
+ import { VirtualClock } from '@crashlab/clock';
10
+ import * as http from 'node:http';
11
+
12
+ const clock = new VirtualClock(0);
13
+ const interceptor = new HttpInterceptor({ clock });
14
+
15
+ interceptor.mock('http://api.stripe.com/v1/charges', {
16
+ status: 200,
17
+ body: { id: 'ch_123', status: 'succeeded' },
18
+ latency: 80,
19
+ });
20
+
21
+ interceptor.install();
22
+
23
+ // Make request — response is delayed by 80 virtual ms
24
+ const req = http.request('http://api.stripe.com/v1/charges', (res) => {
25
+ console.log(res.statusCode); // 200
26
+ });
27
+ req.end();
28
+
29
+ clock.advance(80); // fires the response synchronously
30
+
31
+ // Query recorded calls
32
+ interceptor.calls('GET', 'http://api.stripe.com');
33
+
34
+ interceptor.uninstall();
35
+ ```
36
+
37
+ ## Features
38
+
39
+ - **Static & dynamic mocks** — respond with fixed data or a handler function
40
+ - **Call recording** — inspect all intercepted requests
41
+ - **Failure injection** — handler-level error throwing
42
+ - **Virtual latency** — integrates with any `IClock` implementation (duck-typed, no hard dependency)
@@ -0,0 +1,80 @@
1
+ /** Minimal virtual-clock interface (duck-typed). */
2
+ export interface IClock {
3
+ now(): number;
4
+ setTimeout(cb: (...args: unknown[]) => void, delay: number): number;
5
+ }
6
+ /** Minimal scheduler interface (duck-typed). */
7
+ export interface IScheduler {
8
+ enqueueCompletion(op: {
9
+ id: string;
10
+ when: number;
11
+ run: () => Promise<void> | void;
12
+ }): void;
13
+ requestRunTick?(virtualTime: number): void;
14
+ }
15
+ export interface MockResponseConfig {
16
+ status?: number;
17
+ headers?: Record<string, string>;
18
+ body?: unknown;
19
+ /** Virtual-clock latency in ms (requires an IClock instance). */
20
+ latency?: number;
21
+ /** Dynamic handler — overrides static body/status when provided. */
22
+ handler?: (call: RecordedCall) => {
23
+ status: number;
24
+ body?: unknown;
25
+ headers?: Record<string, string>;
26
+ };
27
+ /**
28
+ * URL match mode.
29
+ * - `'exact'` (default): `url === urlPattern` — only matches the exact URL.
30
+ * - `'prefix'`: `url.startsWith(urlPattern)` — matches any URL that begins with the pattern.
31
+ * - `'regex'`: `new RegExp(urlPattern).test(url)` — matches via regular expression.
32
+ */
33
+ match?: 'exact' | 'prefix' | 'regex';
34
+ }
35
+ export interface FailConfig {
36
+ /** Succeed the first N calls, then error. */
37
+ after?: number;
38
+ error: string;
39
+ }
40
+ export interface RecordedCall {
41
+ method: string;
42
+ url: string;
43
+ headers: Record<string, string>;
44
+ body: string;
45
+ timestamp: number;
46
+ }
47
+ export declare class HttpInterceptor {
48
+ private readonly _routes;
49
+ private readonly _allCalls;
50
+ private readonly _clock?;
51
+ private readonly _scheduler?;
52
+ private _partitioned;
53
+ private _defaultLatency;
54
+ private _origHttpRequest?;
55
+ private _origHttpGet?;
56
+ private _origHttpsRequest?;
57
+ private _origHttpsGet?;
58
+ constructor(opts?: {
59
+ clock?: IClock;
60
+ scheduler?: IScheduler;
61
+ });
62
+ mock(urlPrefix: string, config: MockResponseConfig): this;
63
+ fail(urlPrefix: string, config: FailConfig): this;
64
+ calls(method?: string, urlPrefix?: string): RecordedCall[];
65
+ /**
66
+ * Block all HTTP requests for `duration` virtual ms.
67
+ * Requests made during the partition receive a connection-refused error.
68
+ */
69
+ blockAll(duration: number): void;
70
+ /**
71
+ * Add a global extra latency (ms) to all HTTP responses.
72
+ * Used by FaultInjector.slowDatabase() for HTTP-based DBs.
73
+ */
74
+ setDefaultLatency(ms: number): void;
75
+ install(): void;
76
+ uninstall(): void;
77
+ reset(): void;
78
+ private _intercept;
79
+ }
80
+ //# sourceMappingURL=HttpInterceptor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HttpInterceptor.d.ts","sourceRoot":"","sources":["../src/HttpInterceptor.ts"],"names":[],"mappings":"AAOA,oDAAoD;AACpD,MAAM,WAAW,MAAM;IACrB,GAAG,IAAI,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;CACrE;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,iBAAiB,CAAC,EAAE,EAAE;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAA;KAAE,GAAG,IAAI,CAAC;IAC3F,cAAc,CAAC,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5C;AAED,MAAM,WAAW,kBAAkB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,iEAAiE;IACjE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,YAAY,KAAK;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAAE,CAAC;IACvG;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,GAAG,QAAQ,GAAG,OAAO,CAAC;CACtC;AAED,MAAM,WAAW,UAAU;IACzB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AA6HD,qBAAa,eAAe;IAC1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAa;IAEzC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAAK;IAE5B,OAAO,CAAC,gBAAgB,CAAC,CAAsB;IAC/C,OAAO,CAAC,YAAY,CAAC,CAAkB;IACvC,OAAO,CAAC,iBAAiB,CAAC,CAAuB;IACjD,OAAO,CAAC,aAAa,CAAC,CAAmB;gBAE7B,IAAI,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,UAAU,CAAA;KAAE;IAO7D,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,IAAI;IAKzD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI;IAKjD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,YAAY,EAAE;IAQ1D;;;OAGG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAahC;;;OAGG;IACH,iBAAiB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAMnC,OAAO,IAAI,IAAI;IAoCf,SAAS,IAAI,IAAI;IASjB,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,UAAU;CA8EnB"}
@@ -0,0 +1,3 @@
1
+ import type { HttpInterceptor } from './HttpInterceptor.js';
2
+ export declare function createFetchPatch(interceptor: HttpInterceptor, originalFetch: typeof globalThis.fetch): (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
3
+ //# sourceMappingURL=fetch-patch.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-patch.d.ts","sourceRoot":"","sources":["../src/fetch-patch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAA4B,MAAM,sBAAsB,CAAC;AAKtF,wBAAgB,gBAAgB,CAAC,WAAW,EAAE,eAAe,EAAE,aAAa,EAAE,OAAO,UAAU,CAAC,KAAK,IACnE,OAAO,MAAM,GAAG,GAAG,GAAG,OAAO,EAAE,OAAO,WAAW,KAAG,OAAO,CAAC,QAAQ,CAAC,CA4HtG"}
package/dist/index.cjs ADDED
@@ -0,0 +1,520 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ HttpInterceptor: () => HttpInterceptor,
34
+ createFetchPatch: () => createFetchPatch,
35
+ install: () => install
36
+ });
37
+ module.exports = __toCommonJS(index_exports);
38
+
39
+ // ../../node_modules/tsup/assets/cjs_shims.js
40
+ var getImportMetaUrl = () => typeof document === "undefined" ? new URL(`file:${__filename}`).href : document.currentScript && document.currentScript.tagName.toUpperCase() === "SCRIPT" ? document.currentScript.src : new URL("main.js", document.baseURI).href;
41
+ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
42
+
43
+ // src/HttpInterceptor.ts
44
+ var import_node_events = require("events");
45
+ var http = __toESM(require("http"), 1);
46
+ var import_node_url = require("url");
47
+ var import_node_module = require("module");
48
+ var FakeIncomingMessage = class extends import_node_events.EventEmitter {
49
+ constructor(status, headers, _body) {
50
+ super();
51
+ this._body = _body;
52
+ this.statusCode = status;
53
+ this.statusMessage = http.STATUS_CODES[status] ?? "";
54
+ this.headers = headers;
55
+ }
56
+ statusCode;
57
+ statusMessage;
58
+ headers;
59
+ _flush() {
60
+ queueMicrotask(() => {
61
+ this.emit("data", Buffer.from(this._body));
62
+ this.emit("end");
63
+ });
64
+ }
65
+ };
66
+ var FakeClientRequest = class extends import_node_events.EventEmitter {
67
+ _chunks = [];
68
+ headersSent = false;
69
+ write(chunk) {
70
+ this._chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
71
+ return true;
72
+ }
73
+ end(chunk) {
74
+ if (chunk) this.write(chunk);
75
+ this.emit("_end");
76
+ return this;
77
+ }
78
+ get body() {
79
+ return Buffer.concat(this._chunks).toString();
80
+ }
81
+ // no-ops for compat
82
+ setHeader() {
83
+ return this;
84
+ }
85
+ getHeader() {
86
+ return void 0;
87
+ }
88
+ removeHeader() {
89
+ }
90
+ flushHeaders() {
91
+ }
92
+ setTimeout() {
93
+ return this;
94
+ }
95
+ setNoDelay() {
96
+ }
97
+ setSocketKeepAlive() {
98
+ }
99
+ abort() {
100
+ }
101
+ destroy() {
102
+ return this;
103
+ }
104
+ };
105
+ var MockRoute = class {
106
+ constructor(urlPattern, config, failConfig) {
107
+ this.urlPattern = urlPattern;
108
+ this.config = config;
109
+ this.failConfig = failConfig;
110
+ this._matchMode = config.match ?? "exact";
111
+ if (this._matchMode === "regex") {
112
+ this._regex = new RegExp(urlPattern);
113
+ }
114
+ }
115
+ calls = [];
116
+ _callCount = 0;
117
+ _matchMode;
118
+ _regex;
119
+ matches(url) {
120
+ switch (this._matchMode) {
121
+ case "exact":
122
+ return url === this.urlPattern;
123
+ case "prefix":
124
+ return url.startsWith(this.urlPattern);
125
+ case "regex":
126
+ return this._regex.test(url);
127
+ }
128
+ }
129
+ respond(call) {
130
+ this.calls.push(call);
131
+ this._callCount++;
132
+ if (this.failConfig) {
133
+ const limit = this.failConfig.after ?? 0;
134
+ if (this._callCount > limit) {
135
+ return { error: this.failConfig.error, status: 0, headers: {}, body: "" };
136
+ }
137
+ }
138
+ if (this.config.handler) {
139
+ const r = this.config.handler(call);
140
+ const body2 = typeof r.body === "string" ? r.body : JSON.stringify(r.body ?? "");
141
+ return { status: r.status, headers: r.headers ?? { "content-type": "application/json" }, body: body2 };
142
+ }
143
+ const body = typeof this.config.body === "string" ? this.config.body : JSON.stringify(this.config.body ?? "");
144
+ return {
145
+ status: this.config.status ?? 200,
146
+ headers: this.config.headers ?? { "content-type": "application/json" },
147
+ body
148
+ };
149
+ }
150
+ };
151
+ var _require = (0, import_node_module.createRequire)(importMetaUrl);
152
+ var httpCjs = _require("node:http");
153
+ var httpsCjs = _require("node:https");
154
+ var _httpReqCounter = 0;
155
+ var HttpInterceptor = class {
156
+ _routes = [];
157
+ _allCalls = [];
158
+ _clock;
159
+ _scheduler;
160
+ _partitioned = false;
161
+ _defaultLatency = 0;
162
+ _origHttpRequest;
163
+ _origHttpGet;
164
+ _origHttpsRequest;
165
+ _origHttpsGet;
166
+ constructor(opts) {
167
+ this._clock = opts?.clock;
168
+ this._scheduler = opts?.scheduler;
169
+ }
170
+ // mock registration
171
+ mock(urlPrefix, config) {
172
+ this._routes.push(new MockRoute(urlPrefix, config));
173
+ return this;
174
+ }
175
+ fail(urlPrefix, config) {
176
+ this._routes.push(new MockRoute(urlPrefix, { status: 0 }, config));
177
+ return this;
178
+ }
179
+ calls(method, urlPrefix) {
180
+ return this._allCalls.filter((c) => {
181
+ if (method && c.method !== method.toUpperCase()) return false;
182
+ if (urlPrefix && !c.url.startsWith(urlPrefix)) return false;
183
+ return true;
184
+ });
185
+ }
186
+ /**
187
+ * Block all HTTP requests for `duration` virtual ms.
188
+ * Requests made during the partition receive a connection-refused error.
189
+ */
190
+ blockAll(duration) {
191
+ this._partitioned = true;
192
+ if (this._clock) {
193
+ this._clock.setTimeout(() => {
194
+ this._partitioned = false;
195
+ }, duration);
196
+ } else {
197
+ console.warn(
198
+ "SimNode: HttpInterceptor.blockAll() called without a virtual clock. Falling back to real setTimeout \u2014 partition duration will be wall-clock, not deterministic."
199
+ );
200
+ setTimeout(() => {
201
+ this._partitioned = false;
202
+ }, duration);
203
+ }
204
+ }
205
+ /**
206
+ * Add a global extra latency (ms) to all HTTP responses.
207
+ * Used by FaultInjector.slowDatabase() for HTTP-based DBs.
208
+ */
209
+ setDefaultLatency(ms) {
210
+ this._defaultLatency = ms;
211
+ }
212
+ // patching
213
+ install() {
214
+ this._origHttpRequest = httpCjs.request;
215
+ this._origHttpGet = httpCjs.get;
216
+ this._origHttpsRequest = httpsCjs.request;
217
+ this._origHttpsGet = httpsCjs.get;
218
+ const self = this;
219
+ const origHttpReq = this._origHttpRequest;
220
+ const origHttpsReq = this._origHttpsRequest;
221
+ const makeRequest = (proto, origReq) => function fakeRequest(...args) {
222
+ const { url, method, headers, callback } = normalizeArgs(proto, args);
223
+ if (_isLocalUrl(url) && !self._routes.find((r) => r.matches(url))) {
224
+ return origReq.apply(null, args);
225
+ }
226
+ return self._intercept(url, method, headers, callback);
227
+ };
228
+ const makeGet = (reqFn) => function fakeGet(...args) {
229
+ const req = reqFn(...args);
230
+ req.end();
231
+ return req;
232
+ };
233
+ httpCjs.request = makeRequest("http:", origHttpReq);
234
+ httpCjs.get = makeGet(httpCjs.request);
235
+ httpsCjs.request = makeRequest("https:", origHttpsReq);
236
+ httpsCjs.get = makeGet(httpsCjs.request);
237
+ }
238
+ uninstall() {
239
+ if (this._origHttpRequest) httpCjs.request = this._origHttpRequest;
240
+ if (this._origHttpGet) httpCjs.get = this._origHttpGet;
241
+ if (this._origHttpsRequest) httpsCjs.request = this._origHttpsRequest;
242
+ if (this._origHttpsGet) httpsCjs.get = this._origHttpsGet;
243
+ this._partitioned = false;
244
+ this._defaultLatency = 0;
245
+ }
246
+ reset() {
247
+ this._routes.length = 0;
248
+ this._allCalls.length = 0;
249
+ this._partitioned = false;
250
+ this._defaultLatency = 0;
251
+ }
252
+ // internal
253
+ _intercept(url, method, headers, callback) {
254
+ const route = this._routes.find((r) => r.matches(url));
255
+ const fakeReq = new FakeClientRequest();
256
+ if (callback) fakeReq.on("response", callback);
257
+ fakeReq.on("_end", () => {
258
+ const call = {
259
+ method,
260
+ url,
261
+ headers,
262
+ body: fakeReq.body,
263
+ timestamp: this._clock?.now() ?? Date.now()
264
+ };
265
+ this._allCalls.push(call);
266
+ if (this._partitioned) {
267
+ fakeReq.emit("error", Object.assign(new Error(`Network partition: ${method} ${url} rejected`), { code: "ECONNREFUSED" }));
268
+ return;
269
+ }
270
+ if (!route) {
271
+ fakeReq.emit("error", new Error(`No mock matched: ${method} ${url}`));
272
+ return;
273
+ }
274
+ let result;
275
+ try {
276
+ result = route.respond(call);
277
+ } catch (err) {
278
+ fakeReq.emit("error", err instanceof Error ? err : new Error(String(err)));
279
+ return;
280
+ }
281
+ const deliver = () => {
282
+ if (result.error) {
283
+ fakeReq.emit("error", new Error(result.error));
284
+ return;
285
+ }
286
+ const fakeRes = new FakeIncomingMessage(result.status, result.headers, result.body);
287
+ fakeReq.emit("response", fakeRes);
288
+ fakeRes._flush();
289
+ };
290
+ const latency = (route.config.latency ?? 0) + this._defaultLatency;
291
+ if (this._scheduler) {
292
+ const now = this._clock?.now() ?? 0;
293
+ const when = now + latency;
294
+ const opId = `http-${++_httpReqCounter}`;
295
+ this._scheduler.enqueueCompletion({
296
+ id: opId,
297
+ when,
298
+ run: () => {
299
+ deliver();
300
+ return Promise.resolve();
301
+ }
302
+ });
303
+ if (latency <= 0) {
304
+ this._scheduler.requestRunTick?.(now);
305
+ }
306
+ } else if (this._clock && latency > 0) {
307
+ this._clock.setTimeout(deliver, latency);
308
+ } else {
309
+ throw new Error(
310
+ "[SimNode] HttpInterceptor: a Scheduler is required for deterministic delivery. Pass { scheduler } when constructing HttpInterceptor."
311
+ );
312
+ }
313
+ });
314
+ return fakeReq;
315
+ }
316
+ };
317
+ function _isLocalUrl(url) {
318
+ try {
319
+ const h = new import_node_url.URL(url).hostname.toLowerCase();
320
+ return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "[::1]";
321
+ } catch {
322
+ return false;
323
+ }
324
+ }
325
+ function normalizeArgs(defaultProto, args) {
326
+ let url;
327
+ let options = {};
328
+ let callback;
329
+ if (typeof args[0] === "string" || args[0] instanceof import_node_url.URL) {
330
+ const parsed = new import_node_url.URL(args[0].toString());
331
+ url = parsed.toString();
332
+ if (typeof args[1] === "function") {
333
+ callback = args[1];
334
+ } else if (typeof args[1] === "object" && args[1] !== null) {
335
+ options = args[1];
336
+ if (typeof args[2] === "function") callback = args[2];
337
+ }
338
+ } else {
339
+ options = args[0] ?? {};
340
+ if (typeof args[1] === "function") callback = args[1];
341
+ const proto = options.protocol ?? defaultProto;
342
+ const host = options.hostname ?? options.host ?? "localhost";
343
+ const port = options.port ? `:${options.port}` : "";
344
+ const path = options.path ?? "/";
345
+ url = `${proto}//${host}${port}${path}`;
346
+ }
347
+ return {
348
+ url,
349
+ method: (options.method ?? "GET").toUpperCase(),
350
+ headers: options.headers ?? {},
351
+ callback
352
+ };
353
+ }
354
+
355
+ // src/fetch-patch.ts
356
+ var import_node_buffer = require("buffer");
357
+ var _fetchReqCounter = 0;
358
+ function createFetchPatch(interceptor, originalFetch) {
359
+ return async function fakeFetch(input, init) {
360
+ let url;
361
+ let method = "GET";
362
+ const headers = {};
363
+ let body = "";
364
+ if (input instanceof Request) {
365
+ url = input.url;
366
+ method = input.method;
367
+ input.headers.forEach((value, key) => {
368
+ headers[key.toLowerCase()] = value;
369
+ });
370
+ if (input.body) {
371
+ try {
372
+ const ab = await input.arrayBuffer();
373
+ body = import_node_buffer.Buffer.from(ab).toString("utf-8");
374
+ } catch {
375
+ body = "";
376
+ }
377
+ }
378
+ } else {
379
+ url = typeof input === "string" ? input : input.toString();
380
+ }
381
+ if (init) {
382
+ if (init.method) method = init.method.toUpperCase();
383
+ if (init.headers) {
384
+ if (init.headers instanceof Headers) {
385
+ init.headers.forEach((value, key) => {
386
+ headers[key.toLowerCase()] = value;
387
+ });
388
+ } else if (Array.isArray(init.headers)) {
389
+ for (const [key, value] of init.headers) {
390
+ headers[key.toLowerCase()] = value;
391
+ }
392
+ } else {
393
+ for (const [key, value] of Object.entries(init.headers)) {
394
+ headers[key.toLowerCase()] = value;
395
+ }
396
+ }
397
+ }
398
+ if (init.body) {
399
+ if (typeof init.body === "string") {
400
+ body = init.body;
401
+ } else if (init.body instanceof import_node_buffer.Buffer) {
402
+ body = init.body.toString("utf-8");
403
+ } else {
404
+ body = String(init.body);
405
+ }
406
+ }
407
+ }
408
+ return new Promise((resolve, reject) => {
409
+ const anyInterceptor = interceptor;
410
+ const route = anyInterceptor._routes.find((r) => r.matches(url));
411
+ const call = {
412
+ method,
413
+ url,
414
+ headers,
415
+ body,
416
+ timestamp: anyInterceptor._clock?.now() ?? Date.now()
417
+ };
418
+ anyInterceptor._allCalls.push(call);
419
+ if (anyInterceptor._partitioned) {
420
+ return reject(Object.assign(new TypeError(`fetch failed: Network partition active \u2014 ${method} ${url} rejected`), { code: "ECONNREFUSED" }));
421
+ }
422
+ if (!route) {
423
+ return reject(new TypeError(`fetch failed: No mock matched: ${method} ${url}`));
424
+ }
425
+ let result;
426
+ try {
427
+ result = route.respond(call);
428
+ } catch (err) {
429
+ return reject(err instanceof Error ? err : new TypeError(String(err)));
430
+ }
431
+ const deliver = () => {
432
+ if (result.error) {
433
+ return reject(new TypeError(result.error));
434
+ }
435
+ const responseHeaders = new Headers(result.headers);
436
+ const response = new Response(result.body, {
437
+ status: result.status,
438
+ statusText: "MOCKED",
439
+ headers: responseHeaders
440
+ });
441
+ resolve(response);
442
+ };
443
+ const latency = (route.config.latency ?? 0) + (anyInterceptor._defaultLatency ?? 0);
444
+ if (anyInterceptor._scheduler) {
445
+ const now = anyInterceptor._clock?.now() ?? 0;
446
+ const when = now + latency;
447
+ const opId = `fetch-${++_fetchReqCounter}`;
448
+ anyInterceptor._scheduler.enqueueCompletion({
449
+ id: opId,
450
+ when,
451
+ run: () => {
452
+ deliver();
453
+ return Promise.resolve();
454
+ }
455
+ });
456
+ if (latency <= 0) {
457
+ anyInterceptor._scheduler.requestRunTick?.(now);
458
+ }
459
+ } else if (anyInterceptor._clock && latency > 0) {
460
+ anyInterceptor._clock.setTimeout(deliver, latency);
461
+ } else {
462
+ throw new Error(
463
+ "[SimNode] fetch: a Scheduler is required for deterministic delivery. Pass { scheduler } when constructing HttpInterceptor."
464
+ );
465
+ }
466
+ });
467
+ };
468
+ }
469
+
470
+ // src/install.ts
471
+ var import_node_module2 = require("module");
472
+ var _require2 = (0, import_node_module2.createRequire)(importMetaUrl);
473
+ function install(interceptorOrClock) {
474
+ let interceptor;
475
+ if (interceptorOrClock instanceof HttpInterceptor) {
476
+ interceptor = interceptorOrClock;
477
+ } else {
478
+ interceptor = new HttpInterceptor({ clock: interceptorOrClock });
479
+ }
480
+ interceptor.install();
481
+ const origFetch = globalThis.fetch;
482
+ if (origFetch) {
483
+ globalThis.fetch = createFetchPatch(interceptor, origFetch);
484
+ }
485
+ let undiciUninstall;
486
+ try {
487
+ const customRequire = (0, import_node_module2.createRequire)(process.cwd() + "/");
488
+ let undici;
489
+ try {
490
+ undici = customRequire("undici");
491
+ } catch {
492
+ undici = _require2("undici");
493
+ }
494
+ if (undici) {
495
+ const origUndiciFetch = undici.fetch;
496
+ const origUndiciRequest = undici.request;
497
+ undici.fetch = createFetchPatch(interceptor, origUndiciFetch);
498
+ undiciUninstall = () => {
499
+ undici.fetch = origUndiciFetch;
500
+ undici.request = origUndiciRequest;
501
+ };
502
+ }
503
+ } catch (err) {
504
+ }
505
+ function uninstall() {
506
+ interceptor.uninstall();
507
+ globalThis.fetch = origFetch;
508
+ if (undiciUninstall) {
509
+ undiciUninstall();
510
+ }
511
+ }
512
+ return { interceptor, uninstall };
513
+ }
514
+ // Annotate the CommonJS export names for ESM import in node:
515
+ 0 && (module.exports = {
516
+ HttpInterceptor,
517
+ createFetchPatch,
518
+ install
519
+ });
520
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../../../node_modules/tsup/assets/cjs_shims.js","../src/HttpInterceptor.ts","../src/fetch-patch.ts","../src/install.ts"],"sourcesContent":["export { HttpInterceptor } from './HttpInterceptor.js';\nexport type { IClock, MockResponseConfig, FailConfig, RecordedCall } from './HttpInterceptor.js';\nexport { install } from './install.js';\nexport type { HttpProxyInstallResult } from './install.js';\nexport { createFetchPatch } from './fetch-patch.js';\n","// Shim globals in cjs bundle\n// There's a weird bug that esbuild will always inject importMetaUrl\n// if we export it as `const importMetaUrl = ... __filename ...`\n// But using a function will not cause this issue\n\nconst getImportMetaUrl = () => \n typeof document === \"undefined\" \n ? new URL(`file:${__filename}`).href \n : (document.currentScript && document.currentScript.tagName.toUpperCase() === 'SCRIPT') \n ? document.currentScript.src \n : new URL(\"main.js\", document.baseURI).href;\n\nexport const importMetaUrl = /* @__PURE__ */ getImportMetaUrl()\n","import { EventEmitter } from 'node:events';\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport { URL } from 'node:url';\n\n// Types\n\n/** Minimal virtual-clock interface (duck-typed). */\nexport interface IClock {\n now(): number;\n setTimeout(cb: (...args: unknown[]) => void, delay: number): number;\n}\n\n/** Minimal scheduler interface (duck-typed). */\nexport interface IScheduler {\n enqueueCompletion(op: { id: string; when: number; run: () => Promise<void> | void }): void;\n requestRunTick?(virtualTime: number): void;\n}\n\nexport interface MockResponseConfig {\n status?: number;\n headers?: Record<string, string>;\n body?: unknown;\n /** Virtual-clock latency in ms (requires an IClock instance). */\n latency?: number;\n /** Dynamic handler — overrides static body/status when provided. */\n handler?: (call: RecordedCall) => { status: number; body?: unknown; headers?: Record<string, string> };\n /**\n * URL match mode.\n * - `'exact'` (default): `url === urlPattern` — only matches the exact URL.\n * - `'prefix'`: `url.startsWith(urlPattern)` — matches any URL that begins with the pattern.\n * - `'regex'`: `new RegExp(urlPattern).test(url)` — matches via regular expression.\n */\n match?: 'exact' | 'prefix' | 'regex';\n}\n\nexport interface FailConfig {\n /** Succeed the first N calls, then error. */\n after?: number;\n error: string;\n}\n\nexport interface RecordedCall {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n timestamp: number;\n}\n\n// Fakes\n\nclass FakeIncomingMessage extends EventEmitter {\n statusCode: number;\n statusMessage: string;\n headers: Record<string, string>;\n\n constructor(status: number, headers: Record<string, string>, private readonly _body: string) {\n super();\n this.statusCode = status;\n this.statusMessage = http.STATUS_CODES[status] ?? '';\n this.headers = headers;\n }\n\n _flush(): void {\n queueMicrotask(() => {\n this.emit('data', Buffer.from(this._body));\n this.emit('end');\n });\n }\n}\n\nclass FakeClientRequest extends EventEmitter {\n private _chunks: Buffer[] = [];\n headersSent = false;\n\n write(chunk: string | Buffer): boolean {\n this._chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n return true;\n }\n\n end(chunk?: string | Buffer): this {\n if (chunk) this.write(chunk);\n this.emit('_end');\n return this;\n }\n\n get body(): string {\n return Buffer.concat(this._chunks).toString();\n }\n\n // no-ops for compat\n setHeader(): this { return this; }\n getHeader(): undefined { return undefined; }\n removeHeader(): void {}\n flushHeaders(): void {}\n setTimeout(): this { return this; }\n setNoDelay(): void {}\n setSocketKeepAlive(): void {}\n abort(): void {}\n destroy(): this { return this; }\n}\n\n// Route\n\nclass MockRoute {\n readonly calls: RecordedCall[] = [];\n private _callCount = 0;\n private readonly _matchMode: 'exact' | 'prefix' | 'regex';\n private readonly _regex?: RegExp;\n\n constructor(\n readonly urlPattern: string,\n readonly config: MockResponseConfig,\n readonly failConfig?: FailConfig,\n ) {\n this._matchMode = config.match ?? 'exact';\n if (this._matchMode === 'regex') {\n this._regex = new RegExp(urlPattern);\n }\n }\n\n matches(url: string): boolean {\n switch (this._matchMode) {\n case 'exact': return url === this.urlPattern;\n case 'prefix': return url.startsWith(this.urlPattern);\n case 'regex': return this._regex!.test(url);\n }\n }\n\n respond(call: RecordedCall): { error?: string; status: number; headers: Record<string, string>; body: string } {\n this.calls.push(call);\n this._callCount++;\n\n if (this.failConfig) {\n const limit = this.failConfig.after ?? 0;\n if (this._callCount > limit) {\n return { error: this.failConfig.error, status: 0, headers: {}, body: '' };\n }\n }\n\n if (this.config.handler) {\n const r = this.config.handler(call);\n const body = typeof r.body === 'string' ? r.body : JSON.stringify(r.body ?? '');\n return { status: r.status, headers: r.headers ?? { 'content-type': 'application/json' }, body };\n }\n\n const body = typeof this.config.body === 'string'\n ? this.config.body\n : JSON.stringify(this.config.body ?? '');\n return {\n status: this.config.status ?? 200,\n headers: this.config.headers ?? { 'content-type': 'application/json' },\n body,\n };\n }\n}\n\n// Interceptor\n\n// We need to write directly to the module object's internal property.\n// ESM `import * as http` creates a frozen namespace — we can't assign\n// to it. Instead we grab the *CJS* module from require.cache and\n// patch the exports object there, which IS mutable.\n//\n// We use createRequire so this file can stay ESM.\nimport { createRequire } from 'node:module';\nconst _require = createRequire(import.meta.url);\nconst httpCjs = _require('node:http') as typeof http;\nconst httpsCjs = _require('node:https') as typeof https;\n\nlet _httpReqCounter = 0;\n\nexport class HttpInterceptor {\n private readonly _routes: MockRoute[] = [];\n private readonly _allCalls: RecordedCall[] = [];\n private readonly _clock?: IClock;\n private readonly _scheduler?: IScheduler;\n\n private _partitioned = false;\n private _defaultLatency = 0;\n\n private _origHttpRequest?: typeof http.request;\n private _origHttpGet?: typeof http.get;\n private _origHttpsRequest?: typeof https.request;\n private _origHttpsGet?: typeof https.get;\n\n constructor(opts?: { clock?: IClock; scheduler?: IScheduler }) {\n this._clock = opts?.clock;\n this._scheduler = opts?.scheduler;\n }\n\n // mock registration\n\n mock(urlPrefix: string, config: MockResponseConfig): this {\n this._routes.push(new MockRoute(urlPrefix, config));\n return this;\n }\n\n fail(urlPrefix: string, config: FailConfig): this {\n this._routes.push(new MockRoute(urlPrefix, { status: 0 }, config));\n return this;\n }\n\n calls(method?: string, urlPrefix?: string): RecordedCall[] {\n return this._allCalls.filter((c) => {\n if (method && c.method !== method.toUpperCase()) return false;\n if (urlPrefix && !c.url.startsWith(urlPrefix)) return false;\n return true;\n });\n }\n\n /**\n * Block all HTTP requests for `duration` virtual ms.\n * Requests made during the partition receive a connection-refused error.\n */\n blockAll(duration: number): void {\n this._partitioned = true;\n if (this._clock) {\n this._clock.setTimeout(() => { this._partitioned = false; }, duration);\n } else {\n console.warn(\n 'SimNode: HttpInterceptor.blockAll() called without a virtual clock. ' +\n 'Falling back to real setTimeout — partition duration will be wall-clock, not deterministic.',\n );\n setTimeout(() => { this._partitioned = false; }, duration);\n }\n }\n\n /**\n * Add a global extra latency (ms) to all HTTP responses.\n * Used by FaultInjector.slowDatabase() for HTTP-based DBs.\n */\n setDefaultLatency(ms: number): void {\n this._defaultLatency = ms;\n }\n\n // patching\n\n install(): void {\n this._origHttpRequest = httpCjs.request;\n this._origHttpGet = httpCjs.get;\n this._origHttpsRequest = httpsCjs.request;\n this._origHttpsGet = httpsCjs.get;\n\n const self = this;\n\n const origHttpReq = this._origHttpRequest!;\n const origHttpsReq = this._origHttpsRequest!;\n\n const makeRequest = (proto: string, origReq: typeof http.request) =>\n function fakeRequest(this: unknown, ...args: unknown[]): FakeClientRequest | http.ClientRequest {\n const { url, method, headers, callback } = normalizeArgs(proto, args);\n // Passthrough: unmatched localhost requests (supertest, local test\n // servers) ALWAYS go through the real http stack — even during a\n // network partition. blockAll() only affects external / mocked URLs.\n if (_isLocalUrl(url) && !self._routes.find(r => r.matches(url))) {\n return origReq.apply(null, args as any);\n }\n return self._intercept(url, method, headers, callback);\n } as unknown as typeof http.request;\n\n const makeGet = (reqFn: typeof http.request) =>\n function fakeGet(this: unknown, ...args: unknown[]): FakeClientRequest | http.ClientRequest {\n const req = (reqFn as any)(...args);\n req.end();\n return req;\n } as unknown as typeof http.get;\n\n httpCjs.request = makeRequest('http:', origHttpReq);\n httpCjs.get = makeGet(httpCjs.request);\n httpsCjs.request = makeRequest('https:', origHttpsReq);\n httpsCjs.get = makeGet(httpsCjs.request);\n }\n\n uninstall(): void {\n if (this._origHttpRequest) httpCjs.request = this._origHttpRequest;\n if (this._origHttpGet) httpCjs.get = this._origHttpGet;\n if (this._origHttpsRequest) httpsCjs.request = this._origHttpsRequest;\n if (this._origHttpsGet) httpsCjs.get = this._origHttpsGet;\n this._partitioned = false;\n this._defaultLatency = 0;\n }\n\n reset(): void {\n this._routes.length = 0;\n this._allCalls.length = 0;\n this._partitioned = false;\n this._defaultLatency = 0;\n }\n\n // internal\n\n private _intercept(\n url: string,\n method: string,\n headers: Record<string, string>,\n callback?: (res: FakeIncomingMessage) => void,\n ): FakeClientRequest {\n const route = this._routes.find((r) => r.matches(url));\n const fakeReq = new FakeClientRequest();\n if (callback) fakeReq.on('response', callback);\n\n fakeReq.on('_end', () => {\n const call: RecordedCall = {\n method,\n url,\n headers,\n body: fakeReq.body,\n timestamp: this._clock?.now() ?? Date.now(),\n };\n this._allCalls.push(call);\n\n // Network partition: reject with error\n if (this._partitioned) {\n fakeReq.emit('error', Object.assign(new Error(`Network partition: ${method} ${url} rejected`), { code: 'ECONNREFUSED' }));\n return;\n }\n\n if (!route) {\n fakeReq.emit('error', new Error(`No mock matched: ${method} ${url}`));\n return;\n }\n\n let result: { error?: string; status: number; headers: Record<string, string>; body: string };\n try {\n result = route.respond(call);\n } catch (err: unknown) {\n fakeReq.emit('error', err instanceof Error ? err : new Error(String(err)));\n return;\n }\n\n const deliver = (): void => {\n if (result.error) {\n fakeReq.emit('error', new Error(result.error));\n return;\n }\n const fakeRes = new FakeIncomingMessage(result.status, result.headers, result.body);\n fakeReq.emit('response', fakeRes);\n fakeRes._flush();\n };\n\n // Effective latency = route latency + global default (from fault injection)\n const latency = (route.config.latency ?? 0) + this._defaultLatency;\n\n // Spec v3: Always route through scheduler when available — even zero-latency\n // responses — so PRNG ordering applies to all same-tick HTTP completions.\n if (this._scheduler) {\n const now = this._clock?.now() ?? 0;\n const when = now + latency;\n const opId = `http-${++_httpReqCounter}`;\n this._scheduler.enqueueCompletion({\n id: opId,\n when,\n run: () => { deliver(); return Promise.resolve(); },\n });\n if (latency <= 0) {\n this._scheduler.requestRunTick?.(now);\n }\n } else if (this._clock && latency > 0) {\n this._clock.setTimeout(deliver, latency);\n } else {\n throw new Error(\n '[SimNode] HttpInterceptor: a Scheduler is required for deterministic delivery. ' +\n 'Pass { scheduler } when constructing HttpInterceptor.',\n );\n }\n });\n\n return fakeReq;\n }\n}\n\n// Helpers\n\n/** Return true if the URL targets a loopback address (localhost, 127.0.0.1, ::1). */\nfunction _isLocalUrl(url: string): boolean {\n try {\n const h = new URL(url).hostname.toLowerCase();\n return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '[::1]';\n } catch {\n return false;\n }\n}\n\nfunction normalizeArgs(\n defaultProto: string,\n args: unknown[],\n): { url: string; method: string; headers: Record<string, string>; callback?: (res: any) => void } {\n let url: string;\n let options: Record<string, any> = {};\n let callback: ((res: any) => void) | undefined;\n\n if (typeof args[0] === 'string' || args[0] instanceof URL) {\n const parsed = new URL(args[0].toString());\n url = parsed.toString();\n if (typeof args[1] === 'function') {\n callback = args[1] as (res: any) => void;\n } else if (typeof args[1] === 'object' && args[1] !== null) {\n options = args[1] as Record<string, any>;\n if (typeof args[2] === 'function') callback = args[2] as (res: any) => void;\n }\n } else {\n options = (args[0] ?? {}) as Record<string, any>;\n if (typeof args[1] === 'function') callback = args[1] as (res: any) => void;\n const proto = (options.protocol as string) ?? defaultProto;\n const host = (options.hostname ?? options.host ?? 'localhost') as string;\n const port = options.port ? `:${options.port as string}` : '';\n const path = (options.path ?? '/') as string;\n url = `${proto}//${host}${port}${path}`;\n }\n\n return {\n url,\n method: ((options.method as string) ?? 'GET').toUpperCase(),\n headers: (options.headers ?? {}) as Record<string, string>,\n callback,\n };\n}\n","import type { HttpInterceptor, IScheduler, RecordedCall } from './HttpInterceptor.js';\nimport { Buffer } from 'node:buffer';\n\nlet _fetchReqCounter = 0;\n\nexport function createFetchPatch(interceptor: HttpInterceptor, originalFetch: typeof globalThis.fetch) {\n return async function fakeFetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {\n // 1. Normalize input to URL string and extract method/headers/body\n let url: string;\n let method = 'GET';\n const headers: Record<string, string> = {};\n let body = '';\n\n if (input instanceof Request) {\n url = input.url;\n method = input.method;\n input.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n if (input.body) {\n try {\n const ab = await input.arrayBuffer();\n body = Buffer.from(ab).toString('utf-8');\n } catch {\n body = '';\n }\n }\n } else {\n url = typeof input === 'string' ? input : input.toString();\n }\n\n if (init) {\n if (init.method) method = init.method.toUpperCase();\n if (init.headers) {\n if (init.headers instanceof Headers) {\n init.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n } else if (Array.isArray(init.headers)) {\n for (const [key, value] of init.headers) {\n headers[key.toLowerCase()] = value;\n }\n } else {\n for (const [key, value] of Object.entries(init.headers)) {\n headers[key.toLowerCase()] = value as string;\n }\n }\n }\n if (init.body) {\n if (typeof init.body === 'string') {\n body = init.body;\n } else if (init.body instanceof Buffer) {\n body = init.body.toString('utf-8');\n } else {\n body = String(init.body);\n }\n }\n }\n\n return new Promise((resolve, reject) => {\n const anyInterceptor = interceptor as any;\n const route = anyInterceptor._routes.find((r: any) => r.matches(url));\n\n const call: RecordedCall = {\n method,\n url,\n headers,\n body,\n timestamp: anyInterceptor._clock?.now() ?? Date.now(),\n };\n anyInterceptor._allCalls.push(call);\n\n // Network partition check\n if (anyInterceptor._partitioned) {\n return reject(Object.assign(new TypeError(`fetch failed: Network partition active — ${method} ${url} rejected`), { code: 'ECONNREFUSED' }));\n }\n\n if (!route) {\n return reject(new TypeError(`fetch failed: No mock matched: ${method} ${url}`));\n }\n\n let result: { error?: string; status: number; headers: Record<string, string>; body: string };\n try {\n result = route.respond(call);\n } catch (err: unknown) {\n return reject(err instanceof Error ? err : new TypeError(String(err)));\n }\n\n const deliver = (): void => {\n if (result.error) {\n return reject(new TypeError(result.error));\n }\n\n const responseHeaders = new Headers(result.headers);\n const response = new Response(result.body, {\n status: result.status,\n statusText: 'MOCKED',\n headers: responseHeaders,\n });\n\n resolve(response);\n };\n\n // Effective latency = route latency + global defaultLatency (from fault injection)\n const latency = (route.config.latency ?? 0) + (anyInterceptor._defaultLatency ?? 0);\n\n // Spec v3: Always route through scheduler when available — even zero-latency\n // responses — so PRNG ordering applies to all same-tick completions.\n if (anyInterceptor._scheduler) {\n const now = anyInterceptor._clock?.now() ?? 0;\n const when = now + latency;\n const opId = `fetch-${++_fetchReqCounter}`;\n (anyInterceptor._scheduler as IScheduler).enqueueCompletion({\n id: opId,\n when,\n run: () => { deliver(); return Promise.resolve(); },\n });\n if (latency <= 0) {\n (anyInterceptor._scheduler as IScheduler).requestRunTick?.(now);\n }\n } else if (anyInterceptor._clock && latency > 0) {\n anyInterceptor._clock.setTimeout(deliver, latency);\n } else {\n throw new Error(\n '[SimNode] fetch: a Scheduler is required for deterministic delivery. ' +\n 'Pass { scheduler } when constructing HttpInterceptor.',\n );\n }\n });\n };\n}\n","import { HttpInterceptor } from './HttpInterceptor.js';\nimport { createFetchPatch } from './fetch-patch.js';\nimport { createRequire } from 'node:module';\n\nconst _require = createRequire(import.meta.url);\n\n\nexport interface HttpProxyInstallResult {\n interceptor: HttpInterceptor;\n uninstall: () => void;\n}\n\nexport function install(interceptorOrClock?: HttpInterceptor | any): HttpProxyInstallResult {\n let interceptor: HttpInterceptor;\n \n if (interceptorOrClock instanceof HttpInterceptor) {\n interceptor = interceptorOrClock;\n } else {\n interceptor = new HttpInterceptor({ clock: interceptorOrClock });\n }\n\n // 1. Install http/https patches\n interceptor.install();\n\n // 2. Install fetch patch\n const origFetch = globalThis.fetch;\n if (origFetch) {\n globalThis.fetch = createFetchPatch(interceptor, origFetch);\n }\n\n // 3. Try to patch undici if available in the module graph\n // Undici is what powers Node's native fetch. A lot of libraries (like Prisma, Axios in some cases)\n // might use undici directly.\n let undiciUninstall: (() => void) | undefined;\n try {\n const customRequire = createRequire(process.cwd() + '/'); // Attempt to require from workspace root\n \n let undici: any;\n try {\n undici = customRequire('undici');\n } catch {\n undici = _require('undici');\n }\n\n if (undici) {\n const origUndiciFetch = undici.fetch;\n const origUndiciRequest = undici.request;\n\n undici.fetch = createFetchPatch(interceptor, origUndiciFetch);\n // undici.request is a bit different API, but if any libraries use it, we can wrap it.\n // For this fix, wrapping undici.fetch handles most cases. \n // We can also wrap undici.request if necessary, but returning a dispatcher response is complex.\n // Let's at least wrap fetch.\n \n undiciUninstall = () => {\n undici.fetch = origUndiciFetch;\n undici.request = origUndiciRequest;\n };\n }\n } catch (err) {\n // ignore if undici is not found\n }\n\n function uninstall(): void {\n interceptor.uninstall();\n globalThis.fetch = origFetch;\n if (undiciUninstall) {\n undiciUninstall();\n }\n }\n\n return { interceptor, uninstall };\n}\n"],"mappings":"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","names":["body","import_node_module","_require"]}
@@ -0,0 +1,6 @@
1
+ export { HttpInterceptor } from './HttpInterceptor.js';
2
+ export type { IClock, MockResponseConfig, FailConfig, RecordedCall } from './HttpInterceptor.js';
3
+ export { install } from './install.js';
4
+ export type { HttpProxyInstallResult } from './install.js';
5
+ export { createFetchPatch } from './fetch-patch.js';
6
+ //# 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,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,YAAY,EAAE,MAAM,EAAE,kBAAkB,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACjG,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,YAAY,EAAE,sBAAsB,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,477 @@
1
+ // src/HttpInterceptor.ts
2
+ import { EventEmitter } from "events";
3
+ import * as http from "http";
4
+ import { URL } from "url";
5
+ import { createRequire } from "module";
6
+ var FakeIncomingMessage = class extends EventEmitter {
7
+ constructor(status, headers, _body) {
8
+ super();
9
+ this._body = _body;
10
+ this.statusCode = status;
11
+ this.statusMessage = http.STATUS_CODES[status] ?? "";
12
+ this.headers = headers;
13
+ }
14
+ statusCode;
15
+ statusMessage;
16
+ headers;
17
+ _flush() {
18
+ queueMicrotask(() => {
19
+ this.emit("data", Buffer.from(this._body));
20
+ this.emit("end");
21
+ });
22
+ }
23
+ };
24
+ var FakeClientRequest = class extends EventEmitter {
25
+ _chunks = [];
26
+ headersSent = false;
27
+ write(chunk) {
28
+ this._chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
29
+ return true;
30
+ }
31
+ end(chunk) {
32
+ if (chunk) this.write(chunk);
33
+ this.emit("_end");
34
+ return this;
35
+ }
36
+ get body() {
37
+ return Buffer.concat(this._chunks).toString();
38
+ }
39
+ // no-ops for compat
40
+ setHeader() {
41
+ return this;
42
+ }
43
+ getHeader() {
44
+ return void 0;
45
+ }
46
+ removeHeader() {
47
+ }
48
+ flushHeaders() {
49
+ }
50
+ setTimeout() {
51
+ return this;
52
+ }
53
+ setNoDelay() {
54
+ }
55
+ setSocketKeepAlive() {
56
+ }
57
+ abort() {
58
+ }
59
+ destroy() {
60
+ return this;
61
+ }
62
+ };
63
+ var MockRoute = class {
64
+ constructor(urlPattern, config, failConfig) {
65
+ this.urlPattern = urlPattern;
66
+ this.config = config;
67
+ this.failConfig = failConfig;
68
+ this._matchMode = config.match ?? "exact";
69
+ if (this._matchMode === "regex") {
70
+ this._regex = new RegExp(urlPattern);
71
+ }
72
+ }
73
+ calls = [];
74
+ _callCount = 0;
75
+ _matchMode;
76
+ _regex;
77
+ matches(url) {
78
+ switch (this._matchMode) {
79
+ case "exact":
80
+ return url === this.urlPattern;
81
+ case "prefix":
82
+ return url.startsWith(this.urlPattern);
83
+ case "regex":
84
+ return this._regex.test(url);
85
+ }
86
+ }
87
+ respond(call) {
88
+ this.calls.push(call);
89
+ this._callCount++;
90
+ if (this.failConfig) {
91
+ const limit = this.failConfig.after ?? 0;
92
+ if (this._callCount > limit) {
93
+ return { error: this.failConfig.error, status: 0, headers: {}, body: "" };
94
+ }
95
+ }
96
+ if (this.config.handler) {
97
+ const r = this.config.handler(call);
98
+ const body2 = typeof r.body === "string" ? r.body : JSON.stringify(r.body ?? "");
99
+ return { status: r.status, headers: r.headers ?? { "content-type": "application/json" }, body: body2 };
100
+ }
101
+ const body = typeof this.config.body === "string" ? this.config.body : JSON.stringify(this.config.body ?? "");
102
+ return {
103
+ status: this.config.status ?? 200,
104
+ headers: this.config.headers ?? { "content-type": "application/json" },
105
+ body
106
+ };
107
+ }
108
+ };
109
+ var _require = createRequire(import.meta.url);
110
+ var httpCjs = _require("node:http");
111
+ var httpsCjs = _require("node:https");
112
+ var _httpReqCounter = 0;
113
+ var HttpInterceptor = class {
114
+ _routes = [];
115
+ _allCalls = [];
116
+ _clock;
117
+ _scheduler;
118
+ _partitioned = false;
119
+ _defaultLatency = 0;
120
+ _origHttpRequest;
121
+ _origHttpGet;
122
+ _origHttpsRequest;
123
+ _origHttpsGet;
124
+ constructor(opts) {
125
+ this._clock = opts?.clock;
126
+ this._scheduler = opts?.scheduler;
127
+ }
128
+ // mock registration
129
+ mock(urlPrefix, config) {
130
+ this._routes.push(new MockRoute(urlPrefix, config));
131
+ return this;
132
+ }
133
+ fail(urlPrefix, config) {
134
+ this._routes.push(new MockRoute(urlPrefix, { status: 0 }, config));
135
+ return this;
136
+ }
137
+ calls(method, urlPrefix) {
138
+ return this._allCalls.filter((c) => {
139
+ if (method && c.method !== method.toUpperCase()) return false;
140
+ if (urlPrefix && !c.url.startsWith(urlPrefix)) return false;
141
+ return true;
142
+ });
143
+ }
144
+ /**
145
+ * Block all HTTP requests for `duration` virtual ms.
146
+ * Requests made during the partition receive a connection-refused error.
147
+ */
148
+ blockAll(duration) {
149
+ this._partitioned = true;
150
+ if (this._clock) {
151
+ this._clock.setTimeout(() => {
152
+ this._partitioned = false;
153
+ }, duration);
154
+ } else {
155
+ console.warn(
156
+ "SimNode: HttpInterceptor.blockAll() called without a virtual clock. Falling back to real setTimeout \u2014 partition duration will be wall-clock, not deterministic."
157
+ );
158
+ setTimeout(() => {
159
+ this._partitioned = false;
160
+ }, duration);
161
+ }
162
+ }
163
+ /**
164
+ * Add a global extra latency (ms) to all HTTP responses.
165
+ * Used by FaultInjector.slowDatabase() for HTTP-based DBs.
166
+ */
167
+ setDefaultLatency(ms) {
168
+ this._defaultLatency = ms;
169
+ }
170
+ // patching
171
+ install() {
172
+ this._origHttpRequest = httpCjs.request;
173
+ this._origHttpGet = httpCjs.get;
174
+ this._origHttpsRequest = httpsCjs.request;
175
+ this._origHttpsGet = httpsCjs.get;
176
+ const self = this;
177
+ const origHttpReq = this._origHttpRequest;
178
+ const origHttpsReq = this._origHttpsRequest;
179
+ const makeRequest = (proto, origReq) => function fakeRequest(...args) {
180
+ const { url, method, headers, callback } = normalizeArgs(proto, args);
181
+ if (_isLocalUrl(url) && !self._routes.find((r) => r.matches(url))) {
182
+ return origReq.apply(null, args);
183
+ }
184
+ return self._intercept(url, method, headers, callback);
185
+ };
186
+ const makeGet = (reqFn) => function fakeGet(...args) {
187
+ const req = reqFn(...args);
188
+ req.end();
189
+ return req;
190
+ };
191
+ httpCjs.request = makeRequest("http:", origHttpReq);
192
+ httpCjs.get = makeGet(httpCjs.request);
193
+ httpsCjs.request = makeRequest("https:", origHttpsReq);
194
+ httpsCjs.get = makeGet(httpsCjs.request);
195
+ }
196
+ uninstall() {
197
+ if (this._origHttpRequest) httpCjs.request = this._origHttpRequest;
198
+ if (this._origHttpGet) httpCjs.get = this._origHttpGet;
199
+ if (this._origHttpsRequest) httpsCjs.request = this._origHttpsRequest;
200
+ if (this._origHttpsGet) httpsCjs.get = this._origHttpsGet;
201
+ this._partitioned = false;
202
+ this._defaultLatency = 0;
203
+ }
204
+ reset() {
205
+ this._routes.length = 0;
206
+ this._allCalls.length = 0;
207
+ this._partitioned = false;
208
+ this._defaultLatency = 0;
209
+ }
210
+ // internal
211
+ _intercept(url, method, headers, callback) {
212
+ const route = this._routes.find((r) => r.matches(url));
213
+ const fakeReq = new FakeClientRequest();
214
+ if (callback) fakeReq.on("response", callback);
215
+ fakeReq.on("_end", () => {
216
+ const call = {
217
+ method,
218
+ url,
219
+ headers,
220
+ body: fakeReq.body,
221
+ timestamp: this._clock?.now() ?? Date.now()
222
+ };
223
+ this._allCalls.push(call);
224
+ if (this._partitioned) {
225
+ fakeReq.emit("error", Object.assign(new Error(`Network partition: ${method} ${url} rejected`), { code: "ECONNREFUSED" }));
226
+ return;
227
+ }
228
+ if (!route) {
229
+ fakeReq.emit("error", new Error(`No mock matched: ${method} ${url}`));
230
+ return;
231
+ }
232
+ let result;
233
+ try {
234
+ result = route.respond(call);
235
+ } catch (err) {
236
+ fakeReq.emit("error", err instanceof Error ? err : new Error(String(err)));
237
+ return;
238
+ }
239
+ const deliver = () => {
240
+ if (result.error) {
241
+ fakeReq.emit("error", new Error(result.error));
242
+ return;
243
+ }
244
+ const fakeRes = new FakeIncomingMessage(result.status, result.headers, result.body);
245
+ fakeReq.emit("response", fakeRes);
246
+ fakeRes._flush();
247
+ };
248
+ const latency = (route.config.latency ?? 0) + this._defaultLatency;
249
+ if (this._scheduler) {
250
+ const now = this._clock?.now() ?? 0;
251
+ const when = now + latency;
252
+ const opId = `http-${++_httpReqCounter}`;
253
+ this._scheduler.enqueueCompletion({
254
+ id: opId,
255
+ when,
256
+ run: () => {
257
+ deliver();
258
+ return Promise.resolve();
259
+ }
260
+ });
261
+ if (latency <= 0) {
262
+ this._scheduler.requestRunTick?.(now);
263
+ }
264
+ } else if (this._clock && latency > 0) {
265
+ this._clock.setTimeout(deliver, latency);
266
+ } else {
267
+ throw new Error(
268
+ "[SimNode] HttpInterceptor: a Scheduler is required for deterministic delivery. Pass { scheduler } when constructing HttpInterceptor."
269
+ );
270
+ }
271
+ });
272
+ return fakeReq;
273
+ }
274
+ };
275
+ function _isLocalUrl(url) {
276
+ try {
277
+ const h = new URL(url).hostname.toLowerCase();
278
+ return h === "localhost" || h === "127.0.0.1" || h === "::1" || h === "[::1]";
279
+ } catch {
280
+ return false;
281
+ }
282
+ }
283
+ function normalizeArgs(defaultProto, args) {
284
+ let url;
285
+ let options = {};
286
+ let callback;
287
+ if (typeof args[0] === "string" || args[0] instanceof URL) {
288
+ const parsed = new URL(args[0].toString());
289
+ url = parsed.toString();
290
+ if (typeof args[1] === "function") {
291
+ callback = args[1];
292
+ } else if (typeof args[1] === "object" && args[1] !== null) {
293
+ options = args[1];
294
+ if (typeof args[2] === "function") callback = args[2];
295
+ }
296
+ } else {
297
+ options = args[0] ?? {};
298
+ if (typeof args[1] === "function") callback = args[1];
299
+ const proto = options.protocol ?? defaultProto;
300
+ const host = options.hostname ?? options.host ?? "localhost";
301
+ const port = options.port ? `:${options.port}` : "";
302
+ const path = options.path ?? "/";
303
+ url = `${proto}//${host}${port}${path}`;
304
+ }
305
+ return {
306
+ url,
307
+ method: (options.method ?? "GET").toUpperCase(),
308
+ headers: options.headers ?? {},
309
+ callback
310
+ };
311
+ }
312
+
313
+ // src/fetch-patch.ts
314
+ import { Buffer as Buffer2 } from "buffer";
315
+ var _fetchReqCounter = 0;
316
+ function createFetchPatch(interceptor, originalFetch) {
317
+ return async function fakeFetch(input, init) {
318
+ let url;
319
+ let method = "GET";
320
+ const headers = {};
321
+ let body = "";
322
+ if (input instanceof Request) {
323
+ url = input.url;
324
+ method = input.method;
325
+ input.headers.forEach((value, key) => {
326
+ headers[key.toLowerCase()] = value;
327
+ });
328
+ if (input.body) {
329
+ try {
330
+ const ab = await input.arrayBuffer();
331
+ body = Buffer2.from(ab).toString("utf-8");
332
+ } catch {
333
+ body = "";
334
+ }
335
+ }
336
+ } else {
337
+ url = typeof input === "string" ? input : input.toString();
338
+ }
339
+ if (init) {
340
+ if (init.method) method = init.method.toUpperCase();
341
+ if (init.headers) {
342
+ if (init.headers instanceof Headers) {
343
+ init.headers.forEach((value, key) => {
344
+ headers[key.toLowerCase()] = value;
345
+ });
346
+ } else if (Array.isArray(init.headers)) {
347
+ for (const [key, value] of init.headers) {
348
+ headers[key.toLowerCase()] = value;
349
+ }
350
+ } else {
351
+ for (const [key, value] of Object.entries(init.headers)) {
352
+ headers[key.toLowerCase()] = value;
353
+ }
354
+ }
355
+ }
356
+ if (init.body) {
357
+ if (typeof init.body === "string") {
358
+ body = init.body;
359
+ } else if (init.body instanceof Buffer2) {
360
+ body = init.body.toString("utf-8");
361
+ } else {
362
+ body = String(init.body);
363
+ }
364
+ }
365
+ }
366
+ return new Promise((resolve, reject) => {
367
+ const anyInterceptor = interceptor;
368
+ const route = anyInterceptor._routes.find((r) => r.matches(url));
369
+ const call = {
370
+ method,
371
+ url,
372
+ headers,
373
+ body,
374
+ timestamp: anyInterceptor._clock?.now() ?? Date.now()
375
+ };
376
+ anyInterceptor._allCalls.push(call);
377
+ if (anyInterceptor._partitioned) {
378
+ return reject(Object.assign(new TypeError(`fetch failed: Network partition active \u2014 ${method} ${url} rejected`), { code: "ECONNREFUSED" }));
379
+ }
380
+ if (!route) {
381
+ return reject(new TypeError(`fetch failed: No mock matched: ${method} ${url}`));
382
+ }
383
+ let result;
384
+ try {
385
+ result = route.respond(call);
386
+ } catch (err) {
387
+ return reject(err instanceof Error ? err : new TypeError(String(err)));
388
+ }
389
+ const deliver = () => {
390
+ if (result.error) {
391
+ return reject(new TypeError(result.error));
392
+ }
393
+ const responseHeaders = new Headers(result.headers);
394
+ const response = new Response(result.body, {
395
+ status: result.status,
396
+ statusText: "MOCKED",
397
+ headers: responseHeaders
398
+ });
399
+ resolve(response);
400
+ };
401
+ const latency = (route.config.latency ?? 0) + (anyInterceptor._defaultLatency ?? 0);
402
+ if (anyInterceptor._scheduler) {
403
+ const now = anyInterceptor._clock?.now() ?? 0;
404
+ const when = now + latency;
405
+ const opId = `fetch-${++_fetchReqCounter}`;
406
+ anyInterceptor._scheduler.enqueueCompletion({
407
+ id: opId,
408
+ when,
409
+ run: () => {
410
+ deliver();
411
+ return Promise.resolve();
412
+ }
413
+ });
414
+ if (latency <= 0) {
415
+ anyInterceptor._scheduler.requestRunTick?.(now);
416
+ }
417
+ } else if (anyInterceptor._clock && latency > 0) {
418
+ anyInterceptor._clock.setTimeout(deliver, latency);
419
+ } else {
420
+ throw new Error(
421
+ "[SimNode] fetch: a Scheduler is required for deterministic delivery. Pass { scheduler } when constructing HttpInterceptor."
422
+ );
423
+ }
424
+ });
425
+ };
426
+ }
427
+
428
+ // src/install.ts
429
+ import { createRequire as createRequire2 } from "module";
430
+ var _require2 = createRequire2(import.meta.url);
431
+ function install(interceptorOrClock) {
432
+ let interceptor;
433
+ if (interceptorOrClock instanceof HttpInterceptor) {
434
+ interceptor = interceptorOrClock;
435
+ } else {
436
+ interceptor = new HttpInterceptor({ clock: interceptorOrClock });
437
+ }
438
+ interceptor.install();
439
+ const origFetch = globalThis.fetch;
440
+ if (origFetch) {
441
+ globalThis.fetch = createFetchPatch(interceptor, origFetch);
442
+ }
443
+ let undiciUninstall;
444
+ try {
445
+ const customRequire = createRequire2(process.cwd() + "/");
446
+ let undici;
447
+ try {
448
+ undici = customRequire("undici");
449
+ } catch {
450
+ undici = _require2("undici");
451
+ }
452
+ if (undici) {
453
+ const origUndiciFetch = undici.fetch;
454
+ const origUndiciRequest = undici.request;
455
+ undici.fetch = createFetchPatch(interceptor, origUndiciFetch);
456
+ undiciUninstall = () => {
457
+ undici.fetch = origUndiciFetch;
458
+ undici.request = origUndiciRequest;
459
+ };
460
+ }
461
+ } catch (err) {
462
+ }
463
+ function uninstall() {
464
+ interceptor.uninstall();
465
+ globalThis.fetch = origFetch;
466
+ if (undiciUninstall) {
467
+ undiciUninstall();
468
+ }
469
+ }
470
+ return { interceptor, uninstall };
471
+ }
472
+ export {
473
+ HttpInterceptor,
474
+ createFetchPatch,
475
+ install
476
+ };
477
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/HttpInterceptor.ts","../src/fetch-patch.ts","../src/install.ts"],"sourcesContent":["import { EventEmitter } from 'node:events';\nimport * as http from 'node:http';\nimport * as https from 'node:https';\nimport { URL } from 'node:url';\n\n// Types\n\n/** Minimal virtual-clock interface (duck-typed). */\nexport interface IClock {\n now(): number;\n setTimeout(cb: (...args: unknown[]) => void, delay: number): number;\n}\n\n/** Minimal scheduler interface (duck-typed). */\nexport interface IScheduler {\n enqueueCompletion(op: { id: string; when: number; run: () => Promise<void> | void }): void;\n requestRunTick?(virtualTime: number): void;\n}\n\nexport interface MockResponseConfig {\n status?: number;\n headers?: Record<string, string>;\n body?: unknown;\n /** Virtual-clock latency in ms (requires an IClock instance). */\n latency?: number;\n /** Dynamic handler — overrides static body/status when provided. */\n handler?: (call: RecordedCall) => { status: number; body?: unknown; headers?: Record<string, string> };\n /**\n * URL match mode.\n * - `'exact'` (default): `url === urlPattern` — only matches the exact URL.\n * - `'prefix'`: `url.startsWith(urlPattern)` — matches any URL that begins with the pattern.\n * - `'regex'`: `new RegExp(urlPattern).test(url)` — matches via regular expression.\n */\n match?: 'exact' | 'prefix' | 'regex';\n}\n\nexport interface FailConfig {\n /** Succeed the first N calls, then error. */\n after?: number;\n error: string;\n}\n\nexport interface RecordedCall {\n method: string;\n url: string;\n headers: Record<string, string>;\n body: string;\n timestamp: number;\n}\n\n// Fakes\n\nclass FakeIncomingMessage extends EventEmitter {\n statusCode: number;\n statusMessage: string;\n headers: Record<string, string>;\n\n constructor(status: number, headers: Record<string, string>, private readonly _body: string) {\n super();\n this.statusCode = status;\n this.statusMessage = http.STATUS_CODES[status] ?? '';\n this.headers = headers;\n }\n\n _flush(): void {\n queueMicrotask(() => {\n this.emit('data', Buffer.from(this._body));\n this.emit('end');\n });\n }\n}\n\nclass FakeClientRequest extends EventEmitter {\n private _chunks: Buffer[] = [];\n headersSent = false;\n\n write(chunk: string | Buffer): boolean {\n this._chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));\n return true;\n }\n\n end(chunk?: string | Buffer): this {\n if (chunk) this.write(chunk);\n this.emit('_end');\n return this;\n }\n\n get body(): string {\n return Buffer.concat(this._chunks).toString();\n }\n\n // no-ops for compat\n setHeader(): this { return this; }\n getHeader(): undefined { return undefined; }\n removeHeader(): void {}\n flushHeaders(): void {}\n setTimeout(): this { return this; }\n setNoDelay(): void {}\n setSocketKeepAlive(): void {}\n abort(): void {}\n destroy(): this { return this; }\n}\n\n// Route\n\nclass MockRoute {\n readonly calls: RecordedCall[] = [];\n private _callCount = 0;\n private readonly _matchMode: 'exact' | 'prefix' | 'regex';\n private readonly _regex?: RegExp;\n\n constructor(\n readonly urlPattern: string,\n readonly config: MockResponseConfig,\n readonly failConfig?: FailConfig,\n ) {\n this._matchMode = config.match ?? 'exact';\n if (this._matchMode === 'regex') {\n this._regex = new RegExp(urlPattern);\n }\n }\n\n matches(url: string): boolean {\n switch (this._matchMode) {\n case 'exact': return url === this.urlPattern;\n case 'prefix': return url.startsWith(this.urlPattern);\n case 'regex': return this._regex!.test(url);\n }\n }\n\n respond(call: RecordedCall): { error?: string; status: number; headers: Record<string, string>; body: string } {\n this.calls.push(call);\n this._callCount++;\n\n if (this.failConfig) {\n const limit = this.failConfig.after ?? 0;\n if (this._callCount > limit) {\n return { error: this.failConfig.error, status: 0, headers: {}, body: '' };\n }\n }\n\n if (this.config.handler) {\n const r = this.config.handler(call);\n const body = typeof r.body === 'string' ? r.body : JSON.stringify(r.body ?? '');\n return { status: r.status, headers: r.headers ?? { 'content-type': 'application/json' }, body };\n }\n\n const body = typeof this.config.body === 'string'\n ? this.config.body\n : JSON.stringify(this.config.body ?? '');\n return {\n status: this.config.status ?? 200,\n headers: this.config.headers ?? { 'content-type': 'application/json' },\n body,\n };\n }\n}\n\n// Interceptor\n\n// We need to write directly to the module object's internal property.\n// ESM `import * as http` creates a frozen namespace — we can't assign\n// to it. Instead we grab the *CJS* module from require.cache and\n// patch the exports object there, which IS mutable.\n//\n// We use createRequire so this file can stay ESM.\nimport { createRequire } from 'node:module';\nconst _require = createRequire(import.meta.url);\nconst httpCjs = _require('node:http') as typeof http;\nconst httpsCjs = _require('node:https') as typeof https;\n\nlet _httpReqCounter = 0;\n\nexport class HttpInterceptor {\n private readonly _routes: MockRoute[] = [];\n private readonly _allCalls: RecordedCall[] = [];\n private readonly _clock?: IClock;\n private readonly _scheduler?: IScheduler;\n\n private _partitioned = false;\n private _defaultLatency = 0;\n\n private _origHttpRequest?: typeof http.request;\n private _origHttpGet?: typeof http.get;\n private _origHttpsRequest?: typeof https.request;\n private _origHttpsGet?: typeof https.get;\n\n constructor(opts?: { clock?: IClock; scheduler?: IScheduler }) {\n this._clock = opts?.clock;\n this._scheduler = opts?.scheduler;\n }\n\n // mock registration\n\n mock(urlPrefix: string, config: MockResponseConfig): this {\n this._routes.push(new MockRoute(urlPrefix, config));\n return this;\n }\n\n fail(urlPrefix: string, config: FailConfig): this {\n this._routes.push(new MockRoute(urlPrefix, { status: 0 }, config));\n return this;\n }\n\n calls(method?: string, urlPrefix?: string): RecordedCall[] {\n return this._allCalls.filter((c) => {\n if (method && c.method !== method.toUpperCase()) return false;\n if (urlPrefix && !c.url.startsWith(urlPrefix)) return false;\n return true;\n });\n }\n\n /**\n * Block all HTTP requests for `duration` virtual ms.\n * Requests made during the partition receive a connection-refused error.\n */\n blockAll(duration: number): void {\n this._partitioned = true;\n if (this._clock) {\n this._clock.setTimeout(() => { this._partitioned = false; }, duration);\n } else {\n console.warn(\n 'SimNode: HttpInterceptor.blockAll() called without a virtual clock. ' +\n 'Falling back to real setTimeout — partition duration will be wall-clock, not deterministic.',\n );\n setTimeout(() => { this._partitioned = false; }, duration);\n }\n }\n\n /**\n * Add a global extra latency (ms) to all HTTP responses.\n * Used by FaultInjector.slowDatabase() for HTTP-based DBs.\n */\n setDefaultLatency(ms: number): void {\n this._defaultLatency = ms;\n }\n\n // patching\n\n install(): void {\n this._origHttpRequest = httpCjs.request;\n this._origHttpGet = httpCjs.get;\n this._origHttpsRequest = httpsCjs.request;\n this._origHttpsGet = httpsCjs.get;\n\n const self = this;\n\n const origHttpReq = this._origHttpRequest!;\n const origHttpsReq = this._origHttpsRequest!;\n\n const makeRequest = (proto: string, origReq: typeof http.request) =>\n function fakeRequest(this: unknown, ...args: unknown[]): FakeClientRequest | http.ClientRequest {\n const { url, method, headers, callback } = normalizeArgs(proto, args);\n // Passthrough: unmatched localhost requests (supertest, local test\n // servers) ALWAYS go through the real http stack — even during a\n // network partition. blockAll() only affects external / mocked URLs.\n if (_isLocalUrl(url) && !self._routes.find(r => r.matches(url))) {\n return origReq.apply(null, args as any);\n }\n return self._intercept(url, method, headers, callback);\n } as unknown as typeof http.request;\n\n const makeGet = (reqFn: typeof http.request) =>\n function fakeGet(this: unknown, ...args: unknown[]): FakeClientRequest | http.ClientRequest {\n const req = (reqFn as any)(...args);\n req.end();\n return req;\n } as unknown as typeof http.get;\n\n httpCjs.request = makeRequest('http:', origHttpReq);\n httpCjs.get = makeGet(httpCjs.request);\n httpsCjs.request = makeRequest('https:', origHttpsReq);\n httpsCjs.get = makeGet(httpsCjs.request);\n }\n\n uninstall(): void {\n if (this._origHttpRequest) httpCjs.request = this._origHttpRequest;\n if (this._origHttpGet) httpCjs.get = this._origHttpGet;\n if (this._origHttpsRequest) httpsCjs.request = this._origHttpsRequest;\n if (this._origHttpsGet) httpsCjs.get = this._origHttpsGet;\n this._partitioned = false;\n this._defaultLatency = 0;\n }\n\n reset(): void {\n this._routes.length = 0;\n this._allCalls.length = 0;\n this._partitioned = false;\n this._defaultLatency = 0;\n }\n\n // internal\n\n private _intercept(\n url: string,\n method: string,\n headers: Record<string, string>,\n callback?: (res: FakeIncomingMessage) => void,\n ): FakeClientRequest {\n const route = this._routes.find((r) => r.matches(url));\n const fakeReq = new FakeClientRequest();\n if (callback) fakeReq.on('response', callback);\n\n fakeReq.on('_end', () => {\n const call: RecordedCall = {\n method,\n url,\n headers,\n body: fakeReq.body,\n timestamp: this._clock?.now() ?? Date.now(),\n };\n this._allCalls.push(call);\n\n // Network partition: reject with error\n if (this._partitioned) {\n fakeReq.emit('error', Object.assign(new Error(`Network partition: ${method} ${url} rejected`), { code: 'ECONNREFUSED' }));\n return;\n }\n\n if (!route) {\n fakeReq.emit('error', new Error(`No mock matched: ${method} ${url}`));\n return;\n }\n\n let result: { error?: string; status: number; headers: Record<string, string>; body: string };\n try {\n result = route.respond(call);\n } catch (err: unknown) {\n fakeReq.emit('error', err instanceof Error ? err : new Error(String(err)));\n return;\n }\n\n const deliver = (): void => {\n if (result.error) {\n fakeReq.emit('error', new Error(result.error));\n return;\n }\n const fakeRes = new FakeIncomingMessage(result.status, result.headers, result.body);\n fakeReq.emit('response', fakeRes);\n fakeRes._flush();\n };\n\n // Effective latency = route latency + global default (from fault injection)\n const latency = (route.config.latency ?? 0) + this._defaultLatency;\n\n // Spec v3: Always route through scheduler when available — even zero-latency\n // responses — so PRNG ordering applies to all same-tick HTTP completions.\n if (this._scheduler) {\n const now = this._clock?.now() ?? 0;\n const when = now + latency;\n const opId = `http-${++_httpReqCounter}`;\n this._scheduler.enqueueCompletion({\n id: opId,\n when,\n run: () => { deliver(); return Promise.resolve(); },\n });\n if (latency <= 0) {\n this._scheduler.requestRunTick?.(now);\n }\n } else if (this._clock && latency > 0) {\n this._clock.setTimeout(deliver, latency);\n } else {\n throw new Error(\n '[SimNode] HttpInterceptor: a Scheduler is required for deterministic delivery. ' +\n 'Pass { scheduler } when constructing HttpInterceptor.',\n );\n }\n });\n\n return fakeReq;\n }\n}\n\n// Helpers\n\n/** Return true if the URL targets a loopback address (localhost, 127.0.0.1, ::1). */\nfunction _isLocalUrl(url: string): boolean {\n try {\n const h = new URL(url).hostname.toLowerCase();\n return h === 'localhost' || h === '127.0.0.1' || h === '::1' || h === '[::1]';\n } catch {\n return false;\n }\n}\n\nfunction normalizeArgs(\n defaultProto: string,\n args: unknown[],\n): { url: string; method: string; headers: Record<string, string>; callback?: (res: any) => void } {\n let url: string;\n let options: Record<string, any> = {};\n let callback: ((res: any) => void) | undefined;\n\n if (typeof args[0] === 'string' || args[0] instanceof URL) {\n const parsed = new URL(args[0].toString());\n url = parsed.toString();\n if (typeof args[1] === 'function') {\n callback = args[1] as (res: any) => void;\n } else if (typeof args[1] === 'object' && args[1] !== null) {\n options = args[1] as Record<string, any>;\n if (typeof args[2] === 'function') callback = args[2] as (res: any) => void;\n }\n } else {\n options = (args[0] ?? {}) as Record<string, any>;\n if (typeof args[1] === 'function') callback = args[1] as (res: any) => void;\n const proto = (options.protocol as string) ?? defaultProto;\n const host = (options.hostname ?? options.host ?? 'localhost') as string;\n const port = options.port ? `:${options.port as string}` : '';\n const path = (options.path ?? '/') as string;\n url = `${proto}//${host}${port}${path}`;\n }\n\n return {\n url,\n method: ((options.method as string) ?? 'GET').toUpperCase(),\n headers: (options.headers ?? {}) as Record<string, string>,\n callback,\n };\n}\n","import type { HttpInterceptor, IScheduler, RecordedCall } from './HttpInterceptor.js';\nimport { Buffer } from 'node:buffer';\n\nlet _fetchReqCounter = 0;\n\nexport function createFetchPatch(interceptor: HttpInterceptor, originalFetch: typeof globalThis.fetch) {\n return async function fakeFetch(input: string | URL | Request, init?: RequestInit): Promise<Response> {\n // 1. Normalize input to URL string and extract method/headers/body\n let url: string;\n let method = 'GET';\n const headers: Record<string, string> = {};\n let body = '';\n\n if (input instanceof Request) {\n url = input.url;\n method = input.method;\n input.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n if (input.body) {\n try {\n const ab = await input.arrayBuffer();\n body = Buffer.from(ab).toString('utf-8');\n } catch {\n body = '';\n }\n }\n } else {\n url = typeof input === 'string' ? input : input.toString();\n }\n\n if (init) {\n if (init.method) method = init.method.toUpperCase();\n if (init.headers) {\n if (init.headers instanceof Headers) {\n init.headers.forEach((value, key) => {\n headers[key.toLowerCase()] = value;\n });\n } else if (Array.isArray(init.headers)) {\n for (const [key, value] of init.headers) {\n headers[key.toLowerCase()] = value;\n }\n } else {\n for (const [key, value] of Object.entries(init.headers)) {\n headers[key.toLowerCase()] = value as string;\n }\n }\n }\n if (init.body) {\n if (typeof init.body === 'string') {\n body = init.body;\n } else if (init.body instanceof Buffer) {\n body = init.body.toString('utf-8');\n } else {\n body = String(init.body);\n }\n }\n }\n\n return new Promise((resolve, reject) => {\n const anyInterceptor = interceptor as any;\n const route = anyInterceptor._routes.find((r: any) => r.matches(url));\n\n const call: RecordedCall = {\n method,\n url,\n headers,\n body,\n timestamp: anyInterceptor._clock?.now() ?? Date.now(),\n };\n anyInterceptor._allCalls.push(call);\n\n // Network partition check\n if (anyInterceptor._partitioned) {\n return reject(Object.assign(new TypeError(`fetch failed: Network partition active — ${method} ${url} rejected`), { code: 'ECONNREFUSED' }));\n }\n\n if (!route) {\n return reject(new TypeError(`fetch failed: No mock matched: ${method} ${url}`));\n }\n\n let result: { error?: string; status: number; headers: Record<string, string>; body: string };\n try {\n result = route.respond(call);\n } catch (err: unknown) {\n return reject(err instanceof Error ? err : new TypeError(String(err)));\n }\n\n const deliver = (): void => {\n if (result.error) {\n return reject(new TypeError(result.error));\n }\n\n const responseHeaders = new Headers(result.headers);\n const response = new Response(result.body, {\n status: result.status,\n statusText: 'MOCKED',\n headers: responseHeaders,\n });\n\n resolve(response);\n };\n\n // Effective latency = route latency + global defaultLatency (from fault injection)\n const latency = (route.config.latency ?? 0) + (anyInterceptor._defaultLatency ?? 0);\n\n // Spec v3: Always route through scheduler when available — even zero-latency\n // responses — so PRNG ordering applies to all same-tick completions.\n if (anyInterceptor._scheduler) {\n const now = anyInterceptor._clock?.now() ?? 0;\n const when = now + latency;\n const opId = `fetch-${++_fetchReqCounter}`;\n (anyInterceptor._scheduler as IScheduler).enqueueCompletion({\n id: opId,\n when,\n run: () => { deliver(); return Promise.resolve(); },\n });\n if (latency <= 0) {\n (anyInterceptor._scheduler as IScheduler).requestRunTick?.(now);\n }\n } else if (anyInterceptor._clock && latency > 0) {\n anyInterceptor._clock.setTimeout(deliver, latency);\n } else {\n throw new Error(\n '[SimNode] fetch: a Scheduler is required for deterministic delivery. ' +\n 'Pass { scheduler } when constructing HttpInterceptor.',\n );\n }\n });\n };\n}\n","import { HttpInterceptor } from './HttpInterceptor.js';\nimport { createFetchPatch } from './fetch-patch.js';\nimport { createRequire } from 'node:module';\n\nconst _require = createRequire(import.meta.url);\n\n\nexport interface HttpProxyInstallResult {\n interceptor: HttpInterceptor;\n uninstall: () => void;\n}\n\nexport function install(interceptorOrClock?: HttpInterceptor | any): HttpProxyInstallResult {\n let interceptor: HttpInterceptor;\n \n if (interceptorOrClock instanceof HttpInterceptor) {\n interceptor = interceptorOrClock;\n } else {\n interceptor = new HttpInterceptor({ clock: interceptorOrClock });\n }\n\n // 1. Install http/https patches\n interceptor.install();\n\n // 2. Install fetch patch\n const origFetch = globalThis.fetch;\n if (origFetch) {\n globalThis.fetch = createFetchPatch(interceptor, origFetch);\n }\n\n // 3. Try to patch undici if available in the module graph\n // Undici is what powers Node's native fetch. A lot of libraries (like Prisma, Axios in some cases)\n // might use undici directly.\n let undiciUninstall: (() => void) | undefined;\n try {\n const customRequire = createRequire(process.cwd() + '/'); // Attempt to require from workspace root\n \n let undici: any;\n try {\n undici = customRequire('undici');\n } catch {\n undici = _require('undici');\n }\n\n if (undici) {\n const origUndiciFetch = undici.fetch;\n const origUndiciRequest = undici.request;\n\n undici.fetch = createFetchPatch(interceptor, origUndiciFetch);\n // undici.request is a bit different API, but if any libraries use it, we can wrap it.\n // For this fix, wrapping undici.fetch handles most cases. \n // We can also wrap undici.request if necessary, but returning a dispatcher response is complex.\n // Let's at least wrap fetch.\n \n undiciUninstall = () => {\n undici.fetch = origUndiciFetch;\n undici.request = origUndiciRequest;\n };\n }\n } catch (err) {\n // ignore if undici is not found\n }\n\n function uninstall(): void {\n interceptor.uninstall();\n globalThis.fetch = origFetch;\n if (undiciUninstall) {\n undiciUninstall();\n }\n }\n\n return { interceptor, uninstall };\n}\n"],"mappings":"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","names":["body","Buffer","createRequire","_require"]}
@@ -0,0 +1,7 @@
1
+ import { HttpInterceptor } from './HttpInterceptor.js';
2
+ export interface HttpProxyInstallResult {
3
+ interceptor: HttpInterceptor;
4
+ uninstall: () => void;
5
+ }
6
+ export declare function install(interceptorOrClock?: HttpInterceptor | any): HttpProxyInstallResult;
7
+ //# 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,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAOvD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,eAAe,CAAC;IAC7B,SAAS,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,OAAO,CAAC,kBAAkB,CAAC,EAAE,eAAe,GAAG,GAAG,GAAG,sBAAsB,CA4D1F"}
package/package.json ADDED
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@crashlab/http-proxy",
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
+ }