@jaredwray/mockhttp 2.1.0 → 3.0.1

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/dist/index.d.mts CHANGED
@@ -1,6 +1,100 @@
1
1
  import { RateLimitPluginOptions } from "@fastify/rate-limit";
2
2
  import { FastifyInstance, FastifyRequest } from "fastify";
3
3
  import { Hookified, HookifiedOptions } from "hookified";
4
+ import Keyv from "keyv";
5
+ //#region src/bin-store-diagnostics.d.ts
6
+ type BinStoreErrorInfo = {
7
+ name: string;
8
+ message: string;
9
+ code: string | number | null;
10
+ };
11
+ type BinHealth = {
12
+ status: "ok";
13
+ bins: {
14
+ store: "mongo" | "memory";
15
+ connected: true;
16
+ latencyMs: number;
17
+ uriPresent: boolean;
18
+ uri?: UriDiagnostics;
19
+ };
20
+ } | {
21
+ status: "error";
22
+ bins: {
23
+ store: "mongo" | "memory";
24
+ connected: false;
25
+ uriPresent: boolean;
26
+ uri?: UriDiagnostics;
27
+ error: BinStoreErrorInfo;
28
+ };
29
+ };
30
+ /** Redacted URI facts for `GET /healthz`. No password and no full URI. */
31
+ type UriDiagnostics = {
32
+ length: number;
33
+ leadingWhitespace: boolean;
34
+ trailingWhitespace: boolean;
35
+ newline: boolean;
36
+ wrappedQuotes: boolean;
37
+ atCount: number;
38
+ passwordReserved: string[];
39
+ passwordHasPercent: boolean;
40
+ };
41
+ //#endregion
42
+ //#region src/certificate.d.ts
43
+ type CertificateOptions = {
44
+ /**
45
+ * Common Name for the certificate subject. Defaults to 'localhost'.
46
+ */
47
+ commonName?: string;
48
+ /**
49
+ * Subject Alternative Names. Defaults to [dns:localhost, ip:127.0.0.1, ip:::1].
50
+ */
51
+ altNames?: Array<{
52
+ type: "dns";
53
+ value: string;
54
+ } | {
55
+ type: "ip";
56
+ value: string;
57
+ }>;
58
+ /**
59
+ * Number of days the certificate is valid. Defaults to 365.
60
+ */
61
+ validityDays?: number;
62
+ /**
63
+ * RSA key size in bits. Defaults to 2048.
64
+ */
65
+ keySize?: number;
66
+ };
67
+ type CertificateResult = {
68
+ /**
69
+ * PEM-encoded X.509 certificate.
70
+ */
71
+ cert: string;
72
+ /**
73
+ * PEM-encoded PKCS#8 private key.
74
+ */
75
+ key: string;
76
+ };
77
+ type CertificateFileOptions = CertificateOptions & {
78
+ /**
79
+ * File path to write the certificate PEM.
80
+ */
81
+ certPath: string;
82
+ /**
83
+ * File path to write the private key PEM.
84
+ */
85
+ keyPath: string;
86
+ };
87
+ /**
88
+ * Generate a self-signed certificate using only Node.js built-in crypto.
89
+ * Returns PEM-encoded certificate and private key strings.
90
+ */
91
+ export declare function generateCertificate(options?: CertificateOptions): CertificateResult;
92
+ /**
93
+ * Generate a self-signed certificate and write PEM files to disk.
94
+ * Returns the same CertificateResult as generateCertificate().
95
+ */
96
+ export declare function generateCertificateFiles(options: CertificateFileOptions): Promise<CertificateResult>;
97
+ //#endregion
4
98
  //#region src/bin-store.d.ts
5
99
  /**
6
100
  * A request captured by a bin, including method, headers, body and metadata.
@@ -48,179 +142,110 @@ type Bin = {
48
142
  /** Number of requests currently stored in the bin */
49
143
  requestCount: number;
50
144
  };
145
+ //#endregion
146
+ //#region src/mock-http-bins.d.ts
51
147
  /**
52
- * Storage backend for bins and their captured requests. The default
53
- * implementation is in-memory; alternative backends (Redis, SQLite, etc.) can
54
- * implement this interface without changes to the rest of the codebase.
55
- */
56
- interface BinStore {
57
- /** Create a new bin record. */
58
- createBin(bin: Bin): void;
59
- /** Look up a bin by id. */
60
- getBin(id: string): Bin | undefined;
61
- /** Return all bins. */
62
- listBins(): Bin[];
63
- /** Remove a bin and all its captured requests. Returns true if removed. */
64
- deleteBin(id: string): boolean;
65
- /**
66
- * Append a captured request to a bin, dropping the oldest if the bin has
67
- * already reached `max` requests (FIFO).
68
- */
69
- addRequest(binId: string, req: CapturedRequest, max: number): void;
70
- /** List captured requests for a bin (newest first). */
71
- getRequests(binId: string): CapturedRequest[];
72
- /** Look up a single captured request by id. */
73
- getRequest(binId: string, reqId: string): CapturedRequest | undefined;
74
- /** Clear all captured requests in a bin (the bin itself is preserved). */
75
- clearRequests(binId: string): void;
76
- /**
77
- * Remove bins whose `expiresAt` is in the past relative to `now`.
78
- * Returns the ids of bins that were removed.
79
- */
80
- cleanupExpired(now: number): string[];
81
- }
82
- /**
83
- * In-memory implementation of {@link BinStore} backed by two Maps. State is
84
- * lost when the process exits.
148
+ * One bin and its captured requests, stored as a single Keyv value.
85
149
  */
