Skip to content

Commit

Permalink
add fetchContext option
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed Jun 29, 2022
1 parent 118a078 commit 0bb5d01
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 13 deletions.
16 changes: 14 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ Deprecated alias: `length`
### `fetchMethod`

Function that is used to make background asynchronous fetches.
Called with `fetchMethod(key, staleValue, { signal, options })`.
May return a Promise.
Called with `fetchMethod(key, staleValue, { signal, options,
context })`. May return a Promise.

If `fetchMethod` is not provided, then `cache.fetch(key)` is
equivalent to `Promise.resolve(cache.get(key))`.
Expand All @@ -164,6 +164,18 @@ value is resolved. For example, a DNS cache may update the TTL
based on the value returned from a remote DNS server by changing
`options.ttl` in the `fetchMethod`.

### `fetchContext`

Arbitrary data that can be passed to the `fetchMethod` as the
`context` option.

Note that this will only be relevant when the `cache.fetch()`
call needs to call `fetchMethod()`. Thus, any data which will
meaningfully vary the fetch response needs to be present in the
key. This is primarily intended for including `x-request-id`
headers and the like for debugging purposes, which do not affect
the `fetchMethod()` response.

### `noDeleteOnFetchRejection`

If a `fetchMethod` throws an error or returns a rejected promise,
Expand Down
30 changes: 22 additions & 8 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,15 @@ declare namespace LRUCache {
* @since 7.10.0
*/
noDeleteOnFetchRejection?: boolean

/**
* Set to any value in the constructor or fetch() options to
* pass arbitrary data to the fetch() method in the options.context
* field.
*
* @since 7.12.0
*/
fetchContext?: any
}

type Options<K, V> = SharedOptions<K, V> &
Expand Down Expand Up @@ -545,13 +554,7 @@ declare namespace LRUCache {
allowStale?: boolean
}

/**
* options which override the options set in the LRUCache constructor
* when making `cache.fetch()` calls.
* This is the union of GetOptions and SetOptions, plus the
* `noDeleteOnFetchRejection` boolean.
*/
interface FetchOptions<K, V> {
interface FetcherFetchOptions<K, V> {
allowStale?: boolean
updateAgeOnGet?: boolean
noDeleteOnStaleGet?: boolean
Expand All @@ -563,9 +566,20 @@ declare namespace LRUCache {
noDeleteOnFetchRejection?: boolean
}

/**
* options which override the options set in the LRUCache constructor
* when making `cache.fetch()` calls.
* This is the union of GetOptions and SetOptions, plus the
* `noDeleteOnFetchRejection` and `fetchContext` fields.
*/
interface FetchOptions<K, V> extends FetcherFetchOptions<K, V> {
fetchContext?: any
}

interface FetcherOptions<K, V> {
signal: AbortSignal
options: FetchOptions<K, V>
options: FetcherFetchOptions<K, V>
context: any
}

interface Entry<V> {
Expand Down
16 changes: 13 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,7 @@ class LRUCache {
maxSize = 0,
sizeCalculation,
fetchMethod,
fetchContext,
noDeleteOnFetchRejection,
noDeleteOnStaleGet,
} = options
Expand Down Expand Up @@ -198,6 +199,13 @@ class LRUCache {
)
}

this.fetchContext = fetchContext
if (!this.fetchMethod && fetchContext !== undefined) {
throw new TypeError(
'cannot set fetchContext without fetchMethod'
)
}

this.keyMap = new Map()
this.keyList = new Array(max).fill(null)
this.valList = new Array(max).fill(null)
Expand Down Expand Up @@ -676,7 +684,7 @@ class LRUCache {
}
}

backgroundFetch(k, index, options) {
backgroundFetch(k, index, options, context) {
const v = index === undefined ? undefined : this.valList[index]
if (this.isBackgroundFetch(v)) {
return v
Expand All @@ -685,6 +693,7 @@ class LRUCache {
const fetchOpts = {
signal: ac.signal,
options,
context,
}
const cb = v => {
if (!ac.signal.aborted) {
Expand Down Expand Up @@ -753,6 +762,7 @@ class LRUCache {
noUpdateTTL = this.noUpdateTTL,
// fetch exclusive options
noDeleteOnFetchRejection = this.noDeleteOnFetchRejection,
fetchContext = this.fetchContext,
} = {}
) {
if (!this.fetchMethod) {
Expand All @@ -773,7 +783,7 @@ class LRUCache {

let index = this.keyMap.get(k)
if (index === undefined) {
const p = this.backgroundFetch(k, index, options)
const p = this.backgroundFetch(k, index, options, fetchContext)
return (p.__returned = p)
} else {
// in cache, maybe already fetching
Expand All @@ -794,7 +804,7 @@ class LRUCache {

// ok, it is stale, and not already fetching
// refresh the cache.
const p = this.backgroundFetch(k, index, options)
const p = this.backgroundFetch(k, index, options, fetchContext)
return allowStale && p.__staleWhileFetching !== undefined
? p.__staleWhileFetching
: (p.__returned = p)
Expand Down
27 changes: 27 additions & 0 deletions test/fetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@ t.test('fetchMethod must be a function', async t => {
t.throws(() => new LRU({ fetchMethod: true, max: 2 }))
})

t.test('no fetchContext without fetchMethod', async t => {
t.throws(() => new LRU({ fetchContext: true, max: 2 }))
})

t.test('fetch without fetch method', async t => {
const c = new LRU({ max: 3 })
c.set(0, 0)
Expand Down Expand Up @@ -468,3 +472,26 @@ t.test(
t.equal(e.valList[1], null, 'not in cache')
}
)

t.test('fetchContext', async t => {
const cache = new LRU<string, [string, any]>({
max: 10,
ttl: 10,
allowStale: true,
noDeleteOnFetchRejection: true,
fetchContext: 'default context',
fetchMethod: async (k, _, { context, options }) => {
//@ts-expect-error
t.equal(options.fetchContext, undefined)
t.equal(context, expectContext)
return [k, context]
},
})

let expectContext = 'default context'
t.strictSame(await cache.fetch('x'), ['x', 'default context'])
expectContext = 'overridden'
t.strictSame(await cache.fetch('y', { fetchContext: 'overridden' }), ['y', 'overridden'])
// if still in cache, doesn't call fetchMethod again
t.strictSame(await cache.fetch('x', { fetchContext: 'ignored' }), ['x', 'default context'])
})

0 comments on commit 0bb5d01

Please sign in to comment.