@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/README.md CHANGED
@@ -618,13 +618,13 @@ curl -s "http://localhost:3000/bins/$ID/requests" \
618
618
 
619
619
  ### Truncation
620
620
 
621
- Bodies larger than `maxBodySize` (1 MiB by default) are cut off and flagged:
621
+ Bodies larger than `maxBodySize` (128 KiB by default) are cut off and flagged:
622
622
 
623
623
  ```javascript
624
624
  // In test setup
625
- import { MockHttp, BinManager } from '@jaredwray/mockhttp';
625
+ import { MockHttp, MockHttpBins } from '@jaredwray/mockhttp';
626
626
  const mock = new MockHttp();
627
- mock.bins = new BinManager({ maxBodySize: 1024 }); // 1 KiB cap
627
+ mock.bins = new MockHttpBins({ maxBodySize: 1024 }); // 1 KiB cap
628
628
  await mock.start();
629
629
  ```
630
630
 
@@ -664,8 +664,7 @@ curl -s "http://localhost:3000/bins/$ID/requests" | jq '.requests[0].path'
664
664
 
665
665
  ## Programmatic Access
666
666
 
667
- The bin manager is exposed on the `MockHttp` instance as `mock.bins`. This
668
- makes it easy to drive bins from a test suite without going through HTTP:
667
+ Use `mock.bins` to control request bins from a test. You do not need an HTTP call:
669
668
 
670
669
  ```javascript
671
670
  import { MockHttp } from '@jaredwray/mockhttp';
@@ -674,13 +673,13 @@ const mock = new MockHttp({ logging: false });
674
673
  await mock.start();
675
674
 
676
675
  // Create a bin
677
- const bin = mock.bins.createBin();
676
+ const bin = await mock.bins.createBin();
678
677
  console.log(`Webhook target: http://localhost:${mock.port}/b/${bin.id}`);
679
678
 
680
679
  // ... point your code-under-test at that URL ...
681
680
 
682
681
  // Read captured requests (newest first)
683
- const requests = mock.bins.getRequests(bin.id);
682
+ const requests = await mock.bins.getRequests(bin.id);
684
683
  console.log(`Captured ${requests.length} requests`);
685
684
 
