Skip to content

Commit

Permalink
Add TypeScript definition (#28)
Browse files Browse the repository at this point in the history
  • Loading branch information
BendingBender authored and sindresorhus committed Mar 12, 2019
1 parent a583160 commit 6887496
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 6 deletions.
71 changes: 71 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
export interface CacheStorage<
KeyType extends unknown,
ValueType extends unknown
> {
has(key: KeyType): boolean;
get(key: KeyType): ValueType | undefined;
set(key: KeyType, value: ValueType): void;
delete(key: KeyType): void;
clear?: () => void;
}

export interface Options<
ArgumentsType extends unknown[],
CacheKeyType extends unknown,
ReturnType extends unknown
> {
/**
* Milliseconds until the cache expires.
*
* @default Infinity
*/
readonly maxAge?: number;

/**
* Determines the cache key for storing the result based on the function arguments. By default, if there's only one argument and it's a [primitive](https://developer.mozilla.org/en-US/docs/Glossary/Primitive), it's used directly as a key, otherwise it's all the function arguments JSON stringified as an array.
*
* You could for example change it to only cache on the first argument `x => JSON.stringify(x)`.
*/
readonly cacheKey?: (...args: ArgumentsType) => CacheKeyType;

/**
* Use a different cache storage. Must implement the following methods: `.has(key)`, `.get(key)`, `.set(key, value)`, `.delete(key)`, and optionally `.clear()`. You could for example use a `WeakMap` instead or [`quick-lru`](https://github.com/sindresorhus/quick-lru) for a LRU cache.
*
* @default new Map()
*/
readonly cache?: CacheStorage<CacheKeyType, ReturnType>;

/**
* Cache rejected promises.
*
* @default false
*/
readonly cachePromiseRejection?: boolean;
}

/**
* [Memoize](https://en.wikipedia.org/wiki/Memoization) functions - An optimization used to speed up consecutive function calls by caching the result of calls with identical input.
*
* @param fn - Function to be memoized.
*/
declare const mem: {
<
ArgumentsType extends unknown[],
ReturnType extends unknown,
CacheKeyType extends unknown
>(
fn: (...arguments: ArgumentsType) => ReturnType,
options?: Options<ArgumentsType, CacheKeyType, ReturnType>
): (...arguments: ArgumentsType) => ReturnType;

/**
* Clear all cached data of a memoized function.
*
* @param fn - Memoized function.
*/
clear<ArgumentsType extends unknown[], ReturnType extends unknown>(
fn: (...arguments: ArgumentsType) => ReturnType
): void;
};

export default mem;
5 changes: 4 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const defaultCacheKey = (...args) => {
return JSON.stringify(args);
};

module.exports = (fn, options) => {
const mem = (fn, options) => {
options = Object.assign({
cacheKey: defaultCacheKey,
cache: new Map(),
Expand Down Expand Up @@ -77,6 +77,9 @@ module.exports = (fn, options) => {
return memoized;
};

module.exports = mem;
module.exports.default = mem;

module.exports.clear = fn => {
const cache = cacheStore.get(fn);

Expand Down
17 changes: 17 additions & 0 deletions index.test-d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {expectType} from 'tsd-check';
import mem from '.';

const fn = (string: string) => true;

expectType<(string: string) => boolean>(mem(fn));
expectType<(string: string) => boolean>(mem(fn, {maxAge: 1}));
expectType<(string: string) => boolean>(mem(fn, {cacheKey: (...args) => args}));
expectType<(string: string) => boolean>(
mem(fn, {cacheKey: (...args) => args, cache: new Map<[string], boolean>()})
);
expectType<(string: string) => boolean>(
mem(fn, {cache: new Map<[string], boolean>()})
);
expectType<(string: string) => boolean>(mem(fn, {cachePromiseRejection: true}));

mem.clear(fn);
10 changes: 6 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@
"node": ">=6"
},
"scripts": {
"test": "xo && ava"
"test": "xo && ava && tsd-check"
},
"files": [
"index.js"
"index.js",
"index.d.ts"
],
"keywords": [
"memoize",
Expand All @@ -37,8 +38,9 @@
"p-is-promise": "^2.0.0"
},
"devDependencies": {
"ava": "^1.0.1",
"ava": "^1.3.1",
"delay": "^4.1.0",
"xo": "^0.23.0"
"tsd-check": "^0.3.0",
"xo": "^0.24.0"
}
}
6 changes: 5 additions & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ test('maxAge option deletes old items', async t => {
deleted.push(item);
return remove(item);
};

const memoized = mem(f, {maxAge: 100, cache});
t.is(memoized(1), 0);
t.is(memoized(1), 0);
Expand All @@ -97,8 +98,10 @@ test('maxAge items are deleted even if function throws', async t => {
if (i === 1) {
throw new Error('failure');
}

return i++;
};

const cache = new Map();
const memoized = mem(f, {maxAge: 100, cache});
t.is(memoized(1), 0);
Expand Down Expand Up @@ -188,7 +191,7 @@ test('cache rejected promises if enabled', async t => {
});

test('preserves the original function name', t => {
t.is(mem(function foo() {}).name, 'foo'); // eslint-disable-line func-names, prefer-arrow-callback
t.is(mem(function foo() {}).name, 'foo'); // eslint-disable-line func-names
});

test('.clear()', t => {
Expand All @@ -210,6 +213,7 @@ test('prototype support', t => {
const Unicorn = function () {
this.i = 0;
};

Unicorn.prototype.foo = mem(f);

const unicorn = new Unicorn();
Expand Down

0 comments on commit 6887496

Please sign in to comment.