Skip to content
This repository was archived by the owner on Oct 31, 2024. It is now read-only.

Commit 2f1028d

Browse files
committed
fix: Removes unnessary objects from fetch ponyfill requirement
This removes Request, Response, and Headers as hard requirements for the MongoClient API. This makes it easier to use a library like node-fetch or cross-fetch. The `headers` property may now also be a POJO, similar to node's OutgoingHttpHeaders.
1 parent bc2f98e commit 2f1028d

File tree

7 files changed

+41
-59
lines changed

7 files changed

+41
-59
lines changed

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@ All notable changes to this project will be documented in this file. This projec
77
### Added
88

99
- Created a `CHANGELOG.md` for changelog tracking
10+
- Enabled support for plain JS objects in `headers`
1011

1112
### Fixed
1213

1314
### Changed
1415

16+
- Removed requirement for `Request` and `Response` in the custom fetch interface
17+
1518
### Removed
1619

20+
- Dropped `node-fetch` for `cross-fetch` in tests to minimze type assertions
21+
1722
## [0.1.2] - 2023-05-01
1823

1924
### Added

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,8 @@ const client = new MongoClient(options);
128128
- `options.endpoint` - `string | URL` an endpoint for sending requests to. Your Data API Endpoint is available at your Mongo Data API UI `https://cloud.mongodb.com/v2/<projectId>#/dataAPI`, where `<projectId>` is your project ID. A single Data API is usable for the entire project, with individual data sources routing to specific atlas instances.
129129
- `options.dataSource` - `string` the `Data Source` for your Data API. On the Data API UI, this is the "Data Source" column, and usually is either a 1:1 mapping of your cluster name, or the default `mongodb-atlas` if you enabled Data API through the Atlas Admin UI.
130130
- `options.auth` - `AuthOptions` one of the authentication methods, either api key, email & password, or a custom JWT string. At this time, only [Credential Authentication](https://www.mongodb.com/docs/atlas/api/data-api/#credential-authentication) is supported.
131+
- `options.fetch?` - A custom `fetch` function conforming to the [native fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API). We recommend [cross-fetch](https://www.npmjs.com/package/cross-fetch), as it asserts a complaint `fetch()` interface and avoids you having to do `fetch: _fetch as typeof fetch` to satisfy the TypeScript compiler
132+
- `options.headers?` - Additional [Headers](https://developer.mozilla.org/en-US/docs/Web/API/Headers) to include with the request. May also be a simple object of key/value pairs.
131133

132134
## Select a Database
133135

package.json

-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@
6666
"husky": "^7.0.4",
6767
"lint-staged": "13.0.3",
6868
"msw": "^1.2.1",
69-
"node-fetch": "^3.3.1",
7069
"npm-run-all": "^4.1.5",
7170
"release-it": "^15.10.2",
7271
"shx": "^0.3.4",

pnpm-lock.yaml

-3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/client.ts

+23-22
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { type OutgoingHttpHeaders } from "node:http";
12
import { EJSON } from "bson";
23
import type {
34
Sort,
@@ -25,13 +26,6 @@ export type {
2526
WithoutId,
2627
} from "mongodb";
2728

28-
export type CustomFetch = {
29-
fetch: typeof fetch;
30-
Request: typeof Request;
31-
Response: typeof Response;
32-
Headers: typeof Headers;
33-
};
34-
3529
// eslint-disable-next-line @typescript-eslint/ban-types
3630
type Nullable<T> = T | null;
3731

@@ -66,12 +60,12 @@ export type MongoClientConstructorOptions = {
6660
* a `fetch()` function, as well as the `Request`, `Response`, and `Headers` classes. These
6761
* are available in most ponyfills and polyfills, including node-fetch.
6862
*/
69-
fetch?: CustomFetch;
63+
fetch?: typeof fetch;
7064
/**
7165
* Provide a custom Headers object for the request. In some situations such as testing,
7266
* this allows you to control the response from the mock server.
7367
*/
74-
headers?: Headers;
68+
headers?: OutgoingHttpHeaders | Headers;
7569
};
7670

7771
/**
@@ -90,7 +84,7 @@ export class MongoClient {
9084
endpoint: string;
9185

9286
fetch: typeof fetch;
93-
headers: Headers;
87+
headers: HeadersInit;
9488

9589
constructor({
9690
auth,
@@ -102,38 +96,45 @@ export class MongoClient {
10296
this.dataSource = dataSource;
10397
this.endpoint = endpoint instanceof URL ? endpoint.toString() : endpoint;
10498

105-
this.fetch = customFetch?.fetch ?? globalThis.fetch;
106-
const HeaderClass = customFetch?.Headers ?? globalThis.Headers;
99+
this.fetch = customFetch ?? globalThis.fetch;
100+
this.headers = {};
107101

108-
if (!this.fetch || typeof this.fetch !== "function" || !HeaderClass) {
102+
// accept a node-style or whatwg headers object with .keys() and .get()
103+
if (typeof headers?.keys === "function") {
104+
for (const key of headers.keys()) {
105+
this.headers[key] = (
106+
typeof headers.get === "function" ? headers.get(key) : headers[key]
107+
) as string;
108+
}
109+
}
110+
111+
if (!this.fetch || typeof this.fetch !== "function") {
109112
throw new Error(
110113
"No viable fetch() found. Please provide a fetch interface"
111114
);
112115
}
113116

114-
this.headers = headers ?? new HeaderClass();
115-
116-
this.headers.set("Content-Type", "application/ejson");
117-
this.headers.set("Accept", "application/ejson");
117+
this.headers["Content-Type"] = "application/ejson";
118+
this.headers.Accept = "application/ejson";
118119

119120
if ("apiKey" in auth) {
120-
this.headers.set("apiKey", auth.apiKey);
121+
this.headers.apiKey = auth.apiKey;
121122
return;
122123
}
123124

124125
if ("jwtTokenString" in auth) {
125-
this.headers.set("jwtTokenString", auth.jwtTokenString);
126+
this.headers.jwtTokenString = auth.jwtTokenString;
126127
return;
127128
}
128129

129130
if ("email" in auth && "password" in auth) {
130-
this.headers.set("email", auth.email);
131-
this.headers.set("password", auth.password);
131+
this.headers.email = auth.email;
132+
this.headers.password = auth.password;
132133
return;
133134
}
134135

135136
if ("bearerToken" in auth) {
136-
this.headers.set("Authorization", `Bearer ${auth.bearerToken}`);
137+
this.headers.Authorization = `Bearer ${auth.bearerToken}`;
137138
return;
138139
}
139140

test/configure.spec.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import test from "ava";
2-
import fetch, { Request, Response, Headers } from "cross-fetch";
2+
import fetch from "cross-fetch";
33
import { MongoClient } from "../src/client.js";
44
import { BASE_URL } from "./mocks/handlers.js";
55

6-
const fetchInterface = { fetch, Request, Response, Headers };
7-
86
test("requires a fetch interface", (t) => {
97
t.throws(() => {
108
const c = new MongoClient({
@@ -24,7 +22,7 @@ test("requires a valid auth method", (t) => {
2422
dataSource: "test-datasource",
2523
// @ts-expect-error bypassing type check to throw error on bad auth method
2624
auth: { notValid: "not a valid auth method" },
27-
fetch: fetchInterface,
25+
fetch,
2826
});
2927
});
3028
});
@@ -34,7 +32,7 @@ test("accepts an api key for auth", (t) => {
3432
endpoint: BASE_URL,
3533
dataSource: "test-datasource",
3634
auth: { apiKey: "validApiKey" },
37-
fetch: fetchInterface,
35+
fetch,
3836
});
3937
t.pass();
4038
});
@@ -44,7 +42,7 @@ test("accepts a jwt token for auth", (t) => {
4442
endpoint: BASE_URL,
4543
dataSource: "test-datasource",
4644
auth: { jwtTokenString: "passed through to mongo api" },
47-
fetch: fetchInterface,
45+
fetch,
4846
});
4947
t.pass();
5048
});
@@ -54,7 +52,7 @@ test("accepts an email and password for auth", (t) => {
5452
endpoint: BASE_URL,
5553
dataSource: "test-datasource",
5654
auth: { email: "[email protected]", password: "validPassword" },
57-
fetch: fetchInterface,
55+
fetch,
5856
});
5957
t.pass();
6058
});
@@ -64,7 +62,7 @@ test("accepts a bearer token for auth", (t) => {
6462
endpoint: BASE_URL,
6563
dataSource: "test-datasource",
6664
auth: { bearerToken: "passed through to mongo api" },
67-
fetch: fetchInterface,
65+
fetch,
6866
});
6967
t.pass();
7068
});

test/requests.spec.ts

+5-25
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import anyTest, { type TestFn, type ExecutionContext } from "ava";
2-
import fetch, { Request, Response, Headers } from "cross-fetch";
2+
import fetch, { Headers } from "cross-fetch";
33
import { type RequestHandler } from "msw";
44
import { MongoClient, ObjectId } from "../src/client.js";
55
import { MongoDataAPIError } from "../src/errors.js";
@@ -38,12 +38,7 @@ const createMongoClient = (headers?: Headers) => {
3838
endpoint: BASE_URL,
3939
dataSource: "test-datasource",
4040
auth: { apiKey: "validApiKey" },
41-
fetch: {
42-
fetch,
43-
Request,
44-
Response,
45-
Headers,
46-
},
41+
fetch,
4742
headers,
4843
});
4944
return c;
@@ -222,12 +217,7 @@ test("auth: disabled data source results in 400 from Mongo", async (t) => {
222217
endpoint: BASE_URL,
223218
dataSource: "test-datasource-disabled",
224219
auth: { apiKey: "validApiKey" },
225-
fetch: {
226-
fetch,
227-
Request,
228-
Response,
229-
Headers,
230-
},
220+
fetch,
231221
});
232222
const { data, error } = await c
233223
.db("test-db")
@@ -245,12 +235,7 @@ test("auth: bad auth results in 401 from Mongo", async (t) => {
245235
endpoint: BASE_URL,
246236
dataSource: "test-datasource",
247237
auth: { apiKey: "inValidApiKey" },
248-
fetch: {
249-
fetch,
250-
Request,
251-
Response,
252-
Headers,
253-
},
238+
fetch,
254239
});
255240
const { data, error } = await c
256241
.db("test-db")
@@ -269,12 +254,7 @@ test("auth: bad client ID results in 404 from Mongo", async (t) => {
269254
"https://data.mongodb-api.com/app/invalidClientAppId/endpoint/data/v1",
270255
dataSource: "test-datasource",
271256
auth: { apiKey: "validApiKey" },
272-
fetch: {
273-
fetch,
274-
Request,
275-
Response,
276-
Headers,
277-
},
257+
fetch,
278258
});
279259
const { data, error } = await c
280260
.db("test-db")

0 commit comments

Comments
 (0)