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

Update dependencies #91

Merged
merged 8 commits into from
May 6, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/funding.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
github: [sindresorhus, bfred-it]
github: [sindresorhus, fregante]
tidelift: npm/mem
8 changes: 4 additions & 4 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ jobs:
fail-fast: false
matrix:
node-version:
- 20
- 18
- 16
- 14
- 12
steps:
- uses: actions/checkout@v2
- uses: actions/setup-node@v2
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node-version }}
- run: npm install
Expand Down
16 changes: 8 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,23 @@ type AnyFunction = (...arguments_: any) => any;

const cacheStore = new WeakMap<AnyFunction, CacheStorage<any, any>>();

interface CacheStorageContent<ValueType> {
type CacheStorageContent<ValueType> = {
fregante marked this conversation as resolved.
Show resolved Hide resolved
data: ValueType;
maxAge: number;
}
};

interface CacheStorage<KeyType, ValueType> {
type CacheStorage<KeyType, ValueType> = {
has: (key: KeyType) => boolean;
get: (key: KeyType) => CacheStorageContent<ValueType> | undefined;
set: (key: KeyType, value: CacheStorageContent<ValueType>) => void;
delete: (key: KeyType) => void;
clear?: () => void;
}
};

export interface Options<
export type Options<
FunctionToMemoize extends AnyFunction,
CacheKeyType,
> {
> = {
/**
Milliseconds until the cache expires.

Expand Down Expand Up @@ -63,7 +63,7 @@ export interface Options<
@example new WeakMap()
*/
readonly cache?: CacheStorage<CacheKeyType, ReturnType<FunctionToMemoize>>;
}
};

