Optimized deepCopy & fixed cache object bugs
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@ztimson/utils",
|
"name": "@ztimson/utils",
|
||||||
"version": "0.27.9",
|
"version": "0.27.10",
|
||||||
"description": "Utility library",
|
"description": "Utility library",
|
||||||
"author": "Zak Timson",
|
"author": "Zak Timson",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|||||||
79
src/cache.ts
79
src/cache.ts
@@ -85,6 +85,7 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return <K>(value as any)[this.key];
|
return <K>(value as any)[this.key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Save item to storage */
|
||||||
private save(key?: K) {
|
private save(key?: K) {
|
||||||
const persists: {storage: any, key: string} = <any>this.options.persistentStorage;
|
const persists: {storage: any, key: string} = <any>this.options.persistentStorage;
|
||||||
if(!!persists?.storage) {
|
if(!!persists?.storage) {
|
||||||
@@ -131,16 +132,13 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
const out: CachedValue<T>[] = [];
|
const out: CachedValue<T>[] = [];
|
||||||
for(const v of this.store.values()) {
|
for(const v of this.store.values()) {
|
||||||
const val: any = v;
|
const val: any = v;
|
||||||
if(expired || !val?._expired) out.push(deepCopy<any>(val));
|
if(expired || !val?._expired) out.push(deepCopy(val));
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a new item to the cache. Like set, but finds key automatically
|
* Add a new item to the cache. Like set, but finds key automatically
|
||||||
* @param {T} value Item to add to cache
|
|
||||||
* @param {number | undefined} ttl Override default expiry
|
|
||||||
* @return {this}
|
|
||||||
*/
|
*/
|
||||||
add(value: T, ttl = this.ttl): this {
|
add(value: T, ttl = this.ttl): this {
|
||||||
const key = this.getKey(value);
|
const key = this.getKey(value);
|
||||||
@@ -150,9 +148,6 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Add several rows to the cache
|
* Add several rows to the cache
|
||||||
* @param {T[]} rows Several items that will be cached using the default key
|
|
||||||
* @param complete Mark cache as complete & reliable, defaults to true
|
|
||||||
* @return {this}
|
|
||||||
*/
|
*/
|
||||||
addAll(rows: T[], complete = true): this {
|
addAll(rows: T[], complete = true): this {
|
||||||
this.clear();
|
this.clear();
|
||||||
@@ -161,9 +156,7 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Remove all keys */
|
||||||
* Remove all keys from cache
|
|
||||||
*/
|
|
||||||
clear(): this {
|
clear(): this {
|
||||||
this.complete = false;
|
this.complete = false;
|
||||||
for (const [k, t] of this.timers) clearTimeout(t);
|
for (const [k, t] of this.timers) clearTimeout(t);
|
||||||
@@ -174,10 +167,7 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Delete a cached item */
|
||||||
* Delete an item from the cache
|
|
||||||
* @param {K} key Item's primary key
|
|
||||||
*/
|
|
||||||
delete(key: K): this {
|
delete(key: K): this {
|
||||||
this.clearTimer(key);
|
this.clearTimer(key);
|
||||||
const idx = this.lruOrder.indexOf(key);
|
const idx = this.lruOrder.indexOf(key);
|
||||||
@@ -187,23 +177,17 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Return entries as array */
|
||||||
* Return cache as an array of key-value pairs
|
|
||||||
* @return {[K, T][]} Key-value pairs array
|
|
||||||
*/
|
|
||||||
entries(expired?: boolean): [K, CachedValue<T>][] {
|
entries(expired?: boolean): [K, CachedValue<T>][] {
|
||||||
const out: [K, CachedValue<T>][] = [];
|
const out: [K, CachedValue<T>][] = [];
|
||||||
for(const [k, v] of this.store.entries()) {
|
for(const [k, v] of this.store.entries()) {
|
||||||
const val: any = v;
|
const val: any = v;
|
||||||
if(expired || !val?._expired) out.push([k, deepCopy<any>(val)]);
|
if(expired || !val?._expired) out.push([k, deepCopy(val)]);
|
||||||
}
|
}
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Manually expire a cached item */
|
||||||
* Manually expire a cached item
|
|
||||||
* @param {K} key Key to expire
|
|
||||||
*/
|
|
||||||
expire(key: K): this {
|
expire(key: K): this {
|
||||||
this.complete = false;
|
this.complete = false;
|
||||||
if(this.options.expiryPolicy == 'keep') {
|
if(this.options.expiryPolicy == 'keep') {
|
||||||
@@ -217,39 +201,26 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Find first matching item */
|
||||||
* Find the first cached item to match a filter
|
|
||||||
* @param {Partial<T>} filter Partial item to match
|
|
||||||
* @param {Boolean} expired Include expired items, defaults to false
|
|
||||||
* @returns {T | undefined} Cached item or undefined if nothing matched
|
|
||||||
*/
|
|
||||||
find(filter: Partial<T>, expired?: boolean): T | undefined {
|
find(filter: Partial<T>, expired?: boolean): T | undefined {
|
||||||
for(const v of this.store.values()) {
|
for(const v of this.store.values()) {
|
||||||
const row: any = v;
|
const row: any = v;
|
||||||
if((expired || !row._expired) && includes(row, filter)) return deepCopy<any>(row);
|
if((expired || !row._expired) && includes(row, filter)) return deepCopy(row);
|
||||||
}
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get cached item by key */
|
||||||
* Get item from the cache
|
|
||||||
* @param {K} key Key to lookup
|
|
||||||
* @param expired Include expired items
|
|
||||||
* @return {T} Cached item
|
|
||||||
*/
|
|
||||||
get(key: K, expired?: boolean): CachedValue<T> | null {
|
get(key: K, expired?: boolean): CachedValue<T> | null {
|
||||||
const raw = this.store.get(key);
|
const raw = this.store.get(key);
|
||||||
if(raw == null) return null;
|
if(raw == null) return null;
|
||||||
const cached: any = deepCopy<any>(raw);
|
|
||||||
this.touchLRU(key);
|
this.touchLRU(key);
|
||||||
if(expired || !cached?._expired) return cached;
|
const isExpired = (raw as any)?._expired;
|
||||||
|
if(expired || !isExpired) return deepCopy(raw);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Return list of keys */
|
||||||
* Get a list of cached keys
|
|
||||||
* @return {K[]} Array of keys
|
|
||||||
*/
|
|
||||||
keys(expired?: boolean): K[] {
|
keys(expired?: boolean): K[] {
|
||||||
const out: K[] = [];
|
const out: K[] = [];
|
||||||
for(const [k, v] of this.store.entries()) {
|
for(const [k, v] of this.store.entries()) {
|
||||||
@@ -259,26 +230,17 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Return map of key → item */
|
||||||
* Get map of cached items
|
|
||||||
* @return {Record<K, T>}
|
|
||||||
*/
|
|
||||||
map(expired?: boolean): Record<K, CachedValue<T>> {
|
map(expired?: boolean): Record<K, CachedValue<T>> {
|
||||||
const copy: any = {};
|
const copy: any = {};
|
||||||
for(const [k, v] of this.store.entries()) {
|
for(const [k, v] of this.store.entries()) {
|
||||||
const val: any = v;
|
const val: any = v;
|
||||||
if(expired || !val?._expired) copy[k as any] = deepCopy<any>(val);
|
if(expired || !val?._expired) copy[k as any] = deepCopy(val);
|
||||||
}
|
}
|
||||||
return copy;
|
return copy;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Add item manually specifying the key */
|
||||||
* Add an item to the cache manually specifying the key
|
|
||||||
* @param {K} key Key item will be cached under
|
|
||||||
* @param {T} value Item to cache
|
|
||||||
* @param {number | undefined} ttl Override default expiry in seconds
|
|
||||||
* @return {this}
|
|
||||||
*/
|
|
||||||
set(key: K, value: T, ttl = this.options.ttl): this {
|
set(key: K, value: T, ttl = this.options.ttl): this {
|
||||||
if(this.options.expiryPolicy == 'keep') delete (<any>value)._expired;
|
if(this.options.expiryPolicy == 'keep') delete (<any>value)._expired;
|
||||||
this.clearTimer(key);
|
this.clearTimer(key);
|
||||||
@@ -289,15 +251,12 @@ export class Cache<K extends string | number | symbol, T> {
|
|||||||
const t = setTimeout(() => {
|
const t = setTimeout(() => {
|
||||||
this.expire(key);
|
this.expire(key);
|
||||||
this.save(key);
|
this.save(key);
|
||||||
}, (ttl || 0) * 1000);
|
}, ttl * 1000);
|
||||||
this.timers.set(key, t);
|
this.timers.set(key, t);
|
||||||
}
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Get all cached items */
|
||||||
* Get all cached items
|
values = this.all;
|
||||||
* @return {T[]} Array of items
|
|
||||||
*/
|
|
||||||
values = this.all
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ export function clean<T>(obj: T, undefinedOnly = false): Partial<T> {
|
|||||||
* @returns {T} Type
|
* @returns {T} Type
|
||||||
*/
|
*/
|
||||||
export function deepCopy<T>(value: T): T {
|
export function deepCopy<T>(value: T): T {
|
||||||
|
if(value == null) return value;
|
||||||
|
const t = typeof value;
|
||||||
|
if(t === 'string' || t === 'number' || t === 'boolean' || t === 'function') return value;
|
||||||
try {return structuredClone(value); }
|
try {return structuredClone(value); }
|
||||||
catch { return JSON.parse(JSONSanitize(value)); }
|
catch { return JSON.parse(JSONSanitize(value)); }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user