import { HTTPCache } from './HTTPCache'; import { ApolloError, AuthenticationError, ForbiddenError, } from 'apollo-server-errors'; export abstract class RESTDataSource { abstract baseURL: string; httpCache!: HTTPCache; context!: TContext; protected willSendRequest?(request: Request): void; protected async didReceiveErrorResponse( response: Response, ): Promise { const message = `${response.status} ${ response.statusText }: ${await response.text()}`; if (response.status === 401) { throw new AuthenticationError(message); } else if (response.status === 403) { throw new ForbiddenError(message); } else { throw new ApolloError(message); } } protected async get( path: string, params?: URLSearchParamsInit, options?: RequestInit, ): Promise { return this.fetch( path, params, Object.assign({ method: 'GET' }, options), ); } protected async post( path: string, params?: URLSearchParamsInit, options?: RequestInit, ): Promise { return this.fetch( path, params, Object.assign({ method: 'POST' }, options), ); } protected async patch( path: string, params?: URLSearchParamsInit, options?: RequestInit, ): Promise { return this.fetch( path, params, Object.assign({ method: 'PATCH' }, options), ); } protected async put( path: string, params?: URLSearchParamsInit, options?: RequestInit, ): Promise { return this.fetch( path, params, Object.assign({ method: 'PUT' }, options), ); } protected async delete( path: string, params?: URLSearchParamsInit, options?: RequestInit, ): Promise { return this.fetch( path, params, Object.assign({ method: 'DELETE' }, options), ); } private async fetch( path: string, params?: URLSearchParamsInit, init?: RequestInit, ): Promise { const url = new URL(path, this.baseURL); if (params) { // Append params to existing params in the path for (const [name, value] of new URLSearchParams(params)) { url.searchParams.append(name, value); } } return this.trace(`${(init && init.method) || 'GET'} ${url}`, async () => { const request = new Request(String(url)); if (this.willSendRequest) { this.willSendRequest(request); } const response = await this.httpCache.fetch(request, init); if (response.ok) { const contentType = response.headers.get('Content-Type'); if (contentType && contentType.startsWith('application/json')) { return response.json(); } else { return response.text(); } } else { return this.didReceiveErrorResponse(response); } }); } private async trace( label: string, fn: () => Promise, ): Promise { if (process && process.env && process.env.NODE_ENV === 'development') { // We're not using console.time because that isn't supported on Cloudflare const startTime = Date.now(); try { return await fn(); } finally { const duration = Date.now() - startTime; console.log(`${label} (${duration}ms)`); } } else { return fn(); } } }