diff --git a/index.test-d.ts b/index.test-d.ts index 8d85770..8637ed9 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -15,7 +15,7 @@ expectAssignable>(cache.set('key', 1)); expectAssignable>(cache.set('key', true)); expectAssignable>(cache.set('key', [true, 'string'])); expectAssignable>>(cache.set('key', {wow: [true, 'string']})); -expectAssignable>(cache.set('key', 1, 1)); +expectAssignable>(cache.set('key', 1, {days: 1})); const cachedPower = cache.function(async (n: number) => n ** 1000); expectType<(n: number) => Promise>(cachedPower); @@ -42,14 +42,14 @@ expectNotAssignable>(cache.function(identity)('1')); expectType<(n: string) => Promise>( cache.function(async (n: string) => Number(n), { - maxAge: 20 + maxAge: {days: 20} }) ); expectType<(n: string) => Promise>( cache.function(async (n: string) => Number(n), { - maxAge: 20, - staleWhileRevalidate: 5 + maxAge: {days: 20}, + staleWhileRevalidate: {days: 5} }) ); diff --git a/index.ts b/index.ts index b5eba9f..5aafe62 100644 --- a/index.ts +++ b/index.ts @@ -1,4 +1,5 @@ import {isBackgroundPage} from 'webext-detect-page'; +import toMilliseconds, {TimeDescriptor} from '@sindresorhus/to-milliseconds'; // @ts-ignore // eslint-disable-next-line @typescript-eslint/promise-function-async @@ -13,8 +14,8 @@ const getPromise = (executor: () => void) => (key?): Promise => new Promis }); }); -function daysInTheFuture(days: number): number { - return Date.now() + (1000 * 3600 * 24 * days); +function timeInTheFuture(time: TimeDescriptor): number { + return Date.now() + toMilliseconds(time); } // @ts-ignore @@ -59,7 +60,7 @@ async function get(key: string): Promise(key: string, value: TValue, maxAge = 30 /* days */): Promise { +async function set(key: string, value: TValue, maxAge: TimeDescriptor = {days: 30}): Promise { if (typeof value === 'undefined') { // @ts-ignore This never happens in TS because `value` can't be undefined return; @@ -69,7 +70,7 @@ async function set(key: string, value: TValue, maxAge = 30 await _set({ [internalKey]: { data: value, - maxAge: daysInTheFuture(maxAge) + maxAge: timeInTheFuture(maxAge) } }); @@ -104,8 +105,8 @@ async function clear(): Promise { } interface MemoizedFunctionOptions { - maxAge?: number; - staleWhileRevalidate?: number; + maxAge?: TimeDescriptor; + staleWhileRevalidate?: TimeDescriptor; cacheKey?: (args: TArgs) => string; shouldRevalidate?: (cachedValue: TValue) => boolean; } @@ -116,7 +117,7 @@ function function_< TArgs extends Parameters >( getter: TFunction, - {cacheKey, maxAge = 30, staleWhileRevalidate = 0, shouldRevalidate}: MemoizedFunctionOptions = {} + {cacheKey, maxAge = {days: 30}, staleWhileRevalidate = {days: 0}, shouldRevalidate}: MemoizedFunctionOptions = {} ): TFunction { const getSet = async (key: string, args: TArgs): Promise => { const freshValue = await getter(...args); @@ -125,7 +126,9 @@ function function_< return; } - return set(key, freshValue, maxAge + staleWhileRevalidate); + const milliseconds = toMilliseconds(maxAge) + toMilliseconds(staleWhileRevalidate); + + return set(key, freshValue, {milliseconds}); }; return (async (...args: TArgs) => { @@ -138,7 +141,7 @@ function function_< } // When the expiration is earlier than the number of days specified by `staleWhileRevalidate`, it means `maxAge` has already passed and therefore the cache is stale. - if (daysInTheFuture(staleWhileRevalidate) > cachedItem.maxAge) { + if (timeInTheFuture(staleWhileRevalidate) > cachedItem.maxAge) { setTimeout(getSet, 0, userKey, args); } diff --git a/package.json b/package.json index 2a29e26..7271fc2 100644 --- a/package.json +++ b/package.json @@ -69,6 +69,7 @@ ] }, "dependencies": { + "@sindresorhus/to-milliseconds": "^1.2.0", "webext-detect-page": "^2.0.2" }, "devDependencies": { diff --git a/readme.md b/readme.md index f8700e5..19f3973 100644 --- a/readme.md +++ b/readme.md @@ -36,7 +36,9 @@ import cache from 'webext-storage-cache'; (async () => { if (!await cache.has('unique')) { const cachableItem = await someFunction(); - await cache.set('unique', cachableItem, 3 /* days */); + await cache.set('unique', cachableItem, { + days: 3 + }); } console.log(await cache.get('unique')); @@ -49,7 +51,9 @@ The same code could be also written more effectively with `cache.function`: import cache from 'webext-storage-cache'; const cachedFunction = cache.function(someFunction, { - maxAge: 3, + maxAge: { + days: 3 + }, cacheKey: () => 'unique' }); @@ -88,9 +92,9 @@ const url = await cache.get('cached-url'); Type: `string` -### cache.set(key, value, maxAge /* in days */) +### cache.set(key, value, maxAge) -Caches the given key and value for a given amount of days. It returns the value itself. +Caches the given key and value for a given amount of time. It returns the value itself. ```js const info = await getInfoObject(); @@ -109,10 +113,10 @@ Type: `string | number | boolean` or `array | object` of those three types. #### maxAge -Type: `number`
-Default: 30 +Type: [`TimeDescriptor`](https://github.com/sindresorhus/to-milliseconds#input)
+Default: `{days: 30}` -The number of days after which the cache item will expire. +The amount of time after which the cache item will expire. ### cache.delete(key) @@ -175,22 +179,26 @@ cachedOperate(1, 2, 3); ##### maxAge -Type: `number`
-Default: 30 +Type: [`TimeDescriptor`](https://github.com/sindresorhus/to-milliseconds#input)
+Default: `{days: 30}` -The number of days after which the cache item will expire. +The amount of time after which the cache item will expire. ##### staleWhileRevalidate -Type: `number`
-Default: `0` (disabled) +Type: [`TimeDescriptor`](https://github.com/sindresorhus/to-milliseconds#input)
+Default: `{days: 0}` (disabled) -Specifies how many additional days an item should be kept in cache after its expiration. During this extra time, the item will still be served from cache instantly, but `getter` will be also called asynchronously to update the cache. A later call should return an updated and fresher item. +Specifies how much longer an item should be kept in cache after its expiration. During this extra time, the item will still be served from cache instantly, but `getter` will be also called asynchronously to update the cache. A later call will return the updated and fresher item. ```js const cachedOperate = cache.function(operate, { - maxAge: 10, - staleWhileRevalidate: 2 + maxAge: { + days: 10 + }, + staleWhileRevalidate: { + days: 2 + } }); cachedOperate(); // It will run `operate` and cache it for 10 days diff --git a/test/index.js b/test/index.js index 50ec89c..fe518cd 100644 --- a/test/index.js +++ b/test/index.js @@ -2,11 +2,12 @@ import test from 'ava'; import sinon from 'sinon'; import './_fixtures.js'; import cache from '../index.js'; +import toMilliseconds from '@sindresorhus/to-milliseconds'; const getUsernameDemo = async name => name.slice(1).toUpperCase(); -function daysInTheFuture(days) { - return Date.now() + (1000 * 3600 * 24 * days); +function timeInTheFuture(time) { + return Date.now() + toMilliseconds(time); } function createCache(daysFromToday, wholeCache) { @@ -15,7 +16,7 @@ function createCache(daysFromToday, wholeCache) { .withArgs(key) .yields({[key]: { data, - maxAge: daysInTheFuture(daysFromToday) + maxAge: timeInTheFuture({days: daysFromToday}) }}); } } @@ -55,12 +56,12 @@ test.todo('set() with past maxAge should throw'); test.serial('set() with value', async t => { const maxAge = 20; - await cache.set('name', 'Anne', maxAge); + await cache.set('name', 'Anne', {days: maxAge}); const arguments_ = chrome.storage.local.set.lastCall.args[0]; t.deepEqual(Object.keys(arguments_), ['cache:name']); t.is(arguments_['cache:name'].data, 'Anne'); - t.true(arguments_['cache:name'].maxAge > daysInTheFuture(maxAge - 0.5)); - t.true(arguments_['cache:name'].maxAge < daysInTheFuture(maxAge + 0.5)); + t.true(arguments_['cache:name'].maxAge > timeInTheFuture({days: maxAge - 0.5})); + t.true(arguments_['cache:name'].maxAge < timeInTheFuture({days: maxAge + 0.5})); }); test.serial('function() with empty cache', async t => { @@ -96,8 +97,8 @@ test.serial('function() with empty cache and staleWhileRevalidate', async t => { const spy = sinon.spy(getUsernameDemo); const call = cache.function(spy, { - maxAge, - staleWhileRevalidate + maxAge: {days: maxAge}, + staleWhileRevalidate: {days: staleWhileRevalidate} }); t.is(await call('@anne'), 'ANNE'); @@ -109,8 +110,8 @@ test.serial('function() with empty cache and staleWhileRevalidate', async t => { t.is(arguments_['cache:@anne'].data, 'ANNE'); const expectedExpiration = maxAge + staleWhileRevalidate; - t.true(arguments_['cache:@anne'].maxAge > daysInTheFuture(expectedExpiration - 0.5)); - t.true(arguments_['cache:@anne'].maxAge < daysInTheFuture(expectedExpiration + 0.5)); + t.true(arguments_['cache:@anne'].maxAge > timeInTheFuture({days: expectedExpiration - 0.5})); + t.true(arguments_['cache:@anne'].maxAge < timeInTheFuture({days: expectedExpiration + 0.5})); }); test.serial('function() with fresh cache and staleWhileRevalidate', async t => { @@ -120,8 +121,8 @@ test.serial('function() with fresh cache and staleWhileRevalidate', async t => { const spy = sinon.spy(getUsernameDemo); const call = cache.function(spy, { - maxAge: 1, - staleWhileRevalidate: 29 + maxAge: {days: 1}, + staleWhileRevalidate: {days: 29} }); t.is(await call('@anne'), 'ANNE'); @@ -143,8 +144,8 @@ test.serial('function() with stale cache and staleWhileRevalidate', async t => { const spy = sinon.spy(getUsernameDemo); const call = cache.function(spy, { - maxAge: 1, - staleWhileRevalidate: 29 + maxAge: {days: 1}, + staleWhileRevalidate: {days: 29} }); t.is(await call('@anne'), 'ANNE');