Initial Save
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016-2020 Apollo Graph, Inc. (Formerly Meteor Development Group, Inc.)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
# apollo-server-caching
|
||||
|
||||
[](https://badge.fury.io/js/apollo-server-caching)
|
||||
[](https://circleci.com/gh/apollographql/apollo-server)
|
||||
|
||||
## Implementing your own Cache
|
||||
|
||||
Internally, Apollo Server uses the `KeyValueCache` interface to provide a caching store for the Data Sources. An in-memory LRU cache is used by default, and we provide connectors for [Memcached](../apollo-server-cache-memcached)/[Redis](../apollo-server-cache-redis) backends.
|
||||
|
||||
Built with extensibility in mind, you can also implement your own cache to use with Apollo Server, in a way that best suits your application needs. It needs to implement the following interface that can be exported from `apollo-server-caching`:
|
||||
|
||||
```typescript
|
||||
export interface KeyValueCache {
|
||||
get(key: string): Promise<string | undefined>;
|
||||
set(key: string, value: string, options?: { ttl?: number }): Promise<void>;
|
||||
}
|
||||
```
|
||||
|
||||
> The `ttl` value for the `set` method's `options` is specified in __seconds__.
|
||||
|
||||
## Testing cache implementations
|
||||
|
||||
### Test helpers
|
||||
|
||||
You can export and run a jest test suite from `apollo-server-caching` to test your implementation:
|
||||
|
||||
```typescript
|
||||
// ../__tests__/YourKeyValueCache.test.ts
|
||||
|
||||
import YourKeyValueCache from '../src/YourKeyValueCache';
|
||||
import { testKeyValueCache } from 'apollo-server-caching';
|
||||
testKeyValueCache(new MemcachedCache('localhost'));
|
||||
```
|
||||
|
||||
The default `testKeyValueCache` helper will run all key-value store tests on the specified store, including basic `get` and `set` functionality, along with time-based expunging rules.
|
||||
|
||||
Some key-value cache implementations may not be able to support the full suite of tests (for example, some tests might not be able to expire based on time). For those cases, there are more granular implementations which can be used:
|
||||
|
||||
* `testKeyValueCache_Basic`
|
||||
* `testKeyValueCache_Expiration`
|
||||
|
||||
For more details, consult the [source for `apollo-server-caching`](./src/__tests__/testsuite.ts).
|
||||
|
||||
### Running tests
|
||||
|
||||
Run tests with `jest --verbose`
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
import { TestableKeyValueCache } from './KeyValueCache';
|
||||
export declare class InMemoryLRUCache<V = string> implements TestableKeyValueCache<V> {
|
||||
private store;
|
||||
constructor({ maxSize, sizeCalculator, onDispose, }?: {
|
||||
maxSize?: number;
|
||||
sizeCalculator?: (value: V, key: string) => number;
|
||||
onDispose?: (key: string, value: V) => void;
|
||||
});
|
||||
get(key: string): Promise<V | undefined>;
|
||||
set(key: string, value: V, options?: {
|
||||
ttl?: number;
|
||||
}): Promise<void>;
|
||||
delete(key: string): Promise<void>;
|
||||
flush(): Promise<void>;
|
||||
getTotalSize(): Promise<number>;
|
||||
}
|
||||
//# sourceMappingURL=InMemoryLRUCache.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"InMemoryLRUCache.d.ts","sourceRoot":"","sources":["../src/InMemoryLRUCache.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAYxD,qBAAa,gBAAgB,CAAC,CAAC,GAAG,MAAM,CAAE,YAAW,qBAAqB,CAAC,CAAC,CAAC;IAC3E,OAAO,CAAC,KAAK,CAAsB;gBAGvB,EACV,OAAkB,EAClB,cAAyC,EACzC,SAAS,GACV,GAAE;QACD,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;QACnD,SAAS,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;KACxC;IAQA,GAAG,CAAC,GAAG,EAAE,MAAM;IAGf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE;IAIrD,MAAM,CAAC,GAAG,EAAE,MAAM;IAMlB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAGtB,YAAY;CAGnB"}
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
"use strict";
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.InMemoryLRUCache = void 0;
|
||||
const lru_cache_1 = __importDefault(require("lru-cache"));
|
||||
function defaultLengthCalculation(item) {
|
||||
if (Array.isArray(item) || typeof item === 'string') {
|
||||
return item.length;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
class InMemoryLRUCache {
|
||||
constructor({ maxSize = Infinity, sizeCalculator = defaultLengthCalculation, onDispose, } = {}) {
|
||||
this.store = new lru_cache_1.default({
|
||||
max: maxSize,
|
||||
length: sizeCalculator,
|
||||
dispose: onDispose,
|
||||
});
|
||||
}
|
||||
get(key) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return this.store.get(key);
|
||||
});
|
||||
}
|
||||
set(key, value, options) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const maxAge = options && options.ttl && options.ttl * 1000;
|
||||
this.store.set(key, value, maxAge);
|
||||
});
|
||||
}
|
||||
delete(key) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.store.del(key);
|
||||
});
|
||||
}
|
||||
flush() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
this.store.reset();
|
||||
});
|
||||
}
|
||||
getTotalSize() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
return this.store.length;
|
||||
});
|
||||
}
|
||||
}
|
||||
exports.InMemoryLRUCache = InMemoryLRUCache;
|
||||
//# sourceMappingURL=InMemoryLRUCache.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"InMemoryLRUCache.js","sourceRoot":"","sources":["../src/InMemoryLRUCache.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AAAA,0DAAiC;AAGjC,SAAS,wBAAwB,CAAC,IAAS;IACzC,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QACnD,OAAO,IAAI,CAAC,MAAM,CAAC;KACpB;IAID,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAa,gBAAgB;IAI3B,YAAY,EACV,OAAO,GAAG,QAAQ,EAClB,cAAc,GAAG,wBAAwB,EACzC,SAAS,MAKP,EAAE;QACJ,IAAI,CAAC,KAAK,GAAG,IAAI,mBAAQ,CAAC;YACxB,GAAG,EAAE,OAAO;YACZ,MAAM,EAAE,cAAc;YACtB,OAAO,EAAE,SAAS;SACnB,CAAC,CAAC;IACL,CAAC;IAEK,GAAG,CAAC,GAAW;;YACnB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;KAAA;IACK,GAAG,CAAC,GAAW,EAAE,KAAQ,EAAE,OAA0B;;YACzD,MAAM,MAAM,GAAG,OAAO,IAAI,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,GAAG,IAAI,CAAC;YAC5D,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;QACrC,CAAC;KAAA;IACK,MAAM,CAAC,GAAW;;YACtB,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACtB,CAAC;KAAA;IAIK,KAAK;;YACT,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACrB,CAAC;KAAA;IACK,YAAY;;YAChB,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;QAC3B,CAAC;KAAA;CACF;AAvCD,4CAuCC"}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
export interface KeyValueCacheSetOptions {
|
||||
ttl?: number | null;
|
||||
}
|
||||
export interface KeyValueCache<V = string> {
|
||||
get(key: string): Promise<V | undefined>;
|
||||
set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise<void>;
|
||||
delete(key: string): Promise<boolean | void>;
|
||||
}
|
||||
export interface TestableKeyValueCache<V = string> extends KeyValueCache<V> {
|
||||
flush?(): Promise<void>;
|
||||
close?(): Promise<void>;
|
||||
}
|
||||
//# sourceMappingURL=KeyValueCache.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"KeyValueCache.d.ts","sourceRoot":"","sources":["../src/KeyValueCache.ts"],"names":[],"mappings":"AACA,MAAM,WAAW,uBAAuB;IAKtC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CACpB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC,GAAG,MAAM;IACvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IACzC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,uBAAuB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,GAAG,MAAM,CAAE,SAAQ,aAAa,CAAC,CAAC,CAAC;IAIzE,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IAExB,KAAK,CAAC,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACzB"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
;
|
||||
//# sourceMappingURL=KeyValueCache.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"KeyValueCache.js","sourceRoot":"","sources":["../src/KeyValueCache.ts"],"names":[],"mappings":";;AAOC,CAAC"}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { KeyValueCache, KeyValueCacheSetOptions } from './KeyValueCache';
|
||||
export declare class PrefixingKeyValueCache<V = string> implements KeyValueCache<V> {
|
||||
private wrapped;
|
||||
private prefix;
|
||||
constructor(wrapped: KeyValueCache<V>, prefix: string);
|
||||
get(key: string): Promise<V | undefined>;
|
||||
set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise<void>;
|
||||
delete(key: string): Promise<boolean | void>;
|
||||
}
|
||||
//# sourceMappingURL=PrefixingKeyValueCache.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PrefixingKeyValueCache.d.ts","sourceRoot":"","sources":["../src/PrefixingKeyValueCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAYzE,qBAAa,sBAAsB,CAAC,CAAC,GAAG,MAAM,CAAE,YAAW,aAAa,CAAC,CAAC,CAAC;IAC7D,OAAO,CAAC,OAAO;IAAoB,OAAO,CAAC,MAAM;gBAAzC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,EAAU,MAAM,EAAE,MAAM;IAErE,GAAG,CAAC,GAAG,EAAE,MAAM;IAGf,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,uBAAuB;IAG5D,MAAM,CAAC,GAAG,EAAE,MAAM;CAGnB"}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.PrefixingKeyValueCache = void 0;
|
||||
class PrefixingKeyValueCache {
|
||||
constructor(wrapped, prefix) {
|
||||
this.wrapped = wrapped;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
get(key) {
|
||||
return this.wrapped.get(this.prefix + key);
|
||||
}
|
||||
set(key, value, options) {
|
||||
return this.wrapped.set(this.prefix + key, value, options);
|
||||
}
|
||||
delete(key) {
|
||||
return this.wrapped.delete(this.prefix + key);
|
||||
}
|
||||
}
|
||||
exports.PrefixingKeyValueCache = PrefixingKeyValueCache;
|
||||
//# sourceMappingURL=PrefixingKeyValueCache.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"PrefixingKeyValueCache.js","sourceRoot":"","sources":["../src/PrefixingKeyValueCache.ts"],"names":[],"mappings":";;;AAYA,MAAa,sBAAsB;IACjC,YAAoB,OAAyB,EAAU,MAAc;QAAjD,YAAO,GAAP,OAAO,CAAkB;QAAU,WAAM,GAAN,MAAM,CAAQ;IAAG,CAAC;IAEzE,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAC7C,CAAC;IACD,GAAG,CAAC,GAAW,EAAE,KAAQ,EAAE,OAAiC;QAC1D,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC;IACD,MAAM,CAAC,GAAW;QAChB,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,GAAG,CAAC,CAAC;IAChD,CAAC;CACF;AAZD,wDAYC"}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export { KeyValueCache, TestableKeyValueCache, KeyValueCacheSetOptions, } from './KeyValueCache';
|
||||
export { InMemoryLRUCache } from './InMemoryLRUCache';
|
||||
export { PrefixingKeyValueCache } from './PrefixingKeyValueCache';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,EACb,qBAAqB,EACrB,uBAAuB,GACxB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC"}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
var InMemoryLRUCache_1 = require("./InMemoryLRUCache");
|
||||
Object.defineProperty(exports, "InMemoryLRUCache", { enumerable: true, get: function () { return InMemoryLRUCache_1.InMemoryLRUCache; } });
|
||||
var PrefixingKeyValueCache_1 = require("./PrefixingKeyValueCache");
|
||||
Object.defineProperty(exports, "PrefixingKeyValueCache", { enumerable: true, get: function () { return PrefixingKeyValueCache_1.PrefixingKeyValueCache; } });
|
||||
//# sourceMappingURL=index.js.map
|
||||
+1
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;AAKA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AACzB,mEAAkE;AAAzD,gIAAA,sBAAsB,OAAA"}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "apollo-server-caching",
|
||||
"version": "0.7.0",
|
||||
"author": "Apollo <opensource@apollographql.com>",
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/apollographql/apollo-server",
|
||||
"directory": "packages/apollo-server-caching"
|
||||
},
|
||||
"homepage": "https://github.com/apollographql/apollo-server#readme",
|
||||
"bugs": {
|
||||
"url": "https://github.com/apollographql/apollo-server/issues"
|
||||
},
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
},
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"gitHead": "f2349d0e10633ee79bed152f682e53730175d59b"
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
import LRUCache from 'lru-cache';
|
||||
import { TestableKeyValueCache } from './KeyValueCache';
|
||||
|
||||
function defaultLengthCalculation(item: any) {
|
||||
if (Array.isArray(item) || typeof item === 'string') {
|
||||
return item.length;
|
||||
}
|
||||
|
||||
// Go with the lru-cache default "naive" size, in lieu anything better:
|
||||
// https://github.com/isaacs/node-lru-cache/blob/a71be6cd/index.js#L17
|
||||
return 1;
|
||||
}
|
||||
|
||||
export class InMemoryLRUCache<V = string> implements TestableKeyValueCache<V> {
|
||||
private store: LRUCache<string, V>;
|
||||
|
||||
// TODO: Define reasonable default max size of the cache
|
||||
constructor({
|
||||
maxSize = Infinity,
|
||||
sizeCalculator = defaultLengthCalculation,
|
||||
onDispose,
|
||||
}: {
|
||||
maxSize?: number;
|
||||
sizeCalculator?: (value: V, key: string) => number;
|
||||
onDispose?: (key: string, value: V) => void;
|
||||
} = {}) {
|
||||
this.store = new LRUCache({
|
||||
max: maxSize,
|
||||
length: sizeCalculator,
|
||||
dispose: onDispose,
|
||||
});
|
||||
}
|
||||
|
||||
async get(key: string) {
|
||||
return this.store.get(key);
|
||||
}
|
||||
async set(key: string, value: V, options?: { ttl?: number }) {
|
||||
const maxAge = options && options.ttl && options.ttl * 1000;
|
||||
this.store.set(key, value, maxAge);
|
||||
}
|
||||
async delete(key: string) {
|
||||
this.store.del(key);
|
||||
}
|
||||
|
||||
// Drops all data from the cache. This should only be used by test suites ---
|
||||
// production code should never drop all data from an end user cache.
|
||||
async flush(): Promise<void> {
|
||||
this.store.reset();
|
||||
}
|
||||
async getTotalSize() {
|
||||
return this.store.length;
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
/** Options for {@link KeyValueCache.set} */
|
||||
export interface KeyValueCacheSetOptions {
|
||||
/**
|
||||
* Specified in **seconds**, the time-to-live (TTL) value limits the lifespan
|
||||
* of the data being stored in the cache.
|
||||
*/
|
||||
ttl?: number | null
|
||||
};
|
||||
|
||||
export interface KeyValueCache<V = string> {
|
||||
get(key: string): Promise<V | undefined>;
|
||||
set(key: string, value: V, options?: KeyValueCacheSetOptions): Promise<void>;
|
||||
delete(key: string): Promise<boolean | void>;
|
||||
}
|
||||
|
||||
export interface TestableKeyValueCache<V = string> extends KeyValueCache<V> {
|
||||
// Drops all data from the cache. This should only be used by test suites ---
|
||||
// production code should never drop all data from an end user cache (and
|
||||
// notably, PrefixingKeyValueCache intentionally doesn't implement this).
|
||||
flush?(): Promise<void>;
|
||||
// Close connections associated with this cache.
|
||||
close?(): Promise<void>;
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
import { KeyValueCache, KeyValueCacheSetOptions } from './KeyValueCache';
|
||||
|
||||
// PrefixingKeyValueCache wraps another cache and adds a prefix to all keys used
|
||||
// by all operations. This allows multiple features to share the same
|
||||
// underlying cache without conflicts.
|
||||
//
|
||||
// Note that PrefixingKeyValueCache explicitly does not implement
|
||||
// TestableKeyValueCache, and notably does not implement the flush()
|
||||
// method. Most implementations of TestableKeyValueCache.flush() send a simple
|
||||
// command that wipes the entire backend cache system, which wouldn't support
|
||||
// "only wipe the part of the cache with this prefix", so trying to provide a
|
||||
// flush() method here could be confusingly dangerous.
|
||||
export class PrefixingKeyValueCache<V = string> implements KeyValueCache<V> {
|
||||
constructor(private wrapped: KeyValueCache<V>, private prefix: string) {}
|
||||
|
||||
get(key: string) {
|
||||
return this.wrapped.get(this.prefix + key);
|
||||
}
|
||||
set(key: string, value: V, options?: KeyValueCacheSetOptions) {
|
||||
return this.wrapped.set(this.prefix + key, value, options);
|
||||
}
|
||||
delete(key: string) {
|
||||
return this.wrapped.delete(this.prefix + key);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
import {
|
||||
testKeyValueCache_Basics,
|
||||
testKeyValueCache_Expiration,
|
||||
} from '../../../apollo-server-caching/src/__tests__/testsuite';
|
||||
import { InMemoryLRUCache } from '../InMemoryLRUCache';
|
||||
|
||||
describe('InMemoryLRUCache', () => {
|
||||
const cache = new InMemoryLRUCache();
|
||||
testKeyValueCache_Basics(cache);
|
||||
testKeyValueCache_Expiration(cache);
|
||||
});
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
import { InMemoryLRUCache } from '../InMemoryLRUCache';
|
||||
import { PrefixingKeyValueCache } from '../PrefixingKeyValueCache';
|
||||
|
||||
describe('PrefixingKeyValueCache', () => {
|
||||
it('prefixes', async () => {
|
||||
const inner = new InMemoryLRUCache();
|
||||
const prefixing = new PrefixingKeyValueCache(inner, 'prefix:');
|
||||
await prefixing.set('foo', 'bar');
|
||||
expect(await prefixing.get('foo')).toBe('bar');
|
||||
expect(await inner.get('prefix:foo')).toBe('bar');
|
||||
await prefixing.delete('foo');
|
||||
expect(await prefixing.get('foo')).toBe(undefined);
|
||||
});
|
||||
});
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { advanceTimeBy, mockDate, unmockDate } from '__mocks__/date';
|
||||
import { TestableKeyValueCache } from '../';
|
||||
|
||||
export function testKeyValueCache_Basics(keyValueCache: TestableKeyValueCache) {
|
||||
describe('basic cache functionality', () => {
|
||||
beforeEach(() => {
|
||||
keyValueCache.flush && keyValueCache.flush();
|
||||
});
|
||||
|
||||
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')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('can do a basic set and delete', async () => {
|
||||
await keyValueCache.set('hello', 'world');
|
||||
expect(await keyValueCache.get('hello')).toBe('world');
|
||||
await keyValueCache.delete('hello');
|
||||
expect(await keyValueCache.get('hello')).toBeUndefined();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function testKeyValueCache_Expiration(
|
||||
keyValueCache: TestableKeyValueCache,
|
||||
) {
|
||||
describe('time-based cache expunging', () => {
|
||||
beforeAll(() => {
|
||||
mockDate();
|
||||
jest.useFakeTimers();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
keyValueCache.flush && keyValueCache.flush();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.useRealTimers();
|
||||
unmockDate();
|
||||
});
|
||||
|
||||
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')).toBeUndefined();
|
||||
expect(await keyValueCache.get('long')).toBe('l');
|
||||
advanceTimeBy(4000);
|
||||
jest.advanceTimersByTime(4000);
|
||||
expect(await keyValueCache.get('short')).toBeUndefined();
|
||||
expect(await keyValueCache.get('long')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('does not expire when ttl is null', async () => {
|
||||
await keyValueCache.set('forever', 'yours', { ttl: null });
|
||||
expect(await keyValueCache.get('forever')).toBe('yours');
|
||||
advanceTimeBy(1500);
|
||||
jest.advanceTimersByTime(1500);
|
||||
expect(await keyValueCache.get('forever')).toBe('yours');
|
||||
advanceTimeBy(4000);
|
||||
jest.advanceTimersByTime(4000);
|
||||
expect(await keyValueCache.get('forever')).toBe('yours');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function testKeyValueCache(keyValueCache: TestableKeyValueCache) {
|
||||
describe('KeyValueCache Test Suite', () => {
|
||||
afterAll(async () => {
|
||||
if (keyValueCache.close) {
|
||||
await keyValueCache.close();
|
||||
}
|
||||
});
|
||||
testKeyValueCache_Basics(keyValueCache);
|
||||
testKeyValueCache_Expiration(keyValueCache);
|
||||
});
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.test.base",
|
||||
"include": ["**/*", "../../../../__mocks__"],
|
||||
"references": [
|
||||
{ "path": "../../" },
|
||||
]
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
export {
|
||||
KeyValueCache,
|
||||
TestableKeyValueCache,
|
||||
KeyValueCacheSetOptions,
|
||||
} from './KeyValueCache';
|
||||
export { InMemoryLRUCache } from './InMemoryLRUCache';
|
||||
export { PrefixingKeyValueCache } from './PrefixingKeyValueCache';
|
||||
Reference in New Issue
Block a user