From 5589b894780cf8f603cdcf672a76216446421f8a Mon Sep 17 00:00:00 2001 From: Yufan You Date: Thu, 7 May 2026 01:26:11 +0800 Subject: [PATCH] fix(cache): call JSON.stringify when storing value in cache.tryGet (#20671) * fix(cache): call JSON.stringify when storing value in cache.tryGet The value is returned as-is, so we may assume that it's not JSON stringified inside `getValueFunc`. Otherwise, the caller already needs to distinguish between values that are either JSON parsed or not. * Revert "fix(routes/mastodon): don't refresh account ID cache (#20663)" This reverts commit c2cff410fa3eaab7bb2f5b0b3bd4567de246e30c. * test: add test * test: lint --- lib/routes/mastodon/utils.ts | 51 ++++++++++++++++-------------------- lib/utils/cache.test.ts | 18 +++++++++++++ lib/utils/cache/index.ts | 2 +- 3 files changed, 42 insertions(+), 29 deletions(-) diff --git a/lib/routes/mastodon/utils.ts b/lib/routes/mastodon/utils.ts index e0a2e4783..0bebaab81 100644 --- a/lib/routes/mastodon/utils.ts +++ b/lib/routes/mastodon/utils.ts @@ -103,37 +103,32 @@ async function getAccountIdByAcct(acct) { const search_url = `https://${site}/api/v2/search`; const cacheUid = `mastodon_acct_id/${site}/${acct}`; - const account_id = await cache.tryGet( - cacheUid, - async () => { - const search_response = await got({ - method: 'get', - url: search_url, - headers: apiHeaders(site), - searchParams: { - q: acct, - type: 'accounts', - }, - }); - const [acctUser, acctHost] = acct.split('@').filter(Boolean); - let acctOnServer; + const account_id = await cache.tryGet(cacheUid, async () => { + const search_response = await got({ + method: 'get', + url: search_url, + headers: apiHeaders(site), + searchParams: { + q: acct, + type: 'accounts', + }, + }); + const [acctUser, acctHost] = acct.split('@').filter(Boolean); + let acctOnServer; - if (acctHost) { - acctOnServer = acctHost === acctDomain ? acctUser : acctUser + '@' + acctHost; - } else { - acctOnServer = acctUser; - } + if (acctHost) { + acctOnServer = acctHost === acctDomain ? acctUser : acctUser + '@' + acctHost; + } else { + acctOnServer = acctUser; + } - const accountData = search_response.data.accounts.filter((item) => item.acct === acctOnServer); + const accountData = search_response.data.accounts.filter((item) => item.acct === acctOnServer); - if (accountData.length === 0) { - throw new Error(`acct ${acct} not found`); - } - return accountData[0].id; - }, - config.cache.contentExpire, - false - ); + if (accountData.length === 0) { + throw new Error(`acct ${acct} not found`); + } + return accountData[0].id; + }); return { site, account_id }; } diff --git a/lib/utils/cache.test.ts b/lib/utils/cache.test.ts index b5d37eb89..d703d6c6e 100644 --- a/lib/utils/cache.test.ts +++ b/lib/utils/cache.test.ts @@ -42,6 +42,24 @@ describe('cache', () => { expect(await memory.has('missing')).toBe(false); }); + it('tryGet preserves snowflake ID precision', async () => { + process.env.CACHE_TYPE = 'memory'; + const cache = (await import('@/utils/cache')).default; + if (!cache.clients.memoryCache || !cache.status.available) { + throw new Error('Memory cache client error'); + } + + const snowflakeId = '1234567890123456789'; + const fetcher = vi.fn(() => Promise.resolve(snowflakeId)); + + const fresh = await cache.tryGet('snowflake', fetcher); + expect(fresh).toBe(snowflakeId); + const cached = await cache.tryGet('snowflake', fetcher); + expect(typeof cached).toBe('string'); + expect(cached).toBe(snowflakeId); + expect(fetcher).toHaveBeenCalledTimes(1); + }); + it('redis', async () => { process.env.CACHE_TYPE = 'redis'; const cache = (await import('@/utils/cache')).default; diff --git a/lib/utils/cache/index.ts b/lib/utils/cache/index.ts index 4d8a04fe6..722c4badf 100644 --- a/lib/utils/cache/index.ts +++ b/lib/utils/cache/index.ts @@ -141,7 +141,7 @@ export default { return v as T; } else { const value = await getValueFunc(); - cacheModule.set(key, value, maxAge); + cacheModule.set(key, JSON.stringify(value), maxAge); return value; }