Last active
November 9, 2021 16:41
-
-
Save nitedani/bd6b254b38e266e14b2d69bfd14a0a47 to your computer and use it in GitHub Desktop.
Cache & hot observable/promise sharing decorator
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
import 'reflect-metadata'; | |
import * as LRU from 'lru-cache'; | |
import { from, isObservable } from 'rxjs'; | |
import { finalize, shareReplay, tap } from 'rxjs/operators'; | |
import { Md5 } from 'ts-md5/dist/md5'; | |
export const aroundMethodDecarator = ( | |
decoratorFn: ( | |
args: any[], | |
name: string, | |
next: (..._args: any[]) => any, | |
) => any, | |
) => { | |
return (_target: any, _key: string, descriptor: PropertyDescriptor) => { | |
const originalMethod = descriptor.value; | |
const keys = Reflect.getOwnMetadataKeys(descriptor.value); | |
const metadata = keys.map((key) => ({ | |
key, | |
value: Reflect.getOwnMetadata(key, descriptor.value), | |
})); | |
descriptor.value = function (...args: any[]) { | |
return decoratorFn(args, _key, originalMethod.bind(this)); | |
}; | |
metadata.forEach(({ key, value }) => | |
Reflect.defineMetadata(key, value, descriptor.value), | |
); | |
}; | |
}; | |
export function unique(text: string) { | |
return Md5.hashStr(text) | |
.replace(/[^A-Za-z]/g, '') | |
.toUpperCase() | |
.substring(0, 6); | |
} | |
export type CacheOptions = { | |
days?: number; | |
hours?: number; | |
minutes?: number; | |
seconds?: number; | |
}; | |
export const Cache = (options?: CacheOptions) => { | |
let _seconds = 0; | |
if (options) { | |
if (options.days) { | |
_seconds += options.days * 86400; | |
} | |
if (options.hours) { | |
_seconds += options.hours * 3600; | |
} | |
if (options.minutes) { | |
_seconds += options.minutes * 60; | |
} | |
if (options.seconds) { | |
_seconds += options.seconds; | |
} | |
} | |
return aroundMethodDecarator((args: any[], name, next) => { | |
const key = unique(JSON.stringify(args) + name); | |
if (tempCache[key]) { | |
return tempCache[key]; | |
} | |
const cached = cache.get(key); | |
if (cached) { | |
if (cached.__rx) { | |
return from(cached.__rx); | |
} else if (cached.__promise) { | |
return Promise.resolve(cached.__promise); | |
} else { | |
return cached; | |
} | |
} | |
const result = next(...args); | |
if (result?.subscribe) { | |
const results: any[] = []; | |
tempCache[key] = result.pipe(shareReplay(1)); | |
return (tempCache[key] as Observable<any>).pipe( | |
tap((value) => results.push(value)), | |
finalize(() => { | |
cache.set(key, { __rx: results }, _seconds * 1000); | |
delete tempCache[key]; | |
}), | |
); | |
} else if (result?.then) { | |
tempCache[key] = result; | |
return tempCache[key].then((value) => { | |
cache.set(key, { __promise: value }, _seconds * 1000); | |
delete tempCache[key]; | |
return value; | |
}); | |
} else { | |
cache.set(key, result, _seconds * 1000); | |
return result; | |
} | |
}); | |
}; | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment