diff --git a/packages/apollo-datasource-rest/tsconfig.json b/packages/apollo-datasource-rest/tsconfig.json index 3cc3ec7661a..bbb66fb641f 100644 --- a/packages/apollo-datasource-rest/tsconfig.json +++ b/packages/apollo-datasource-rest/tsconfig.json @@ -8,9 +8,9 @@ "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noUnusedParameters": true, - "noUnusedLocals": true + "noUnusedLocals": true, + "types": ["node", "jest"] }, "include": ["src/**/*"], - "exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"], - "types": ["node", "jest"] + "exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"] } diff --git a/packages/apollo-server-caching/.gitignore b/packages/apollo-server-caching/.gitignore new file mode 100644 index 00000000000..723ef36f4e4 --- /dev/null +++ b/packages/apollo-server-caching/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/packages/apollo-server-caching/.npmignore b/packages/apollo-server-caching/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-server-caching/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-server-caching/package.json b/packages/apollo-server-caching/package.json new file mode 100644 index 00000000000..a6b00bc1be2 --- /dev/null +++ b/packages/apollo-server-caching/package.json @@ -0,0 +1,24 @@ +{ + "name": "apollo-server-caching", + "version": "2.0.0-rc.0", + "author": "opensource@apollographql.com", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-caching" + }, + "homepage": "https://github.com/apollographql/apollo-server#readme", + "bugs": { + "url": "https://github.com/apollographql/apollo-server/issues" + }, + "scripts": { + "clean": "rm -rf lib", + "compile": "tsc", + "prepublish": "npm run clean && npm run compile" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=6" + } +} diff --git a/packages/apollo-server-caching/src/KeyValueCache.ts b/packages/apollo-server-caching/src/KeyValueCache.ts new file mode 100644 index 00000000000..5896ce048f9 --- /dev/null +++ b/packages/apollo-server-caching/src/KeyValueCache.ts @@ -0,0 +1,4 @@ +export interface KeyValueCache { + get(key: string): Promise; + set(key: string, value: string, options?: { ttl?: number }): Promise; +} diff --git a/packages/apollo-server-caching/src/__mocks__/date.ts b/packages/apollo-server-caching/src/__mocks__/date.ts new file mode 100644 index 00000000000..62180933f65 --- /dev/null +++ b/packages/apollo-server-caching/src/__mocks__/date.ts @@ -0,0 +1,32 @@ +const RealDate = global.Date; + +export function mockDate() { + global.Date = new Proxy(RealDate, handler); +} + +export function unmockDate() { + global.Date = RealDate; +} + +let now = Date.now(); + +export function advanceTimeBy(ms: number) { + now += ms; +} + +const handler = { + construct(target, args) { + if (args.length === 0) { + return new Date(now); + } else { + return new target(...args); + } + }, + get(target, propKey) { + if (propKey === 'now') { + return () => now; + } else { + return target[propKey]; + } + }, +}; diff --git a/packages/apollo-server-caching/src/index.ts b/packages/apollo-server-caching/src/index.ts new file mode 100644 index 00000000000..a9e5f26d048 --- /dev/null +++ b/packages/apollo-server-caching/src/index.ts @@ -0,0 +1,2 @@ +export { KeyValueCache } from './KeyValueCache'; +export { testKeyValueCache } from './tests'; diff --git a/packages/apollo-server-caching/src/tests.ts b/packages/apollo-server-caching/src/tests.ts new file mode 100644 index 00000000000..894462e54ba --- /dev/null +++ b/packages/apollo-server-caching/src/tests.ts @@ -0,0 +1,40 @@ +import { advanceTimeBy, mockDate, unmockDate } from './__mocks__/date'; + +export function testKeyValueCache(keyValueCache) { + describe('KeyValueCache Test Suite', () => { + beforeAll(() => { + mockDate(); + jest.useFakeTimers(); + }); + + beforeEach(() => { + keyValueCache.flush(); + }); + + afterAll(() => { + unmockDate(); + keyValueCache.close(); + }); + + it('can do a basic get and set', async () => { + await keyValueCache.set('hello', 'world'); + expect(await keyValueCache.get('hello')).toBe('world'); + expect(await keyValueCache.get('missing')).not.toBeDefined(); + }); + + it('is able to expire keys based on ttl', async () => { + await keyValueCache.set('short', 's', { ttl: 1 }); + await keyValueCache.set('long', 'l', { ttl: 5 }); + expect(await keyValueCache.get('short')).toBe('s'); + expect(await keyValueCache.get('long')).toBe('l'); + advanceTimeBy(1500); + jest.advanceTimersByTime(1500); + expect(await keyValueCache.get('short')).not.toBeDefined(); + expect(await keyValueCache.get('long')).toBe('l'); + advanceTimeBy(4000); + jest.advanceTimersByTime(4000); + expect(await keyValueCache.get('short')).not.toBeDefined(); + expect(await keyValueCache.get('long')).not.toBeDefined(); + }); + }); +} diff --git a/packages/apollo-server-caching/tsconfig.json b/packages/apollo-server-caching/tsconfig.json new file mode 100644 index 00000000000..bbb66fb641f --- /dev/null +++ b/packages/apollo-server-caching/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "removeComments": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "types": ["node", "jest"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"] +} diff --git a/packages/apollo-server-memcached/.gitignore b/packages/apollo-server-memcached/.gitignore new file mode 100644 index 00000000000..723ef36f4e4 --- /dev/null +++ b/packages/apollo-server-memcached/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/packages/apollo-server-memcached/.npmignore b/packages/apollo-server-memcached/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-server-memcached/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-server-memcached/__tests__/Memcached.test.ts b/packages/apollo-server-memcached/__tests__/Memcached.test.ts new file mode 100644 index 00000000000..0a29cdb458e --- /dev/null +++ b/packages/apollo-server-memcached/__tests__/Memcached.test.ts @@ -0,0 +1,7 @@ +// use mock implementations for underlying databases +jest.mock('memcached', () => require('memcached-mock')); + +import MemcachedKeyValueCache from '../src/index'; +import { testKeyValueCache } from 'apollo-server-caching'; + +testKeyValueCache(new MemcachedKeyValueCache('localhost')); diff --git a/packages/apollo-server-memcached/package.json b/packages/apollo-server-memcached/package.json new file mode 100644 index 00000000000..902c1219ed2 --- /dev/null +++ b/packages/apollo-server-memcached/package.json @@ -0,0 +1,54 @@ +{ + "name": "apollo-server-memcached", + "version": "2.0.0-rc.0", + "author": "opensource@apollographql.com", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-memcached" + }, + "homepage": "https://github.com/apollographql/apollo-server#readme", + "bugs": { + "url": "https://github.com/apollographql/apollo-server/issues" + }, + "scripts": { + "clean": "rm -rf lib", + "compile": "tsc", + "prepublish": "npm run clean && npm run compile", + "test": "jest --verbose" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=6" + }, + "dependencies": { + "apollo-server-env": "^2.0.0-rc.0", + "apollo-server-caching": "^2.0.0-rc.0", + "memcached": "^2.2.2" + }, + "devDependencies": { + "@types/jest": "^23.0.0", + "@types/memcached": "^2.2.5", + "jest": "^23.1.0", + "memcached-mock": "^0.1.0", + "ts-jest": "^22.4.6" + }, + "jest": { + "testEnvironment": "node", + "transform": { + "^.+\\.(ts|js)$": "ts-jest" + }, + "moduleFileExtensions": [ + "ts", + "js", + "json" + ], + "testRegex": "/__tests__/.*$", + "globals": { + "ts-jest": { + "skipBabel": true + } + } + } +} diff --git a/packages/apollo-server-memcached/src/index.ts b/packages/apollo-server-memcached/src/index.ts new file mode 100644 index 00000000000..76e66520d04 --- /dev/null +++ b/packages/apollo-server-memcached/src/index.ts @@ -0,0 +1,39 @@ +import { KeyValueCache } from 'apollo-server-caching'; +import Memcached from 'memcached'; +import { promisify } from 'util'; + +export default class MemcachedKeyValueCache implements KeyValueCache { + readonly client; + readonly defaultSetOptions = { + ttl: 300, + }; + + constructor(serverLocation: Memcached.Location, options?: Memcached.options) { + this.client = new Memcached(serverLocation, options); + // promisify client calls for convenience + this.client.get = promisify(this.client.get).bind(this.client); + this.client.set = promisify(this.client.set).bind(this.client); + this.client.flush = promisify(this.client.flush).bind(this.client); + } + + async set( + key: string, + data: string, + options?: { ttl?: number }, + ): Promise { + const { ttl } = Object.assign({}, this.defaultSetOptions, options); + await this.client.set(key, data, ttl); + } + + async get(key: string): Promise { + return await this.client.get(key); + } + + async flush(): Promise { + await this.client.flush(); + } + + async close(): Promise { + this.client.end(); + } +} diff --git a/packages/apollo-server-memcached/tsconfig.json b/packages/apollo-server-memcached/tsconfig.json new file mode 100644 index 00000000000..bbb66fb641f --- /dev/null +++ b/packages/apollo-server-memcached/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "removeComments": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "types": ["node", "jest"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"] +} diff --git a/packages/apollo-server-redis/.gitignore b/packages/apollo-server-redis/.gitignore new file mode 100644 index 00000000000..723ef36f4e4 --- /dev/null +++ b/packages/apollo-server-redis/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/packages/apollo-server-redis/.npmignore b/packages/apollo-server-redis/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-server-redis/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-server-redis/__tests__/Redis.test.ts b/packages/apollo-server-redis/__tests__/Redis.test.ts new file mode 100644 index 00000000000..41bf1bb8627 --- /dev/null +++ b/packages/apollo-server-redis/__tests__/Redis.test.ts @@ -0,0 +1,8 @@ +// use mock implementations for underlying databases +jest.mock('redis', () => require('redis-mock')); +jest.useFakeTimers(); // mocks out setTimeout that is used in redis-mock + +import RedisKeyValueCache from '../src/index'; +import { testKeyValueCache } from 'apollo-server-caching'; + +testKeyValueCache(new RedisKeyValueCache({ host: 'localhost' })); diff --git a/packages/apollo-server-redis/package.json b/packages/apollo-server-redis/package.json new file mode 100644 index 00000000000..ab8a406290b --- /dev/null +++ b/packages/apollo-server-redis/package.json @@ -0,0 +1,54 @@ +{ + "name": "apollo-server-redis", + "version": "2.0.0-rc.0", + "author": "opensource@apollographql.com", + "license": "MIT", + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-redis" + }, + "homepage": "https://github.com/apollographql/apollo-server#readme", + "bugs": { + "url": "https://github.com/apollographql/apollo-server/issues" + }, + "scripts": { + "clean": "rm -rf lib", + "compile": "tsc", + "prepublish": "npm run clean && npm run compile", + "test": "jest --verbose" + }, + "main": "dist/index.js", + "types": "dist/index.d.ts", + "engines": { + "node": ">=6" + }, + "dependencies": { + "apollo-server-caching": "^2.0.0-rc.0", + "apollo-server-env": "^2.0.0-rc.0", + "redis": "^2.8.0" + }, + "devDependencies": { + "@types/jest": "^23.0.0", + "@types/redis": "^2.8.6", + "jest": "^23.1.0", + "redis-mock": "^0.27.0", + "ts-jest": "^22.4.6" + }, + "jest": { + "testEnvironment": "node", + "transform": { + "^.+\\.(ts|js)$": "ts-jest" + }, + "moduleFileExtensions": [ + "ts", + "js", + "json" + ], + "testRegex": "/__tests__/.*$", + "globals": { + "ts-jest": { + "skipBabel": true + } + } + } +} diff --git a/packages/apollo-server-redis/src/index.ts b/packages/apollo-server-redis/src/index.ts new file mode 100644 index 00000000000..f3711217aba --- /dev/null +++ b/packages/apollo-server-redis/src/index.ts @@ -0,0 +1,46 @@ +import { KeyValueCache } from 'apollo-server-caching'; +import Redis from 'redis'; +import { promisify } from 'util'; + +export default class RedisKeyValueCache implements KeyValueCache { + readonly client; + readonly defaultSetOptions = { + ttl: 300, + }; + + constructor(options: Redis.ClientOpts) { + this.client = Redis.createClient(options); + // promisify client calls for convenience + this.client.get = promisify(this.client.get).bind(this.client); + this.client.set = promisify(this.client.set).bind(this.client); + this.client.flushdb = promisify(this.client.flushdb).bind(this.client); + this.client.quit = promisify(this.client.quit).bind(this.client); + } + + async set( + key: string, + data: string, + options?: { ttl?: number }, + ): Promise { + const { ttl } = Object.assign({}, this.defaultSetOptions, options); + await this.client.set(key, data, 'EX', ttl); + } + + async get(key: string): Promise { + const reply = await this.client.get(key); + // reply is null if key is not found + if (reply !== null) { + return reply; + } + return; + } + + async flush(): Promise { + await this.client.flushdb(); + } + + async close(): Promise { + await this.client.quit(); + return; + } +} diff --git a/packages/apollo-server-redis/tsconfig.json b/packages/apollo-server-redis/tsconfig.json new file mode 100644 index 00000000000..bbb66fb641f --- /dev/null +++ b/packages/apollo-server-redis/tsconfig.json @@ -0,0 +1,16 @@ +{ + "extends": "../../tsconfig", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + "removeComments": true, + "strict": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + "noUnusedParameters": true, + "noUnusedLocals": true, + "types": ["node", "jest"] + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "**/__tests__/*", "**/__mocks__/*"] +}