Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use objects to define readable expiration values #20

Merged
merged 7 commits into from
Aug 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions index.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ expectAssignable<Promise<number>>(cache.set('key', 1));
expectAssignable<Promise<boolean>>(cache.set('key', true));
expectAssignable<Promise<[boolean, string]>>(cache.set('key', [true, 'string']));
expectAssignable<Promise<Record<string, any[]>>>(cache.set('key', {wow: [true, 'string']}));
expectAssignable<Promise<number>>(cache.set('key', 1, 1));
expectAssignable<Promise<number>>(cache.set('key', 1, {days: 1}));

const cachedPower = cache.function(async (n: number) => n ** 1000);
expectType<(n: number) => Promise<number>>(cachedPower);
Expand All @@ -42,14 +42,14 @@ expectNotAssignable<Promise<number>>(cache.function(identity)('1'));

expectType<(n: string) => Promise<number>>(
cache.function(async (n: string) => Number(n), {
maxAge: 20
maxAge: {days: 20}
})
);

expectType<(n: string) => Promise<number>>(
cache.function(async (n: string) => Number(n), {
maxAge: 20,
staleWhileRevalidate: 5
maxAge: {days: 20},
staleWhileRevalidate: {days: 5}
})
);

Expand Down
21 changes: 12 additions & 9 deletions index.ts
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,8 +14,8 @@ const getPromise = (executor: () => void) => <T>(key?): Promise<T> => 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
Expand Down Expand Up @@ -59,7 +60,7 @@ async function get<TValue extends Value>(key: string): Promise<TValue | undefine
return cachedItem.data;
}

async function set<TValue extends Value>(key: string, value: TValue, maxAge = 30 /* days */): Promise<TValue> {
async function set<TValue extends Value>(key: string, value: TValue, maxAge: TimeDescriptor = {days: 30}): Promise<TValue> {
if (typeof value === 'undefined') {
// @ts-ignore This never happens in TS because `value` can't be undefined
return;
Expand All @@ -69,7 +70,7 @@ async function set<TValue extends Value>(key: string, value: TValue, maxAge = 30
await _set({
[internalKey]: {
data: value,
maxAge: daysInTheFuture(maxAge)
maxAge: timeInTheFuture(maxAge)
}
});

Expand Down Expand Up @@ -104,8 +105,8 @@ async function clear(): Promise<void> {
}

interface MemoizedFunctionOptions<TArgs extends any[], TValue> {
maxAge?: number;
staleWhileRevalidate?: number;
maxAge?: TimeDescriptor;
staleWhileRevalidate?: TimeDescriptor;
cacheKey?: (args: TArgs) => string;
shouldRevalidate?: (cachedValue: TValue) => boolean;
}
Expand All @@ -116,7 +117,7 @@ function function_<
TArgs extends Parameters<TFunction>
>(
getter: TFunction,
{cacheKey, maxAge = 30, staleWhileRevalidate = 0, shouldRevalidate}: MemoizedFunctionOptions<TArgs, TValue> = {}
{cacheKey, maxAge = {days: 30}, staleWhileRevalidate = {days: 0}, shouldRevalidate}: MemoizedFunctionOptions<TArgs, TValue> = {}
): TFunction {
const getSet = async (key: string, args: TArgs): Promise<TValue | undefined> => {
const freshValue = await getter(...args);
Expand All @@ -125,7 +126,9 @@ function function_<
return;
}

return set<TValue>(key, freshValue, maxAge + staleWhileRevalidate);
const milliseconds = toMilliseconds(maxAge) + toMilliseconds(staleWhileRevalidate);

return set<TValue>(key, freshValue, {milliseconds});
};

return (async (...args: TArgs) => {
Expand All @@ -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);
}

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
]
},
"dependencies": {
"@sindresorhus/to-milliseconds": "^1.2.0",
"webext-detect-page": "^2.0.2"
},
"devDependencies": {
Expand Down
38 changes: 23 additions & 15 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
Expand All @@ -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'
});

Expand Down Expand Up @@ -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();
Expand All @@ -109,10 +113,10 @@ Type: `string | number | boolean` or `array | object` of those three types.

#### maxAge

Type: `number`<br>
Default: 30
Type: [`TimeDescriptor`](https://github.com/sindresorhus/to-milliseconds#input)<br>
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)

Expand Down Expand Up @@ -175,22 +179,26 @@ cachedOperate(1, 2, 3);

##### maxAge

Type: `number`<br>
Default: 30
Type: [`TimeDescriptor`](https://github.com/sindresorhus/to-milliseconds#input)<br>
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`<br>
Default: `0` (disabled)
Type: [`TimeDescriptor`](https://github.com/sindresorhus/to-milliseconds#input)<br>
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
Expand Down
29 changes: 15 additions & 14 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -15,7 +16,7 @@ function createCache(daysFromToday, wholeCache) {
.withArgs(key)
.yields({[key]: {
data,
maxAge: daysInTheFuture(daysFromToday)
maxAge: timeInTheFuture({days: daysFromToday})
}});
}
}
Expand Down Expand Up @@ -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 => {
Expand Down Expand Up @@ -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');
Expand All @@ -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 => {
Expand All @@ -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');
Expand All @@ -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');
Expand Down