@crvouga/mockingbird-adapter-node 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 +68 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +39 -0
- package/dist/index.js.map +1 -0
- package/package.json +53 -0
package/README.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# @crvouga/mockingbird-adapter-node
|
|
2
|
+
|
|
3
|
+
Serve any Mockingbird `FetchAPI` (a provider mock such as `StripeAPI`, or your own) as a real HTTP
|
|
4
|
+
server over `node:http`. Use it when the code under test needs a URL (a subprocess, a browser, an
|
|
5
|
+
SDK you cannot hand a `fetch`). If you can inject `fetch`, call the mock's `fetch` directly instead;
|
|
6
|
+
on Bun, `@crvouga/mockingbird-adapter-bun` is lighter.
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install @crvouga/mockingbird-adapter-node
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
Requires Node >=22 (also runs on Bun, which implements `node:http`). ESM only.
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import type { AddressInfo } from "node:net"
|
|
20
|
+
import { serve } from "@crvouga/mockingbird-adapter-node"
|
|
21
|
+
import type { FetchAPI } from "@crvouga/mockingbird-core"
|
|
22
|
+
|
|
23
|
+
// Any FetchAPI works, e.g. `new StripeAPI()` from @crvouga/mockingbird-service-stripe.
|
|
24
|
+
const api: FetchAPI = {
|
|
25
|
+
fetch: async (request) =>
|
|
26
|
+
Response.json({ method: request.method, url: request.url, body: await request.text() }),
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Port defaults to 0: the OS picks a free port.
|
|
30
|
+
const server = await serve(api, { host: "127.0.0.1" })
|
|
31
|
+
const { port } = server.address() as AddressInfo
|
|
32
|
+
const baseUrl = `http://127.0.0.1:${port}`
|
|
33
|
+
|
|
34
|
+
const response = await fetch(`${baseUrl}/v1/customers`, { method: "POST", body: "email=a@b.c" })
|
|
35
|
+
console.log(await response.json())
|
|
36
|
+
|
|
37
|
+
// Tear down (e.g. in afterAll). closeAllConnections drops keep-alive sockets so the process exits.
|
|
38
|
+
server.close()
|
|
39
|
+
server.closeAllConnections()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## API
|
|
43
|
+
|
|
44
|
+
| Export | Signature | Description |
|
|
45
|
+
| --- | --- | --- |
|
|
46
|
+
| `serve` | `(api: FetchAPI, options?: NodeServeOptions) => Promise<http.Server>` | Start listening; resolves once bound, rejects on listen errors (e.g. `EADDRINUSE`). |
|
|
47
|
+
|
|
48
|
+
Types:
|
|
49
|
+
|
|
50
|
+
- `NodeServeOptions`: `{ port?: number; host?: string }`. `port` defaults to `0` (ephemeral; read it
|
|
51
|
+
from `server.address()`); `host` is passed to `server.listen` (Node's default when omitted).
|
|
52
|
+
|
|
53
|
+
Behavior:
|
|
54
|
+
|
|
55
|
+
- Each incoming request is buffered and converted to a Fetch `Request`. The URL is built from the
|
|
56
|
+
`Host` header; leading repeated slashes in the path collapse to one.
|
|
57
|
+
- The body is forwarded only for methods other than `GET`/`HEAD`, and only when non-empty.
|
|
58
|
+
- The `Response` status, headers and body are written back in full (no streaming). Repeated
|
|
59
|
+
`Set-Cookie` headers are preserved, one header line per cookie.
|
|
60
|
+
- Errors thrown by `api.fetch` are not caught by the adapter; return error responses from your
|
|
61
|
+
`FetchAPI` instead.
|
|
62
|
+
|
|
63
|
+
## Related
|
|
64
|
+
|
|
65
|
+
- `@crvouga/mockingbird-core`: the `FetchAPI` contract.
|
|
66
|
+
- `@crvouga/mockingbird-adapter-bun`: the same adapter for `Bun.serve`.
|
|
67
|
+
|
|
68
|
+
Part of [mockingbird](https://github.com/crvouga/mockingbird) — agent integration guide: [`@crvouga/mockingbird`](https://github.com/crvouga/mockingbird/tree/main/packages/facade#readme).
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { FetchAPI } from "@crvouga/mockingbird-core";
|
|
2
|
+
/** Options for {@link serve}. */
|
|
3
|
+
export type NodeServeOptions = {
|
|
4
|
+
port?: number;
|
|
5
|
+
host?: string;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Serve any Mockingbird {@link FetchAPI} over `node:http`.
|
|
9
|
+
* Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).
|
|
10
|
+
*/
|
|
11
|
+
export declare const serve: (api: FetchAPI, options?: NodeServeOptions) => Promise<import("http").Server<typeof import("http").IncomingMessage, typeof import("http").ServerResponse>>;
|
|
12
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAEzD,iCAAiC;AACjC,MAAM,MAAM,gBAAgB,GAAG;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,IAAI,CAAC,EAAE,MAAM,CAAA;CACd,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,KAAK,GAAU,KAAK,QAAQ,EAAE,UAAS,gBAAqB,gHA+BxE,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { createServer } from "node:http";
|
|
2
|
+
/**
|
|
3
|
+
* Serve any Mockingbird {@link FetchAPI} over `node:http`.
|
|
4
|
+
* Port defaults to `0`, so the OS assigns an ephemeral port (read from `server.address()`).
|
|
5
|
+
*/
|
|
6
|
+
export const serve = async (api, options = {}) => {
|
|
7
|
+
const server = createServer(async (req, res) => {
|
|
8
|
+
const chunks = [];
|
|
9
|
+
for await (const chunk of req) {
|
|
10
|
+
chunks.push(Buffer.from(chunk));
|
|
11
|
+
}
|
|
12
|
+
const body = Buffer.concat(chunks);
|
|
13
|
+
const address = server.address();
|
|
14
|
+
const port = typeof address === "object" && address !== null ? address.port : undefined;
|
|
15
|
+
const base = `http://${req.headers.host ?? `localhost:${port ?? 80}`}`;
|
|
16
|
+
const raw = req.url ?? "/";
|
|
17
|
+
const url = new URL(raw.replace(/^\/+/, "/"), base);
|
|
18
|
+
const method = req.method ?? "GET";
|
|
19
|
+
const init = { method, headers: req.headers };
|
|
20
|
+
if (method !== "GET" && method !== "HEAD" && body.length > 0) {
|
|
21
|
+
init.body = body;
|
|
22
|
+
}
|
|
23
|
+
const request = new Request(url, init);
|
|
24
|
+
const response = await api.fetch(request);
|
|
25
|
+
// Headers#entries() joins repeated headers; Set-Cookie must stay one header per cookie.
|
|
26
|
+
const headers = Object.fromEntries(response.headers);
|
|
27
|
+
const cookies = response.headers.getSetCookie();
|
|
28
|
+
if (cookies.length > 0)
|
|
29
|
+
headers["set-cookie"] = cookies;
|
|
30
|
+
res.writeHead(response.status, headers);
|
|
31
|
+
res.end(Buffer.from(await response.arrayBuffer()));
|
|
32
|
+
});
|
|
33
|
+
await new Promise((resolve, reject) => {
|
|
34
|
+
server.once("error", reject);
|
|
35
|
+
server.listen(options.port ?? 0, options.host, resolve);
|
|
36
|
+
});
|
|
37
|
+
return server;
|
|
38
|
+
};
|
|
39
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAA;AASxC;;;GAGG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,KAAK,EAAE,GAAa,EAAE,UAA4B,EAAE,EAAE,EAAE;IAC3E,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,EAAE;QAC7C,MAAM,MAAM,GAAa,EAAE,CAAA;QAC3B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,GAAG,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAA;QACjC,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;QAClC,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,EAAE,CAAA;QAChC,MAAM,IAAI,GAAG,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAA;QACvF,MAAM,IAAI,GAAG,UAAU,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,aAAa,IAAI,IAAI,EAAE,EAAE,EAAE,CAAA;QACtE,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,IAAI,GAAG,CAAA;QAC1B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,IAAI,CAAC,CAAA;QACnD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAA;QAClC,MAAM,IAAI,GAAgB,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,OAAiC,EAAE,CAAA;QACpF,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7D,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAClB,CAAC;QACD,MAAM,OAAO,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,IAAI,CAAC,CAAA;QACtC,MAAM,QAAQ,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACzC,wFAAwF;QACxF,MAAM,OAAO,GAAsC,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAA;QACvF,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,CAAA;QAC/C,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,CAAC,YAAY,CAAC,GAAG,OAAO,CAAA;QACvD,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QACvC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IACpD,CAAC,CAAC,CAAA;IACF,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QAC1C,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAC5B,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,EAAE,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACzD,CAAC,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC,CAAA"}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crvouga/mockingbird-adapter-node",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Serve any Mockingbird FetchAPI over node:http.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md"
|
|
11
|
+
],
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/index.d.ts",
|
|
15
|
+
"default": "./dist/index.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public",
|
|
20
|
+
"provenance": true
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/crvouga/mockingbird.git",
|
|
25
|
+
"directory": "packages/adapters/node"
|
|
26
|
+
},
|
|
27
|
+
"homepage": "https://github.com/crvouga/mockingbird/tree/main/packages/adapters/node#readme",
|
|
28
|
+
"keywords": [
|
|
29
|
+
"mockingbird",
|
|
30
|
+
"adapter",
|
|
31
|
+
"node"
|
|
32
|
+
],
|
|
33
|
+
"mockingbird": {
|
|
34
|
+
"runtime": "node",
|
|
35
|
+
"layer": "adapter"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc -p tsconfig.build.json",
|
|
39
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
40
|
+
"lint": "biome check .",
|
|
41
|
+
"test": "bun test",
|
|
42
|
+
"portability": "bun ../../../scripts/portability.ts",
|
|
43
|
+
"pack:check": "bun ../../../scripts/pack-check.ts"
|
|
44
|
+
},
|
|
45
|
+
"dependencies": {
|
|
46
|
+
"@crvouga/mockingbird-core": "0.0.0-development"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@types/node": "22.20.1",
|
|
50
|
+
"fast-check": "4.9.0",
|
|
51
|
+
"@crvouga/mockingbird-testing": "0.1.0"
|
|
52
|
+
}
|
|
53
|
+
}
|