Skip to content

Commit c60a466

Browse files
committed
Refactor APIClient to use client.interceptors object
1 parent 4ba8eed commit c60a466

3 files changed

Lines changed: 49 additions & 50 deletions

File tree

README.md

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -83,14 +83,14 @@ You can add interceptors to the `APIClient` instance to customize the request an
8383
```ts
8484
let client = new APIClient("https://api.example.com");
8585

86-
client.on("before", async (request) => {
86+
client.interceptors.before.on(async (request) => {
8787
// Add a custom header to the request
8888
request.headers.set("X-Custom-Header", "value");
8989

9090
return request;
9191
});
9292

93-
client.on("after", async (request, response) => {
93+
client.interceptors.after.on(async (request, response) => {
9494
if (response.status === 401) {
9595
// Handle unauthorized error
9696
throw new Error("Unauthorized");
@@ -112,8 +112,8 @@ async function beforeInterceptor(request) {
112112

113113
let client = new APIClient("https://api.example.com");
114114

115-
client.on("before", beforeInterceptor); // Add the interceptor
116-
client.off("before", beforeInterceptor); // Remove the interceptor
115+
client.interceptors.before.on(beforeInterceptor); // Add the interceptor
116+
client.interceptors.before.off(beforeInterceptor); // Remove the interceptor
117117
```
118118

