-
Notifications
You must be signed in to change notification settings - Fork 41
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
9919c04
commit 5b28d60
Showing
6 changed files
with
134 additions
and
34 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,36 +1,116 @@ | ||
/** | ||
* @description Simple data key/value store | ||
* @description Typed key-value store with expiration support using Map | ||
*/ | ||
const store = {}; | ||
|
||
export const saveValue = (key: string, value: {}, exp: Date | number): void => { | ||
if (!key || !value) { | ||
throw Error("key and value are required"); | ||
interface StoreEntry<T> { | ||
value: T; | ||
exp: number; | ||
} | ||
|
||
class KeyValueStore { | ||
private store: Map<string, StoreEntry<any>>; | ||
private cleanupInterval: ReturnType<typeof setInterval>; | ||
|
||
constructor(cleanupIntervalMs: number = 60 * 1000) { | ||
this.store = new Map(); | ||
this.cleanupInterval = setInterval(() => this.clearExpiredKeys(), cleanupIntervalMs); | ||
} | ||
|
||
const expMs = exp instanceof Date ? exp.getTime() : exp; | ||
store[key] = { ...value, exp: expMs }; | ||
}; | ||
/** | ||
* Saves a value in the store with expiration | ||
* @throws {ValidationError} If key or value is invalid | ||
*/ | ||
public save<T>(key: string, value: T, exp: Date | number): void { | ||
if (!key?.trim()) { | ||
throw new Error("Key must be a non-empty string"); | ||
} | ||
|
||
export const getValue = (key: string): unknown => store[key]; | ||
if (value === undefined || value === null) { | ||
throw new Error("Value cannot be null or undefined"); | ||
} | ||
|
||
const expMs = exp instanceof Date ? exp.getTime() : exp; | ||
|
||
if (typeof expMs !== "number" || expMs <= Date.now()) { | ||
throw new Error("Expiration must be a future timestamp or Date"); | ||
} | ||
|
||
export const clearValue = (key: string): void => { | ||
if (store[key]) { | ||
delete store[key]; | ||
this.store.set(key, { value, exp: expMs }); | ||
} | ||
}; | ||
|
||
export const clearExpiredKeys = (): void => { | ||
const now = new Date(); | ||
const nowMs = now.getTime(); | ||
Object.keys(store).forEach((key) => { | ||
// key is expired | ||
if (store?.[key]?.exp && nowMs > store?.[key]?.exp) { | ||
clearValue(key); | ||
/** | ||
* Retrieves a value from the store | ||
* @returns The value if found and not expired, undefined otherwise | ||
*/ | ||
public get<T>(key: string): T | undefined { | ||
const entry = this.store.get(key); | ||
if (!entry) return undefined; | ||
|
||
if (Date.now() > entry.exp) { | ||
this.clear(key); | ||
return undefined; | ||
} | ||
}); | ||
}; | ||
|
||
setInterval(clearExpiredKeys, 1000 * 60); | ||
return entry.value as T; | ||
} | ||
|
||
/** | ||
* Removes a value from the store | ||
* @returns boolean indicating if the value was cleared | ||
*/ | ||
public clear(key: string): boolean { | ||
return this.store.delete(key); | ||
} | ||
|
||
/** | ||
* Clears all expired keys from the store | ||
* @returns number of cleared keys | ||
*/ | ||
public clearExpiredKeys(): number { | ||
const now = Date.now(); | ||
let cleared = 0; | ||
|
||
for (const [key, entry] of this.store) { | ||
if (entry.exp <= now) { | ||
this.store.delete(key); | ||
cleared++; | ||
} | ||
} | ||
|
||
return cleared; | ||
} | ||
|
||
/** | ||
* Returns all non-expired values in the store | ||
*/ | ||
public getAll<T>(): Map<string, T> { | ||
const now = Date.now(); | ||
const result = new Map<string, T>(); | ||
|
||
for (const [key, entry] of this.store) { | ||
if (entry.exp > now) { | ||
result.set(key, entry.value); | ||
} | ||
} | ||
|
||
return result; | ||
} | ||
|
||
/** | ||
* Returns the number of entries in the store | ||
*/ | ||
public size(): number { | ||
return this.store.size; | ||
} | ||
|
||
/** | ||
* Stops the cleanup interval and clears all entries | ||
*/ | ||
public dispose(): void { | ||
clearInterval(this.cleanupInterval); | ||
this.store.clear(); | ||
} | ||
} | ||
|
||
export const getAllValues = (): any => store; | ||
// Export a singleton instance | ||
export const kvStore = new KeyValueStore(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters