Socket
Socket
Sign inDemoInstall

apollo-datasource-http

Package Overview
Dependencies
Maintainers
1
Versions
45
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

apollo-datasource-http - npm Package Compare versions

Comparing version 0.5.0 to 0.6.0

44

dist/src/http-data-source.d.ts
import { DataSource, DataSourceConfig } from 'apollo-datasource';
import { RequestError, NormalizedOptions, OptionsOfJSONResponseBody, Response, GotReturn as Request, PlainResponse } from 'got';
export declare type RequestOptions = OptionsOfJSONResponseBody | NormalizedOptions;
import { Client, Pool } from 'undici';
import { DispatchOptions, ResponseData } from 'undici/types/dispatcher';
export declare type CacheTTLOptions = {
requestCache?: {
maxTtl: number;
maxTtlIfError: number;
};
};
export declare type RequestOptions = Omit<DispatchOptions, 'origin' | 'path' | 'method'> & CacheTTLOptions;
declare type InternalRequestOptions = DispatchOptions & CacheTTLOptions;
export declare type Response<TResult> = {
body: TResult;
} & Omit<ResponseData, 'body'>;
export interface LRUOptions {

@@ -9,28 +20,33 @@ readonly maxAge?: number;

export interface HTTPDataSourceOptions {
requestOptions?: Partial<RequestOptions>;
pool?: Pool;
requestOptions?: RequestOptions;
clientOptions?: Client.Options;
lru?: Partial<LRUOptions>;
}
export declare abstract class HTTPDataSource<TContext = any> extends DataSource {
readonly baseURL: string;
private readonly options?;
private static readonly agents;
baseURL?: string;
context: TContext;
private storageAdapter;
private readonly pool;
private readonly globalRequestOptions?;
private readonly abortController;
private readonly memoizedResults;
constructor(options?: HTTPDataSourceOptions | undefined);
constructor(baseURL: string, options?: HTTPDataSourceOptions | undefined);
initialize(config: DataSourceConfig<TContext>): void;
abort(): void;
protected isResponseOk(response: PlainResponse): boolean;
protected onCacheKeyCalculation(requestOptions: RequestOptions): string;
protected isResponseOk(statusCode: number): boolean;
protected isResponseCacheable<TResult = unknown>(requestOptions: InternalRequestOptions, response: Response<TResult>): boolean;
protected onCacheKeyCalculation(requestOptions: InternalRequestOptions): string;
protected onRequest?(requestOptions: RequestOptions): void;
protected onResponse<TResult = unknown>(_request: Request, response: Response<TResult>): Response<TResult>;
protected onError?(_error: RequestError): void;
protected get<TResult = unknown>(url: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected post<TResult = unknown>(url: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected delete<TResult = unknown>(url: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected put<TResult = unknown>(url: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected onResponse<TResult = unknown>(response: Response<TResult>): Response<TResult>;
protected onError?(_error: Error): void;
protected get<TResult = unknown>(path: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected post<TResult = unknown>(path: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected delete<TResult = unknown>(path: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
protected put<TResult = unknown>(path: string, requestOptions?: RequestOptions): Promise<Response<TResult>>;
private performRequest;
private request;
}
export {};
//# sourceMappingURL=http-data-source.d.ts.map
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {

@@ -27,9 +8,9 @@ return (mod && mod.__esModule) ? mod : { "default": mod };

const apollo_datasource_1 = require("apollo-datasource");
const got_1 = __importStar(require("got"));
const undici_1 = require("undici");
const http_1 = require("http");
const quick_lru_1 = __importDefault(require("@alloc/quick-lru"));
const secure_json_parse_1 = __importDefault(require("secure-json-parse"));
const abort_controller_1 = __importDefault(require("abort-controller"));
const agentkeepalive_1 = __importDefault(require("agentkeepalive"));
const keyv_1 = __importDefault(require("keyv"));
const apollo_server_errors_1 = require("apollo-server-errors");
const keyv_1 = __importDefault(require("keyv"));
const { HttpsAgent } = agentkeepalive_1.default;
function apolloKeyValueCacheToKeyv(cache) {

@@ -57,5 +38,7 @@ return {

}
const cacheableStatusCodes = [200, 201, 202, 203, 206];
class HTTPDataSource extends apollo_datasource_1.DataSource {
constructor(options) {
constructor(baseURL, options) {
super();
this.baseURL = baseURL;
this.options = options;

@@ -65,2 +48,4 @@ this.memoizedResults = new quick_lru_1.default({

});
this.pool = options?.pool ?? new undici_1.Pool(this.baseURL, options?.clientOptions);
this.globalRequestOptions = options?.requestOptions;
this.abortController = new abort_controller_1.default();

@@ -77,114 +62,113 @@ }

}
isResponseOk(response) {
const { statusCode } = response;
const limitStatusCode = response.request.options.followRedirect ? 299 : 399;
return (statusCode >= 200 && statusCode <= limitStatusCode) || statusCode === 304;
isResponseOk(statusCode) {
return (statusCode >= 200 && statusCode <= 399) || statusCode === 304;
}
isResponseCacheable(requestOptions, response) {
return cacheableStatusCodes.indexOf(response.statusCode) > -1 && requestOptions.method === 'GET';
}
onCacheKeyCalculation(requestOptions) {
if (requestOptions.url)
return requestOptions.url.toString();
throw new Error('No Cache key provided');
return requestOptions.origin + requestOptions.path;
}
onResponse(_request, response) {
if (this.isResponseOk(response)) {
onResponse(response) {
if (this.isResponseOk(response.statusCode)) {
return response;
}
throw new got_1.HTTPError(response);
throw new apollo_server_errors_1.ApolloError(`Response code ${response.statusCode} (${http_1.STATUS_CODES[response.statusCode]})`, response.statusCode.toString());
}
async get(url, requestOptions) {
return await this.request(url, {
async get(path, requestOptions) {
return await this.request({
...requestOptions,
method: 'GET',
...requestOptions,
path,
origin: this.baseURL,
});
}
async post(url, requestOptions) {
return await this.request(url, {
async post(path, requestOptions) {
return await this.request({
...requestOptions,
method: 'POST',
...requestOptions,
path,
origin: this.baseURL,
});
}
async delete(url, requestOptions) {
return await this.request(url, {
async delete(path, requestOptions) {
return await this.request({
...requestOptions,
method: 'DELETE',
...requestOptions,
path,
origin: this.baseURL,
});
}
async put(url, requestOptions) {
return await this.request(url, {
async put(path, requestOptions) {
return await this.request({
...requestOptions,
method: 'PUT',
...requestOptions,
path,
origin: this.baseURL,
});
}
async performRequest(options) {
async performRequest(options, cacheKey) {
this.onRequest?.(options);
const cancelableRequest = got_1.default(options);
const abort = () => {
cancelableRequest.cancel('abortController');
};
this.abortController.signal.addEventListener('abort', abort);
try {
const response = await cancelableRequest;
this.abortController.signal.removeEventListener('abort', abort);
this.onResponse(response.request, response);
const responseData = await this.pool.request(options);
responseData.body.setEncoding('utf8');
let data = '';
for await (const chunk of responseData.body) {
data += chunk;
}
let json;
if (data) {
json = secure_json_parse_1.default.parse(data);
}
const response = {
...responseData,
body: json,
};
this.onResponse(response);
if (options.requestCache && this.isResponseCacheable(options, response)) {
this.storageAdapter.set(cacheKey, response, options.requestCache?.maxTtl);
this.storageAdapter.set(`staleIfError:${cacheKey}`, response, options.requestCache?.maxTtlIfError);
}
return response;
}
catch (error) {
let error_ = error;
if (error instanceof got_1.HTTPError) {
if (error.response.statusCode === 401) {
const err = new apollo_server_errors_1.AuthenticationError(error.message);
err.originalError = error;
error_ = err;
this.onError?.(error);
if (options.requestCache) {
const hasFallback = await this.storageAdapter.get(`staleIfError:${cacheKey}`);
if (hasFallback) {
return hasFallback;
}
else if (error.response.statusCode === 403) {
const err = new apollo_server_errors_1.ForbiddenError(error.message);
err.originalError = error;
error_ = err;
}
else {
const err = new apollo_server_errors_1.ApolloError(error.message, error.code);
err.originalError = error;
error_ = err;
}
}
this.onError?.(error);
this.abortController.signal.removeEventListener('abort', abort);
throw error_;
throw error;
}
}
async request(path, requestOptions) {
const options = got_1.default.mergeOptions({
cache: this.storageAdapter,
path,
responseType: 'json',
throwHttpErrors: false,
timeout: 5000,
agent: HTTPDataSource.agents,
prefixUrl: this.baseURL,
}, {
...this.options?.requestOptions,
}, requestOptions);
const cacheKey = this.onCacheKeyCalculation(options);
async request(requestOptions) {
const cacheKey = this.onCacheKeyCalculation(requestOptions);
const ttlCacheEnabled = requestOptions.requestCache;
if (requestOptions.method === 'GET' && ttlCacheEnabled) {
const cachedResponse = await this.storageAdapter.get(cacheKey);
if (cachedResponse) {
return cachedResponse;
}
}
const options = {
...this.globalRequestOptions,
...requestOptions,
signal: this.abortController.signal,
};
if (options.method === 'GET') {
const cachedResponse = this.memoizedResults.get(cacheKey);
if (cachedResponse)
if (cachedResponse) {
return cachedResponse;
const response = await this.performRequest(options);
this.memoizedResults.set(cacheKey, response);
}
const response = await this.performRequest(options, cacheKey);
if (this.isResponseCacheable(options, response)) {
this.memoizedResults.set(cacheKey, response);
}
return response;
}
return this.performRequest(options);
return this.performRequest(options, cacheKey);
}
}
exports.HTTPDataSource = HTTPDataSource;
HTTPDataSource.agents = {
http: new agentkeepalive_1.default({
keepAlive: true,
scheduling: 'lifo',
}),
https: new HttpsAgent({
keepAlive: true,
scheduling: 'lifo',
}),
};
//# sourceMappingURL=http-data-source.js.map

@@ -1,3 +0,2 @@

export { HTTPDataSource, HTTPDataSourceOptions, LRUOptions, RequestOptions } from './http-data-source';
export { Response, CacheError, CancelError, MaxRedirectsError, ReadError, ParseError, UploadError, HTTPError, TimeoutError, RequestError, UnsupportedProtocolError, GotReturn as Request } from 'got';
export { HTTPDataSource, HTTPDataSourceOptions, LRUOptions, RequestOptions, Response } from './http-data-source';
//# sourceMappingURL=index.d.ts.map
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.UnsupportedProtocolError = exports.RequestError = exports.TimeoutError = exports.HTTPError = exports.UploadError = exports.ParseError = exports.ReadError = exports.MaxRedirectsError = exports.CancelError = exports.CacheError = exports.HTTPDataSource = void 0;
exports.HTTPDataSource = void 0;
var http_data_source_1 = require("./http-data-source");
Object.defineProperty(exports, "HTTPDataSource", { enumerable: true, get: function () { return http_data_source_1.HTTPDataSource; } });
var got_1 = require("got");
Object.defineProperty(exports, "CacheError", { enumerable: true, get: function () { return got_1.CacheError; } });
Object.defineProperty(exports, "CancelError", { enumerable: true, get: function () { return got_1.CancelError; } });
Object.defineProperty(exports, "MaxRedirectsError", { enumerable: true, get: function () { return got_1.MaxRedirectsError; } });
Object.defineProperty(exports, "ReadError", { enumerable: true, get: function () { return got_1.ReadError; } });
Object.defineProperty(exports, "ParseError", { enumerable: true, get: function () { return got_1.ParseError; } });
Object.defineProperty(exports, "UploadError", { enumerable: true, get: function () { return got_1.UploadError; } });
Object.defineProperty(exports, "HTTPError", { enumerable: true, get: function () { return got_1.HTTPError; } });
Object.defineProperty(exports, "TimeoutError", { enumerable: true, get: function () { return got_1.TimeoutError; } });
Object.defineProperty(exports, "RequestError", { enumerable: true, get: function () { return got_1.RequestError; } });
Object.defineProperty(exports, "UnsupportedProtocolError", { enumerable: true, get: function () { return got_1.UnsupportedProtocolError; } });
//# sourceMappingURL=index.js.map
{
"name": "apollo-datasource-http",
"version": "0.5.0",
"version": "0.6.0",
"author": "Dustin Deus <deusdustin@gmail.com>",

@@ -54,9 +54,9 @@ "license": "MIT",

"abort-controller": "^3.0.0",
"agentkeepalive": "^4.1.4",
"apollo-datasource": "^0.9.0",
"apollo-server-caching": "^0.7.0",
"apollo-server-errors": "^2.5.0",
"got": "^11.8.2",
"graphql": "^15.5.0",
"keyv": "^4.0.3"
"keyv": "^4.0.3",
"secure-json-parse": "^2.4.0",
"undici": "^4.1.0"
},

@@ -63,0 +63,0 @@ "devDependencies": {

@@ -5,13 +5,6 @@ # Apollo HTTP Data Source

Optimized HTTP Data Source for Apollo Server
Optimized JSON HTTP Data Source for Apollo Server
- JSON by default
- HTTP/1 [Keep-alive agents](https://github.com/node-modules/agentkeepalive) for socket reuse
- HTTP/2 support (requires Node.js 15.10.0 or newer)
- Uses [Got](https://github.com/sindresorhus/got) a modern HTTP Client shipped with:
- Retry mechanism
- Request cancellation
- Timeout handling
- RFC 7234 compliant HTTP caching
- Request Deduplication and a Resource Cache
- Uses [Undici](https://github.com/nodejs/undici) under the hood
- Request Deduplication (LRU), Request Cache (TTL) and `stale-if-error` Cache (TTL)
- Support [AbortController ](https://github.com/mysticatea/abort-controller) to manually cancel all running requests

@@ -35,2 +28,6 @@ - Support for [Apollo Cache Storage backend](https://www.apollographql.com/docs/apollo-server/data/data-sources/#using-memcachedredis-as-a-cache-storage-backend)

```ts
// instantiate a pool outside of your hotpath
const baseURL = 'https://movies-api.example.com'
const pool = new Pool(baseURL)
const server = new ApolloServer({

@@ -41,3 +38,3 @@ typeDefs,

return {
moviesAPI: new MoviesAPI(),
moviesAPI: new MoviesAPI(baseURL, pool),
}

@@ -51,18 +48,21 @@ },

```ts
import { HTTPDataSource } from "apollo-datasource-http";
import { Pool } from 'undici'
import { HTTPDataSource } from 'apollo-datasource-http'
const datasource = new (class MoviesAPI extends HTTPDataSource {
constructor() {
constructor(baseURL: string, pool: Pool) {
// global client options
super({
super(baseURL, {
pool,
clientOptions: {
bodyTimeout: 100,
headersTimeout: 100,
},
requestOptions: {
timeout: 2000,
http2: true,
headers: {
"X-Client": "client",
'X-Client': 'client',
},
},
});
this.baseURL = "https://movies-api.example.com";
})
})
}

@@ -72,15 +72,13 @@ onCacheKeyCalculation(requestOptions: RequestOptions): string {

}
onRequest(requestOptions: RequestOptions): void {
// manipulate request
// manipulate request before it is send
}
onResponse<TResult = unknown>(
request: Request,
response: Response<TResult>,
): void {
onResponse<TResult = unknown>(request: Request, response: Response<TResult>): void {
// manipulate response or handle unsuccessful response in a different way
return super.onResponse(request, response)
}
onError(
error: RequestError
): void {
onError(error: RequestError): void {
// log errors

@@ -92,10 +90,9 @@ }

headers: {
"X-Foo": "bar",
},
timeout: 3000,
});
'X-Foo': 'bar',
}
})
}
}
})()
// cancel all running requests e.g when request is closed prematurely
// cancel all running requests e.g when the request is closed prematurely
datasource.abort()

@@ -113,2 +110,13 @@ ```

The http client throws for unsuccessful responses (statusCode >= 400). In case of an request error `onError` is executed. By default the error is rethrown as an instance of `ApolloError`.
The http client throws for unsuccessful responses (statusCode >= 400). In case of an request error `onError` is executed. By default the error is rethrown in form of the original error.
## Production checklist
This setup is in use with Redis. If you use Redis ensure that limits are set:
```
maxmemory 10mb
maxmemory-policy allkeys-lru
```
This will limit the cache to 10MB and removes the least recently used keys from the cache when the cache hits the limits.

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc