Skip to content

Commit 31ec3c1

Browse files
committed
feat: should not stringify cached value by default
1 parent f48ef5d commit 31ec3c1

File tree

4 files changed

+89
-52
lines changed

4 files changed

+89
-52
lines changed

packages/document/main-doc/docs/en/guides/basic-features/data/data-cache.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,8 @@ class RedisContainer implements Container {
477477
}
478478

479479
async get(key: string): Promise<string | null> {
480-
return this.client.get(key);
480+
const value = await this.redis.get(key);
481+
return value ? JSON.parse(value) : null;
481482
}
482483

483484
async set(
@@ -486,9 +487,9 @@ class RedisContainer implements Container {
486487
options?: { ttl?: number },
487488
): Promise<'OK'> {
488489
if (options?.ttl) {
489-
return this.client.set(key, value, 'EX', options.ttl);
490+
return this.client.set(key, JSON.stringify(value), 'EX', options.ttl);
490491
}
491-
return this.client.set(key, value);
492+
return this.client.set(key, JSON.stringify(value));
492493
}
493494

494495
async has(key: string): Promise<boolean> {

packages/document/main-doc/docs/zh/guides/basic-features/data/data-cache.mdx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -451,7 +451,8 @@ class RedisContainer implements Container {
451451
}
452452

453453
async get(key: string): Promise<string | null> {
454-
return this.client.get(key);
454+
const value = await this.redis.get(key);
455+
return value ? JSON.parse(value) : null;
455456
}
456457

457458
async set(
@@ -460,9 +461,9 @@ class RedisContainer implements Container {
460461
options?: { ttl?: number },
461462
): Promise<'OK'> {
462463
if (options?.ttl) {
463-
return this.client.set(key, value, 'EX', options.ttl);
464+
return this.client.set(key, JSON.stringify(value), 'EX', options.ttl);
464465
}
465-
return this.client.set(key, value);
466+
return this.client.set(key, JSON.stringify(value));
466467
}
467468

468469
async has(key: string): Promise<boolean> {

packages/toolkit/runtime-utils/src/universal/cache.ts

Lines changed: 77 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -35,34 +35,72 @@ export interface CacheStatsInfo {
3535
}
3636

3737
export interface Container {
38-
get: (key: string) => Promise<string | undefined | null>;
39-
set: (key: string, value: string, options?: { ttl?: number }) => Promise<any>;
38+
get: (key: string) => Promise<any | undefined | null>;
39+
set: (key: string, value: any, options?: { ttl?: number }) => Promise<any>;
4040
has: (key: string) => Promise<boolean>;
4141
delete: (key: string) => Promise<boolean>;
4242
clear: () => Promise<void>;
4343
}
4444

45+
function estimateObjectSize(data: unknown): number {
46+
const type = typeof data;
47+
48+
if (type === 'number') return 8;
49+
if (type === 'boolean') return 4;
50+
if (type === 'string') return Math.max((data as string).length * 2, 1);
51+
if (data === null || data === undefined) return 1;
52+
53+
if (ArrayBuffer.isView(data)) {
54+
return Math.max(data.byteLength, 1);
55+
}
56+
57+
if (Array.isArray(data)) {
58+
return Math.max(
59+
data.reduce((acc, item) => acc + estimateObjectSize(item), 0),
60+
1,
61+
);
62+
}
63+
64+
if (data instanceof Map || data instanceof Set) {
65+
return 1024;
66+
}
67+
68+
if (data instanceof Date) {
69+
return 8;
70+
}
71+
72+
if (type === 'object') {
73+
return Math.max(
74+
Object.entries(data).reduce(
75+
(acc, [key, value]) => acc + key.length * 2 + estimateObjectSize(value),
76+
0,
77+
),
78+
1,
79+
);
80+
}
81+
82+
return 1;
83+
}
84+
4585
class MemoryContainer implements Container {
46-
private lru: LRUCache<string, string>;
86+
private lru: LRUCache<string, any>;
4787

4888
constructor(options?: { maxSize?: number }) {
49-
this.lru = new LRUCache<string, string>({
89+
this.lru = new LRUCache<string, any>({
5090
maxSize: options?.maxSize ?? CacheSize.GB,
51-
sizeCalculation: (value: string): number => {
52-
return value.length * 2;
53-
},
91+
sizeCalculation: estimateObjectSize,
5492
updateAgeOnGet: true,
5593
updateAgeOnHas: true,
5694
});
5795
}
5896

59-
async get(key: string): Promise<string | undefined> {
97+
async get(key: string): Promise<any | undefined> {
6098
return this.lru.get(key);
6199
}
62100

63101
async set(
64102
key: string,
65-
value: string,
103+
value: any,
66104
options?: { ttl?: number },
67105
): Promise<void> {
68106
if (options?.ttl) {
@@ -274,28 +312,28 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
274312
}
275313

276314
if (!shouldDisableCaching) {
277-
const cachedRaw = await currentStorage.get(storageKey);
278-
if (cachedRaw) {
315+
const cached = await currentStorage.get(storageKey);
316+
if (cached) {
279317
try {
280-
const cached = JSON.parse(cachedRaw) as CacheItem<any>;
281-
const age = now - cached.timestamp;
318+
const cacheItem = cached as CacheItem<any>;
319+
const age = now - cacheItem.timestamp;
282320

283321
if (age < maxAge) {
284322
onCache?.({
285323
status: 'hit',
286324
key: finalKey,
287325
params: args,
288-
result: cached.data,
326+
result: cacheItem.data,
289327
});
290-
return cached.data;
328+
return cacheItem.data;
291329
}
292330

293331
if (revalidate > 0 && age < maxAge + revalidate) {
294332
onCache?.({
295333
status: 'stale',
296334
key: finalKey,
297335
params: args,
298-
result: cached.data,
336+
result: cacheItem.data,
299337
});
300338

301339
if (!ongoingRevalidations.has(storageKey)) {
@@ -338,7 +376,7 @@ export function cache<T extends (...args: any[]) => Promise<any>>(
338376
ongoingRevalidations.set(storageKey, revalidationPromise);
339377
}
340378

341-
return cached.data;
379+
return cacheItem.data;
342380
}
343381
missReason = 3; // cache-expired
344382
} catch (error) {
@@ -431,7 +469,7 @@ async function setCacheItem(
431469
};
432470

433471
const ttl = (maxAge + revalidate) / 1000;
434-
await storage.set(storageKey, JSON.stringify(newItem), {
472+
await storage.set(storageKey, newItem, {
435473
ttl: ttl > 0 ? ttl : undefined,
436474
});
437475

@@ -445,12 +483,12 @@ async function updateTagRelationships(
445483
): Promise<void> {
446484
for (const tag of tags) {
447485
const tagStoreKey = `${TAG_PREFIX}${tag}`;
448-
const keyListJson = await storage.get(tagStoreKey);
449-
const keyList: string[] = keyListJson ? JSON.parse(keyListJson) : [];
450-
if (!keyList.includes(storageKey)) {
451-
keyList.push(storageKey);
486+
const keyList = await storage.get(tagStoreKey);
487+
const keyArray: string[] = keyList || [];
488+
if (!keyArray.includes(storageKey)) {
489+
keyArray.push(storageKey);
452490
}
453-
await storage.set(tagStoreKey, JSON.stringify(keyList));
491+
await storage.set(tagStoreKey, keyArray);
454492
}
455493
}
456494

@@ -461,18 +499,18 @@ async function removeKeyFromTags(
461499
): Promise<void> {
462500
for (const tag of tags) {
463501
const tagStoreKey = `${TAG_PREFIX}${tag}`;
464-
const keyListJson = await storage.get(tagStoreKey);
465-
if (keyListJson) {
502+
const keyList = await storage.get(tagStoreKey);
503+
if (keyList) {
466504
try {
467-
const keyList: string[] = JSON.parse(keyListJson);
468-
const updatedKeyList = keyList.filter(key => key !== storageKey);
505+
const keyArray: string[] = Array.isArray(keyList) ? keyList : [];
506+
const updatedKeyList = keyArray.filter(key => key !== storageKey);
469507
if (updatedKeyList.length > 0) {
470-
await storage.set(tagStoreKey, JSON.stringify(updatedKeyList));
508+
await storage.set(tagStoreKey, updatedKeyList);
471509
} else {
472510
await storage.delete(tagStoreKey);
473511
}
474512
} catch (error) {
475-
console.warn(`Failed to parse tag key list for tag ${tag}:`, error);
513+
console.warn(`Failed to process tag key list for tag ${tag}:`, error);
476514
}
477515
}
478516
}
@@ -495,23 +533,21 @@ export async function revalidateTag(tag: string): Promise<void> {
495533
const currentStorage = getStorage();
496534
const tagStoreKey = `${TAG_PREFIX}${tag}`;
497535

498-
const keyListJson = await currentStorage.get(tagStoreKey);
499-
if (keyListJson) {
536+
const keyList = await currentStorage.get(tagStoreKey);
537+
if (keyList) {
500538
try {
501-
const keyList: string[] = JSON.parse(keyListJson);
502-
539+
const keyArray: string[] = Array.isArray(keyList) ? keyList : [];
503540
// For each cache key, we need to:
504541
// 1. Get the cache item to find its associated tags
505542
// 2. Remove this key from all other tag relationships
506543
// 3. Delete the cache item itself
507-
for (const cacheKey of keyList) {
508-
const cachedRaw = await currentStorage.get(cacheKey);
509-
if (cachedRaw) {
544+
for (const cacheKey of keyArray) {
545+
const cached = await currentStorage.get(cacheKey);
546+
if (cached) {
510547
try {
511-
const cached = JSON.parse(cachedRaw) as CacheItem<any>;
512-
if (cached.tags) {
513-
// Remove this cache key from all its associated tags (except the current one being revalidated)
514-
const otherTags = cached.tags.filter(t => t !== tag);
548+
const cacheItem = cached as CacheItem<any>;
549+
if (cacheItem.tags) {
550+
const otherTags = cacheItem.tags.filter(t => t !== tag);
515551
await removeKeyFromTags(currentStorage, cacheKey, otherTags);
516552
}
517553
} catch (error) {
@@ -522,14 +558,12 @@ export async function revalidateTag(tag: string): Promise<void> {
522558
}
523559
}
524560

525-
// Delete the cache item itself
526561
await currentStorage.delete(cacheKey);
527562
}
528563

529-
// Delete the tag relationship record
530564
await currentStorage.delete(tagStoreKey);
531565
} catch (error) {
532-
console.warn('Failed to parse tag key list:', error);
566+
console.warn('Failed to process tag key list:', error);
533567
}
534568
}
535569
}

packages/toolkit/runtime-utils/tests/universal/cache-container.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ class RedisContainer implements Container {
3232

3333
async get(key: string): Promise<string | null> {
3434
this.operations.push(`get:${key}`);
35-
return this.redis.get(key);
35+
const value = await this.redis.get(key);
36+
return value ? JSON.parse(value) : null;
3637
}
3738

3839
async set(
@@ -44,9 +45,9 @@ class RedisContainer implements Container {
4445
`set:${key}${options?.ttl ? `:ttl=${options.ttl}` : ''}`,
4546
);
4647
if (options?.ttl && options.ttl > 0) {
47-
await this.redis.set(key, value, 'EX', options.ttl);
48+
await this.redis.set(key, JSON.stringify(value), 'EX', options.ttl);
4849
} else {
49-
await this.redis.set(key, value);
50+
await this.redis.set(key, JSON.stringify(value));
5051
}
5152
}
5253

0 commit comments

Comments
 (0)