feat: Add CacheX for cache management (#281)

This commit is contained in:
Florian Bouillon 2024-11-11 16:50:45 +01:00 committed by GitHub
parent e501faa823
commit 1f3aae5401
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 58 additions and 303 deletions

54
package-lock.json generated
View File

@ -1,14 +1,16 @@
{ {
"name": "@tcgdex/sdk", "name": "@tcgdex/sdk",
"version": "2.6.0-beta.1", "version": "2.6.0-beta.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@tcgdex/sdk", "name": "@tcgdex/sdk",
"version": "2.6.0-beta.1", "version": "2.6.0-beta.3",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@cachex/memory": "^1.0.0",
"@cachex/web-storage": "^1.0.1",
"@dzeio/object-util": "^1", "@dzeio/object-util": "^1",
"isomorphic-unfetch": "^3" "isomorphic-unfetch": "^3"
}, },
@ -1723,6 +1725,30 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true "dev": true
}, },
"node_modules/@cachex/core": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@cachex/core/-/core-1.0.0.tgz",
"integrity": "sha512-JZMGqh2mEAB1JqX2A7yMk6FwGMEoGTjw/Hn5UcqWXUq/Etl2cl8Z6xFUraAN9bv7wX3tTwWol/mh5Ej34to8ng==",
"dependencies": {
"@dzeio/object-util": "^1.8.3"
}
},
"node_modules/@cachex/memory": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@cachex/memory/-/memory-1.0.0.tgz",
"integrity": "sha512-x/oLaj9ZbwCd4Z+3CA20kPd0mRchNFYt2VMz7vlCczWrhX5Lk1b6Z9xUFWMHpjEb2x1G9K194R+vC5qyj4QslQ==",
"dependencies": {
"@cachex/core": "^1"
}
},
"node_modules/@cachex/web-storage": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@cachex/web-storage/-/web-storage-1.0.1.tgz",
"integrity": "sha512-E8Xa9qDZgNgr+lcj3eixowg7PH2CVZbp3huuoc5xVVTtwYrZi5YqbHBG12yG3r6C6Fts/2Yoq6cbVBSm6c8VRA==",
"dependencies": {
"@cachex/core": "^1"
}
},
"node_modules/@dzeio/config": { "node_modules/@dzeio/config": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/@dzeio/config/-/config-1.1.12.tgz", "resolved": "https://registry.npmjs.org/@dzeio/config/-/config-1.1.12.tgz",
@ -10384,6 +10410,30 @@
"integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
"dev": true "dev": true
}, },
"@cachex/core": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@cachex/core/-/core-1.0.0.tgz",
"integrity": "sha512-JZMGqh2mEAB1JqX2A7yMk6FwGMEoGTjw/Hn5UcqWXUq/Etl2cl8Z6xFUraAN9bv7wX3tTwWol/mh5Ej34to8ng==",
"requires": {
"@dzeio/object-util": "^1.8.3"
}
},
"@cachex/memory": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@cachex/memory/-/memory-1.0.0.tgz",
"integrity": "sha512-x/oLaj9ZbwCd4Z+3CA20kPd0mRchNFYt2VMz7vlCczWrhX5Lk1b6Z9xUFWMHpjEb2x1G9K194R+vC5qyj4QslQ==",
"requires": {
"@cachex/core": "^1"
}
},
"@cachex/web-storage": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@cachex/web-storage/-/web-storage-1.0.1.tgz",
"integrity": "sha512-E8Xa9qDZgNgr+lcj3eixowg7PH2CVZbp3huuoc5xVVTtwYrZi5YqbHBG12yG3r6C6Fts/2Yoq6cbVBSm6c8VRA==",
"requires": {
"@cachex/core": "^1"
}
},
"@dzeio/config": { "@dzeio/config": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/@dzeio/config/-/config-1.1.12.tgz", "resolved": "https://registry.npmjs.org/@dzeio/config/-/config-1.1.12.tgz",

View File

@ -1,6 +1,6 @@
{ {
"name": "@tcgdex/sdk", "name": "@tcgdex/sdk",
"version": "2.6.0-beta.3", "version": "2.6.0-beta.4",
"main": "./dist/tcgdex.node.js", "main": "./dist/tcgdex.node.js",
"module": "./dist/tcgdex.node.mjs", "module": "./dist/tcgdex.node.mjs",
"types": "./dist/tcgdex.node.d.ts", "types": "./dist/tcgdex.node.d.ts",
@ -53,6 +53,8 @@
"node": ">=12" "node": ">=12"
}, },
"dependencies": { "dependencies": {
"@cachex/memory": "^1",
"@cachex/web-storage": "^1",
"@dzeio/object-util": "^1", "@dzeio/object-util": "^1",
"isomorphic-unfetch": "^3" "isomorphic-unfetch": "^3"
}, },

View File

@ -1,38 +0,0 @@
import { objectLoop } from '@dzeio/object-util'
import type CacheInterface from './CacheInterface'
export default abstract class CacheAsbract implements CacheInterface {
public getMultiple<T>(keys: Array<string>, defaultValues?: Array<T> | undefined): Record<string, T> {
const res: Record<string, T> = {}
for (let idx = 0; idx < keys.length; idx++) {
const key = keys[idx] as string
const value = this.get(key, defaultValues?.[idx]) as T | undefined
if (typeof value === 'undefined') {
continue
}
res[key] = value
}
return res
}
public setMultiple<T>(values: Record<string, T>, ttl?: number | undefined): boolean {
objectLoop(values, (v, k) => {
this.set(k, v, ttl)
})
return true
}
public deleteMultiple(keys: Array<string>): boolean {
for (const key of keys) {
this.delete(key)
}
return true
}
public abstract get<T>(key: string, defaultValue?: T): T | undefined
public abstract set<T>(key: string, value: T, ttl?: number): boolean
public abstract delete(key: string): boolean
public abstract clear(): boolean
public abstract has(key: string): boolean
}

View File

@ -1,109 +0,0 @@
export default interface CacheInterface {
/**
* Fetches a value from the cache.
*
* @param key The unique key of this item in the cache.
* @param defaultValue Default value to return if the key does not exist.
*
* @return T The value of the item from the cache, or $default in case of cache miss.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
get<T>(key: string, defaultValue?: T): T | undefined
/**
* Persists data in the cache, uniquely referenced by a key with an optional expiration TTL time.
*
* @param key The key of the item to store.
* @param value The value of the item to store. Must be serializable.
* @param {null|number} ttl The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @return bool True on success and false on failure.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
set<T>(key: string, value: T, ttl?: number): boolean
/**
* Delete an item from the cache by its unique key.
*
* @param key The unique cache key of the item to delete.
*
* @return True if the item was successfully removed. False if there was an error.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
delete(key: string): boolean
/**
* Wipes clean the entire cache's keys.
*
* @return boolean True on success and false on failure.
*/
clear(): boolean
/**
* Obtains multiple cache items by their unique keys.
*
* @param keys A list of keys that can obtained in a single operation.
* @param defaultValues $default Default value to return for keys that do not exist.
*
* @return iterable A list of key => value pairs. Cache keys that do not exist or are stale will have $default as value.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if $keys is neither an array nor a Traversable,
* or if any of the $keys are not a legal value.
*/
getMultiple<T>(keys: Array<string>, defaultValues?: Array<T>): Record<string, T>
/**
* Persists a set of key => value pairs in the cache, with an optional TTL.
*
* @param values A list of key => value pairs for a multiple-set operation.
* @param ttl Optional. The TTL value of this item. If no value is sent and
* the driver supports TTL then the library may set a default value
* for it or let the driver take care of that.
*
* @return bool True on success and false on failure.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if $values is neither an array nor a Traversable,
* or if any of the $values are not a legal value.
*/
setMultiple<T>(values: Record<string, T>, ttl?: number): boolean
/**
* Deletes multiple cache items in a single operation.
*
* @param keys A list of string-based keys to be deleted.
*
* @return bool True if the items were successfully removed. False if there was an error.
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if $keys is neither an array nor a Traversable,
* or if any of the $keys are not a legal value.
*/
deleteMultiple(keys: Array<string>): boolean
/**
* Determines whether an item is present in the cache.
*
* NOTE: It is recommended that has() is only to be used for cache warming type purposes
* and not to be used within your live applications operations for get/set, as this method
* is subject to a race condition where your has() will return true and immediately after,
* another script can remove it, making the state of your app out of date.
*
* @param key The cache item key.
*
* @return bool
*
* @throws \Psr\SimpleCache\InvalidArgumentException
* MUST be thrown if the $key string is not a legal value.
*/
has(key: string): boolean
}

View File

@ -1,91 +0,0 @@
import CacheAsbract from './CacheAbstract'
interface CacheItem<T> {
data: T
expire?: number | undefined
}
/**
* A cache implementation that uses browser storage.
*
* This class extends `CacheAsbract` and provides a concrete implementation
* of the caching interface. It stores cached items in browser storage,
* which is suitable for storing small amounts of data.
*/
export default class BrowserStorageCache extends CacheAsbract {
private storage: Storage
public constructor(private readonly prefix?: string, session = false) {
super()
if (session) {
this.storage = window.sessionStorage
} else {
this.storage = window.localStorage
}
}
public get<T>(key: string, defaultValue?: T | undefined): T | undefined {
const raw = this.storage.getItem(this.getFinalKey(key))
if (!raw) {
return defaultValue ?? undefined
}
const item: CacheItem<T> = JSON.parse(raw)
if (item.expire && item.expire < new Date().getTime()) {
this.delete(key)
return defaultValue ?? undefined
}
return item.data
}
public set<T>(key: string, value: T, ttl?: number | undefined): boolean {
let expire = undefined
if (ttl) {
expire = (new Date()).getTime() + (ttl * 1000)
}
const data: CacheItem<unknown> = {
data: value,
expire: expire
}
this.storage.setItem(this.getFinalKey(key), JSON.stringify(data))
return true
}
public delete(key: string): boolean {
this.storage.removeItem(this.getFinalKey(key))
return true
}
public clear(): boolean {
const keys = this.keys()
return this.deleteMultiple(keys)
}
public has(key: string): boolean {
return !!this.storage.getItem(this.getFinalKey(key))
}
private keys(): Array<string> {
const list: Array<string> = []
for (let idx = 0; idx < this.storage.length; idx++) {
const key = this.storage.key(idx)
if (!key || this.prefix && !key?.startsWith(`${this.prefix}/`)) {
continue
}
list.push(key)
}
return list
}
private getFinalKey(key: string): string {
if (!this.prefix) {
return key
}
return `${this.prefix}/${key}`
}
}

View File

@ -1,58 +0,0 @@
import CacheAsbract from './CacheAbstract'
interface CacheItem<T> {
data: T
expire?: number | undefined
}
/**
* Memory cache implementation that stores cached items in memory.
* This class extends the abstract `CacheAbstract` and provides a basic in-memory caching mechanism.
*
* @class MemoryCache
*/
export default class MemoryCache extends CacheAsbract {
private cache: Map<string, CacheItem<unknown>> = new Map()
public get<T>(key: string, defaultValue?: T | undefined): T | undefined {
const item = this.cache.get(key)
if (!item) {
return defaultValue ?? undefined
}
if (item.expire && item.expire < new Date().getTime()) {
this.delete(key)
return defaultValue ?? undefined
}
return item.data as T | undefined
}
public set<T>(key: string, value: T, ttl?: number | undefined): boolean {
let expire: number | undefined
if (ttl) {
expire = new Date().getTime() + ttl * 1000
}
this.cache.set(key, {
data: value,
expire: expire
})
return true
}
public delete(key: string): boolean {
this.cache.delete(key)
return true
}
public clear(): boolean {
this.cache.clear()
return true
}
public has(key: string): boolean {
return this.cache.has(key)
}
}

View File

@ -1,6 +1,6 @@
import type CacheInterface from './Psr/SimpleCache/CacheInterface' import type CacheInterface from '@cachex/core'
import LocalStorageCache from './Psr/SimpleCache/LocalStorageCache' import LocalStorageCache from '@cachex/web-storage'
import MemoryCache from './Psr/SimpleCache/MemoryCache' import MemoryCache from '@cachex/memory'
import Query from './Query' import Query from './Query'
import Endpoint from './endpoints/Endpoint' import Endpoint from './endpoints/Endpoint'
import SimpleEndpoint from './endpoints/SimpleEndpoint' import SimpleEndpoint from './endpoints/SimpleEndpoint'
@ -402,4 +402,3 @@ export * from './models/Card'
export { export {
Query Query
} }