686
685
  for (const req of requests) {
@@ -688,8 +687,8 @@ for (const req of requests) {
688
687
  }
689
688
 
690
689
  // Clean up
691
- mock.bins.deleteBin(bin.id);
692
- await mock.close(); // also stops the bin cleanup timer
690
+ await mock.bins.deleteBin(bin.id);
691
+ await mock.close();
693
692
  ```
694
693
 
695
694
  ### Using Bins in a Vitest Test
@@ -710,14 +709,14 @@ afterAll(async () => {
710
709
  });
711
710
 
712
711
  test('my SDK sends the right webhook payload', async () => {
713
- const bin = mock.bins.createBin();
712
+ const bin = await mock.bins.createBin();
714
713
  const webhookUrl = `http://localhost:${mock.port}/b/${bin.id}`;
715
714
 
716
715
  // Drive your SDK at the bin
717
716
  await mySdk.notify(webhookUrl, { event: 'user.created', id: 42 });
718
717
 
719
718
  // Assert on what actually arrived
720
- const [captured] = mock.bins.getRequests(bin.id);
719
+ const [captured] = await mock.bins.getRequests(bin.id);
721
720
  expect(captured.method).toBe('POST');
722
721
  expect(captured.contentType).toBe('application/json');
723
722
  expect(JSON.parse(captured.body!)).toEqual({
@@ -738,18 +737,17 @@ const mock = new MockHttp({
738
737
  });
739
738
  ```
740
739
 
741
- To tune limits, replace the default `BinManager` before starting:
740
+ To change the limits, replace the default `MockHttpBins` before you start the server:
742
741
 
743
742
  ```javascript
744
- import { MockHttp, BinManager } from '@jaredwray/mockhttp';
743
+ import { MockHttp, MockHttpBins } from '@jaredwray/mockhttp';
745
744
 
746
745
  const mock = new MockHttp();
747
- mock.bins = new BinManager({
746
+ mock.bins = new MockHttpBins({
748
747
  defaultTtlMs: 60 * 60 * 1000, // 1 hour (default: 24h)
749
748
  maxRequestsPerBin: 500, // (default: 100)
750
- maxBodySize: 5 * 1024 * 1024, // 5 MiB (default: 1 MiB)
749
+ maxBodySize: 512 * 1024, // 512 KiB (default: 128 KiB)
751
750
  idLength: 16, // (default: 12)
752
- cleanupIntervalMs: 30 * 1000, // sweep every 30s (default: 60s)
753
751
  });
754
752
 
755
753
  await mock.start();
@@ -759,44 +757,40 @@ await mock.start();
759
757
 
760
758
  | Option | Default | Description |
761
759
  |--------|---------|-------------|
762
- | `defaultTtlMs` | `86400000` (24h) | Bin lifetime. Expired bins return 404 and are lazily removed. |
760
+ | `defaultTtlMs` | `86400000` (24h) | Bin lifetime, set as the Keyv TTL. An expired bin returns 404. |
763
761
  | `maxRequestsPerBin` | `100` | When exceeded, oldest captures are dropped (FIFO). |
764
- | `maxBodySize` | `1048576` (1 MiB) | Larger bodies are truncated; `truncated: true` is set on the capture. |
762
+ | `maxBodySize` | `131072` (128 KiB) | Larger bodies are truncated; `truncated: true` is set on the capture. |
765
763
  | `idLength` | `12` | Length of generated bin and request ids. |
766
- | `cleanupIntervalMs` | `60000` (1 min) | How often expired bins are swept. The timer is `unref()`'d so it never keeps the process alive. |
764
+ | `keyv` | `new Keyv()` | Keyv store for bins. The default keeps bins in memory. |
765
+
766
+ ### Storage
767
767
 
768
- ### Pluggable Storage
768
+ `MockHttpBins` stores each bin in [Keyv](https://github.com/jaredwray/keyv) 6. The default is `new Keyv()`. That store keeps the data in memory for this process.
769
769
 
770
- `BinManager` accepts a `store: BinStore` so you can plug in alternative
771
- backends (Redis, SQLite, etc.) without changing the rest of the codebase. The
772
- default `InMemoryBinStore` keeps state in process memory.
770
+ Pass a Keyv 6 instance when you want a different adapter. Install the adapter in your project. This package does not include an adapter. `@keyv/redis` 6 matches this Keyv version.
773
771
 
774
772
  ```typescript
775
- import {
776
- BinManager,
777
- InMemoryBinStore,
778
- type Bin,
779
- type BinStore,
780
- type CapturedRequest,
781
- } from '@jaredwray/mockhttp';
782
-
783
- class RedisBinStore implements BinStore {
784
- createBin(bin: Bin): void { /* SET bin:${bin.id} ... */ }
785
- getBin(id: string): Bin | undefined { /* GET ... */ }
786
- listBins(): Bin[] { /* SCAN ... */ }
787
- deleteBin(id: string): boolean { /* DEL ... */ }
788
- addRequest(binId: string, req: CapturedRequest, max: number): void {
789
- /* LPUSH bin:${binId}:requests + LTRIM to max */
790
- }
791
- getRequests(binId: string): CapturedRequest[] { /* LRANGE ... */ }
792
- getRequest(binId: string, reqId: string): CapturedRequest | undefined { /* ... */ }
793
- clearRequests(binId: string): void { /* DEL bin:${binId}:requests */ }
794
- cleanupExpired(now: number): string[] { /* scan + delete */ return []; }
795
- }
773
+ import Keyv from 'keyv';
774
+ import KeyvRedis from '@keyv/redis'; // pnpm add @keyv/redis
775
+ import { MockHttpBins } from '@jaredwray/mockhttp';
796
776
 
797
- mock.bins = new BinManager({ store: new RedisBinStore() });
777
+ mock.bins = new MockHttpBins({
778
+ keyv: new Keyv({ store: new KeyvRedis('redis://localhost:6379') }),
779
+ });
798
780
  ```
799
781
 
782
+ Each capture writes the full bin again. A large body or a high request limit makes that write large. Set `maxBodySize` and `maxRequestsPerBin` to limit the size. With the defaults, a full bin of binary bodies is about 17 MB.
783
+
784
+ Two processes that share one Keyv can lose a capture. This occurs when both processes write the same bin at the same time.
785
+
786
+ If Keyv does not store a write, the bin route returns `500` with the standard Fastify error body: `statusCode`, `error`, and `message`.
787
+
788
+ A failed read depends on the Keyv `error` listener. Without a listener, Keyv throws the store error and the route returns `500`. With a listener, Keyv does not throw. A failed read then looks like a missing bin, and the route returns `404`. `listBins()` returns only the bins that it read before the error.
789
+
790
+ `listBins()` throws an error when the adapter has no iterator.
791
+
792
+ An expired bin returns 404. The in-memory Keyv store deletes an expired bin only when a read or `listBins()` reaches it. Until then, the bin stays in memory.
793
+
800
794
  # Rate Limiting
801
795
 
802
796
  MockHttp supports rate limiting using [@fastify/rate-limit](https://github.com/fastify/fastify-rate-limit). Rate limiting is **enabled by default** at **1000 requests per minute** with **localhost (127.0.0.1 and ::1) excluded** from rate limiting.
@@ -1012,7 +1006,6 @@ new MockHttp(options?)
1012
1006
  - `logging?`: boolean - Enable logging (default: true)
1013
1007
  - `staticFiles?`: boolean - Serve files from the package `public/` directory (default: true)
1014
1008
  - `pluginTimeout?`: number - Fastify plugin boot timeout in milliseconds; `0` disables it (default: Fastify's timeout)
1015
- - `startBins?`: boolean - Start the request-bin cleanup timer during `initialize()` (default: true)
1016
1009
  - `httpBin?`: HttpBinOptions - Configure which httpbin routes to enable
1017
1010
  - `httpMethods?`: boolean - Enable HTTP method routes (default: true)
1018
1011
  - `redirects?`: boolean - Enable redirect routes (default: true)
@@ -1041,7 +1034,6 @@ new MockHttp(options?)
1041
1034
  - `logging`: boolean - Get/set logging enabled state
1042
1035
  - `staticFiles`: boolean - Get/set whether files from `public/` are served
1043
1036
  - `pluginTimeout`: number | undefined - Get/set Fastify plugin boot timeout
1044
- - `startBins`: boolean - Get/set whether bin cleanup starts during `initialize()`
1045
1037
  - `rateLimit`: RateLimitPluginOptions | undefined - Get/set rate limiting options
1046
1038
  - `httpBin`: HttpBinOptions - Get/set httpbin route options
1047
1039
  - `https`: HttpsOptions | undefined - Get/set HTTPS configuration
@@ -1050,7 +1042,7 @@ new MockHttp(options?)
1050
1042
  - `http1`: boolean - Get/set HTTP/1.1 fallback for HTTP/2 with HTTPS
1051
1043
  - `server`: FastifyInstance - Get/set the Fastify server instance
1052
1044
  - `taps`: TapManager - Get/set the TapManager instance
1053
- - `bins`: BinManager - Get/set the BinManager instance for request bins
1045
+ - `bins`: MockHttpBins - Get or set the request bins
1054
1046
 
1055
1047
  ### Methods
1056
1048
 
@@ -1130,57 +1122,55 @@ Register the request bin routes — management at `/bins` and capture at `/b/:id
1130
1122
 
1131
1123
  ## Bins (Request Capture)
1132
1124
 
1133
- Access the BinManager via `mockHttp.bins` to manage request bins programmatically. See [Request Bins](#request-bins) for usage examples.
1125
+ Use `mockHttp.bins` to control request bins. See [Request Bins](#request-bins) for usage examples.
1134
1126
 
1135
1127
  ### `bins.createBin()`
1136
1128
 
1137
- Create a new bin with the configured TTL.
1129
+ Create a new bin with the configured TTL. Throws when Keyv does not store the bin.
1138
1130
 
1139
- **Returns:** `Bin` — `{ id, createdAt, expiresAt, requestCount }`
1131
+ **Returns:** `Promise<Bin>` — `{ id, createdAt, expiresAt, requestCount }`
1140
1132
 
1141
1133
  ### `bins.getBin(id)`
1142
1134
 
1143
- Look up a bin by id. Returns `undefined` if the bin does not exist or has expired (and lazily removes the expired entry).
1135
+ Look up a bin by id. Returns `undefined` if the bin does not exist or Keyv has expired it. When the Keyv instance has an `error` listener, a failed read also returns `undefined`.
1144
1136
 
1145
- **Returns:** `Bin | undefined`
1137
+ **Returns:** `Promise<Bin | undefined>`
1146
1138
 
1147
1139
  ### `bins.listBins()`
1148
1140
 
1149
- List all non-expired bins.
1141
+ List the bins that Keyv still holds.
1150
1142
 
1151
- **Returns:** `Bin[]`
1143
+ **Returns:** `Promise<Bin[]>`
1152
1144
 
1153
1145
  ### `bins.deleteBin(id)`
1154
1146
 
1155
- Delete a bin and all its captured requests.
1147
+ Delete a bin and all its captured requests. The delete waits for captures already queued for that bin.
1156
1148
 
1157
- **Returns:** `boolean` — `true` if the bin existed
1149
+ **Returns:** `Promise<boolean>` — `true` when the bin existed
1158
1150
 
1159
1151
  ### `bins.getRequests(binId)`
1160
1152
 
1161
1153
  List captured requests for a bin, newest first.
1162
1154
 
1163
- **Returns:** `CapturedRequest[]`
1155
+ **Returns:** `Promise<CapturedRequest[]>`
1164
1156
 
1165
1157
  ### `bins.getRequest(binId, reqId)`
1166
1158
 
1167
1159
  Look up a single captured request.
1168
1160
 
1169
- **Returns:** `CapturedRequest | undefined`
1161
+ **Returns:** `Promise<CapturedRequest | undefined>`
1170
1162
 
1171
1163
  ### `bins.clearRequests(binId)`
1172
1164
 
1173
- Clear all captured requests in a bin (the bin itself is preserved).
1165
+ Clear all captured requests in a bin. The bin stays. Throws when Keyv does not store the change.
1174
1166
 
1175
- ### `bins.recordRequest(binId, raw)`
1176
-
1177
- Manually record a captured request. Returns `undefined` if the bin does not exist or is expired. Normally invoked by the capture route, but available for programmatic use.
1167
+ **Returns:** `Promise<boolean>` — `false` when the bin does not exist
1178
1168
 
1179
- **Returns:** `CapturedRequest | undefined`
1169
+ ### `bins.recordRequest(binId, raw)`
1180
1170
 
1181
- ### `bins.start()` / `bins.stop()`
1171
+ Manually record a captured request. Returns `undefined` if the bin does not exist. Throws when Keyv does not store the capture. Normally invoked by the capture route, but available for programmatic use.
1182
1172
 
1183
- Start or stop the periodic cleanup of expired bins. `MockHttp.start()` and `MockHttp.close()` call these automatically. The interval is `unref()`'d so it never keeps the Node process alive.
1173
+ **Returns:** `Promise<CapturedRequest | undefined>`
1184
1174
 
1185
1175
  ### `bins.maxBodySize` / `bins.maxRequestsPerBin` / `bins.defaultTtlMs`
1186
1176
 
@@ -1240,7 +1230,7 @@ A getter that returns whether there are any active injections.
1240
1230
 
1241
1231
  # About mockhttp.org
1242
1232
 
1243
- [mockhttp.org](https://mockhttp.org) is a free hosted instance of this codebase for testing. It runs on a [Cloudflare Worker](https://developers.cloudflare.com/workers/) (no containers): documentation is served from Workers Assets, and mock APIs such as `/get` and `/post` run in the Worker. The service is globally available and rate-limited (1000 requests per minute per IP) to prevent abuse.
1233
+ [mockhttp.org](https://mockhttp.org) is a free hosted instance of this codebase for testing. It runs on a [Cloudflare Worker](https://developers.cloudflare.com/workers/) (no containers): documentation is served from Workers Assets, and mock APIs such as `/get` and `/post` run in the Worker. The service is globally available and rate-limited (1000 requests per minute per IP) to prevent abuse. Request bins on this service are stored in MongoDB Atlas, in the `bins` collection, so they are shared across isolates.
1244
1234
 
1245
1235
  All [mockhttp.org](https://mockhttp.org) traffic stays on Cloudflare. A separate test app runs the Node server on Wasmer Edge at [mockhttp.wasmer.app](https://mockhttp.wasmer.app).
1246
1236