Skip to content

Instantly share code, notes, and snippets.

@nitedani
Last active November 9, 2021 16:41
Show Gist options
  • Save nitedani/bd6b254b38e266e14b2d69bfd14a0a47 to your computer and use it in GitHub Desktop.
Save nitedani/bd6b254b38e266e14b2d69bfd14a0a47 to your computer and use it in GitHub Desktop.
Cache & hot observable/promise sharing decorator
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