86
- export declare class InMemoryBinStore implements BinStore {
87
- private readonly _bins;
88
- private readonly _requests;
89
- createBin(bin: Bin): void;
90
- getBin(id: string): Bin | undefined;
91
- listBins(): Bin[];
92
- deleteBin(id: string): boolean;
93
- addRequest(binId: string, req: CapturedRequest, max: number): void;
94
- getRequests(binId: string): CapturedRequest[];
95
- getRequest(binId: string, reqId: string): CapturedRequest | undefined;
96
- clearRequests(binId: string): void;
97
- cleanupExpired(now: number): string[];
98
- }
99
- //#endregion
100
- //#region src/bin-manager.d.ts
150
+ type BinDocument = {
151
+ bin: Bin;
152
+ requests: CapturedRequest[];
153
+ };
101
154
  /**
102
- * Configuration options for {@link BinManager}.
155
+ * Options for a {@link MockHttpBins} instance.
103
156
  */
104
- type BinManagerOptions = {
105
- /** Storage backend. Defaults to a new {@link InMemoryBinStore}. */
106
- store?: BinStore;
107
- /** Bin lifetime in milliseconds. Defaults to 24 hours. */
157
+ type MockHttpBinsOptions = {
158
+ /**
159
+ * Keyv store for bins. The default is `new Keyv()`, which keeps data in memory.
160
+ * Pass an instance when you want a different Keyv adapter.
161
+ */
162
+ keyv?: Keyv<BinDocument>;
163
+ /** Bin lifetime in milliseconds. The default is 24 hours. */
108
164
  defaultTtlMs?: number;
109
- /** Maximum number of requests retained per bin (FIFO). Defaults to 100. */
165
+ /** Maximum number of requests in one bin. The default is 100. */
110
166
  maxRequestsPerBin?: number;
111
- /** Maximum captured body size in bytes. Larger bodies are truncated. Defaults to 1 MiB. */
167
+ /** Maximum captured body size in bytes. The default is 128 KiB. */
112
168
  maxBodySize?: number;
113
- /** Length of generated bin and request ids. Defaults to 12. */
169
+ /** Length of a generated bin id or request id. The default is 12. */
114
170
  idLength?: number;
115
- /** Interval between cleanup sweeps in milliseconds. Defaults to 1 minute. */
116
- cleanupIntervalMs?: number;
117
171
  };
118
172
  /**
119
- * Manages request bins: ephemeral URL endpoints that capture incoming HTTP
120
- * requests so they can be inspected later.
173
+ * Stores request bins in Keyv.
174
+ * The default store is `new Keyv()`, which keeps data in memory.
121
175
  */
122
- export declare class BinManager {
123
- private readonly _store;
176
+ export declare class MockHttpBins {
177
+ private readonly _keyv;
124
178
  private readonly _defaultTtlMs;
125
179
  private readonly _maxRequestsPerBin;
126
180
  private readonly _maxBodySize;
127
181
  private readonly _idLength;
128
- private readonly _cleanupIntervalMs;
129
- private _cleanupTimer;
130
- constructor(options?: BinManagerOptions);
131
- /** Underlying storage backend. */
132
- get store(): BinStore;
182
+ private readonly _locks;
183
+ constructor(options?: MockHttpBinsOptions);
184
+ /** Keyv instance that stores the bins. */
185
+ get keyv(): Keyv<BinDocument>;
133
186
  /** Maximum captured body size in bytes. */
134
187
  get maxBodySize(): number;
135
- /** Maximum number of requests retained per bin. */
188
+ /** Maximum number of requests in one bin. */
136
189
  get maxRequestsPerBin(): number;
137
190
  /** Default bin lifetime in milliseconds. */
138
191
  get defaultTtlMs(): number;
139
- /** Create a new bin with the configured TTL. */
140
- createBin(): Bin;
141
- /**
142
- * Look up a bin by id. Returns `undefined` if the bin does not exist or
143
- * has expired (in which case it is also lazily removed).
144
- */
145
- getBin(id: string): Bin | undefined;
146
- /** List all non-expired bins. */
147
- listBins(): Bin[];
148
- /** Delete a bin. Returns true if the bin existed. */
149
- deleteBin(id: string): boolean;
150
- /**
151
- * Record an incoming request against a bin. Returns the stored
152
- * {@link CapturedRequest}, or `undefined` if the bin does not exist or is
153
- * expired.
154
- */
155
- recordRequest(binId: string, raw: Omit<CapturedRequest, "id" | "binId" | "capturedAt">): CapturedRequest | undefined;
156
- /** List captured requests for a bin (newest first). */
157
- getRequests(binId: string): CapturedRequest[];
158
- /** Look up a single captured request. */
159
- getRequest(binId: string, reqId: string): CapturedRequest | undefined;
160
- /** Clear all captured requests in a bin. */
161
- clearRequests(binId: string): void;
162
- /** Start the periodic cleanup of expired bins. */
163
- start(): void;
164
- /** Stop the periodic cleanup. Idempotent. */
165
- stop(): void;
166
- private generateId;
167
- }
168
- //#endregion
169
- //#region src/certificate.d.ts
170
- type CertificateOptions = {
171
192
  /**
172
- * Common Name for the certificate subject. Defaults to 'localhost'.
193
+ * Create a new bin with the configured lifetime.
194
+ * Throws when Keyv does not store the bin.
173
195
  */
174
- commonName?: string;
196
+ createBin(): Promise<Bin>;
175
197
  /**
176
- * Subject Alternative Names. Defaults to [dns:localhost, ip:127.0.0.1, ip:::1].
198
+ * Look up a bin by id.
199
+ * Returns `undefined` when the bin does not exist or Keyv has expired it.
200
+ * A failed read also returns `undefined` when the Keyv instance has an `error` listener.
177
201
  */
178
- altNames?: Array<{
179
- type: "dns";
180
- value: string;
181
- } | {
182
- type: "ip";
183
- value: string;
184
- }>;
202
+ getBin(id: string): Promise<Bin | undefined>;
185
203
  /**
186
- * Number of days the certificate is valid. Defaults to 365.
204
+ * List the bins that Keyv still holds.
205
+ * Throws when the Keyv store has no iterator.
206
+ * When the Keyv instance has an `error` listener, a failed walk returns the bins read before the error.
187
207
  */
188
- validityDays?: number;
208
+ listBins(): Promise<Bin[]>;
189
209
  /**
190
- * RSA key size in bits. Defaults to 2048.
210
+ * Delete a bin. Returns true when the bin existed.
211
+ * The delete waits for captures already queued for that bin, so a capture cannot write the bin back.
191
212
  */
192
- keySize?: number;
193
- };
194
- type CertificateResult = {
213
+ deleteBin(id: string): Promise<boolean>;
195
214
  /**
196
- * PEM-encoded X.509 certificate.
215
+ * Record a request for a bin.
216
+ * Returns `undefined` when the bin does not exist.
217
+ * Throws when Keyv does not store the capture.
197
218
  */
198
- cert: string;
219
+ recordRequest(binId: string, raw: Omit<CapturedRequest, "id" | "binId" | "capturedAt">): Promise<CapturedRequest | undefined>;
220
+ /** List captured requests for a bin, newest first. */
221
+ getRequests(binId: string): Promise<CapturedRequest[]>;
222
+ /** Look up one captured request. */
223
+ getRequest(binId: string, reqId: string): Promise<CapturedRequest | undefined>;
199
224
  /**
200
- * PEM-encoded PKCS#8 private key.
225
+ * Remove captured requests and keep the bin.
226
+ * Returns false when the bin does not exist.
227
+ * Throws when Keyv does not store the change.
201
228
  */
202
- key: string;
203
- };
204
- type CertificateFileOptions = CertificateOptions & {
229
+ clearRequests(binId: string): Promise<boolean>;
230
+ /** Keyv 6 yields nothing when the store has no iterator. Listing must fail instead. */
231
+ private canIterate;
232
+ /** Returns false when the bin does not exist. */
233
+ private appendRequest;
205
234
  /**
206
- * File path to write the certificate PEM.
235
+ * Write a bin document with a TTL from its lifetime.
236
+ * Keyv returns false or throws when the write fails. Both cases throw here.
207
237
  */
208
- certPath: string;
238
+ private save;
209
239
  /**
210
- * File path to write the private key PEM.
240
+ * Run writes for one bin in order.
241
+ * A failed write does not block the next write for that bin.
211
242
  */
212
- keyPath: string;
213
- };
214
- /**
215
- * Generate a self-signed certificate using only Node.js built-in crypto.
216
- * Returns PEM-encoded certificate and private key strings.
217
- */
218
- export declare function generateCertificate(options?: CertificateOptions): CertificateResult;
219
- /**
220
- * Generate a self-signed certificate and write PEM files to disk.
221
- * Returns the same CertificateResult as generateCertificate().
222
- */
223
- export declare function generateCertificateFiles(options: CertificateFileOptions): Promise<CertificateResult>;
243
+ private enqueue;
244
+ /** Keyv keeps a key without a TTL forever, so the TTL never drops below 1 ms. */
245
+ private ttlFor;
246
+ private binKey;
247
+ private generateId;
248
+ }
224
249
  //#endregion
225
250
  //#region src/tap-manager.d.ts
226
251
  /**
@@ -430,12 +455,6 @@ type MockHttpOptions = {
430
455
  * timeout (needed on Cloudflare Workers, where avvio timers are unreliable).
431
456
  */
432
457
  pluginTimeout?: number;
433
- /**
434
- * Whether to start the request-bin cleanup timer during `initialize()`.
435
- * Defaults to true. Disable this on Cloudflare Workers, where `setInterval`
436
- * during startup can hang the isolate.
437
- */
438
- startBins?: boolean;
439
458
  /**
440
459
  * HTTPS configuration. Set to true to auto-generate a self-signed certificate,
441
460
  * or provide an object with cert/key strings/paths or auto-generation options.
@@ -464,7 +483,6 @@ declare class MockHttp extends Hookified {
464
483
  private _siteDistPath;
465
484
  private _logging;
466
485
  private _pluginTimeout?;
467
- private _startBins;
468
486
  private _httpBin;
469
487
  private _rateLimit?;
470
488
  private _http2;
@@ -474,6 +492,7 @@ declare class MockHttp extends Hookified {
474
492
  private _server;
475
493
  private _taps;
476
494
  private _bins;
495
+ private _binHealthCheck?;
477
496
  constructor(options?: MockHttpOptions);
478
497
  /**
479
498
  * The port to listen on. If not provided, defaults to 3000 and detects the next available port if in use.
@@ -563,16 +582,6 @@ declare class MockHttp extends Hookified {
563
582
  * Fastify plugin boot timeout in milliseconds. `undefined` uses Fastify's default.
564
583
  */
565
584
  set pluginTimeout(pluginTimeout: number | undefined);
566
- /**
567
- * Whether to start the request-bin cleanup timer during `initialize()`.
568
- * @default true
569
- */
570
- get startBins(): boolean;
571
- /**
572
- * Whether to start the request-bin cleanup timer during `initialize()`.
573
- * @default true
574
- */
575
- set startBins(startBins: boolean);
576
585
  /**
577
586
  * HTTPS configuration. Set to true to auto-generate a self-signed certificate,
578
587
  * or provide an HttpsOptions object with cert/key or auto-generation options.
@@ -646,13 +655,20 @@ declare class MockHttp extends Hookified {
646
655
  */
647
656
  set taps(taps: TapManager);
648
657
  /**
649
- * The BinManager instance for managing request bins.
658
+ * The MockHttpBins instance for request bins.
659
+ */
660
+ get bins(): MockHttpBins;
661
+ /**
662
+ * The MockHttpBins instance for request bins.
650
663
  */
651
- get bins(): BinManager;
664
+ set bins(bins: MockHttpBins);
652
665
  /**
653
- * The BinManager instance for managing request bins.
666
+ * Override the bin storage probe used by `GET /healthz`.
667
+ * The default probe reads the in-memory Keyv.
654
668
  */
655
- set bins(bins: BinManager);
669
+ set binHealthCheck(check: () => Promise<BinHealth>);
670
+ /** Whether bin storage can serve a read or a MongoDB ping. */
671
+ checkBinStore(): Promise<BinHealth>;
656
672
  /**
657
673
  * Register plugins and routes without listening. Use `inject()` against
658
674
  * `server`, or call `start()` to bind a port.
@@ -749,4 +765,4 @@ declare class MockHttp extends Hookified {
749
765
  //#region src/index.d.ts
750
766
  export declare const start: () => Promise<MockHttp>;
751
767
  //#endregion
752
- export { type Bin, type BinManagerOptions, type BinStore, type CapturedRequest, type CertificateFileOptions, type CertificateOptions, type CertificateResult, type HttpsOptions, type InjectionMatcher, type InjectionResponse, type InjectionTap, MockHttp as default, MockHttp as mockhttp };
768
+ export { type Bin, type CapturedRequest, type CertificateFileOptions, type CertificateOptions, type CertificateResult, type HttpsOptions, type InjectionMatcher, type InjectionResponse, type InjectionTap, type MockHttpBinsOptions, MockHttp as default, MockHttp as mockhttp };