Skip to content

Commit

Permalink
Support a "common" request init config option
Browse files Browse the repository at this point in the history
  • Loading branch information
danbahrami committed Sep 8, 2024
1 parent af2e815 commit 9d8f8bf
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 15 deletions.
30 changes: 15 additions & 15 deletions src/createClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
DecoratedResponsePromise,
Modifiers,
} from "@/types";
import { callbackStore, mergeHeaders } from "@/utils";
import { callbackStore, mergeInits } from "@/utils";

export const createClient = (options: ClientOptions = {}): Client => {
/**
Expand All @@ -34,6 +34,7 @@ export const createClient = (options: ClientOptions = {}): Client => {
* Setup out default per-method request inits
*/
let defaults: {
common: RequestInit;
request: RequestInit;
get: RequestInit;
put: RequestInit;
Expand All @@ -42,9 +43,10 @@ export const createClient = (options: ClientOptions = {}): Client => {
delete: RequestInit;
options: RequestInit;
head: RequestInit;
baseUrl: string | undefined;
};

let baseUrl: string | undefined;

/**
* `_configure()` initialises all the per-method defaults, callbacks &
* modifiers. It's called once internally to build the client with the
Expand All @@ -67,6 +69,7 @@ export const createClient = (options: ClientOptions = {}): Client => {
};

defaults = {
common: { ...options.defaults?.common },
request: { ...options.defaults?.request },
get: { method: "GET", ...options.defaults?.get },
put: { method: "PUT", ...options.defaults?.put },
Expand All @@ -75,8 +78,9 @@ export const createClient = (options: ClientOptions = {}): Client => {
delete: { method: "DELETE", ...options.defaults?.delete },
options: { method: "OPTIONS", ...options.defaults?.options },
head: { method: "HEAD", ...options.defaults?.head },
baseUrl: options.baseUrl,
};

baseUrl = options.baseUrl;
};

/**
Expand Down Expand Up @@ -134,26 +138,22 @@ export const createClient = (options: ClientOptions = {}): Client => {
* default if JSON is passed as the request body.
*/
const { json, ...requestInit } = init ?? {};
const defaultInit = getDefaultInit();
const combinedRequestInit: RequestInit = {
...defaultInit,
...requestInit,
headers: mergeHeaders([
json ? { "content-type": "application/json" } : {},
defaultInit.headers,
requestInit.headers,
]),
};
const combinedRequestInit = mergeInits(
json ? { headers: { "content-type": "application/json" } } : undefined,
defaults.common,
getDefaultInit,
requestInit
);

// Append the base URL
let requestInfo = info;

if (!(info instanceof Request)) {
try {
requestInfo = new URL(info, defaults.baseUrl);
requestInfo = new URL(info, baseUrl);
} catch (e) {
throw new TypeError(`Could not build valid URL from parts:
baseUrl: "${defaults.baseUrl}"
baseUrl: "${baseUrl}"
path: "${info}"
`);
}
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ export type ClientOptions = {
delete?: Omit<RequestInit, "method">;
options?: Omit<RequestInit, "method">;
head?: Omit<RequestInit, "method">;
common?: Omit<RequestInit, "method">;
};
callbacks?: {
onRequestStart?: Callbacks["onRequestStart"][];
Expand Down
26 changes: 26 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ export const mergeHeaders = (inits: (HeadersInit | undefined)[]): Headers => {
const result: Record<string, string> = {};

for (const init of inits) {
if (!init) continue;

new Headers(init).forEach((value, key) => {
result[key] = value;
});
Expand All @@ -16,6 +18,30 @@ export const mergeHeaders = (inits: (HeadersInit | undefined)[]): Headers => {
return new Headers(result);
};

export const mergeInits = (
...inits: (RequestInit | (() => RequestInit) | undefined)[]
): RequestInit => {
let result: RequestInit = {};
const headers: HeadersInit[] = [];

for (const init of inits) {
if (!init) continue;

const r = typeof init === "function" ? init() : init;

if (r.headers) {
headers.push(r.headers);
}

result = { ...result, ...r };
}

return {
...result,
headers: mergeHeaders(headers),
};
};

/**
* Provides mechanism for adding/removing callbacks and then emitting data to
* all callbacks
Expand Down
34 changes: 34 additions & 0 deletions test/createClient.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,40 @@ describe("createClient", () => {
expect(response2).toEqual({ firstName: "Bob", lastName: "Dylan", age: 83 });
});

test("The order of precedence for configs is common default -> method default -> request", async () => {
const f = createClient({
defaults: {
common: {
headers: {
"header-1": "common-header-1",
"header-2": "common-header-2",
"header-3": "common-header-3",
},
},
get: {
headers: {
"header-2": "get-header-2",
"header-3": "get-header-3",
"header-4": "get-header-4",
},
},
},
});

nock(HOST)
.get("/api/user")
.matchHeader("header-1", "common-header-1")
.matchHeader("header-2", "get-header-2")
.matchHeader("header-3", "request-header-3")
.reply(200, { firstName: "Shane", lastName: "MacGowan", age: 65 });

const user = await f
.get(HOST + "/api/user", { headers: { "header-3": "request-header-3" } })
.json<User>();

expect(user).toEqual({ firstName: "Shane", lastName: "MacGowan", age: 65 });
});

test("It lets you instantiate the client with some built in callbacks", async () => {
nock(HOST)
.post("/api/user", { id: "6686" })
Expand Down

0 comments on commit 9d8f8bf

Please sign in to comment.