Skip to content

Commit 9b025ee

Browse files
committed
Merge pull request #18 from ryanspradlin/features/prime
Implement ability to directly prime the cache
2 parents a57c44f + 5e21df6 commit 9b025ee

File tree

3 files changed

+122
-3
lines changed

3 files changed

+122
-3
lines changed

README.md

+6
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,12 @@ Clears the entire cache. To be used when some event results in unknown
196196
invalidations across this particular `DataLoader`. Returns itself for
197197
method chaining.
198198

199+
##### `prime(key, value)`
200+
201+
Primes the cache with the provided key and value. If the key already exists, no
202+
change is made. (To forcefully prime the cache, clear the key first with
203+
`loader.clear(key).prime(key, value)`.) Returns itself for method chaining.
204+
199205

200206
## Using with GraphQL
201207

src/__tests__/dataloader-test.js

+91
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,64 @@ describe('Primary API', () => {
165165
expect(loadCalls).to.deep.equal([ [ 'A', 'B' ], [ 'A', 'B' ] ]);
166166
});
167167

168+
it('allows priming the cache', async () => {
169+
var [ identityLoader, loadCalls ] = idLoader();
170+
171+
identityLoader.prime('A', 'A');
172+
173+
var [ a, b ] = await Promise.all([
174+
identityLoader.load('A'),
175+
identityLoader.load('B')
176+
]);
177+
178+
expect(a).to.equal('A');
179+
expect(b).to.equal('B');
180+
181+
expect(loadCalls).to.deep.equal([ [ 'B' ] ]);
182+
});
183+
184+
it('does not prime keys that already exist', async () => {
185+
var [ identityLoader, loadCalls ] = idLoader();
186+
187+
identityLoader.prime('A', 'X');
188+
189+
var a1 = await identityLoader.load('A');
190+
var b1 = await identityLoader.load('B');
191+
expect(a1).to.equal('X');
192+
expect(b1).to.equal('B');
193+
194+
identityLoader.prime('A', 'Y');
195+
identityLoader.prime('B', 'Y');
196+
197+
var a2 = await identityLoader.load('A');
198+
var b2 = await identityLoader.load('B');
199+
expect(a2).to.equal('X');
200+
expect(b2).to.equal('B');
201+
202+
expect(loadCalls).to.deep.equal([ [ 'B' ] ]);
203+
});
204+
205+
it('allows forcefully priming the cache', async () => {
206+
var [ identityLoader, loadCalls ] = idLoader();
207+
208+
identityLoader.prime('A', 'X');
209+
210+
var a1 = await identityLoader.load('A');
211+
var b1 = await identityLoader.load('B');
212+
expect(a1).to.equal('X');
213+
expect(b1).to.equal('B');
214+
215+
identityLoader.clear('A').prime('A', 'Y');
216+
identityLoader.clear('B').prime('B', 'Y');
217+
218+
var a2 = await identityLoader.load('A');
219+
var b2 = await identityLoader.load('B');
220+
expect(a2).to.equal('Y');
221+
expect(b2).to.equal('Y');
222+
223+
expect(loadCalls).to.deep.equal([ [ 'B' ] ]);
224+
});
225+
168226
});
169227

170228
describe('Represents Errors', () => {
@@ -249,6 +307,23 @@ describe('Represents Errors', () => {
249307
expect(loadCalls).to.deep.equal([ [ 1 ] ]);
250308
});
251309

310+
it('Handles priming the cache with an error', async () => {
311+
var [ identityLoader, loadCalls ] = idLoader();
312+
313+
identityLoader.prime(1, new Error('Error: 1'));
314+
315+
var caughtErrorA;
316+
try {
317+
await identityLoader.load(1);
318+
} catch (error) {
319+
caughtErrorA = error;
320+
}
321+
expect(caughtErrorA).to.be.instanceof(Error);
322+
expect((caughtErrorA: any).message).to.equal('Error: 1');
323+
324+
expect(loadCalls).to.deep.equal([]);
325+
});
326+
252327
it('Can clear values from cache after errors', async () => {
253328
var loadCalls = [];
254329
var errorLoader = new DataLoader(keys => {
@@ -485,6 +560,22 @@ describe('Accepts options', () => {
485560
expect(identityLoadCalls[0][0]).to.equal(keyA);
486561
});
487562

563+
it('Allows priming the cache with an object key', async () => {
564+
var [ identityLoader, loadCalls ] = idLoader({ cacheKeyFn: cacheKey });
565+
566+
var key1 = { id: 123 };
567+
var key2 = { id: 123 };
568+
569+
identityLoader.prime(key1, key1);
570+
571+
var value1 = await identityLoader.load(key1);
572+
var value2 = await identityLoader.load(key2);
573+
574+
expect(loadCalls).to.deep.equal([]);
575+
expect(value1).to.equal(key1);
576+
expect(value2).to.equal(key1);
577+
});
578+
488579
});
489580

490581
describe('Accepts custom cacheMap instance', () => {

src/index.js

+25-3
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010

1111
// A Function, which when given an Array of keys, returns a Promise of an Array
1212
// of values or Errors.
13-
type BatchLoadFn<K, V> = (keys: Array<K>) => Promise<Array<V | Error>>
13+
type BatchLoadFn<K, V> = (keys: Array<K>) => Promise<Array<V | Error>>;
1414

1515
type CacheMap<K, V> = {
1616
get(key: K): V | void;
1717
set(key: K, value: V): any;
1818
delete(key: K): any;
1919
clear(): any;
20-
}
20+
};
2121

2222
// Optionally turn off batching or caching or provide a cache key function or a
2323
// custom cache instance.
@@ -26,7 +26,7 @@ type Options<K, V> = {
2626
cache?: boolean,
2727
cacheMap?: CacheMap<K, Promise<V>>,
2828
cacheKeyFn?: (key: any) => any
29-
}
29+
};
3030

3131
/**
3232
* A `DataLoader` creates a public API for loading data from a particular
@@ -157,6 +157,28 @@ export default class DataLoader<K, V> {
157157
this._promiseCache.clear();
158158
return this;
159159
}
160+
161+
/**
162+
* Adds the provied key and value to the cache. If the key already exists, no
163+
* change is made. Returns itself for method chaining.
164+
*/
165+
prime(key: K, value: V): DataLoader<K, V> {
166+
var cacheKeyFn = this._options && this._options.cacheKeyFn;
167+
var cacheKey = cacheKeyFn ? cacheKeyFn(key) : key;
168+
169+
// Cache a rejected promise if the value is an Error, in order to match the
170+
// behavior of load(key).
171+
var promise = value instanceof Error ?
172+
Promise.reject(value) :
173+
Promise.resolve(value);
174+
175+
// Only add the key if it does not already exist.
176+
if (this._promiseCache.get(cacheKey) === undefined) {
177+
this._promiseCache.set(cacheKey, promise);
178+
}
179+
180+
return this;
181+
}
160182
}
161183

162184
// Private: Enqueue a Job to be executed after all "PromiseJobs" Jobs.

0 commit comments

Comments
 (0)