119119
The sub-class interceptors run before the instance interceptors.
@@ -130,7 +130,7 @@ class CustomAPIClient extends APIClient {
130130

131131
let client = new CustomAPIClient("https://api.example.com");
132132

133-
client.on("before", async (request) => {
133+
client.interceptors.before.on(async (request) => {
134134
// Add a custom header to the request
135135
request.headers.set("X-Custom-Header", "2");
136136

@@ -201,7 +201,7 @@ Or you can use the `on` method to add an interceptor to handle common API errors
201201
```ts
202202
let client = new APIClient("https://api.example.com");
203203

204-
client.on("after", async (request, response) => {
204+
client.interceptors.after.on(async (_, response) => {
205205
if (response.status === 401) {
206206
// Handle unauthorized error
207207
throw new Error("Unauthorized");

src/index.test.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ describe(APIClient.name, () => {
121121

122122
let client = new APIClient(new URL("https://example.com"));
123123

124-
client.on("before", async (request) => {
124+
client.interceptors.before.on(async (request) => {
125125
request.headers.set("X-Custom", randomValue);
126126
return request;
127127
});
@@ -139,7 +139,7 @@ describe(APIClient.name, () => {
139139
test("can attach after interceptor to instance", async () => {
140140
let client = new APIClient(new URL("https://example.com"));
141141

142-
client.on("after", async (_, response) => {
142+
client.interceptors.after.on(async (_, response) => {
143143
if (response.status === 400) throw new Error("Bad request");
144144
return response;
145145
});
@@ -156,12 +156,12 @@ describe(APIClient.name, () => {
156156
test("can attach multiple before interceptors to instance", async () => {
157157
let client = new APIClient(new URL("https://example.com"));
158158

159-
client.on("before", async (request) => {
159+
client.interceptors.before.on(async (request) => {
160160
request.headers.set("X-Custom", "First");
161161
return request;
162162
});
163163

164-
client.on("before", async (request) => {
164+
client.interceptors.before.on(async (request) => {
165165
request.headers.set("X-Custom", "Second");
166166
return request;
167167
});
@@ -179,12 +179,12 @@ describe(APIClient.name, () => {
179179
test("can attach multiple after interceptors to instance", async () => {
180180
let client = new APIClient(new URL("https://example.com"));
181181

182-
client.on("after", async (_, response) => {
182+
client.interceptors.after.on(async (_, response) => {
183183
if (response.status === 400) throw new Error("Bad request");
184184
return response;
185185
});
186186

187-
client.on("after", async (_, response) => {
187+
client.interceptors.after.on(async (_, response) => {
188188
if (response.status === 401) throw new Error("Unauthorized");
189189
return response;
190190
});
@@ -212,7 +212,7 @@ describe(APIClient.name, () => {
212212

213213
let client = new CustomClient();
214214

215-
client.on("before", async (request) => {
215+
client.interceptors.before.on(async (request) => {
216216
request.headers.set("X-Custom", "Instance");
217217
return request;
218218
});
@@ -241,7 +241,7 @@ describe(APIClient.name, () => {
241241

242242
let client = new CustomClient();
243243

244-
client.on("after", async (_, response) => {
244+
client.interceptors.after.on(async (_, response) => {
245245
if (response.status === 400) throw new TypeError("Bad request");
246246
return response;
247247
});

src/index.ts

Lines changed: 35 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,19 @@
2020
* }
2121
* }
2222
*/
23-
export class APIClient {
24-
private beforeListeners = new Set<APIClient.BeforeListenerFunction>();
25-
private afterListeners = new Set<APIClient.AfterListenerFunction>();
26-
23+
export class APIClient implements APIClient.Type {
2724
/** The base URL for all API requests. Must be defined in subclasses. */
2825
protected readonly baseURL: URL;
2926

3027
constructor(baseURL: URL) {
3128
this.baseURL = baseURL;
3229
}
3330

31+
public interceptors = {
32+
before: new Interceptor<APIClient.BeforeListenerFunction>(),
33+
after: new Interceptor<APIClient.AfterListenerFunction>(),
34+
};
35+
3436
/**
3537
* This method is called before the request is sent.
3638
* It can be used to modify the request before it is sent (e.g., adding
@@ -68,20 +70,14 @@ export class APIClient {
6870

6971
let request = new Request(url.toString(), init);
7072
request = await this.before(request);
71-
72-
if (this.beforeListeners.size > 0) {
73-
for (let listener of this.beforeListeners) {
74-
request = await listener(request);
75-
}
73+
for (let listener of this.interceptors.before.listeners) {
74+
request = await listener(request);
7675
}
7776

7877
let response = await fetch(request);
7978
response = await this.after(request, response);
80-
81-
if (this.afterListeners.size > 0) {
82-
for (let listener of this.afterListeners) {
83-
response = await listener(request, response);
84-
}
79+
for (let listener of this.interceptors.after.listeners) {
80+
response = await listener(request, response);
8581
}
8682

8783
return response;
@@ -141,33 +137,22 @@ export class APIClient {
141137
delete(path: string, init?: Omit<RequestInit, "method">) {
142138
return this.fetch(path, { ...init, method: "DELETE" });
143139
}
140+
}
144141

145-
public on(event: "before", listener: APIClient.BeforeListenerFunction): this;
146-
public on(event: "after", listener: APIClient.AfterListenerFunction): this;
147-
public on(event: "before" | "after", listener: APIClient.ListenerFunction) {
148-
if (event === "before") {
149-
this.beforeListeners.add(listener as APIClient.BeforeListenerFunction);
150-
}
151-
152-
if (event === "after") {
153-
this.afterListeners.add(listener as APIClient.AfterListenerFunction);
154-
}
142+
// biome-ignore lint/complexity/noBannedTypes: I need to use `Function` here
143+
class Interceptor<Listener extends Function> {
144+
#listeners = new Set<Listener>();
155145

156-
return this;
146+
public on(event: Listener) {
147+
this.#listeners.add(event);
157148
}
158149

159-
public off(event: "before", listener: APIClient.BeforeListenerFunction): this;
160-
public off(event: "after", listener: APIClient.AfterListenerFunction): this;
161-
public off(event: "before" | "after", listener: APIClient.ListenerFunction) {
162-
if (event === "before") {
163-
this.beforeListeners.delete(listener as APIClient.BeforeListenerFunction);
164-
}
165-
166-
if (event === "after") {
167-
this.afterListeners.delete(listener as APIClient.AfterListenerFunction);
168-
}
150+
public off(event: Listener) {
151+
this.#listeners.delete(event);
152+
}
169153

170-
return this;
154+
get listeners() {
155+
return Array.from(this.#listeners);
171156
}
172157
}
173158

@@ -180,4 +165,18 @@ export namespace APIClient {
180165
) => Promise<Response>;
181166

182167
export type ListenerFunction = BeforeListenerFunction | AfterListenerFunction;
168+
169+
export interface Type {
170+
interceptors: {
171+
before: Interceptor<BeforeListenerFunction>;
172+
after: Interceptor<AfterListenerFunction>;
173+
};
174+
175+
fetch(path: string, init?: RequestInit): Promise<Response>;
176+
get(path: string, init?: Omit<RequestInit, "method">): Promise<Response>;
177+
post(path: string, init?: Omit<RequestInit, "method">): Promise<Response>;
178+
put(path: string, init?: Omit<RequestInit, "method">): Promise<Response>;
179+
patch(path: string, init?: Omit<RequestInit, "method">): Promise<Response>;
180+
delete(path: string, init?: Omit<RequestInit, "method">): Promise<Response>;
181+
}
183182
}

0 commit comments

Comments
 (0)