Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 | 15x 15x 15x 15x 91x 14x 39x 39x 39x 10x 1x 1x 9x 38x 38x 38x 48x 48x 35x 33x 6x 6x 6x 4x 4x 2x 2x 2x 2x 4x 4x 4x 4x 3x 3x 3x 2x 2x 2x 2x 2x 1x 1x 1x 1x 15x | import AsyncStorage from "@react-native-async-storage/async-storage";
interface CacheEnvelope<T> {
value: T;
expiresAt: number | null;
}
class CacheService {
private static instance: CacheService | null = null;
private readonly memory = new Map<string, CacheEnvelope<unknown>>();
private constructor() {}
static getInstance(): CacheService {
CacheService.instance ??= new CacheService();
return CacheService.instance;
}
private makeKey(namespace: string, key: string): string {
return `${namespace}:${key}`;
}
private isExpired(expiresAt: number | null): boolean {
return expiresAt !== null && Date.now() > expiresAt;
}
getMemory<T>(namespace: string, key: string): T | null {
const cacheKey = this.makeKey(namespace, key);
const cached = this.memory.get(cacheKey);
if (!cached) return null;
if (this.isExpired(cached.expiresAt)) {
this.memory.delete(cacheKey);
return null;
}
return cached.value as T;
}
setMemory<T>(namespace: string, key: string, value: T, ttlMs?: number): void {
const cacheKey = this.makeKey(namespace, key);
const expiresAt = ttlMs ? Date.now() + ttlMs : null;
this.memory.set(cacheKey, { value, expiresAt });
}
clearMemoryNamespace(namespace: string): void {
const prefix = `${namespace}:`;
for (const key of this.memory.keys()) {
if (key.startsWith(prefix)) {
this.memory.delete(key);
}
}
}
async getPersistent<T>(namespace: string, key: string): Promise<T | null> {
const cacheKey = this.makeKey(namespace, key);
const raw = await AsyncStorage.getItem(cacheKey);
if (!raw) return null;
const envelope = JSON.parse(raw) as CacheEnvelope<T>;
if (this.isExpired(envelope.expiresAt)) {
await AsyncStorage.removeItem(cacheKey);
return null;
}
this.memory.set(cacheKey, envelope as CacheEnvelope<unknown>);
return envelope.value;
}
async setPersistent<T>(
namespace: string,
key: string,
value: T,
ttlMs?: number,
): Promise<void> {
const cacheKey = this.makeKey(namespace, key);
const envelope: CacheEnvelope<T> = {
value,
expiresAt: ttlMs ? Date.now() + ttlMs : null,
};
this.memory.set(cacheKey, envelope as CacheEnvelope<unknown>);
await AsyncStorage.setItem(cacheKey, JSON.stringify(envelope));
}
async getPersistentRaw<T>(namespace: string, key: string): Promise<T | null> {
const cacheKey = this.makeKey(namespace, key);
const raw = await AsyncStorage.getItem(cacheKey);
if (!raw) return null;
const parsed = JSON.parse(raw) as unknown;
const maybeEnvelope = parsed as Partial<CacheEnvelope<T>>;
// Backward compatible with legacy raw values that were stored without envelope.
const envelope: CacheEnvelope<T> =
maybeEnvelope &&
typeof maybeEnvelope === "object" &&
"value" in maybeEnvelope &&
"expiresAt" in maybeEnvelope
? (maybeEnvelope as CacheEnvelope<T>)
: { value: parsed as T, expiresAt: null };
this.memory.set(cacheKey, envelope as CacheEnvelope<unknown>);
return envelope.value;
}
async setPersistentRaw<T>(
namespace: string,
key: string,
value: T,
): Promise<void> {
const cacheKey = this.makeKey(namespace, key);
const envelope: CacheEnvelope<T> = { value, expiresAt: null };
this.memory.set(cacheKey, envelope as CacheEnvelope<unknown>);
await AsyncStorage.setItem(cacheKey, JSON.stringify(envelope));
}
}
const cacheService = CacheService.getInstance();
export default cacheService;
|