diff --git a/.github/funding.yml b/.github/funding.yml index 10ed145..5919969 100644 --- a/.github/funding.yml +++ b/.github/funding.yml @@ -1,2 +1,2 @@ -github: [sindresorhus, bfred-it] +github: [sindresorhus, fregante] tidelift: npm/mem diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3b8aa86..ed4040b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/index.ts b/index.ts index f1b4366..6062813 100644 --- a/index.ts +++ b/index.ts @@ -5,23 +5,23 @@ type AnyFunction = (...arguments_: any) => any; const cacheStore = new WeakMap>(); -interface CacheStorageContent { +type CacheStorageContent = { data: ValueType; maxAge: number; -} +}; -interface CacheStorage { +type CacheStorage = { has: (key: KeyType) => boolean; get: (key: KeyType) => CacheStorageContent | undefined; set: (key: KeyType, value: CacheStorageContent) => void; delete: (key: KeyType) => void; clear?: () => void; -} +}; -export interface Options< +export type Options< FunctionToMemoize extends AnyFunction, CacheKeyType, -> { +> = { /** Milliseconds until the cache expires. @@ -63,7 +63,7 @@ export interface Options< @example new WeakMap() */ readonly cache?: CacheStorage>; -} +}; /** [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. @@ -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'); diff --git a/package.json b/package.json index be83c59..5b70288 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "type": "module", "exports": "./dist/index.js", "engines": { - "node": ">=12.20" + "node": ">=16" }, "scripts": { "test": "xo && ava && npm run build && tsd", @@ -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", + "@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 - }, "nodeArguments": [ "--loader=ts-node/esm" ] diff --git a/test-d/index.test-d.ts b/test-d/index.test-d.ts index e1b6f19..c698ce5 100644 --- a/test-d/index.test-d.ts +++ b/test-d/index.test-d.ts @@ -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(mem(fn)); @@ -33,26 +34,27 @@ 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(key); return { @@ -60,15 +62,15 @@ mem((_arguments: {key: string}) => 1, { maxAge: 2, }; }, - set: (key, data) => { + set(key, data) { expectType(key); expectType<{data: number; maxAge: number}>(data); }, - has: key => { + has(key) { expectType(key); return true; }, - delete: key => { + delete(key) { expectType(key); }, clear: () => undefined, diff --git a/test.ts b/test.ts index 23c68a8..41bf19a 100644 --- a/test.ts +++ b/test.ts @@ -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); }); @@ -63,31 +45,23 @@ 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); }); @@ -95,38 +69,30 @@ 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(); const deleted: number[] = []; const _delete = cache.delete.bind(cache); @@ -135,19 +101,14 @@ 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); @@ -155,7 +116,7 @@ test('maxAge option deletes old items', async t => { 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'); } @@ -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); @@ -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); }); diff --git a/tsconfig.json b/tsconfig.json index b6ac98e..b8dfe5b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,5 +6,8 @@ }, "files": [ "index.ts" - ] + ], + "ts-node": { + "transpileOnly": true + } }