Skip to content

Commit eab05a4

Browse files
author
dustin deus
committed
improve interface
1 parent 21a6f03 commit eab05a4

File tree

6 files changed

+66
-55
lines changed

6 files changed

+66
-55
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,20 +62,20 @@ const datasource = new (class MoviesAPI extends HTTPDataSource {
6262
})
6363
}
6464

65-
onCacheKeyCalculation(requestOptions: RequestOptions): string {
65+
onCacheKeyCalculation(request: Request): string {
6666
// return different key based on request options
6767
}
6868

69-
onRequest(requestOptions: RequestOptions): void {
69+
onRequest(request: Request): void {
7070
// manipulate request before it is send
7171
}
7272

73-
onResponse<TResult = unknown>(request: RequestOptions, response: Response<TResult>): void {
73+
onResponse<TResult = unknown>(request: Request, response: Response<TResult>): void {
7474
// manipulate response or handle unsuccessful response in a different way
7575
return super.onResponse(request, response)
7676
}
7777

78-
onError(error: RequestError, request: RequestOptions): void {
78+
onError(error: RequestError, request: Request): void {
7979
// log errors
8080
}
8181

package-lock.json

Lines changed: 3 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
},
5151
"dependencies": {
5252
"@alloc/quick-lru": "^5.2.0",
53-
"abort-controller": "^3.0.0",
5453
"apollo-datasource": "^0.9.0",
5554
"apollo-server-caching": "^0.7.0",
5655
"apollo-server-errors": "^2.5.0",
@@ -62,6 +61,7 @@
6261
"devDependencies": {
6362
"@types/node": "^14.17.3",
6463
"apollo-datasource-rest": "^0.14.0",
64+
"abort-controller": "^3.0.0",
6565
"ava": "^3.15.0",
6666
"h2url": "^0.2.0",
6767
"nock": "^13.1.0",

src/http-data-source.ts

Lines changed: 43 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { DataSource, DataSourceConfig } from 'apollo-datasource'
2-
import { Client, Pool } from 'undici'
2+
import { Pool } from 'undici'
33
import { STATUS_CODES } from 'http'
44
import QuickLRU from '@alloc/quick-lru'
55
import sjson from 'secure-json-parse'
6-
import AbortController from 'abort-controller'
76

87
import Keyv, { Store } from 'keyv'
98
import { KeyValueCache } from 'apollo-server-caching'
10-
import { DispatchOptions, ResponseData } from 'undici/types/dispatcher'
9+
import { ResponseData, RequestOptions as UndiciRequestOptions } from 'undici/types/dispatcher'
1110
import { ApolloError } from 'apollo-server-errors'
11+
import { EventEmitter, Readable } from 'stream'
12+
13+
type AbortSignal = unknown
1214

1315
export type CacheTTLOptions = {
1416
requestCache?: {
@@ -19,9 +21,19 @@ export type CacheTTLOptions = {
1921
}
2022
}
2123

22-
export type ClientRequestOptions = Omit<DispatchOptions, 'origin' | 'path' | 'method'> & CacheTTLOptions
24+
interface Dictionary<T> {
25+
[Key: string]: T
26+
}
2327

24-
export type RequestOptions = DispatchOptions & CacheTTLOptions
28+
export type RequestOptions = {
29+
body?: string | Buffer | Uint8Array | Readable | null
30+
headers?: Dictionary<string>
31+
signal?: AbortSignal | EventEmitter | null
32+
} & CacheTTLOptions
33+
34+
export type Request = UndiciRequestOptions & CacheTTLOptions & {
35+
headers: Dictionary<string>
36+
}
2537

2638
export type Response<TResult> = {
2739
body: TResult
@@ -34,8 +46,8 @@ export interface LRUOptions {
3446

3547
export interface HTTPDataSourceOptions {
3648
pool?: Pool
37-
requestOptions?: ClientRequestOptions
38-
clientOptions?: Client.Options
49+
requestOptions?: RequestOptions
50+
clientOptions?: Pool.Options
3951
lru?: Partial<LRUOptions>
4052
}
4153

@@ -73,9 +85,8 @@ const cacheableStatusCodes = [200, 201, 202, 203, 206]
7385
export abstract class HTTPDataSource<TContext = any> extends DataSource {
7486
public context!: TContext
7587
private storageAdapter!: Keyv
76-
private readonly pool: Pool
77-
private readonly globalRequestOptions?: ClientRequestOptions
78-
private readonly abortController: AbortController
88+
private pool: Pool
89+
private globalRequestOptions?: RequestOptions
7990
private readonly memoizedResults: QuickLRU<string, Response<any>>
8091

8192
constructor(public readonly baseURL: string, private readonly options?: HTTPDataSourceOptions) {
@@ -85,7 +96,6 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
8596
})
8697
this.pool = options?.pool ?? new Pool(this.baseURL, options?.clientOptions)
8798
this.globalRequestOptions = options?.requestOptions
88-
this.abortController = new AbortController()
8999
}
90100

91101
/**
@@ -100,22 +110,15 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
100110
})
101111
}
102112

103-
/**
104-
* Abort and signal to any request that the associated activity is to be aborted.
105-
*/
106-
abort() {
107-
this.abortController.abort()
108-
}
109-
110113
protected isResponseOk(statusCode: number): boolean {
111114
return (statusCode >= 200 && statusCode <= 399) || statusCode === 304
112115
}
113116

114117
protected isResponseCacheable<TResult = unknown>(
115-
requestOptions: RequestOptions,
118+
request: Request,
116119
response: Response<TResult>,
117120
): boolean {
118-
return cacheableStatusCodes.indexOf(response.statusCode) > -1 && requestOptions.method === 'GET'
121+
return cacheableStatusCodes.indexOf(response.statusCode) > -1 && request.method === 'GET'
119122
}
120123

121124
/**
@@ -125,8 +128,8 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
125128
* @param request
126129
* @returns
127130
*/
128-
protected onCacheKeyCalculation(requestOptions: RequestOptions): string {
129-
return requestOptions.origin + requestOptions.path
131+
protected onCacheKeyCalculation(request: Request): string {
132+
return request.origin + request.path
130133
}
131134

132135
/**
@@ -135,17 +138,17 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
135138
*
136139
* @param request
137140
*/
138-
protected onRequest?(requestOptions: RequestOptions): void
141+
protected onRequest?(request: Request): void
139142

140143
/**
141144
* onResponse is executed when a response has been received.
142145
* By default the implementation will throw for for unsuccessful responses.
143146
*
144-
* @param _requestOptions
147+
* @param _request
145148
* @param response
146149
*/
147150
protected onResponse<TResult = unknown>(
148-
_requestOptions: RequestOptions,
151+
_request: Request,
149152
response: Response<TResult>,
150153
): Response<TResult> {
151154
if (this.isResponseOk(response.statusCode)) {
@@ -158,13 +161,14 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
158161
)
159162
}
160163

161-
protected onError?(_error: Error, requestOptions: RequestOptions): void
164+
protected onError?(_error: Error, requestOptions: Request): void
162165

163166
protected async get<TResult = unknown>(
164167
path: string,
165-
requestOptions?: ClientRequestOptions,
168+
requestOptions?: RequestOptions,
166169
): Promise<Response<TResult>> {
167170
return await this.request<TResult>({
171+
headers: {},
168172
...requestOptions,
169173
method: 'GET',
170174
path,
@@ -174,9 +178,10 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
174178

175179
protected async post<TResult = unknown>(
176180
path: string,
177-
requestOptions?: ClientRequestOptions,
181+
requestOptions?: RequestOptions,
178182
): Promise<Response<TResult>> {
179183
return await this.request<TResult>({
184+
headers: {},
180185
...requestOptions,
181186
method: 'POST',
182187
path,
@@ -186,9 +191,10 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
186191

187192
protected async delete<TResult = unknown>(
188193
path: string,
189-
requestOptions?: ClientRequestOptions,
194+
requestOptions?: RequestOptions,
190195
): Promise<Response<TResult>> {
191196
return await this.request<TResult>({
197+
headers: {},
192198
...requestOptions,
193199
method: 'DELETE',
194200
path,
@@ -198,9 +204,10 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
198204

199205
protected async put<TResult = unknown>(
200206
path: string,
201-
requestOptions?: ClientRequestOptions,
207+
requestOptions?: RequestOptions,
202208
): Promise<Response<TResult>> {
203209
return await this.request<TResult>({
210+
headers: {},
204211
...requestOptions,
205212
method: 'PUT',
206213
path,
@@ -209,7 +216,7 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
209216
}
210217

211218
private async performRequest<TResult>(
212-
options: RequestOptions,
219+
options: Request,
213220
cacheKey: string,
214221
): Promise<Response<TResult>> {
215222
this.onRequest?.(options)
@@ -261,14 +268,12 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
261268
}
262269
}
263270

264-
private async request<TResult = unknown>(
265-
requestOptions: RequestOptions,
266-
): Promise<Response<TResult>> {
267-
const cacheKey = this.onCacheKeyCalculation(requestOptions)
268-
const ttlCacheEnabled = requestOptions.requestCache
271+
private async request<TResult = unknown>(request: Request): Promise<Response<TResult>> {
272+
const cacheKey = this.onCacheKeyCalculation(request)
273+
const ttlCacheEnabled = request.requestCache
269274

270275
// check if we have any GET call in the cache and respond immediatly
271-
if (requestOptions.method === 'GET' && ttlCacheEnabled) {
276+
if (request.method === 'GET' && ttlCacheEnabled) {
272277
const cachedResponse = await this.storageAdapter.get(cacheKey)
273278
if (cachedResponse) {
274279
return cachedResponse
@@ -277,8 +282,7 @@ export abstract class HTTPDataSource<TContext = any> extends DataSource {
277282

278283
const options = {
279284
...this.globalRequestOptions,
280-
...requestOptions,
281-
signal: this.abortController.signal,
285+
...request,
282286
}
283287

284288
// Memoize GET calls for the same data source instance

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ export {
22
HTTPDataSource,
33
HTTPDataSourceOptions,
44
LRUOptions,
5-
ClientRequestOptions,
6-
Response,
75
RequestOptions,
6+
Response,
7+
Request,
88
CacheTTLOptions,
99
} from './http-data-source'

test/rest-data-source.test.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import anyTest, { TestInterface } from 'ava'
22
import http from 'http'
33
import { setGlobalDispatcher, Agent, Pool } from 'undici'
4-
import { HTTPDataSource, RequestOptions, Response } from '../src'
4+
import AbortController from 'abort-controller'
5+
import { HTTPDataSource, Request, Response } from '../src'
56
import { AddressInfo } from 'net'
67
import { KeyValueCacheSetOptions } from 'apollo-server-caching'
78

@@ -253,9 +254,9 @@ test('Should be able to define a custom cache key for request memoization', asyn
253254
constructor() {
254255
super(baseURL)
255256
}
256-
onCacheKeyCalculation(requestOptions: RequestOptions) {
257+
onCacheKeyCalculation(request: Request) {
257258
t.pass('onCacheKeyCalculation')
258-
t.truthy(requestOptions)
259+
t.truthy(request)
259260
return 'foo'
260261
}
261262
getFoo() {
@@ -293,7 +294,7 @@ test('Should call onError on request error', async (t) => {
293294
super(baseURL)
294295
}
295296

296-
onResponse<TResult = any>(requestOptions: RequestOptions, response: Response<TResult>) {
297+
onResponse<TResult = any>(requestOptions: Request, response: Response<TResult>) {
297298
t.truthy(requestOptions)
298299
t.truthy(response)
299300
t.pass('onResponse')
@@ -342,13 +343,17 @@ test.cb('Should abort request', (t) => {
342343

343344
const baseURL = `http://localhost:${(server.address() as AddressInfo)?.port}`
344345

346+
const abortController = new AbortController()
347+
345348
const dataSource = new (class extends HTTPDataSource {
346349
constructor() {
347350
super(baseURL)
348351
}
349352

350353
async getFoo() {
351-
return await this.get(path)
354+
return await this.get(path, {
355+
signal: abortController.signal,
356+
})
352357
}
353358
})()
354359

@@ -369,7 +374,7 @@ test.cb('Should abort request', (t) => {
369374
'Timeout',
370375
).finally(t.end)
371376

372-
dataSource.abort()
377+
abortController.abort()
373378
})
374379

375380
test.cb('Should timeout', (t) => {
@@ -452,8 +457,8 @@ test('Should be able to modify request in willSendRequest', async (t) => {
452457
constructor() {
453458
super(baseURL)
454459
}
455-
onRequest(requestOptions: RequestOptions) {
456-
requestOptions.headers = {
460+
onRequest(request: Request) {
461+
request.headers = {
457462
'X-Foo': 'bar',
458463
}
459464
}

0 commit comments

Comments
 (0)