mirror of
https://github.com/shishantbiswas/bknd.git
synced 2026-03-17 04:46:05 +00:00
public commit
This commit is contained in:
127
app/src/core/cache/adapters/CloudflareKvCache.ts
vendored
Normal file
127
app/src/core/cache/adapters/CloudflareKvCache.ts
vendored
Normal file
@@ -0,0 +1,127 @@
|
||||
import type { ICacheItem, ICachePool } from "../cache-interface";
|
||||
|
||||
export class CloudflareKVCachePool<Data = any> implements ICachePool<Data> {
|
||||
constructor(private namespace: KVNamespace) {}
|
||||
|
||||
supports = () => ({
|
||||
metadata: true,
|
||||
clear: false,
|
||||
});
|
||||
|
||||
async get(key: string): Promise<ICacheItem<Data>> {
|
||||
const result = await this.namespace.getWithMetadata<any>(key);
|
||||
const hit = result.value !== null && typeof result.value !== "undefined";
|
||||
// Assuming metadata is not supported directly;
|
||||
// you may adjust if Cloudflare KV supports it in future.
|
||||
return new CloudflareKVCacheItem(key, result.value ?? undefined, hit, result.metadata) as any;
|
||||
}
|
||||
|
||||
async getMany(keys: string[] = []): Promise<Map<string, ICacheItem<Data>>> {
|
||||
const items = new Map<string, ICacheItem<Data>>();
|
||||
await Promise.all(
|
||||
keys.map(async (key) => {
|
||||
const item = await this.get(key);
|
||||
items.set(key, item);
|
||||
}),
|
||||
);
|
||||
return items;
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
const data = await this.namespace.get(key);
|
||||
return data !== null;
|
||||
}
|
||||
|
||||
async clear(): Promise<boolean> {
|
||||
// Cloudflare KV does not support clearing all keys in one operation
|
||||
return false;
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<boolean> {
|
||||
await this.namespace.delete(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteMany(keys: string[]): Promise<boolean> {
|
||||
const results = await Promise.all(keys.map((key) => this.delete(key)));
|
||||
return results.every((result) => result);
|
||||
}
|
||||
|
||||
async save(item: CloudflareKVCacheItem<Data>): Promise<boolean> {
|
||||
await this.namespace.put(item.key(), (await item.value()) as string, {
|
||||
expirationTtl: item._expirationTtl,
|
||||
metadata: item.metadata(),
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async put(
|
||||
key: string,
|
||||
value: any,
|
||||
options?: { ttl?: number; expiresAt?: Date; metadata?: Record<string, string> },
|
||||
): Promise<boolean> {
|
||||
const item = new CloudflareKVCacheItem(key, value, true, options?.metadata);
|
||||
|
||||
if (options?.expiresAt) item.expiresAt(options.expiresAt);
|
||||
if (options?.ttl) item.expiresAfter(options.ttl);
|
||||
|
||||
return await this.save(item);
|
||||
}
|
||||
}
|
||||
|
||||
export class CloudflareKVCacheItem<Data = any> implements ICacheItem<Data> {
|
||||
_expirationTtl: number | undefined;
|
||||
|
||||
constructor(
|
||||
private _key: string,
|
||||
private data: Data | undefined,
|
||||
private _hit: boolean = false,
|
||||
private _metadata: Record<string, string> = {},
|
||||
) {}
|
||||
|
||||
key(): string {
|
||||
return this._key;
|
||||
}
|
||||
|
||||
value(): Data | undefined {
|
||||
if (this.data) {
|
||||
try {
|
||||
return JSON.parse(this.data as string);
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
return this.data ?? undefined;
|
||||
}
|
||||
|
||||
metadata(): Record<string, string> {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
hit(): boolean {
|
||||
return this._hit;
|
||||
}
|
||||
|
||||
set(value: Data, metadata: Record<string, string> = {}): this {
|
||||
this.data = value;
|
||||
this._metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
expiresAt(expiration: Date | null): this {
|
||||
// Cloudflare KV does not support specific date expiration; calculate ttl instead.
|
||||
if (expiration) {
|
||||
const now = new Date();
|
||||
const ttl = (expiration.getTime() - now.getTime()) / 1000;
|
||||
return this.expiresAfter(Math.max(0, Math.floor(ttl)));
|
||||
}
|
||||
return this.expiresAfter(null);
|
||||
}
|
||||
|
||||
expiresAfter(time: number | null): this {
|
||||
// Dummy implementation as Cloudflare KV requires setting expiration during PUT operation.
|
||||
// This method will be effectively implemented in the Cache Pool save methods.
|
||||
this._expirationTtl = time ?? undefined;
|
||||
return this;
|
||||
}
|
||||
}
|
||||
139
app/src/core/cache/adapters/MemoryCache.ts
vendored
Normal file
139
app/src/core/cache/adapters/MemoryCache.ts
vendored
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { ICacheItem, ICachePool } from "../cache-interface";
|
||||
|
||||
export class MemoryCache<Data = any> implements ICachePool<Data> {
|
||||
private cache: Map<string, MemoryCacheItem<Data>> = new Map();
|
||||
private maxSize?: number;
|
||||
|
||||
constructor(options?: { maxSize?: number }) {
|
||||
this.maxSize = options?.maxSize;
|
||||
}
|
||||
|
||||
supports = () => ({
|
||||
metadata: true,
|
||||
clear: true
|
||||
});
|
||||
|
||||
async get(key: string): Promise<MemoryCacheItem<Data>> {
|
||||
if (!this.cache.has(key)) {
|
||||
// use undefined to denote a miss initially
|
||||
return new MemoryCacheItem<Data>(key, undefined!);
|
||||
}
|
||||
return this.cache.get(key)!;
|
||||
}
|
||||
|
||||
async getMany(keys: string[] = []): Promise<Map<string, MemoryCacheItem<Data>>> {
|
||||
const items = new Map<string, MemoryCacheItem<Data>>();
|
||||
for (const key of keys) {
|
||||
items.set(key, await this.get(key));
|
||||
}
|
||||
return items;
|
||||
}
|
||||
|
||||
async has(key: string): Promise<boolean> {
|
||||
return this.cache.has(key) && this.cache.get(key)!.hit();
|
||||
}
|
||||
|
||||
async clear(): Promise<boolean> {
|
||||
this.cache.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
async delete(key: string): Promise<boolean> {
|
||||
return this.cache.delete(key);
|
||||
}
|
||||
|
||||
async deleteMany(keys: string[]): Promise<boolean> {
|
||||
let success = true;
|
||||
for (const key of keys) {
|
||||
if (!this.delete(key)) {
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
||||
async save(item: MemoryCacheItem<Data>): Promise<boolean> {
|
||||
this.checkSizeAndPurge();
|
||||
this.cache.set(item.key(), item);
|
||||
return true;
|
||||
}
|
||||
|
||||
async put(
|
||||
key: string,
|
||||
value: Data,
|
||||
options: { expiresAt?: Date; ttl?: number; metadata?: Record<string, string> } = {}
|
||||
): Promise<boolean> {
|
||||
const item = await this.get(key);
|
||||
item.set(value, options.metadata || {});
|
||||
if (options.expiresAt) {
|
||||
item.expiresAt(options.expiresAt);
|
||||
} else if (typeof options.ttl === "number") {
|
||||
item.expiresAfter(options.ttl);
|
||||
}
|
||||
return this.save(item);
|
||||
}
|
||||
|
||||
private checkSizeAndPurge(): void {
|
||||
if (!this.maxSize) return;
|
||||
|
||||
if (this.cache.size >= this.maxSize) {
|
||||
// Implement logic to purge items, e.g., LRU (Least Recently Used)
|
||||
// For simplicity, clear the oldest item inserted
|
||||
const keyToDelete = this.cache.keys().next().value;
|
||||
this.cache.delete(keyToDelete!);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class MemoryCacheItem<Data = any> implements ICacheItem<Data> {
|
||||
private _key: string;
|
||||
private _value: Data | undefined;
|
||||
private expiration: Date | null = null;
|
||||
private _metadata: Record<string, string> = {};
|
||||
|
||||
constructor(key: string, value: Data, metadata: Record<string, string> = {}) {
|
||||
this._key = key;
|
||||
this.set(value, metadata);
|
||||
}
|
||||
|
||||
key(): string {
|
||||
return this._key;
|
||||
}
|
||||
|
||||
metadata(): Record<string, string> {
|
||||
return this._metadata;
|
||||
}
|
||||
|
||||
value(): Data | undefined {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
hit(): boolean {
|
||||
if (this.expiration !== null && new Date() > this.expiration) {
|
||||
return false;
|
||||
}
|
||||
return this.value() !== undefined;
|
||||
}
|
||||
|
||||
set(value: Data, metadata: Record<string, string> = {}): this {
|
||||
this._value = value;
|
||||
this._metadata = metadata;
|
||||
return this;
|
||||
}
|
||||
|
||||
expiresAt(expiration: Date | null): this {
|
||||
this.expiration = expiration;
|
||||
return this;
|
||||
}
|
||||
|
||||
expiresAfter(time: number | null): this {
|
||||
if (typeof time === "number") {
|
||||
const expirationDate = new Date();
|
||||
expirationDate.setSeconds(expirationDate.getSeconds() + time);
|
||||
this.expiration = expirationDate;
|
||||
} else {
|
||||
this.expiration = null;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
178
app/src/core/cache/cache-interface.ts
vendored
Normal file
178
app/src/core/cache/cache-interface.ts
vendored
Normal file
@@ -0,0 +1,178 @@
|
||||
/**
|
||||
* CacheItem defines an interface for interacting with objects inside a cache.
|
||||
* based on https://www.php-fig.org/psr/psr-6/
|
||||
*/
|
||||
export interface ICacheItem<Data = any> {
|
||||
/**
|
||||
* Returns the key for the current cache item.
|
||||
*
|
||||
* The key is loaded by the Implementing Library, but should be available to
|
||||
* the higher level callers when needed.
|
||||
*
|
||||
* @returns The key string for this cache item.
|
||||
*/
|
||||
key(): string;
|
||||
|
||||
/**
|
||||
* Retrieves the value of the item from the cache associated with this object's key.
|
||||
*
|
||||
* The value returned must be identical to the value originally stored by set().
|
||||
*
|
||||
* If isHit() returns false, this method MUST return null. Note that null
|
||||
* is a legitimate cached value, so the isHit() method SHOULD be used to
|
||||
* differentiate between "null value was found" and "no value was found."
|
||||
*
|
||||
* @returns The value corresponding to this cache item's key, or undefined if not found.
|
||||
*/
|
||||
value(): Data | undefined;
|
||||
|
||||
/**
|
||||
* Retrieves the metadata of the item from the cache associated with this object's key.
|
||||
*/
|
||||
metadata(): Record<string, string>;
|
||||
|
||||
/**
|
||||
* Confirms if the cache item lookup resulted in a cache hit.
|
||||
*
|
||||
* Note: This method MUST NOT have a race condition between calling isHit()
|
||||
* and calling get().
|
||||
*
|
||||
* @returns True if the request resulted in a cache hit. False otherwise.
|
||||
*/
|
||||
hit(): boolean;
|
||||
|
||||
/**
|
||||
* Sets the value represented by this cache item.
|
||||
*
|
||||
* The value argument may be any item that can be serialized by PHP,
|
||||
* although the method of serialization is left up to the Implementing
|
||||
* Library.
|
||||
*
|
||||
* @param value The serializable value to be stored.
|
||||
* @param metadata The metadata to be associated with the item.
|
||||
* @returns The invoked object.
|
||||
*/
|
||||
set(value: Data, metadata?: Record<string, string>): this;
|
||||
|
||||
/**
|
||||
* Sets the expiration time for this cache item.
|
||||
*
|
||||
* @param expiration The point in time after which the item MUST be considered expired.
|
||||
* If null is passed explicitly, a default value MAY be used. If none is set,
|
||||
* the value should be stored permanently or for as long as the
|
||||
* implementation allows.
|
||||
* @returns The called object.
|
||||
*/
|
||||
expiresAt(expiration: Date | null): this;
|
||||
|
||||
/**
|
||||
* Sets the expiration time for this cache item.
|
||||
*
|
||||
* @param time The period of time from the present after which the item MUST be considered
|
||||
* expired. An integer parameter is understood to be the time in seconds until
|
||||
* expiration. If null is passed explicitly, a default value MAY be used.
|
||||
* If none is set, the value should be stored permanently or for as long as the
|
||||
* implementation allows.
|
||||
* @returns The called object.
|
||||
*/
|
||||
expiresAfter(time: number | null): this;
|
||||
}
|
||||
|
||||
/**
|
||||
* CachePool generates CacheItem objects.
|
||||
* based on https://www.php-fig.org/psr/psr-6/
|
||||
*/
|
||||
export interface ICachePool<Data = any> {
|
||||
supports(): {
|
||||
metadata: boolean;
|
||||
clear: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a Cache Item representing the specified key.
|
||||
* This method must always return a CacheItemInterface object, even in case of
|
||||
* a cache miss. It MUST NOT return null.
|
||||
*
|
||||
* @param key The key for which to return the corresponding Cache Item.
|
||||
* @throws Error If the key string is not a legal value an Error MUST be thrown.
|
||||
* @returns The corresponding Cache Item.
|
||||
*/
|
||||
get(key: string): Promise<ICacheItem<Data>>;
|
||||
|
||||
/**
|
||||
* Returns a traversable set of cache items.
|
||||
*
|
||||
* @param keys An indexed array of keys of items to retrieve.
|
||||
* @throws Error If any of the keys in keys are not a legal value an Error MUST be thrown.
|
||||
* @returns A traversable collection of Cache Items keyed by the cache keys of
|
||||
* each item. A Cache item will be returned for each key, even if that
|
||||
* key is not found. However, if no keys are specified then an empty
|
||||
* traversable MUST be returned instead.
|
||||
*/
|
||||
getMany(keys?: string[]): Promise<Map<string, ICacheItem<Data>>>;
|
||||
|
||||
/**
|
||||
* Confirms if the cache contains specified cache item.
|
||||
*
|
||||
* Note: This method MAY avoid retrieving the cached value for performance reasons.
|
||||
* This could result in a race condition with CacheItemInterface.get(). To avoid
|
||||
* such situation use CacheItemInterface.isHit() instead.
|
||||
*
|
||||
* @param key The key for which to check existence.
|
||||
* @throws Error If the key string is not a legal value an Error MUST be thrown.
|
||||
* @returns True if item exists in the cache, false otherwise.
|
||||
*/
|
||||
has(key: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Deletes all items in the pool.
|
||||
* @returns True if the pool was successfully cleared. False if there was an error.
|
||||
*/
|
||||
clear(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Removes the item from the pool.
|
||||
*
|
||||
* @param key The key to delete.
|
||||
* @throws Error If the key string is not a legal value an Error MUST be thrown.
|
||||
* @returns True if the item was successfully removed. False if there was an error.
|
||||
*/
|
||||
delete(key: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Removes multiple items from the pool.
|
||||
*
|
||||
* @param keys An array of keys that should be removed from the pool.
|
||||
* @throws Error If any of the keys in keys are not a legal value an Error MUST be thrown.
|
||||
* @returns True if the items were successfully removed. False if there was an error.
|
||||
*/
|
||||
deleteMany(keys: string[]): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Persists a cache item immediately.
|
||||
*
|
||||
* @param item The cache item to save.
|
||||
* @returns True if the item was successfully persisted. False if there was an error.
|
||||
*/
|
||||
save(item: ICacheItem<Data>): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Persists any deferred cache items.
|
||||
* @returns True if all not-yet-saved items were successfully saved or there were none. False otherwise.
|
||||
*/
|
||||
put(
|
||||
key: string,
|
||||
value: any,
|
||||
options?: { expiresAt?: Date; metadata?: Record<string, string> },
|
||||
): Promise<boolean>;
|
||||
put(
|
||||
key: string,
|
||||
value: any,
|
||||
options?: { ttl?: number; metadata?: Record<string, string> },
|
||||
): Promise<boolean>;
|
||||
put(
|
||||
key: string,
|
||||
value: any,
|
||||
options?: ({ ttl?: number } | { expiresAt?: Date }) & { metadata?: Record<string, string> },
|
||||
): Promise<boolean>;
|
||||
}
|
||||
Reference in New Issue
Block a user