Skip to content

Commit

Permalink
Allow specifying cacheOptions per-request or in a data source method
Browse files Browse the repository at this point in the history
  • Loading branch information
martijnwalraven committed Jul 17, 2018
1 parent 2592060 commit 4aa21b7
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 13 deletions.
39 changes: 29 additions & 10 deletions packages/apollo-datasource-rest/src/HTTPCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ import { fetch, Request, Response, Headers } from 'apollo-server-env';
import CachePolicy = require('http-cache-semantics');

import { KeyValueCache, InMemoryLRUCache } from 'apollo-server-caching';

interface CacheOptions {
cacheKey?: string;
ttl?: number;
}
import { CacheOptions } from './RESTDataSource';

export class HTTPCache {
constructor(private keyValueCache: KeyValueCache = new InMemoryLRUCache()) {}

async fetch(request: Request, options: CacheOptions = {}): Promise<Response> {
async fetch(
request: Request,
options: {
cacheKey?: string;
cacheOptions?:
| CacheOptions
| ((response: Response, request: Request) => CacheOptions | undefined);
} = {},
): Promise<Response> {
const cacheKey = options.cacheKey ? options.cacheKey : request.url;
const ttl = options.ttl;

const entry = await this.keyValueCache.get(`httpcache:${cacheKey}`);
if (!entry) {
Expand All @@ -25,7 +28,13 @@ export class HTTPCache {
policyResponseFrom(response),
);

return this.storeResponseAndReturnClone(response, policy, cacheKey, ttl);
return this.storeResponseAndReturnClone(
response,
request,
policy,
cacheKey,
options.cacheOptions,
);
}

const { policy: policyRaw, body } = JSON.parse(entry);
Expand Down Expand Up @@ -63,19 +72,29 @@ export class HTTPCache {
status: revalidatedPolicy._status,
headers: revalidatedPolicy.responseHeaders(),
}),
request,
revalidatedPolicy,
cacheKey,
ttl,
options.cacheOptions,
);
}
}

private async storeResponseAndReturnClone(
response: Response,
request: Request,
policy: CachePolicy,
cacheKey: string,
ttl?: number,
cacheOptions?:
| CacheOptions
| ((response: Response, request: Request) => CacheOptions | undefined),
): Promise<Response> {
if (cacheOptions && typeof cacheOptions === 'function') {
cacheOptions = cacheOptions(response, request);
}

let ttl = cacheOptions && cacheOptions.ttl;

if (ttl) {
(policy as any)._rescc['max-age'] = ttl;
}
Expand Down
25 changes: 24 additions & 1 deletion packages/apollo-datasource-rest/src/RESTDataSource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,25 @@ import {
ForbiddenError,
} from 'apollo-server-errors';

declare module 'apollo-server-env/dist/fetch' {
interface RequestInit {
cacheOptions?:
| CacheOptions
| ((response: Response, request: Request) => CacheOptions | undefined);
}
}

export type RequestOptions = RequestInit & {
path: string;
params: URLSearchParams;
headers: Headers;
body?: Body;
};

export interface CacheOptions {
ttl?: number;
}

export type Body = BodyInit | object;
export { Request };

Expand Down Expand Up @@ -70,6 +82,11 @@ export abstract class RESTDataSource<TContext = any> extends DataSource {
}
}

protected cacheOptionsFor?(
response: Response,
request: Request,
): CacheOptions | undefined;

protected async didReceiveResponse<TResult = any>(
response: Response,
_request: Request,
Expand Down Expand Up @@ -215,8 +232,14 @@ export abstract class RESTDataSource<TContext = any> extends DataSource {

return this.memoize(cacheKey, async () => {
return this.trace(`${options.method || 'GET'} ${url}`, async () => {
const cacheOptions = options.cacheOptions
? options.cacheOptions
: this.cacheOptionsFor && this.cacheOptionsFor.bind(this);
try {
const response = await this.httpCache.fetch(request, { cacheKey });
const response = await this.httpCache.fetch(request, {
cacheKey,
cacheOptions,
});
return this.didReceiveResponse(response, request);
} catch (error) {
this.didEncounterError(error, request);
Expand Down
30 changes: 28 additions & 2 deletions packages/apollo-datasource-rest/src/__tests__/HTTPCache.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { fetch, Request } from '../../../../__mocks__/apollo-server-env';
import {
fetch,
Request,
Response,
} from '../../../../__mocks__/apollo-server-env';

import {
mockDate,
Expand Down Expand Up @@ -86,7 +90,29 @@ describe('HTTPCache', () => {
fetch.mockJSONResponseOnce({ name: 'Ada Lovelace' });

await httpCache.fetch(new Request('https://api.example.com/people/1'), {
ttl: 30,
cacheOptions: {
ttl: 30,
},
});

advanceTimeBy(10000);

const response = await httpCache.fetch(
new Request('https://api.example.com/people/1'),
);

expect(fetch.mock.calls.length).toEqual(1);
expect(await response.json()).toEqual({ name: 'Ada Lovelace' });
expect(response.headers.get('Age')).toEqual('10');
});

it('allows overriding the TTL dynamically', async () => {
fetch.mockJSONResponseOnce({ name: 'Ada Lovelace' });

await httpCache.fetch(new Request('https://api.example.com/people/1'), {
cacheOptions: (response: Response, request: Request) => ({
ttl: 30,
}),
});

advanceTimeBy(10000);
Expand Down
1 change: 1 addition & 0 deletions packages/apollo-datasource-rest/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { RESTDataSource, RequestOptions } from './RESTDataSource';
export { HTTPCache } from './HTTPCache';
export { Request, Response } from 'apollo-server-env';

0 comments on commit 4aa21b7

Please sign in to comment.