/**
[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.
Expand Down Expand Up @@ -174,7 +174,7 @@ export function memDecorator<
propertyKey: string,
descriptor: PropertyDescriptor,
): void => {
const input = target[propertyKey]; // eslint-disable-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
const input = target[propertyKey]; // eslint-disable-line @typescript-eslint/no-unsafe-assignment

if (typeof input !== 'function') {
throw new TypeError('The decorated value must be a function');
Expand Down
28 changes: 12 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"type": "module",
"exports": "./dist/index.js",
"engines": {
"node": ">=12.20"
"node": ">=16"
},
"scripts": {
"test": "xo && ava && npm run build && tsd",
Expand All @@ -38,30 +38,26 @@
"promise"
],
"dependencies": {
"map-age-cleaner": "^0.1.3",
"map-age-cleaner": "^0.2.0",
"mimic-fn": "^4.0.0"
},
"devDependencies": {
"@ava/typescript": "^1.1.1",
"@sindresorhus/tsconfig": "^1.0.2",
"@types/serialize-javascript": "^4.0.0",
"ava": "^3.15.0",
"del-cli": "^3.0.1",
"delay": "^4.4.0",
"serialize-javascript": "^5.0.1",
"ts-node": "^10.1.0",
"tsd": "^0.13.1",
"typescript": "^4.3.5",
"xo": "^0.41.0"
"@sindresorhus/tsconfig": "^3.0.1",
fregante marked this conversation as resolved.
Show resolved Hide resolved
"@types/serialize-javascript": "^5.0.2",
"ava": "^5.2.0",
"del-cli": "^5.0.0",
"delay": "^5.0.0",
"serialize-javascript": "^6.0.1",
"ts-node": "^10.9.1",
"tsd": "^0.28.1",
"typescript": "^5.0.4",
"xo": "^0.54.2"
},
"ava": {
"timeout": "1m",
"extensions": {
"ts": "module"
},
"nonSemVerExperiments": {
"configurableModuleFormat": true
},
fregante marked this conversation as resolved.
Show resolved Hide resolved
"nodeArguments": [
"--loader=ts-node/esm"
]
Expand Down
18 changes: 10 additions & 8 deletions test-d/index.test-d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {expectType} from 'tsd';
import mem, {memClear} from '..';
import mem, {memClear} from '../index.js';

// eslint-disable-next-line unicorn/prefer-native-coercion-functions -- Required `string` type
const fn = (text: string) => Boolean(text);

expectType<typeof fn>(mem(fn));
Expand Down Expand Up @@ -33,42 +34,43 @@ memClear(fn);

// `cacheKey` tests.
// The argument should match the memoized function’s parameters
// eslint-disable-next-line unicorn/prefer-native-coercion-functions -- Required `string` type
mem((text: string) => Boolean(text), {
cacheKey: arguments_ => {
cacheKey(arguments_) {
expectType<[string]>(arguments_);
},
});

mem(() => 1, {
cacheKey: arguments_ => {
cacheKey(arguments_) {
expectType<[]>(arguments_); // eslint-disable-line @typescript-eslint/ban-types
},
});

// Ensures that the various cache functions infer their arguments type from the return type of `cacheKey`
mem((_arguments: {key: string}) => 1, {
cacheKey: (arguments_: [{key: string}]) => {
cacheKey(arguments_: [{key: string}]) {
expectType<[{key: string}]>(arguments_);
return new Date();
},
cache: {
get: key => {
get(key) {
expectType<Date>(key);

return {
data: 5,
maxAge: 2,
};
},
set: (key, data) => {
set(key, data) {
expectType<Date>(key);
expectType<{data: number; maxAge: number}>(data);
},
has: key => {
has(key) {
expectType<Date>(key);
return true;
},
delete: key => {
delete(key) {
expectType<Date>(key);
},
clear: () => undefined,
Expand Down
60 changes: 8 additions & 52 deletions test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,48 +5,30 @@ import mem, {memDecorator, memClear} from './index.js';

test('memoize', t => {
let i = 0;
const fixture = () => i++;
const fixture = (a?: unknown, b?: unknown) => i++;
const memoized = mem(fixture);
t.is(memoized(), 0);
t.is(memoized(), 0);
t.is(memoized(), 0);
// @ts-expect-error
t.is(memoized(undefined), 0);
// @ts-expect-error
t.is(memoized(undefined), 0);
// @ts-expect-error
t.is(memoized('foo'), 1);
// @ts-expect-error
t.is(memoized('foo'), 1);
// @ts-expect-error
t.is(memoized('foo'), 1);
// @ts-expect-error
t.is(memoized('foo', 'bar'), 1);
// @ts-expect-error
t.is(memoized('foo', 'bar'), 1);
// @ts-expect-error
t.is(memoized('foo', 'bar'), 1);
// @ts-expect-error
t.is(memoized(1), 2);
// @ts-expect-error
t.is(memoized(1), 2);
// @ts-expect-error
t.is(memoized(null), 3);
// @ts-expect-error
t.is(memoized(null), 3);
// @ts-expect-error
t.is(memoized(fixture), 4);
// @ts-expect-error
t.is(memoized(fixture), 4);
// @ts-expect-error
t.is(memoized(true), 5);
// @ts-expect-error
t.is(memoized(true), 5);

// Ensure that functions are stored by reference and not by "value" (e.g. their `.toString()` representation)
// @ts-expect-error
t.is(memoized(() => i++), 6);
// @ts-expect-error
t.is(memoized(() => i++), 7);
});

Expand All @@ -63,70 +45,54 @@ test('cacheKey option', t => {

test('memoize with multiple non-primitive arguments', t => {
let i = 0;
const memoized = mem(() => i++, {cacheKey: JSON.stringify});
const memoized = mem((a?: unknown, b?: unknown, c?: unknown) => i++, {cacheKey: JSON.stringify});
t.is(memoized(), 0);
t.is(memoized(), 0);
// @ts-expect-error
t.is(memoized({foo: true}, {bar: false}), 1);
// @ts-expect-error
t.is(memoized({foo: true}, {bar: false}), 1);
// @ts-expect-error
t.is(memoized({foo: true}, {bar: false}, {baz: true}), 2);
// @ts-expect-error
t.is(memoized({foo: true}, {bar: false}, {baz: true}), 2);
});

test('memoize with regexp arguments', t => {
let i = 0;
const memoized = mem(() => i++, {cacheKey: serializeJavascript});
const memoized = mem((a?: unknown) => i++, {cacheKey: serializeJavascript});
t.is(memoized(), 0);
t.is(memoized(), 0);
// @ts-expect-error
t.is(memoized(/Sindre Sorhus/), 1);
// @ts-expect-error
t.is(memoized(/Sindre Sorhus/), 1);
// @ts-expect-error
t.is(memoized(/Elvin Peng/), 2);
// @ts-expect-error
t.is(memoized(/Elvin Peng/), 2);
});

test('memoize with Symbol arguments', t => {
let i = 0;
const argument1 = Symbol('fixture1');
const argument2 = Symbol('fixture2');
const memoized = mem(() => i++);
const memoized = mem((a?: unknown) => i++);
t.is(memoized(), 0);
t.is(memoized(), 0);
// @ts-expect-error
t.is(memoized(argument1), 1);
// @ts-expect-error
t.is(memoized(argument1), 1);
// @ts-expect-error
t.is(memoized(argument2), 2);
// @ts-expect-error
t.is(memoized(argument2), 2);
});

test('maxAge option', async t => {
let i = 0;
const fixture = () => i++;
const fixture = (a?: unknown) => i++;
const memoized = mem(fixture, {maxAge: 100});
// @ts-expect-error
t.is(memoized(1), 0);
// @ts-expect-error
t.is(memoized(1), 0);
await delay(50);
// @ts-expect-error
t.is(memoized(1), 0);
await delay(200);
// @ts-expect-error
t.is(memoized(1), 1);
});

test('maxAge option deletes old items', async t => {
let i = 0;
const fixture = () => i++;
const fixture = (a?: unknown) => i++;
const cache = new Map<number, number>();
const deleted: number[] = [];
const _delete = cache.delete.bind(cache);
Expand All @@ -135,27 +101,22 @@ test('maxAge option deletes old items', async t => {
return _delete(item);
};

// @ts-expect-error
const memoized = mem(fixture, {maxAge: 100, cache});
// @ts-expect-error
t.is(memoized(1), 0);
// @ts-expect-error
t.is(memoized(1), 0);
t.is(cache.has(1), true);
await delay(50);
// @ts-expect-error
t.is(memoized(1), 0);
t.is(deleted.length, 0);
await delay(200);
// @ts-expect-error
t.is(memoized(1), 1);
t.is(deleted.length, 1);
t.is(deleted[0], 1);
});

test('maxAge items are deleted even if function throws', async t => {
let i = 0;
const fixture = () => {
const fixture = (a?: unknown) => {
if (i === 1) {
throw new Error('failure');
}
Expand All @@ -165,17 +126,13 @@ test('maxAge items are deleted even if function throws', async t => {

const cache = new Map();
const memoized = mem(fixture, {maxAge: 100, cache});
// @ts-expect-error
t.is(memoized(1), 0);
// @ts-expect-error
t.is(memoized(1), 0);
t.is(cache.size, 1);
await delay(50);
// @ts-expect-error
t.is(memoized(1), 0);
await delay(200);
t.throws(() => {
// @ts-expect-error
memoized(1);
}, {message: 'failure'});
t.is(cache.size, 0);
Expand All @@ -198,10 +155,9 @@ test('cache option', t => {

test('promise support', async t => {
let i = 0;
const memoized = mem(async () => i++);
const memoized = mem(async (a?: unknown) => i++);
t.is(await memoized(), 0);
t.is(await memoized(), 0);
// @ts-expect-error
t.is(await memoized(10), 1);
});

Expand Down
5 changes: 4 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
},
"files": [
"index.ts"
]
],
"ts-node": {
"transpileOnly": true
Copy link
Collaborator Author

@fregante fregante May 5, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This fixes the tests in <=18, but Node 20 tests still broken even with the full

		"transpileOnly": true
		"files": true,
		"experimentalResolver": true

That's why I had initially excluded them


> xo && ava && npm run build && tsd


(node:1742) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
  Uncaught exception in test.ts

  TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts" for /home/runner/work/mem/mem/test.ts

  ✘ test.ts exited with a non-zero exit code: 1
  ─

  1 uncaught exception
Error: Process completed with exit code 1.

}
}