Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 13 additions & 9 deletions storages/lru-redis/src/LRUWithRedisStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import LRU from 'lru-cache';
import * as Redis from 'ioredis';

export class LRUWithRedisStorage implements IAsynchronousCacheType {
private myCache: LRU<string, any>;
private myCache: LRU<string, unknown>;

/** maxAge and ttl in seconds! */
private options: LRU.Options<string, any>;
private options: LRU.Options<string, unknown>;

constructor(options: LRU.Options<string, any>, private redis: () => Redis.Redis) {
constructor(options: LRU.Options<string, unknown>, private redis: () => Redis.Redis) {
this.options = {
max: 500,
maxAge: 86400,
Expand All @@ -23,15 +23,15 @@ export class LRUWithRedisStorage implements IAsynchronousCacheType {

public async getItem<T>(key: string): Promise<T | undefined> {
// check local cache
let localCache = this.myCache.get(key);
let localCache: unknown = this.myCache.get(key);

if (localCache === undefined) {
// check central cache
localCache = await this.redis().get(key);
const redisValue = await this.redis().get(key);

if (localCache !== undefined) {
if (redisValue !== null) {
try {
localCache = JSON.parse(localCache);
localCache = JSON.parse(redisValue);
} catch (err) {
console.error('lru redis cache failed parsing data', err);
localCache = undefined;
Expand All @@ -41,11 +41,15 @@ export class LRUWithRedisStorage implements IAsynchronousCacheType {
}
}

return localCache ?? undefined;
return localCache as T | undefined;
}

/** ttl in seconds! */
public async setItem(key: string, content: any, options?: { ttl?: number }): Promise<void> {
public async setItem<T = unknown>(
key: string,
content: T | undefined,
options?: { ttl?: number }
): Promise<void> {
this.myCache.set(key, content);
if (this.options?.maxAge) {
await this.redis().setex(key, options?.ttl || this.options.maxAge, JSON.stringify(content));
Expand Down
14 changes: 8 additions & 6 deletions storages/lru/src/LRUStorage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,32 @@ import type { IMultiSynchronousCacheType, ISynchronousCacheType } from '@hokify/
import LRU from 'lru-cache';

export class LRUStorage implements ISynchronousCacheType, IMultiSynchronousCacheType {
myCache: LRU<string, any>;
myCache: LRU<string, unknown>;

constructor(/** maxAge in seconds! */ private options: LRU.Options<string, any>) {
constructor(/** maxAge in seconds! */ private options: LRU.Options<string, unknown>) {
this.myCache = new LRU({
...options,
maxAge: options.maxAge ? options.maxAge * 1000 : undefined
});
}

getItems<T>(keys: string[]): { [key: string]: T | undefined } {
return Object.fromEntries(keys.map(key => [key, this.myCache.get(key)]));
return Object.fromEntries(keys.map(key => [key, this.myCache.get(key)])) as {
[key: string]: T | undefined;
};
}

setItems(values: { key: string; content: any }[]): void {
setItems<T = unknown>(values: { key: string; content: T | undefined }[]): void {
values.forEach(val => {
this.myCache.set(val.key, val.content);
});
}

public getItem<T>(key: string): T | undefined {
return this.myCache.get(key) || undefined;
return this.myCache.get(key) as T | undefined;
}

public setItem(key: string, content: any): void {
public setItem<T = unknown>(key: string, content: T | undefined): void {
this.myCache.set(key, content);
}

Expand Down
4 changes: 2 additions & 2 deletions storages/node-cache/src/node-cache.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ export class NodeCacheStorage implements ISynchronousCacheType, IMultiSynchronou
return this.myCache.mget(keys);
}

setItems(values: { key: string; content: any }[]): void {
setItems<T = unknown>(values: { key: string; content: T | undefined }[]): void {
this.myCache.mset(values.map(v => ({ key: v.key, val: v.content })));
}

public getItem<T>(key: string): T | undefined {
return this.myCache.get(key) || undefined;
}

public setItem(key: string, content: any): void {
public setItem<T = unknown>(key: string, content: T | undefined): void {
this.myCache.set(key, content);
}

Expand Down
31 changes: 18 additions & 13 deletions storages/redis/src/redis.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,27 +15,32 @@ export class RedisStorage implements IAsynchronousCacheType {
this.client = redis.createClient(this.redisOptions) as IRedisClient;
}

public async getItem<T>(key: string): Promise<T> {
const entry: any = await this.client.getAsync(key);
let finalItem = entry;
public async getItem<T>(key: string): Promise<T | undefined> {
const entry: string | null = await this.client.getAsync(key);
if (entry === null) {
return undefined;
}
// Try to parse as JSON, fallback to raw string
let parsedItem: T | string = entry;
try {
finalItem = JSON.parse(entry);
parsedItem = JSON.parse(entry) as T;
} catch (error) {
/** ignore */
/** Not JSON, keep as string */
}
return finalItem || undefined;
return parsedItem as T | undefined;
}

public async setItem(key: string, content: any): Promise<void> {
if (typeof content === 'object') {
content = JSON.stringify(content);
} else if (content === undefined) {
return this.client.delAsync(key);
public async setItem<T = unknown>(key: string, content: T | undefined): Promise<void> {
if (content === undefined) {
await this.client.delAsync(key);
return;
}
return this.client.setAsync(key, content);
const stringContent: string =
typeof content === 'object' ? JSON.stringify(content) : String(content);
await this.client.setAsync(key, stringContent);
}

public async clear(): Promise<void> {
return this.client.flushdbAsync();
await this.client.flushdbAsync();
}
}
67 changes: 40 additions & 27 deletions storages/redisio/src/redisio.storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,37 @@ export class RedisIOStorage implements IAsynchronousCacheType, IMultiIAsynchrono
}

async getItems<T>(keys: string[]): Promise<{ [key: string]: T | undefined }> {
const mget = this.options.compress
? await (this.redis() as any).mgetBuffer(...keys)
const mget: (Buffer | string | null)[] = this.options.compress
? await this.redis().mgetBuffer(...keys)
: await this.redis().mget(...keys);
const res = Object.fromEntries(
await Promise.all(
mget.map(async (entry: Buffer | string, i: number) => {
mget.map(async (entry: Buffer | string | null, i: number) => {
if (entry === null) {
return [keys[i], undefined]; // value does not exist yet
}

if (entry === '') {
return [keys[i], null as any]; // value does exist, but is empty
return [keys[i], null]; // value does exist, but is empty
}

let finalItem: string =
// Decompress if needed, result is a string
const stringValue: string =
entry && this.options.compress
? await this.uncompress(entry as Buffer)
: (entry as string);

// Try to parse as JSON
let parsedItem: T | string = stringValue;
try {
finalItem = finalItem && JSON.parse(finalItem);
if (stringValue) {
parsedItem = JSON.parse(stringValue) as T;
}
} catch (error) {
/** ignore */
/** Not JSON, keep as string */
}

return [keys[i], finalItem];
return [keys[i], parsedItem];
})
)
);
Expand All @@ -61,8 +66,8 @@ export class RedisIOStorage implements IAsynchronousCacheType, IMultiIAsynchrono
return result.toString();
}

async setItems(
values: { key: string; content: any }[],
async setItems<T = unknown>(
values: { key: string; content: T | undefined }[],
options?: { ttl?: number }
): Promise<void> {
const redisPipeline = this.redis().pipeline();
Expand Down Expand Up @@ -95,45 +100,53 @@ export class RedisIOStorage implements IAsynchronousCacheType, IMultiIAsynchrono
}

public async getItem<T>(key: string): Promise<T | undefined> {
const entry = this.options.compress
const entry: Buffer | string | null = this.options.compress
? await this.redis().getBuffer(key)
: await this.redis().get(key);
if (entry === null) {
return undefined;
}
if (entry === '') {
return null as any;
return null as T; // value exists but is empty
}
let finalItem: string =

// Decompress if needed, result is a string
const stringValue: string =
entry && this.options.compress ? await this.uncompress(entry as Buffer) : (entry as string);

// Try to parse as JSON
let parsedItem: T | string = stringValue;
try {
finalItem = JSON.parse(finalItem);
parsedItem = JSON.parse(stringValue) as T;
} catch (error) {
/** ignore */
/** Not JSON, keep as string */
}
return finalItem as unknown as T;
return parsedItem as T;
}

public async setItem(key: string, content: unknown, options?: { ttl?: number }): Promise<void> {
if (typeof content === 'object') {
content = JSON.stringify(content);
} else if (content === undefined) {
public async setItem<T = unknown>(
key: string,
content: T | undefined,
options?: { ttl?: number }
): Promise<void> {
if (content === undefined) {
await this.redis().del(key);
return;
}

// Serialize to string, then optionally compress
let serialized: string | Buffer =
typeof content === 'object' ? JSON.stringify(content) : String(content);

if (this.options.compress) {
content = await this.compress(content as string);
serialized = await this.compress(serialized);
}

const ttl = options?.ttl ?? this.options.maxAge;
let savePromise: Promise<any>;
if (ttl) {
savePromise = this.redis().setex(key, ttl, content as Buffer | string);
} else {
savePromise = this.redis().set(key, content as Buffer | string);
}
const savePromise: Promise<'OK' | null> = ttl
? this.redis().setex(key, ttl, serialized)
: this.redis().set(key, serialized);

if (this.errorHandler) {
// if we have an error handler, we do not need to await the result
savePromise.catch(err => this.errorHandler && this.errorHandler(err));
Expand Down
2 changes: 1 addition & 1 deletion storages/redisio/test/redis.storage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ describe('RedisIOStorage', () => {
});

it('Mutli Should set and retrieve item correclty', async () => {
await storage.setItems([
await storage.setItems<{ asdf: number } | string>([
{ key: 'test', content: { asdf: 2 } },
{ key: 'test2', content: '2' }
]);
Expand Down
14 changes: 9 additions & 5 deletions ts-cache/src/decorator/cache.decorator.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
import { JSONStringifyKeyStrategy } from '../strategy/key/json.stringify.strategy.js';
import { IAsyncKeyStrategy } from '../types/key.strategy.types.js';
import { IAsynchronousCacheType, ISynchronousCacheType } from '../types/cache.types.js';
import {
IAsynchronousCacheType,
ISynchronousCacheType,
ICacheOptions
} from '../types/cache.types.js';

const defaultKeyStrategy = new JSONStringifyKeyStrategy();

export function Cache(
cachingStrategy: IAsynchronousCacheType | ISynchronousCacheType,
options?: any,
options?: ICacheOptions,
keyStrategy: IAsyncKeyStrategy = defaultKeyStrategy
) {
return function (

Check warning on line 16 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Unexpected unnamed function
// eslint-disable-next-line @typescript-eslint/ban-types
target: Object & {
__cache_decarator_pending_results?: {
[key: string]: Promise<any> | undefined;
[key: string]: Promise<unknown> | undefined;
};
},
methodName: string,
Expand All @@ -22,7 +26,7 @@
const originalMethod = descriptor.value;
const className = target.constructor.name;

descriptor.value = async function (...args: any[]) {
descriptor.value = async function (...args: unknown[]) {

Check warning on line 29 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Unexpected unnamed async function

Check warning on line 29 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Assignment to property of function parameter 'descriptor'
const cacheKey = await keyStrategy.getKey(className, methodName, args);

const runMethod = async () => {
Expand All @@ -47,14 +51,14 @@
}

if (!target.__cache_decarator_pending_results) {
target.__cache_decarator_pending_results = {};

Check warning on line 54 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Assignment to property of function parameter 'target'
}

if (!target.__cache_decarator_pending_results[cacheKey]) {
target.__cache_decarator_pending_results[cacheKey] = (async () => {

Check warning on line 58 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Assignment to property of function parameter 'target'
try {
try {
const entry = await (cachingStrategy.getItem as any)(cacheKey);
const entry = await cachingStrategy.getItem<unknown>(cacheKey);
if (entry !== undefined) {
return entry;
}
Expand All @@ -72,7 +76,7 @@
return methodResult;
} finally {
// reset pending result object
target.__cache_decarator_pending_results![cacheKey] = undefined;

Check warning on line 79 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Assignment to property of function parameter 'target'

Check warning on line 79 in ts-cache/src/decorator/cache.decorator.ts

View workflow job for this annotation

GitHub Actions / Lint & Format

Forbidden non-null assertion
}
})();
}
Expand Down
Loading