Skip to content

Commit e4eb424

Browse files
committed
feat: use ioredis
1 parent 8aafda9 commit e4eb424

File tree

6 files changed

+179
-223
lines changed

6 files changed

+179
-223
lines changed

packages/utils/package.json

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,11 @@
4343
"ethers": ">=6.13.0"
4444
},
4545
"dependencies": {
46-
"@keyv/redis": "^4.2.0",
4746
"@murongg/utils": "^0.1.28",
48-
"cache-manager": "^6.3.2",
49-
"cacheable": "^1.8.7",
47+
"cache-manager": "5.7.6",
48+
"cache-manager-ioredis-yet": "2.1.1",
5049
"debug": "^4.3.7",
5150
"ioredis": "^5.4.1",
52-
"keyv": "^5.2.3",
5351
"lodash.clonedeep": "^4.5.0",
5452
"lru-cache": "^11.0.1",
5553
"picocolors": "^1.0.1",

packages/utils/src/store/index.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
1-
export type { CacheManagerStore } from 'cache-manager'
2-
export type { Store } from './types'
1+
export type { Store, StoreConfig, Stores } from 'cache-manager'
32
export { redisStore } from './redis'
43
export * from './manager'
54
export { memoryStore } from './memory'
6-
export type { MemoryConfig } from './memory'
5+
export type { MemoryStore, MemoryCache, MemoryConfig } from './memory'

packages/utils/src/store/manager.ts

Lines changed: 18 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,33 @@
1-
import type { Cache, CreateCacheOptions } from 'cache-manager'
2-
import { createCache } from 'cache-manager'
3-
import type { KeyvStoreAdapter } from 'keyv'
4-
import { Keyv } from 'keyv'
5-
import { KeyvCacheableMemory } from 'cacheable'
1+
import type { Cache, Milliseconds, Store } from 'cache-manager'
2+
import { createCache, memoryStore } from 'cache-manager'
3+
import type { RedisStore } from 'cache-manager-ioredis-yet'
64

75
export class SimpleStoreManager {
86
private cache: Cache
9-
private store: Keyv
107

118
constructor(
12-
options?: (Omit<CreateCacheOptions, 'stores'>) & { store: Keyv },
9+
store?: Store,
1310
) {
14-
if (options) {
15-
this.cache = createCache({
16-
...options,
17-
stores: [
18-
options.store,
19-
],
20-
})
21-
this.store = options.store
11+
if (store) {
12+
this.cache = createCache(store)
2213
}
2314
else {
24-
const store = new KeyvCacheableMemory({ ttl: 10 * 1000, lruSize: 100 })
25-
const keyv = new Keyv({ store })
26-
this.store = keyv
27-
this.cache = createCache({ stores: [keyv] })
15+
this.cache = createCache(memoryStore({
16+
max: 100,
17+
ttl: 10 * 1000 /* milliseconds */,
18+
}))
2819
}
2920
}
3021

31-
get redisStore(): KeyvStoreAdapter {
32-
return this.store.store
22+
get redisStore(): RedisStore {
23+
return <RedisStore> this.cache.store
3324
}
3425

3526
async get<T>(key: string) {
3627
return await this.cache.get<T>(key)
3728
}
3829

39-
async set(key: string, value: unknown, ttl?: number) {
30+
async set(key: string, value: unknown, ttl?: Milliseconds) {
4031
await this.cache.set(key, value, ttl)
4132
}
4233

@@ -45,28 +36,16 @@ export class SimpleStoreManager {
4536
}
4637

4738
async keys(pattern?: string): Promise<string[]> {
48-
const keys = []
49-
for await (const [key] of this.store.store.entries()) {
50-
if (pattern) {
51-
const regex = new RegExp(`(?<!.)${pattern}`)
52-
if (regex.test(key))
53-
keys.push(key)
54-
}
55-
else {
56-
keys.push(key)
57-
}
58-
}
59-
return keys
39+
return await this.cache.store.keys(pattern)
6040
}
6141

6242
async has(key: string): Promise<boolean> {
63-
return !!await this.cache.get(key)
43+
return !!await this.cache.store.get(key)
6444
}
6545

6646
async getAll<T>(): Promise<T[]> {
67-
const values = []
68-
for await (const [, value] of this.store.store.entries())
69-
values.push(value)
70-
return values
47+
const keys = await this.keys()
48+
const values = await Promise.all(keys.map(async key => await this.get<T>(key)))
49+
return values.filter(value => value !== undefined)
7150
}
7251
}

packages/utils/src/store/memory.ts

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,113 @@
1-
import { Keyv } from 'keyv'
2-
import type { CacheableMemoryOptions } from 'cacheable'
3-
import { KeyvCacheableMemory } from 'cacheable'
1+
import { LRUCache } from 'lru-cache'
2+
import cloneDeep from 'lodash.clonedeep'
3+
import type { Cache, Config, Store } from 'cache-manager'
44

5-
export type MemoryConfig = CacheableMemoryOptions & {
6-
namespace?: string
5+
function clone<T>(object: T): T {
6+
if (typeof object === 'object' && object !== null)
7+
return cloneDeep(object)
8+
9+
return object
710
}
811

9-
export function memoryStore(options?: MemoryConfig) {
10-
const store = new KeyvCacheableMemory(options)
11-
const keyv = new Keyv({ store }, { namespace: '' })
12-
return keyv
12+
type LRU = LRUCache<string, any>
13+
14+
type Pre = LRUCache.OptionsTTLLimit<string, any, unknown>
15+
type Options = Omit<Pre, 'ttlAutopurge'> & Partial<Pick<Pre, 'ttlAutopurge'>>
16+
export type MemoryConfig = {
17+
max?: number
18+
sizeCalculation?: (value: unknown, key: string) => number
19+
shouldCloneBeforeSet?: boolean
20+
} & Options &
21+
Config
22+
23+
export type MemoryStore = Store & {
24+
dump: LRU['dump']
25+
load: LRU['load']
26+
calculatedSize: LRU['calculatedSize']
27+
get size(): number
28+
}
29+
export type MemoryCache = Cache<MemoryStore>
30+
31+
/**
32+
* Wrapper for lru-cache.
33+
*/
34+
export function memoryStore(arguments_?: MemoryConfig): MemoryStore {
35+
const shouldCloneBeforeSet = arguments_?.shouldCloneBeforeSet !== false // Clone by default
36+
const isCacheable = arguments_?.isCacheable ?? (value => value !== undefined)
37+
38+
const lruOptions = {
39+
ttlAutopurge: true,
40+
...arguments_,
41+
max: arguments_?.max ?? 500,
42+
ttl: arguments_?.ttl === undefined ? 0 : arguments_.ttl,
43+
}
44+
45+
const lruCache = new LRUCache(lruOptions)
46+
47+
return {
48+
async del(key) {
49+
lruCache.delete(key)
50+
},
51+
get: async <T>(key: string) => lruCache.get(key) as T,
52+
keys: async (pattern?: string) => {
53+
const keys = [...lruCache.keys()]
54+
if (!pattern)
55+
return keys
56+
57+
const regex = new RegExp(`(?<!.)${pattern}`)
58+
return keys.filter(key => regex.test(key))
59+
},
60+
61+
mget: async (...arguments_) => arguments_.map(x => lruCache.get(x)),
62+
async mset(arguments_, ttl?) {
63+
const opt = { ttl: ttl ?? lruOptions.ttl } as const
64+
for (const [key, value] of arguments_) {
65+
if (!isCacheable(value))
66+
throw new Error(`no cacheable value ${JSON.stringify(value)}`)
67+
68+
if (shouldCloneBeforeSet)
69+
lruCache.set(key, clone(value), opt)
70+
else
71+
lruCache.set(key, value, opt)
72+
}
73+
},
74+
async mdel(...arguments_) {
75+
for (const key of arguments_)
76+
lruCache.delete(key)
77+
},
78+
async reset() {
79+
lruCache.clear()
80+
},
81+
ttl: async key => lruCache.getRemainingTTL(key),
82+
async set(key, value, opt) {
83+
if (!isCacheable(value))
84+
throw new Error(`no cacheable value ${JSON.stringify(value)}`)
85+
86+
if (shouldCloneBeforeSet)
87+
value = clone(value)
88+
89+
const ttl = opt ?? lruOptions.ttl
90+
91+
lruCache.set(key, value, { ttl })
92+
},
93+
get calculatedSize() {
94+
return lruCache.calculatedSize
95+
},
96+
/**
97+
* This method is not available in the caching modules.
98+
*/
99+
get size() {
100+
return lruCache.size
101+
},
102+
/**
103+
* This method is not available in the caching modules.
104+
*/
105+
dump: () => lruCache.dump(),
106+
/**
107+
* This method is not available in the caching modules.
108+
*/
109+
load(...arguments_: Parameters<LRU['load']>) {
110+
lruCache.load(...arguments_)
111+
},
112+
}
13113
}

packages/utils/src/store/redis.ts

Lines changed: 19 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,24 @@
1-
import type { KeyvRedisOptions, RedisClientConnectionType, RedisClientOptions, RedisClusterOptions } from '@keyv/redis'
2-
import KeyvRedis, { Keyv } from '@keyv/redis'
3-
4-
export interface RedisConnect {
5-
host?: string
6-
port?: number
7-
username?: string
8-
password?: string
9-
db?: number
10-
}
1+
import type { RedisOptions } from 'ioredis'
2+
import { Redis } from 'ioredis'
3+
import type { Config } from 'cache-manager'
4+
import { RedisClusterConfig, redisInsStore } from 'cache-manager-ioredis-yet'
115

126
export function redisStore(
13-
connect?: string | RedisClientOptions | RedisClusterOptions | RedisClientConnectionType | RedisConnect,
14-
options?: KeyvRedisOptions,
7+
options?: (RedisOptions | { clusterConfig: RedisClusterConfig }) & Config,
158
) {
16-
if (typeof connect === 'object' && connect) {
17-
if (Reflect.get(connect, 'host')) {
18-
const c = connect as RedisConnect
19-
let connectStr = 'redis://'
20-
if (c.username)
21-
connectStr += `${c.username}:${c.password}@`
22-
connectStr += `${c.host}:${c.port}`
23-
if (c.db)
24-
connectStr += `/${c.db}`
25-
connect = connectStr
26-
}
27-
}
9+
options ||= {}
10+
const redisCache
11+
= 'clusterConfig' in options
12+
? new Redis.Cluster(
13+
options.clusterConfig.nodes,
14+
options.clusterConfig.options,
15+
)
16+
: new Redis(options)
17+
18+
return redisInsStore(redisCache, options)
19+
}
2820

29-
const keyv = new Keyv(new KeyvRedis(connect, options), {
30-
namespace: '',
31-
})
32-
return keyv
21+
export {
22+
RedisClusterConfig,
23+
redisInsStore,
3324
}

0 commit comments

Comments
 (0)