From f7976fd0486079247a76ff3d3cecfbc2f6f2dae9 Mon Sep 17 00:00:00 2001 From: jxom Date: Mon, 31 Jul 2023 17:24:55 +0200 Subject: [PATCH] feat: add `cacheTime` on client (#958) * feat: cacheTime * chore: changeset --- .changeset/cool-goats-approve.md | 5 +++ site/docs/actions/public/getBlockNumber.md | 14 +++---- site/docs/clients/public.md | 23 +++++++++-- site/docs/clients/test.md | 19 ++++++++- site/docs/clients/wallet.md | 15 +++++++ src/actions/public/getBlockNumber.ts | 8 ++-- src/actions/public/watchBlockNumber.ts | 2 +- src/actions/test/mine.test.ts | 12 ++++-- src/actions/test/setIntervalMining.test.ts | 8 ++-- src/clients/createClient.test.ts | 48 ++++++++++++++++++++++ src/clients/createClient.ts | 9 ++++ src/clients/createPublicClient.test.ts | 5 +++ src/clients/createPublicClient.ts | 8 +++- src/clients/createTestClient.test.ts | 4 ++ src/clients/createTestClient.ts | 8 +++- src/clients/createWalletClient.test.ts | 7 ++++ src/clients/createWalletClient.ts | 8 +++- src/clients/transports/fallback.test.ts | 1 + src/utils/promise/withCache.test.ts | 8 ++-- src/utils/promise/withCache.ts | 10 ++--- 20 files changed, 185 insertions(+), 37 deletions(-) create mode 100644 .changeset/cool-goats-approve.md diff --git a/.changeset/cool-goats-approve.md b/.changeset/cool-goats-approve.md new file mode 100644 index 0000000000..f77516f34a --- /dev/null +++ b/.changeset/cool-goats-approve.md @@ -0,0 +1,5 @@ +--- +"viem": minor +--- + +Added `cacheTime` as a parameter to `getBlockNumber` & `createClient`. diff --git a/site/docs/actions/public/getBlockNumber.md b/site/docs/actions/public/getBlockNumber.md index 3b24dab1e2..77dd0f2554 100644 --- a/site/docs/actions/public/getBlockNumber.md +++ b/site/docs/actions/public/getBlockNumber.md @@ -47,26 +47,24 @@ The number of the block. ## Parameters -### maxAge (optional) +### cacheTime (optional) - **Type:** `number` -- **Default:** [Client's `pollingInterval`](/docs/clients/public#pollinginterval-optional) +- **Default:** [Client's `cacheTime`](/docs/clients/public#cachetime-optional) -The maximum age (in ms) of the cached value. +Time (in ms) that cached block number will remain in memory. ```ts const block = await publicClient.getBlockNumber({ - maxAge: 4_000 // [!code focus] + cacheTime: 4_000 // [!code focus] }) ``` -By default, block numbers are cached for the period of the [Client's `pollingInterval`](/docs/clients/public#pollinginterval-optional). +By default, block numbers are cached for the period of the [Client's `cacheTime`](/docs/clients/public#cacheTime-optional). - Setting a value of above zero will make block number remain in the cache for that period. - Setting a value of `0` will disable the cache, and always retrieve a fresh block number. - - ## Example Check out the usage of `getBlockNumber` in the live [Fetching Blocks Example](https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/blocks/fetching-blocks) below. @@ -75,4 +73,4 @@ Check out the usage of `getBlockNumber` in the live [Fetching Blocks Example](ht ## JSON-RPC Method -[`eth_blockNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber) \ No newline at end of file +[`eth_blockNumber`](https://ethereum.org/en/developers/docs/apis/json-rpc/#eth_blocknumber) diff --git a/site/docs/clients/public.md b/site/docs/clients/public.md index b3dd9b6bc5..49725ba7ec 100644 --- a/site/docs/clients/public.md +++ b/site/docs/clients/public.md @@ -50,7 +50,7 @@ The Public Client also supports [`eth_call` Aggregation](#multicall) for improve ### `eth_call` Aggregation (via Multicall) -The Public Client supports the aggregation of `eth_call` requests into a single multicall (`aggregate3`) request. +The Public Client supports the aggregation of `eth_call` requests into a single multicall (`aggregate3`) request. This means for every Action that utilizes an `eth_call` request (ie. `readContract`), the Public Client will batch the requests (over a timed period) and send it to the RPC Provider in a single multicall request. This can dramatically improve network performance, and decrease the amount of [Compute Units (CU)](https://docs.alchemy.com/reference/compute-units) used by RPC Providers like Alchemy, Infura, etc. @@ -93,7 +93,7 @@ const [name, totalSupply, symbol, tokenUri, balance] = await Promise.all([ - **Type:** [Transport](/docs/glossary/types#transport) -The [Transport](/docs/clients/intro) of the Public Client. +The [Transport](/docs/clients/intro) of the Public Client. ```ts const client = createPublicClient({ @@ -106,7 +106,7 @@ const client = createPublicClient({ - **Type:** [Chain](/docs/glossary/types#chain) -The [Chain](/docs/clients/chains) of the Public Client. +The [Chain](/docs/clients/chains) of the Public Client. ```ts const client = createPublicClient({ @@ -176,6 +176,21 @@ const client = createPublicClient({ }) ``` +### cacheTime (optional) + +- **Type:** `number` +- **Default:** `client.pollingInterval` + +Time (in ms) that cached data will remain in memory. + +```ts +const client = createPublicClient({ + cacheTime: 10_000, // [!code focus] + chain: mainnet, + transport: http(), +}) +``` + ### key (optional) - **Type:** `string` @@ -225,4 +240,4 @@ const client = createPublicClient({ Check out the usage of `createPublicClient` in the live [Public Client Example](https://stackblitz.com/github/wagmi-dev/viem/tree/main/examples/clients/public-client) below. - \ No newline at end of file + diff --git a/site/docs/clients/test.md b/site/docs/clients/test.md index 90354d39ab..098079cf94 100644 --- a/site/docs/clients/test.md +++ b/site/docs/clients/test.md @@ -109,7 +109,8 @@ import { privateKeyToAccount } from 'viem/accounts' const client = createTestClient({ account: privateKeyToAccount('0x...') // [!code focus] - chain: mainnet, + chain: foundry, + mode: 'anvil', transport: http(), }) ``` @@ -128,6 +129,22 @@ const client = createTestClient({ }) ``` +### cacheTime (optional) + +- **Type:** `number` +- **Default:** `client.pollingInterval` + +Time (in ms) that cached data will remain in memory. + +```ts +const client = createTestClient({ + cacheTime: 10_000, // [!code focus] + chain: foundry, + mode: 'anvil', + transport: http(), +}) +``` + ### name (optional) - **Type:** `string` diff --git a/site/docs/clients/wallet.md b/site/docs/clients/wallet.md index 5fcdac6bf6..7277995795 100644 --- a/site/docs/clients/wallet.md +++ b/site/docs/clients/wallet.md @@ -266,6 +266,21 @@ const client = createWalletClient({ }) ``` +### cacheTime (optional) + +- **Type:** `number` +- **Default:** `client.pollingInterval` + +Time (in ms) that cached data will remain in memory. + +```ts +const client = createWalletClient({ + cacheTime: 10_000, // [!code focus] + chain: mainnet, + transport: custom(window.ethereum) +}) +``` + ### key (optional) - **Type:** `string` diff --git a/src/actions/public/getBlockNumber.ts b/src/actions/public/getBlockNumber.ts index ff89d9f13f..8e168ddff6 100644 --- a/src/actions/public/getBlockNumber.ts +++ b/src/actions/public/getBlockNumber.ts @@ -4,7 +4,9 @@ import type { Chain } from '../../types/chain.js' import { getCache, withCache } from '../../utils/promise/withCache.js' export type GetBlockNumberParameters = { - /** The maximum age (in ms) of the cached value. */ + /** Time (in ms) that cached block number will remain in memory. */ + cacheTime?: number + /** @deprecated use `cacheTime` instead. */ maxAge?: number } @@ -41,14 +43,14 @@ export function getBlockNumberCache(id: string) { */ export async function getBlockNumber( client: Client, - { maxAge = client.pollingInterval }: GetBlockNumberParameters = {}, + { cacheTime = client.cacheTime, maxAge }: GetBlockNumberParameters = {}, ): Promise { const blockNumberHex = await withCache( () => client.request({ method: 'eth_blockNumber', }), - { cacheKey: cacheKey(client.uid), maxAge }, + { cacheKey: cacheKey(client.uid), cacheTime: maxAge ?? cacheTime }, ) return BigInt(blockNumberHex) } diff --git a/src/actions/public/watchBlockNumber.ts b/src/actions/public/watchBlockNumber.ts index 4e2c0d6c61..3577ba7769 100644 --- a/src/actions/public/watchBlockNumber.ts +++ b/src/actions/public/watchBlockNumber.ts @@ -105,7 +105,7 @@ export function watchBlockNumber< poll( async () => { try { - const blockNumber = await getBlockNumber(client, { maxAge: 0 }) + const blockNumber = await getBlockNumber(client, { cacheTime: 0 }) if (prevBlockNumber) { // If the current block number is the same as the previous, diff --git a/src/actions/test/mine.test.ts b/src/actions/test/mine.test.ts index 64b62d771b..d0f1372dac 100644 --- a/src/actions/test/mine.test.ts +++ b/src/actions/test/mine.test.ts @@ -6,15 +6,19 @@ import { getBlockNumber } from '../public/getBlockNumber.js' import { mine } from './mine.js' test('mines 1 block', async () => { - const currentBlockNumber = await getBlockNumber(publicClient, { maxAge: 0 }) + const currentBlockNumber = await getBlockNumber(publicClient, { + cacheTime: 0, + }) await expect(mine(testClient, { blocks: 1 })).resolves.toBeUndefined() - const nextBlockNumber = await getBlockNumber(publicClient, { maxAge: 0 }) + const nextBlockNumber = await getBlockNumber(publicClient, { cacheTime: 0 }) expect(nextBlockNumber).toEqual(currentBlockNumber + 1n) }) test('mines 5 blocks', async () => { - const currentBlockNumber = await getBlockNumber(publicClient, { maxAge: 0 }) + const currentBlockNumber = await getBlockNumber(publicClient, { + cacheTime: 0, + }) await mine(testClient, { blocks: 5 }) - const nextBlockNumber = await getBlockNumber(publicClient, { maxAge: 0 }) + const nextBlockNumber = await getBlockNumber(publicClient, { cacheTime: 0 }) expect(nextBlockNumber).toEqual(currentBlockNumber + 5n) }) diff --git a/src/actions/test/setIntervalMining.test.ts b/src/actions/test/setIntervalMining.test.ts index ba3bb996c1..eba8ee0f2f 100644 --- a/src/actions/test/setIntervalMining.test.ts +++ b/src/actions/test/setIntervalMining.test.ts @@ -10,22 +10,22 @@ import { setIntervalMining } from './setIntervalMining.js' test('sets mining interval', async () => { await mine(testClient, { blocks: 1 }) - const blockNumber1 = await getBlockNumber(publicClient, { maxAge: 0 }) + const blockNumber1 = await getBlockNumber(publicClient, { cacheTime: 0 }) await expect( setIntervalMining(testClient, { interval: 1 }), ).resolves.toBeUndefined() await wait(2000) - const blockNumber2 = await getBlockNumber(publicClient, { maxAge: 0 }) + const blockNumber2 = await getBlockNumber(publicClient, { cacheTime: 0 }) expect(blockNumber2 - blockNumber1).toBe(2n) await setIntervalMining(testClient, { interval: 2 }) await wait(2000) - const blockNumber3 = await getBlockNumber(publicClient, { maxAge: 0 }) + const blockNumber3 = await getBlockNumber(publicClient, { cacheTime: 0 }) expect(blockNumber3 - blockNumber2).toBe(1n) await setIntervalMining(testClient, { interval: 0 }) await wait(2000) - const blockNumber4 = await getBlockNumber(publicClient, { maxAge: 0 }) + const blockNumber4 = await getBlockNumber(publicClient, { cacheTime: 0 }) expect(blockNumber4 - blockNumber3).toBe(0n) await setIntervalMining(testClient, { interval: 1 }) diff --git a/src/clients/createClient.test.ts b/src/clients/createClient.test.ts index 75f48cde68..c3171db7ce 100644 --- a/src/clients/createClient.test.ts +++ b/src/clients/createClient.test.ts @@ -29,6 +29,7 @@ test('creates', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": undefined, "extend": [Function], "key": "base", @@ -61,6 +62,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": { "formatters": undefined, "id": 1337, @@ -116,6 +118,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": { "formatters": undefined, "id": 1337, @@ -171,6 +174,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": undefined, "extend": [Function], "key": "base", @@ -193,6 +197,45 @@ describe('transports', () => { }) describe('config', () => { + test('cacheTime', () => { + const mockTransport = () => + createTransport({ + key: 'mock', + name: 'Mock Transport', + request: vi.fn(async () => null) as unknown as EIP1193RequestFn, + type: 'mock', + }) + const { uid, ...client } = createClient({ + cacheTime: 10_000, + transport: mockTransport, + }) + + expect(uid).toBeDefined() + expect(client).toMatchInlineSnapshot(` + { + "account": undefined, + "batch": undefined, + "cacheTime": 10000, + "chain": undefined, + "extend": [Function], + "key": "base", + "name": "Base Client", + "pollingInterval": 4000, + "request": [Function], + "transport": { + "key": "mock", + "name": "Mock Transport", + "request": [MockFunction spy], + "retryCount": 3, + "retryDelay": 150, + "timeout": undefined, + "type": "mock", + }, + "type": "base", + } + `) + }) + test('key', () => { const mockTransport = () => createTransport({ @@ -212,6 +255,7 @@ describe('config', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": undefined, "extend": [Function], "key": "bar", @@ -251,6 +295,7 @@ describe('config', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": undefined, "extend": [Function], "key": "base", @@ -290,6 +335,7 @@ describe('config', () => { { "account": undefined, "batch": undefined, + "cacheTime": 10000, "chain": undefined, "extend": [Function], "key": "base", @@ -329,6 +375,7 @@ describe('config', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": undefined, "extend": [Function], "key": "base", @@ -372,6 +419,7 @@ describe('extends', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": { "formatters": undefined, diff --git a/src/clients/createClient.ts b/src/clients/createClient.ts index 405ba8576b..c51eef4283 100644 --- a/src/clients/createClient.ts +++ b/src/clients/createClient.ts @@ -29,6 +29,11 @@ export type ClientConfig< multicall?: boolean | Prettify | undefined } | undefined + /** + * Time (in ms) that cached data will remain in memory. + * @default 4_000 + */ + cacheTime?: number | undefined /** Chain for the client. */ chain?: Chain | undefined | chain /** A key for the client. */ @@ -79,6 +84,8 @@ type Client_Base< account: account /** Flags for batch settings. */ batch?: ClientConfig['batch'] + /** Time (in ms) that cached data will remain in memory. */ + cacheTime: number /** Chain for the client. */ chain: chain /** A key for the client. */ @@ -135,6 +142,7 @@ export function createClient< export function createClient(parameters: ClientConfig): Client { const { batch, + cacheTime = parameters.pollingInterval ?? 4_000, key = 'base', name = 'Base Client', pollingInterval = 4_000, @@ -154,6 +162,7 @@ export function createClient(parameters: ClientConfig): Client { const client = { account, batch, + cacheTime, chain, key, name, diff --git a/src/clients/createPublicClient.test.ts b/src/clients/createPublicClient.test.ts index 6508c4a6b9..1c126c83ea 100644 --- a/src/clients/createPublicClient.test.ts +++ b/src/clients/createPublicClient.test.ts @@ -31,6 +31,7 @@ test('creates', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": undefined, "createBlockFilter": [Function], @@ -139,6 +140,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": { "formatters": undefined, @@ -234,6 +236,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": { "formatters": undefined, @@ -329,6 +332,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": undefined, "createBlockFilter": [Function], @@ -403,6 +407,7 @@ test('extend', () => { "account": undefined, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": { "formatters": undefined, diff --git a/src/clients/createPublicClient.ts b/src/clients/createPublicClient.ts index 4a3e5fc6be..64b7413319 100644 --- a/src/clients/createPublicClient.ts +++ b/src/clients/createPublicClient.ts @@ -11,7 +11,13 @@ export type PublicClientConfig< > = Prettify< Pick< ClientConfig, - 'batch' | 'chain' | 'key' | 'name' | 'pollingInterval' | 'transport' + | 'batch' + | 'cacheTime' + | 'chain' + | 'key' + | 'name' + | 'pollingInterval' + | 'transport' > > diff --git a/src/clients/createTestClient.test.ts b/src/clients/createTestClient.test.ts index c22cfeee64..f7ec50d095 100644 --- a/src/clients/createTestClient.test.ts +++ b/src/clients/createTestClient.test.ts @@ -31,6 +31,7 @@ test('creates', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": { "formatters": undefined, "id": 1337, @@ -116,6 +117,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": { "formatters": undefined, "id": 1337, @@ -201,6 +203,7 @@ describe('transports', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": { "formatters": undefined, "id": 1337, @@ -294,6 +297,7 @@ test('extend', () => { }, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": { "formatters": undefined, diff --git a/src/clients/createTestClient.ts b/src/clients/createTestClient.ts index be621dd009..0ef94bc392 100644 --- a/src/clients/createTestClient.ts +++ b/src/clients/createTestClient.ts @@ -21,7 +21,13 @@ export type TestClientConfig< > = Prettify< Pick< ClientConfig, - 'account' | 'chain' | 'key' | 'name' | 'pollingInterval' | 'transport' + | 'account' + | 'cacheTime' + | 'chain' + | 'key' + | 'name' + | 'pollingInterval' + | 'transport' > & { /** Mode of the test client. */ mode: mode | ('anvil' | 'hardhat' | 'ganache') // TODO: Type utility that expands `TestClientMode` diff --git a/src/clients/createWalletClient.test.ts b/src/clients/createWalletClient.test.ts index ce81dde4a5..9f288b5229 100644 --- a/src/clients/createWalletClient.test.ts +++ b/src/clients/createWalletClient.test.ts @@ -36,6 +36,7 @@ test('creates', () => { "account": undefined, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "chain": undefined, "deployContract": [Function], "extend": [Function], @@ -86,6 +87,7 @@ describe('args: account', () => { }, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "chain": undefined, "deployContract": [Function], "extend": [Function], @@ -140,6 +142,7 @@ describe('args: account', () => { }, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "chain": undefined, "deployContract": [Function], "extend": [Function], @@ -185,6 +188,7 @@ describe('args: transport', () => { "account": undefined, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "chain": undefined, "deployContract": [Function], "extend": [Function], @@ -228,6 +232,7 @@ describe('args: transport', () => { "account": undefined, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "chain": undefined, "deployContract": [Function], "extend": [Function], @@ -273,6 +278,7 @@ describe('args: transport', () => { "account": undefined, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "chain": { "formatters": undefined, "id": 1337, @@ -348,6 +354,7 @@ test('extend', () => { }, "addChain": [Function], "batch": undefined, + "cacheTime": 4000, "call": [Function], "chain": { "formatters": undefined, diff --git a/src/clients/createWalletClient.ts b/src/clients/createWalletClient.ts index e49e6cfa10..f138cfd774 100644 --- a/src/clients/createWalletClient.ts +++ b/src/clients/createWalletClient.ts @@ -19,7 +19,13 @@ export type WalletClientConfig< > = Prettify< Pick< ClientConfig, - 'account' | 'chain' | 'key' | 'name' | 'pollingInterval' | 'transport' + | 'account' + | 'cacheTime' + | 'chain' + | 'key' + | 'name' + | 'pollingInterval' + | 'transport' > > diff --git a/src/clients/transports/fallback.test.ts b/src/clients/transports/fallback.test.ts index 45287ea157..5978b0b51e 100644 --- a/src/clients/transports/fallback.test.ts +++ b/src/clients/transports/fallback.test.ts @@ -416,6 +416,7 @@ describe('client', () => { { "account": undefined, "batch": undefined, + "cacheTime": 4000, "chain": undefined, "extend": [Function], "key": "base", diff --git a/src/utils/promise/withCache.test.ts b/src/utils/promise/withCache.test.ts index 6cb5649925..499907e273 100644 --- a/src/utils/promise/withCache.test.ts +++ b/src/utils/promise/withCache.test.ts @@ -18,13 +18,13 @@ test('caches responses', async () => { expect(fn).toBeCalledTimes(1) }) -describe('args: maxAge', () => { - test('invalidates when maxAge = 0', async () => { +describe('args: cacheTime', () => { + test('invalidates when cacheTime = 0', async () => { const fn = vi.fn().mockResolvedValue('bar') let data = await withCache(fn, { cacheKey: 'foo' }) expect(data).toBe('bar') - data = await withCache(fn, { cacheKey: 'foo', maxAge: 0 }) + data = await withCache(fn, { cacheKey: 'foo', cacheTime: 0 }) expect(data).toBe('bar') expect(fn).toBeCalledTimes(2) }) @@ -39,7 +39,7 @@ describe('args: maxAge', () => { expect(fn).toBeCalledTimes(1) await wait(150) - data = await withCache(fn, { cacheKey: 'foo', maxAge: 100 }) + data = await withCache(fn, { cacheKey: 'foo', cacheTime: 100 }) expect(data).toBe('bar') expect(fn).toBeCalledTimes(2) }) diff --git a/src/utils/promise/withCache.ts b/src/utils/promise/withCache.ts index 737a4348f2..6b268f1e62 100644 --- a/src/utils/promise/withCache.ts +++ b/src/utils/promise/withCache.ts @@ -27,8 +27,8 @@ export function getCache(cacheKey: string) { export type WithCacheParameters = { /** The key to cache the data against. */ cacheKey: string - /** The maximum age (in ms) of the cached value. Default: Infinity (no expiry) */ - maxAge?: number + /** The time that cached data will remain in memory. Default: Infinity (no expiry) */ + cacheTime?: number } /** @@ -37,7 +37,7 @@ export type WithCacheParameters = { */ export async function withCache( fn: () => Promise, - { cacheKey, maxAge = Infinity }: WithCacheParameters, + { cacheKey, cacheTime = Infinity }: WithCacheParameters, ) { const cache = getCache(cacheKey) @@ -45,9 +45,9 @@ export async function withCache( // and do not invoke the promise. // If the max age is 0, the cache is disabled. const response = cache.response.get() - if (response && maxAge > 0) { + if (response && cacheTime > 0) { const age = new Date().getTime() - response.created.getTime() - if (age < maxAge) return response.data + if (age < cacheTime) return response.data } let promise = cache.promise.get()