Skip to content

Commit

Permalink
Misc
Browse files Browse the repository at this point in the history
  • Loading branch information
cmdcolin committed Sep 5, 2024
1 parent f38354c commit 51950c1
Show file tree
Hide file tree
Showing 8 changed files with 902 additions and 418 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
.eslintcache
.vscode/
node_modules/
dist/
Expand Down
53 changes: 18 additions & 35 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
@@ -1,47 +1,30 @@
import prettier from 'eslint-plugin-prettier'
import typescriptEslint from '@typescript-eslint/eslint-plugin'
import tsParser from '@typescript-eslint/parser'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import js from '@eslint/js'
import { FlatCompat } from '@eslint/eslintrc'
import eslint from '@eslint/js'
import eslintPluginUnicorn from 'eslint-plugin-unicorn'
import tseslint from 'typescript-eslint'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
})

export default [
...compat.extends(
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-type-checked',
'plugin:@typescript-eslint/stylistic-type-checked',
'plugin:prettier/recommended',
'plugin:unicorn/recommended',
),
export default tseslint.config(
{
ignores: ['esm/**/*', 'dist/**/*', '*.js', '*.mjs', 'example/*'],
},
{
plugins: {
prettier,
'@typescript-eslint': typescriptEslint,
},

languageOptions: {
parser: tsParser,
ecmaVersion: 5,
sourceType: 'script',

parserOptions: {
project: './tsconfig.lint.json',
project: ['./tsconfig.lint.json'],
tsconfigRootDir: import.meta.dirname,
},
},

},
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.stylisticTypeChecked,
...tseslint.configs.strictTypeChecked,
eslintPluginUnicorn.configs['flat/recommended'],
{
rules: {
'no-underscore-dangle': 0,
curly: 'error',
'unicorn/no-null': 0,
'unicorn/prevent-abbreviations': 0,
'unicorn/filename-case': 0,
'@typescript-eslint/filename-case': 0,
'@typescript-eslint/no-explicit-any': 0,
Expand All @@ -53,4 +36,4 @@ export default [
semi: ['error', 'never'],
},
},
]
)
24 changes: 10 additions & 14 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@
"src"
],
"scripts": {
"test": "jest",
"lint": "eslint --report-unused-disable-directives --max-warnings 0 src test",
"test": "vitest",
"lint": "eslint --report-unused-disable-directives --max-warnings 0",
"docs": "documentation readme --shallow src/AbortablePromiseCache.js --section=API",
"clean": "rimraf dist esm",
"format": "prettier --write .",
"prebuild": "npm run clean",
"build:esm": "tsc --target es2018 --outDir esm",
"build:es5": "tsc --target es2015 --module commonjs --outDir dist",
Expand All @@ -29,23 +30,18 @@
"postversion": "git push --follow-tags"
},
"devDependencies": {
"@eslint/eslintrc": "^3.1.0",
"@eslint/js": "^9.7.0",
"@types/jest": "^29.2.4",
"@types/node": "^20.14.11",
"@typescript-eslint/eslint-plugin": "^7.17.0",
"@typescript-eslint/parser": "^7.17.0",
"@typescript-eslint/eslint-plugin": "^8.0.1",
"@typescript-eslint/parser": "^8.0.1",
"documentation": "^14.0.1",
"eslint": "^9.7.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"eslint-plugin-unicorn": "^54.0.0",
"jest": "^29.3.1",
"eslint": "^9.9.0",
"eslint-plugin-unicorn": "^55.0.0",
"prettier": "^3.3.3",
"quick-lru": "^4.0.0",
"rimraf": "^6.0.1",
"ts-jest": "^29.0.3",
"typescript": "^5.5.4"
"typescript": "^5.5.4",
"typescript-eslint": "^8.4.0",
"vitest": "^2.0.5"
},
"publishConfig": {
"access": "public"
Expand Down
15 changes: 8 additions & 7 deletions src/AbortablePromiseCache.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import AggregateAbortController from './AggregateAbortController'
import AggregateStatusReporter from './AggregateStatusReporter'

type Callback = (arg: unknown) => void

interface Cache<U> {
delete: (key: string) => void
keys: () => Iterator<string>
Expand All @@ -11,7 +13,7 @@ interface Cache<U> {
type FillCallback<T, U> = (
data: T,
signal?: AbortSignal,
statusCallback?: Function,
statusCallback?: Callback,
) => Promise<U>

interface Entry<U> {
Expand Down Expand Up @@ -79,7 +81,7 @@ export default class AbortablePromiseCache<T, U> {
}
}

fill(key: string, data: T, signal?: AbortSignal, statusCallback?: Function) {
fill(key: string, data: T, signal?: AbortSignal, statusCallback?: Callback) {
const aborter = new AggregateAbortController()
const statusReporter = new AggregateStatusReporter()
statusReporter.addCallback(statusCallback)
Expand Down Expand Up @@ -116,7 +118,7 @@ export default class AbortablePromiseCache<T, U> {
this.evict(key, newEntry)
},
)
.catch(error => {
.catch((error: unknown) => {
// this will only be reached if there is some kind of
// bad bug in this library
console.error(error)
Expand All @@ -141,7 +143,7 @@ export default class AbortablePromiseCache<T, U> {
checkForSingleAbort()
return result
},
error => {
(error: unknown) => {
checkForSingleAbort()
throw error
},
Expand Down Expand Up @@ -169,7 +171,7 @@ export default class AbortablePromiseCache<T, U> {
key: string,
data: T,
signal?: AbortSignal,
statusCallback?: Function,
statusCallback?: Callback,
): Promise<U> {
if (!signal && data instanceof AbortSignal) {
throw new TypeError(
Expand Down Expand Up @@ -204,8 +206,7 @@ export default class AbortablePromiseCache<T, U> {
// if we got here, it is not in the cache. fill.
this.fill(key, data, signal, statusCallback)
return AbortablePromiseCache.checkSinglePromise(
//see https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#non-null-assertion-operator-postfix-

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
this.cache.get(key)!.promise,
signal,
)
Expand Down
1 change: 1 addition & 0 deletions src/AggregateAbortController.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
class NullSignal {}

/**
Expand Down
6 changes: 4 additions & 2 deletions src/AggregateStatusReporter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
type Callback = (arg: unknown) => void

export default class AggregateStatusReporter {
callbacks = new Set<Function>()
callbacks = new Set<Callback>()
currentMessage: unknown

addCallback(callback: Function = () => {}): void {
addCallback(callback: Callback = () => {}): void {
this.callbacks.add(callback)
callback(this.currentMessage)
}
Expand Down
47 changes: 24 additions & 23 deletions test/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
//@ts-nocheck
import { vi, expect, test, beforeEach } from 'vitest'
import QuickLRU from 'quick-lru'

import AbortablePromiseCache from '../src'

jest.useFakeTimers()
vi.useFakeTimers()

function delay(ms: number) {
return new Promise(r => setTimeout(r, ms))
}

beforeEach(() => {
jest.useFakeTimers()
vi.useFakeTimers()
})

test('no aborting', async () => {
Expand All @@ -27,7 +28,7 @@ test('no aborting', async () => {
})

const resultP = cache.get('foo')
jest.runAllTimers()
vi.runAllTimers()
expect(await resultP).toBe(42)
})

Expand Down Expand Up @@ -68,7 +69,7 @@ test('simple abort', async () => {
const aborter = new AbortController()
const resultP = cache.get('foo', null, aborter.signal)
aborter.abort()
jest.runAllTimers()
vi.runAllTimers()
await expect(resultP).rejects.toThrow(/aborted/)
})

Expand All @@ -93,11 +94,11 @@ test('cache 2 requests, one aborted', async () => {

const aborter1 = new AbortController()
const resultP1 = cache.get('foo', { whichCall: 1 }, aborter1.signal)
jest.advanceTimersByTime(10)
vi.advanceTimersByTime(10)
const aborter2 = new AbortController()
const resultP2 = cache.get('foo', { whichCall: 2 }, aborter2.signal)
aborter1.abort()
jest.runAllTimers()
vi.runAllTimers()
expect(callCount).toBe(1)
expect(which).toBe(1)
expect(await resultP2).toBe(42)
Expand Down Expand Up @@ -129,12 +130,12 @@ test('cache 2 requests, both aborted, and fill aborted', async () => {

const aborter1 = new AbortController()
const resultP1 = cache.get('foo', { whichCall: 1 }, aborter1.signal)
jest.advanceTimersByTime(10)
vi.advanceTimersByTime(10)
const aborter2 = new AbortController()
const resultP2 = cache.get('foo', { whichCall: 2 }, aborter2.signal)
aborter1.abort()
aborter2.abort()
jest.runAllTimers()
vi.runAllTimers()
expect(callCount).toBe(1)
expect(which).toBe(1)
await expect(resultP2).rejects.toThrow(/aborted/)
Expand Down Expand Up @@ -165,12 +166,12 @@ test('cache 2 requests, both aborted, one pre-aborted, and fill aborted', async

const aborter1 = new AbortController()
const resultP1 = cache.get('foo', { whichCall: 1 }, aborter1.signal)
jest.advanceTimersByTime(10)
vi.advanceTimersByTime(10)
const aborter2 = new AbortController()
aborter1.abort() //< this aborts call 1 before it finishes, and also makes it get evicted from the cache
aborter2.abort() //< we abort call 2 before we even start it
const resultP2 = cache.get('foo', { whichCall: 2 }, aborter2.signal)
jest.runAllTimers()
vi.runAllTimers()
expect(callCount).toBe(2)
expect(which).toBe(2)
expect(finishedCount).toBe(0)
Expand Down Expand Up @@ -201,14 +202,14 @@ test('cache 2 requests, abort one and wait for it, then make another and check t
const aborter1 = new AbortController()
const resultP1 = cache.get('foo', { whichCall: 1 }, aborter1.signal)
aborter1.abort()
jest.runAllTimers()
vi.runAllTimers()
await expect(resultP1).rejects.toThrow(/aborted/)
expect(callCount).toBe(1)
expect(abortCount).toBe(1)
expect(which).toBe(1)
const aborter2 = new AbortController()
const resultP2 = cache.get('foo', { whichCall: 2 }, aborter2.signal)
jest.runAllTimers()
vi.runAllTimers()
expect(callCount).toBe(2)
expect(which).toBe(2)
expect(await resultP2).toBe(42)
Expand All @@ -235,14 +236,14 @@ test('cache 3 requests, 2 aborted, but fill and last request did not abort', asy

const aborter1 = new AbortController()
const resultP1 = cache.get('foo', { whichCall: 1 }, aborter1.signal)
jest.advanceTimersByTime(10)
vi.advanceTimersByTime(10)
const aborter2 = new AbortController()
const resultP2 = cache.get('foo', { whichCall: 2 }, aborter2.signal)
const aborter3 = new AbortController()
const resultP3 = cache.get('foo', { whichCall: 3 }, aborter3.signal)
aborter1.abort()
aborter2.abort()
jest.runAllTimers()
vi.runAllTimers()
expect(callCount).toBe(1)
expect(which).toBe(1)
await expect(resultP2).rejects.toThrow(/aborted/)
Expand Down Expand Up @@ -271,12 +272,12 @@ test('deleting aborts', async () => {
})

const resultP1 = cache.get('foo', { whichCall: 1 })
jest.advanceTimersByTime(10)
vi.advanceTimersByTime(10)
cache.delete('foo')
expect(callCount).toBe(1)
expect(which).toBe(1)
expect(abortCount).toBe(0)
jest.runAllTimers()
vi.runAllTimers()
await expect(resultP1).rejects.toThrow(/aborted/)
expect(abortCount).toBe(1)
})
Expand Down Expand Up @@ -325,12 +326,12 @@ test('clear can delete one', async () => {
})

const resultP1 = cache.get('foo', { whichCall: 1 })
jest.advanceTimersByTime(10)
vi.advanceTimersByTime(10)
expect(cache.clear()).toBe(1)
expect(callCount).toBe(1)
expect(which).toBe(1)
expect(abortCount).toBe(0)
jest.runAllTimers()
vi.runAllTimers()
await expect(resultP1).rejects.toThrow(/aborted/)
expect(abortCount).toBe(1)
})
Expand All @@ -357,15 +358,15 @@ test('clear can delete two', async () => {
const aborter1 = new AbortController()
const resultP1 = cache.get('foo', { whichCall: 1 }, aborter1.signal)
expect(cache.has('foo')).toBe(true)
jest.runAllTimers()
vi.runAllTimers()
expect(await resultP1).toBe(42)
expect(callCount).toBe(1)
expect(abortCount).toBe(0)
expect(which).toBe(1)
const aborter2 = new AbortController()
const resultP2 = cache.get('bar', { whichCall: 2 }, aborter2.signal)
expect(cache.has('bar')).toBe(true)
jest.runAllTimers()
vi.runAllTimers()
expect(callCount).toBe(2)
expect(which).toBe(2)
expect(await resultP2).toBe(42)
Expand Down Expand Up @@ -402,12 +403,12 @@ test('status callback', async () => {
},
})

const s1 = jest.fn()
const s2 = jest.fn()
const s1 = vi.fn()
const s2 = vi.fn()
const p1 = cache.get('foo', { testing: 'test1' }, aborter.signal, s1)
const p2 = cache.get('foo', { testing: 'test2' }, aborter.signal, s2)

jest.runAllTimers()
vi.runAllTimers()
await Promise.all([p1, p2])
expect(s1).toHaveBeenCalledWith('working...')
expect(s2).toHaveBeenCalledWith('working...')
Expand Down
Loading

0 comments on commit 51950c1

Please sign in to comment.