From 83d3f7775b727de0c459f18fc499bfc118f7b5eb Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Mon, 15 Aug 2022 18:33:49 +0300 Subject: [PATCH 01/12] add new features --- src/core/cache/CHANGELOG.md | 10 ++ .../helpers/add-emitter/CHANGELOG.md | 6 + .../decorators/helpers/add-emitter/index.ts | 6 +- .../helpers/add-emitter/interface.ts | 10 +- .../cache/decorators/persistent/CHANGELOG.md | 6 + .../decorators/persistent/engines/active.ts | 2 +- .../persistent/engines/interface.ts | 2 +- src/core/cache/decorators/persistent/index.ts | 5 +- .../cache/decorators/persistent/interface.ts | 4 +- .../cache/decorators/persistent/wrapper.ts | 8 +- src/core/cache/decorators/ttl/CHANGELOG.md | 10 ++ src/core/cache/decorators/ttl/index.ts | 12 +- src/core/cache/decorators/ttl/interface.ts | 6 +- src/core/cache/default/CHANGELOG.md | 16 +++ src/core/cache/default/README.md | 21 +++ src/core/cache/default/index.ts | 47 +++++++ src/core/cache/default/spec.js | 129 ++++++++++++++++++ src/core/cache/interface.ts | 9 +- src/core/cache/never/CHANGELOG.md | 6 + src/core/cache/never/index.ts | 7 +- src/core/cache/never/spec.js | 15 ++ src/core/cache/restricted/CHANGELOG.md | 6 + src/core/cache/restricted/index.ts | 12 +- src/core/cache/restricted/spec.js | 21 +++ src/core/cache/simple/CHANGELOG.md | 6 + src/core/cache/simple/index.ts | 15 +- src/core/cache/simple/spec.js | 20 +++ 27 files changed, 386 insertions(+), 31 deletions(-) create mode 100644 src/core/cache/default/CHANGELOG.md create mode 100644 src/core/cache/default/README.md create mode 100644 src/core/cache/default/index.ts create mode 100644 src/core/cache/default/spec.js diff --git a/src/core/cache/CHANGELOG.md b/src/core/cache/CHANGELOG.md index 1c114e8db..06871dc55 100644 --- a/src/core/cache/CHANGELOG.md +++ b/src/core/cache/CHANGELOG.md @@ -9,6 +9,16 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :rocket: New Feature + +* Add a new method `clone` + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.50.0 (2021-06-07) #### :rocket: New Feature diff --git a/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md b/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md index 288ed22e4..5e95d7a6b 100644 --- a/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md +++ b/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.59.1 (2021-09-21) #### :bug: Bug Fix diff --git a/src/core/cache/decorators/helpers/add-emitter/index.ts b/src/core/cache/decorators/helpers/add-emitter/index.ts index 6269653cf..b175a0651 100644 --- a/src/core/cache/decorators/helpers/add-emitter/index.ts +++ b/src/core/cache/decorators/helpers/add-emitter/index.ts @@ -40,12 +40,12 @@ export const * * @param cache */ -const addEmitter: AddEmitter = , V = unknown, K extends string = string>(cache) => { +const addEmitter: AddEmitter = , K = unknown, V = unknown>(cache: T) => { const - expandedCache = , {[eventEmitter]?: EventEmitter}>>cache; + expandedCache = , {[eventEmitter]?: EventEmitter}>>cache; const - cacheWithEmitter = >expandedCache; + cacheWithEmitter = >expandedCache; let emitter; diff --git a/src/core/cache/decorators/helpers/add-emitter/interface.ts b/src/core/cache/decorators/helpers/add-emitter/interface.ts index 01f938e4e..bb8d3dd08 100644 --- a/src/core/cache/decorators/helpers/add-emitter/interface.ts +++ b/src/core/cache/decorators/helpers/add-emitter/interface.ts @@ -25,7 +25,11 @@ export interface MutationHandler { (e: MutationEvent): void; } -export interface CacheWithEmitter = Cache> extends Cache { +export interface CacheWithEmitter< + K = unknown, + V = unknown, + T extends Cache = Cache + > extends Cache { /** @override */ set(key: K, value: V, opts?: Parameters[2]): V; @@ -36,9 +40,9 @@ export interface CacheWithEmitter } export type AddEmitter = - , V = unknown, K extends string = string>(cache: T) => AddEmitterReturn; + , K = unknown, V = unknown>(cache: T) => AddEmitterReturn; -export interface AddEmitterReturn, V = unknown, K extends string = string> { +export interface AddEmitterReturn, K = unknown, V = unknown> { /** @see [[Cache.set]] */ set: T['set']; diff --git a/src/core/cache/decorators/persistent/CHANGELOG.md b/src/core/cache/decorators/persistent/CHANGELOG.md index d9e2f441f..bd6bc5017 100644 --- a/src/core/cache/decorators/persistent/CHANGELOG.md +++ b/src/core/cache/decorators/persistent/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.60.2 (2021-10-04) #### :bug: Bug Fix diff --git a/src/core/cache/decorators/persistent/engines/active.ts b/src/core/cache/decorators/persistent/engines/active.ts index 4d6b0e061..067b0d4a9 100644 --- a/src/core/cache/decorators/persistent/engines/active.ts +++ b/src/core/cache/decorators/persistent/engines/active.ts @@ -18,7 +18,7 @@ export default class ActivePersistentEngine extends UncheckablePersistentEngi */ protected ttlIndex: Dictionary = Object.createDict(); - override async initCache(cache: Cache): Promise { + override async initCache(cache: Cache): Promise { if (await this.storage.has(INDEX_STORAGE_NAME)) { this.ttlIndex = (await this.storage.get>(INDEX_STORAGE_NAME))!; diff --git a/src/core/cache/decorators/persistent/engines/interface.ts b/src/core/cache/decorators/persistent/engines/interface.ts index f86097abd..fe7809d8f 100644 --- a/src/core/cache/decorators/persistent/engines/interface.ts +++ b/src/core/cache/decorators/persistent/engines/interface.ts @@ -16,7 +16,7 @@ export interface AbstractPersistentEngine { * Initializes a new cache instance from the past one * @param cache */ - initCache?(cache: Cache): CanPromise; + initCache?(cache: Cache): CanPromise; } export abstract class AbstractPersistentEngine { diff --git a/src/core/cache/decorators/persistent/index.ts b/src/core/cache/decorators/persistent/index.ts index f9db60dd7..bf79e1c78 100644 --- a/src/core/cache/decorators/persistent/index.ts +++ b/src/core/cache/decorators/persistent/index.ts @@ -50,9 +50,10 @@ export * from 'core/cache/decorators/persistent/interface'; * ``` */ const addPersistent = ( - cache: Cache, + cache: Cache, storage: SyncStorageNamespace | AsyncStorageNamespace, opts?: PersistentOptions -): Promise> => new PersistentWrapper, V>(cache, storage, opts).getInstance(); +): Promise> => + new PersistentWrapper, V>(cache, storage, opts).getInstance(); export default addPersistent; diff --git a/src/core/cache/decorators/persistent/interface.ts b/src/core/cache/decorators/persistent/interface.ts index 79f4bada6..07d13357c 100644 --- a/src/core/cache/decorators/persistent/interface.ts +++ b/src/core/cache/decorators/persistent/interface.ts @@ -9,8 +9,8 @@ import type { CacheWithEmitter } from 'core/cache/decorators/helpers/add-emitter/interface'; import type { eventEmitter } from 'core/cache/decorators/helpers/add-emitter'; -export type PersistentCache = CacheWithEmitter> = { - [key in Exclude<(keyof CacheWithEmitter), 'set' | 'size' | typeof eventEmitter>]: ReturnPromise[key]> +export type PersistentCache = CacheWithEmitter> = { + [key in Exclude<(keyof CacheWithEmitter), 'set' | 'size' | typeof eventEmitter>]: ReturnPromise[key]> } & { /** @see [[Cache.size]] */ size: [T['size']]; diff --git a/src/core/cache/decorators/persistent/wrapper.ts b/src/core/cache/decorators/persistent/wrapper.ts index d0a214c6e..20df3d3bd 100644 --- a/src/core/cache/decorators/persistent/wrapper.ts +++ b/src/core/cache/decorators/persistent/wrapper.ts @@ -18,7 +18,7 @@ import addEmitter from 'core/cache/decorators/helpers/add-emitter'; import type { PersistentEngine, CheckablePersistentEngine } from 'core/cache/decorators/persistent/engines/interface'; import type { PersistentOptions, PersistentCache, PersistentTTLDecoratorOptions } from 'core/cache/decorators/persistent/interface'; -export default class PersistentWrapper, V = unknown> { +export default class PersistentWrapper, V = unknown> { /** * Default TTL to store items */ @@ -32,7 +32,7 @@ export default class PersistentWrapper, V = unknown> /** * Wrapped cache object */ - protected readonly wrappedCache: PersistentCache; + protected readonly wrappedCache: PersistentCache; /** * Engine to save cache items within a storage @@ -61,7 +61,7 @@ export default class PersistentWrapper, V = unknown> /** * Returns an instance of the wrapped cache */ - async getInstance(): Promise> { + async getInstance(): Promise> { if (this.engine.initCache) { await this.engine.initCache(this.cache); } @@ -80,7 +80,7 @@ export default class PersistentWrapper, V = unknown> set: originalSet, clear: originalClear, subscribe - } = addEmitter(this.cache); + } = addEmitter(this.cache); this.wrappedCache.has = this.getDefaultImplementation('has'); this.wrappedCache.get = this.getDefaultImplementation('get'); diff --git a/src/core/cache/decorators/ttl/CHANGELOG.md b/src/core/cache/decorators/ttl/CHANGELOG.md index 97a9316e2..161e62835 100644 --- a/src/core/cache/decorators/ttl/CHANGELOG.md +++ b/src/core/cache/decorators/ttl/CHANGELOG.md @@ -9,6 +9,16 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :bug: Bug Fix + +* Fixed TS type inferring + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.47.0 (2021-05-17) #### :bug: Bug Fix diff --git a/src/core/cache/decorators/ttl/index.ts b/src/core/cache/decorators/ttl/index.ts index 123d1c871..21e99e552 100644 --- a/src/core/cache/decorators/ttl/index.ts +++ b/src/core/cache/decorators/ttl/index.ts @@ -22,8 +22,8 @@ export * from 'core/cache/decorators/ttl/interface'; /** * Wraps the specified cache object to add a feature of the cache expiring * - * @typeparam V - value type of the cache object * @typeparam K - key type of the cache object + * @typeparam V - value type of the cache object * * @param cache - cache object to wrap * @param ttl - default ttl value in milliseconds @@ -41,20 +41,20 @@ export * from 'core/cache/decorators/ttl/interface'; * ``` */ export default function addTTL< - T extends Cache, + T extends Cache, + K = unknown, V = unknown, - K extends string = string, ->(cache: T, ttl?: number): TTLCache> { +>(cache: Cache, ttl?: number): TTLCache> { // eslint-disable-next-line @typescript-eslint/unbound-method const { remove: originalRemove, set: originalSet, clear: originalClear, subscribe - } = addEmitter, V, K>(>cache); + } = addEmitter, K, V>(>cache); const - cacheWithTTL: TTLCache = Object.create(cache), + cacheWithTTL: TTLCache = Object.create(cache), ttlTimers = new Map(); cacheWithTTL.set = (key: K, value: V, opts?: TTLDecoratorOptions & Parameters[2]) => { diff --git a/src/core/cache/decorators/ttl/interface.ts b/src/core/cache/decorators/ttl/interface.ts index 0d7e4c95b..a49925636 100644 --- a/src/core/cache/decorators/ttl/interface.ts +++ b/src/core/cache/decorators/ttl/interface.ts @@ -9,10 +9,10 @@ import type { CacheWithEmitter } from 'core/cache/decorators/helpers/add-emitter/interface'; export interface TTLCache< + K = unknown, V = unknown, - K = string, - T extends CacheWithEmitter = CacheWithEmitter -> extends CacheWithEmitter { + T extends CacheWithEmitter = CacheWithEmitter +> extends CacheWithEmitter { /** * Saves a value to the cache by the specified key * diff --git a/src/core/cache/default/CHANGELOG.md b/src/core/cache/default/CHANGELOG.md new file mode 100644 index 000000000..26e302c16 --- /dev/null +++ b/src/core/cache/default/CHANGELOG.md @@ -0,0 +1,16 @@ +Changelog +========= + +> **Tags:** +> - :boom: [Breaking Change] +> - :rocket: [New Feature] +> - :bug: [Bug Fix] +> - :memo: [Documentation] +> - :house: [Internal] +> - :nail_care: [Polish] + +## v3.??.? (2022-0?-??) + +#### :rocket: New Feature + +* Initial release diff --git a/src/core/cache/default/README.md b/src/core/cache/default/README.md new file mode 100644 index 000000000..8a5805e90 --- /dev/null +++ b/src/core/cache/default/README.md @@ -0,0 +1,21 @@ +# core/cache/default + +This module provides a class for a [[Cache]] data structure with support for adding default value via the passed factory +when a non-existent key is accessed. + +```js +import DefaultCache from 'core/cache/default'; + +const + cache = new DefaultCache(Array); + +cache.get('key') + +console.log(cache.keys().length); // 1 +console.log(Object.isArray(cache.get('key'))); // true + +``` + +## API + +See [[Cache]]. diff --git a/src/core/cache/default/index.ts b/src/core/cache/default/index.ts new file mode 100644 index 000000000..a5daafa41 --- /dev/null +++ b/src/core/cache/default/index.ts @@ -0,0 +1,47 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +/** + * [[include:core/cache/simple/README.md]] + * @packageDocumentation + */ + +import SimpleCache from 'core/cache/simple'; + +export * from 'core/cache/interface'; + +/** + * Implementation for a simple in-memory cache data structure + * + * @typeparam K - key type + * @typeparam V - value type + */ +export default class DefaultCache extends SimpleCache { + /** + * Function that returns a value which would be set as default value + */ + defaultFactory: () => V; + + /** + * @param [defaultFactory] - function that returns a value which would be set as default value + */ + constructor(defaultFactory: () => V) { + super(); + + this.defaultFactory = defaultFactory; + } + + /** @see [[Cache.get]] */ + override get(key: K): CanUndef { + if (!this.storage.has(key)) { + this.storage.set(key, this.defaultFactory()); + } + + return this.storage.get(key); + } +} diff --git a/src/core/cache/default/spec.js b/src/core/cache/default/spec.js new file mode 100644 index 000000000..1e7c3fe13 --- /dev/null +++ b/src/core/cache/default/spec.js @@ -0,0 +1,129 @@ +/*! + * V4Fire Core + * https://github.com/V4Fire/Core + * + * Released under the MIT license + * https://github.com/V4Fire/Core/blob/master/LICENSE + */ + +import DefaultCache from 'core/cache/default'; + +describe('core/cache/simple', () => { + it('default value', () => { + const + cache = new DefaultCache(Array); + + expect(cache.has('foo')).toBeFalse(); + + expect(cache.get('foo')).toEqual([]); + expect(cache.size).toBe(1); + + cache.defaultFactory = () => 10; + + expect(cache.get('bla')).toEqual(10); + expect(cache.size).toBe(2); + + expect(cache.set('bar', 10)).toBe(10); + expect(cache.size).toBe(3); + }); + + it('crud', () => { + const + cache = new DefaultCache(); + + expect(cache.has('foo')).toBeFalse(); + expect(cache.set('foo', 1)).toBe(1); + expect(cache.get('foo')).toBe(1); + expect(cache.has('foo')).toBeTrue(); + expect(cache.size).toBe(1); + expect(cache.remove('foo')).toBe(1); + expect(cache.has('foo')).toBeFalse(); + }); + + it('default iterator', () => { + const + cache = new DefaultCache(); + + cache.set('1', 1); + cache.set('2', 2); + + expect(cache[Symbol.iterator]().next()).toEqual({value: '1', done: false}); + expect([...cache]).toEqual(['1', '2']); + }); + + it('`keys`', () => { + const + cache = new DefaultCache(); + + cache.set('1', 1); + cache.set('2', 2); + + expect([...cache.keys()]).toEqual(['1', '2']); + }); + + it('`values`', () => { + const + cache = new DefaultCache(); + + cache.set('1', 1); + cache.set('2', 2); + + expect([...cache.values()]).toEqual([1, 2]); + }); + + it('`entries`', () => { + const + cache = new DefaultCache(); + + cache.set('1', 1); + cache.set('2', 2); + + expect([...cache.entries()]).toEqual([['1', 1], ['2', 2]]); + }); + + it('`clear`', () => { + const + cache = new DefaultCache(); + + cache.set('foo', 1); + cache.set('bar', 2); + + expect(cache.has('foo')).toBeTrue(); + expect(cache.has('bar')).toBeTrue(); + + expect(cache.clear()).toEqual(new Map([['foo', 1], ['bar', 2]])); + }); + + it('`clear` with a filter', () => { + const + cache = new DefaultCache(); + + cache.set('foo', 1); + cache.set('bar', 2); + + expect(cache.has('foo')).toBeTrue(); + expect(cache.has('bar')).toBeTrue(); + + expect(cache.clear((el) => el > 1)).toEqual(new Map([['bar', 2]])); + }); + + it('`clones`', () => { + const + cache = new DefaultCache(), + obj = {a: 1}; + + cache.set('foo', 1); + cache.set('bar', obj); + + expect(cache.has('foo')).toBeTrue(); + expect(cache.has('bar')).toBeTrue(); + + const newCache = cache.clone(); + + expect(cache !== newCache).toBeTrue(); + expect(newCache.has('foo')).toBeTrue(); + expect(newCache.has('bar')).toBeTrue(); + expect(cache.storage !== newCache.storage).toBeTrue(); + expect(cache.get('bla') === newCache.get('bla')).toBeTrue(); + }); +}); diff --git a/src/core/cache/interface.ts b/src/core/cache/interface.ts index 9f3d27ba8..8762d6bbe 100644 --- a/src/core/cache/interface.ts +++ b/src/core/cache/interface.ts @@ -13,10 +13,10 @@ export interface ClearFilter { /** * Base interface for a cache data structure * + * @typeparam K - key type * @typeparam V - value type - * @typeparam K - key type (`string` by default) */ -export default interface Cache { +export default interface Cache { /** * Number of elements within the cache */ @@ -55,6 +55,11 @@ export default interface Cache { */ clear(filter?: ClearFilter): Map; + /** + * Clones the cache and returns a cloned one + */ + clone(): Cache; + /** * Returns an iterator by the cache keys */ diff --git a/src/core/cache/never/CHANGELOG.md b/src/core/cache/never/CHANGELOG.md index 5dde52bda..f329fedd0 100644 --- a/src/core/cache/never/CHANGELOG.md +++ b/src/core/cache/never/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.50.0 (2021-06-07) #### :rocket: New Feature diff --git a/src/core/cache/never/index.ts b/src/core/cache/never/index.ts index 148b9fe31..6beaccda6 100644 --- a/src/core/cache/never/index.ts +++ b/src/core/cache/never/index.ts @@ -21,7 +21,7 @@ export * from 'core/cache/interface'; /** * Loopback class for a cache data structure */ -export default class NeverCache implements Cache { +export default class NeverCache implements Cache { /** @see [[Cache.size]] */ get size(): number { return this.storage.size; @@ -75,4 +75,9 @@ export default class NeverCache implements Cache { clear(filter?: ClearFilter): Map { return new Map(); } + + /** @see [[Cache.clone]] */ + clone(): NeverCache { + return new NeverCache(); + } } diff --git a/src/core/cache/never/spec.js b/src/core/cache/never/spec.js index 78755078e..af480c328 100644 --- a/src/core/cache/never/spec.js +++ b/src/core/cache/never/spec.js @@ -78,4 +78,19 @@ describe('core/cache/never', () => { expect(cache.clear((el) => el > 1)).toEqual(new Map([])); }); + + it('`clones`', () => { + const + cache = new NeverCache(); + + cache.set('foo', 1); + expect(cache.has('foo')).toBeFalse(); + + const + newCache = cache.clone(); + + expect(cache !== newCache).toBeTrue(); + expect(newCache.has('foo')).toBeFalse(); + expect(cache.storage !== newCache.storage).toBeTrue(); + }); }); diff --git a/src/core/cache/restricted/CHANGELOG.md b/src/core/cache/restricted/CHANGELOG.md index 095519926..899b9794c 100644 --- a/src/core/cache/restricted/CHANGELOG.md +++ b/src/core/cache/restricted/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.xx.x (2021-06-01) #### :rocket: New Feature diff --git a/src/core/cache/restricted/index.ts b/src/core/cache/restricted/index.ts index 96482391a..29d159d88 100644 --- a/src/core/cache/restricted/index.ts +++ b/src/core/cache/restricted/index.ts @@ -21,7 +21,7 @@ export * from 'core/cache/simple'; * @typeparam V - value type * @typeparam K - key type (`string` by default) */ -export default class RestrictedCache extends SimpleCache { +export default class RestrictedCache extends SimpleCache { /** * Queue object */ @@ -92,6 +92,16 @@ export default class RestrictedCache extends SimpleCach return removed; } + override clone(): RestrictedCache { + const + newCache = new RestrictedCache(this.capacity), + mixin = {queue: new Set(this.queue), storage: new Map(this.storage)}; + + Object.assign(newCache, mixin); + + return newCache; + } + /** * Sets a new capacity of the cache. * The method returns a map of truncated elements that the cache can't fit anymore. diff --git a/src/core/cache/restricted/spec.js b/src/core/cache/restricted/spec.js index c9f5e1c8c..97ea37862 100644 --- a/src/core/cache/restricted/spec.js +++ b/src/core/cache/restricted/spec.js @@ -124,4 +124,25 @@ describe('core/cache/restricted', () => { expect(cache.has('foo')).toBe(false); expect(cache.has('bar')).toBe(false); }); + + it('`clones`', () => { + const + cache = new RestrictedCache(), + obj = {a: 1}; + + cache.set('foo', 1); + cache.set('bar', obj); + + expect(cache.has('foo')).toBeTrue(); + expect(cache.has('bar')).toBeTrue(); + + const newCache = cache.clone(); + + expect(cache !== newCache).toBeTrue(); + expect(newCache.has('foo')).toBeTrue(); + expect(newCache.has('bar')).toBeTrue(); + expect(cache.storage !== newCache.storage).toBeTrue(); + expect(cache.queue !== newCache.queue).toBeTrue(); + expect(cache.get('bar') === newCache.get('bar')).toBeTrue(); + }); }); diff --git a/src/core/cache/simple/CHANGELOG.md b/src/core/cache/simple/CHANGELOG.md index ea3c6ac4f..2ee4af74a 100644 --- a/src/core/cache/simple/CHANGELOG.md +++ b/src/core/cache/simple/CHANGELOG.md @@ -9,6 +9,12 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] +## v3.??.? (2022-0?-??) + +#### :boom: Breaking Change + +* Change type parameters from `` to `` + ## v3.20.0 (2020-07-05) #### :boom: Breaking Change diff --git a/src/core/cache/simple/index.ts b/src/core/cache/simple/index.ts index 775246c09..279689e52 100644 --- a/src/core/cache/simple/index.ts +++ b/src/core/cache/simple/index.ts @@ -19,10 +19,10 @@ export * from 'core/cache/interface'; /** * Implementation for a simple in-memory cache data structure * + * @typeparam K - key type * @typeparam V - value type - * @typeparam K - key type (`string` by default) */ -export default class SimpleCache implements Cache { +export default class SimpleCache implements Cache { /** @see [[Cache.size]] */ get size(): number { return this.storage.size; @@ -102,4 +102,15 @@ export default class SimpleCache implements Cache this.storage.clear(); return removed; } + + /** @see [[Cache.clone]] */ + clone(): SimpleCache { + const + newCache = new SimpleCache(), + mixin = {storage: new Map(this.storage)}; + + Object.assign(newCache, mixin); + + return newCache; + } } diff --git a/src/core/cache/simple/spec.js b/src/core/cache/simple/spec.js index b68232428..77df1f631 100644 --- a/src/core/cache/simple/spec.js +++ b/src/core/cache/simple/spec.js @@ -84,4 +84,24 @@ describe('core/cache/simple', () => { expect(cache.clear((el) => el > 1)).toEqual(new Map([['bar', 2]])); }); + + it('`clones`', () => { + const + cache = new SimpleCache(), + obj = {a: 1}; + + cache.set('foo', 1); + cache.set('bar', obj); + + expect(cache.has('foo')).toBeTrue(); + expect(cache.has('bar')).toBeTrue(); + + const newCache = cache.clone(); + + expect(cache !== newCache).toBeTrue(); + expect(newCache.has('foo')).toBeTrue(); + expect(newCache.has('bar')).toBeTrue(); + expect(cache.storage !== newCache.storage).toBeTrue(); + expect(cache.get('bar') === newCache.get('bar')).toBeTrue(); + }); }); From 274e7bc0273dc8042c968d00ca4bee7d2bb0df0a Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Mon, 15 Aug 2022 18:57:11 +0300 Subject: [PATCH 02/12] fixes --- src/core/cache/default/index.ts | 13 ++++++++++++- src/core/cache/default/spec.js | 4 ++-- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/core/cache/default/index.ts b/src/core/cache/default/index.ts index a5daafa41..ac6dea1c4 100644 --- a/src/core/cache/default/index.ts +++ b/src/core/cache/default/index.ts @@ -7,7 +7,7 @@ */ /** - * [[include:core/cache/simple/README.md]] + * [[include:core/cache/default/README.md]] * @packageDocumentation */ @@ -44,4 +44,15 @@ export default class DefaultCache extends SimpleCache< return this.storage.get(key); } + + /** @see [[Cache.clone]] */ + override clone(): DefaultCache { + const + newCache = new DefaultCache(this.defaultFactory), + mixin = {storage: new Map(this.storage)}; + + Object.assign(newCache, mixin); + + return newCache; + } } diff --git a/src/core/cache/default/spec.js b/src/core/cache/default/spec.js index 1e7c3fe13..abc495618 100644 --- a/src/core/cache/default/spec.js +++ b/src/core/cache/default/spec.js @@ -8,7 +8,7 @@ import DefaultCache from 'core/cache/default'; -describe('core/cache/simple', () => { +describe('core/cache/default', () => { it('default value', () => { const cache = new DefaultCache(Array); @@ -109,7 +109,7 @@ describe('core/cache/simple', () => { it('`clones`', () => { const - cache = new DefaultCache(), + cache = new DefaultCache(() => 10), obj = {a: 1}; cache.set('foo', 1); From bdefb9bb5459f64b872d0f7f2f57625faafed820 Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Mon, 15 Aug 2022 19:12:01 +0300 Subject: [PATCH 03/12] fixes --- src/core/request/modules/context/modules/params.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core/request/modules/context/modules/params.ts b/src/core/request/modules/context/modules/params.ts index 13d0243ee..0b881d7d1 100644 --- a/src/core/request/modules/context/modules/params.ts +++ b/src/core/request/modules/context/modules/params.ts @@ -73,12 +73,12 @@ export default class RequestContext { /** * Storage to cache the resolved request */ - readonly cache!: AbstractCache>; + readonly cache!: AbstractCache>; /** * Storage to cache the request while it is pending a response */ - readonly pendingCache: AbstractCache< + readonly pendingCache: AbstractCache> | RequestResponse > = Object.cast(pendingCache); From fa74d837c9655bd4bf70e9bdf63876c66ea55e8a Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Tue, 23 Aug 2022 12:14:18 +0300 Subject: [PATCH 04/12] doc --- src/core/cache/CHANGELOG.md | 13 +++++++---- src/core/cache/README.md | 26 ++++++++++++++++++++++ src/core/cache/decorators/ttl/CHANGELOG.md | 2 +- src/core/cache/never/CHANGELOG.md | 4 ++++ src/core/cache/restricted/CHANGELOG.md | 4 ++++ src/core/cache/simple/CHANGELOG.md | 4 ++++ 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/src/core/cache/CHANGELOG.md b/src/core/cache/CHANGELOG.md index 06871dc55..d1cade5bb 100644 --- a/src/core/cache/CHANGELOG.md +++ b/src/core/cache/CHANGELOG.md @@ -9,15 +9,20 @@ Changelog > - :house: [Internal] > - :nail_care: [Polish] -## v3.??.? (2022-0?-??) +## v3.??.0 (2022-0?-??) + +#### :boom: Breaking Change +* Changed typed parameters of `Cache` interface from `` to `` +* Change a default `Cache` interface key type #### :rocket: New Feature -* Add a new method `clone` +* Added new cache `DefaultCache` +* Added a new method `clone` -#### :boom: Breaking Change +#### :bug: Bug Fix -* Change type parameters from `` to `` +* Fixed type inference in decorator ## v3.50.0 (2021-06-07) diff --git a/src/core/cache/README.md b/src/core/cache/README.md index 3cf3445fd..b7113e080 100644 --- a/src/core/cache/README.md +++ b/src/core/cache/README.md @@ -227,3 +227,29 @@ cache.clear(); console.log(cache.has('foo1')); // true ``` + +### clone + +Makes a new copy of the current cache and returns it. + +```js +import SimpleCache from 'core/cache/simple'; + +const + cache = new SimpleCache(), + obj = {foo: 'bar'}; + +cache.set('foo1', 'bar1'); +cache.set('foo2', obj); + +const + clonedCache = cache.clone(); + +console.log(cache !== clonedCache); // true + +console.log(clonedCache.has('foo1')); // true +console.log(clonedCache.has('foo2')); // true +console.log(clonedCache.has('foo5')); // false + +console.log(cache.get('foo2') === clonedCache.get('foo2')); // true +``` diff --git a/src/core/cache/decorators/ttl/CHANGELOG.md b/src/core/cache/decorators/ttl/CHANGELOG.md index 161e62835..de45cb774 100644 --- a/src/core/cache/decorators/ttl/CHANGELOG.md +++ b/src/core/cache/decorators/ttl/CHANGELOG.md @@ -13,7 +13,7 @@ Changelog #### :bug: Bug Fix -* Fixed TS type inferring +* Fixed type inference #### :boom: Breaking Change diff --git a/src/core/cache/never/CHANGELOG.md b/src/core/cache/never/CHANGELOG.md index f329fedd0..06dc24f45 100644 --- a/src/core/cache/never/CHANGELOG.md +++ b/src/core/cache/never/CHANGELOG.md @@ -15,6 +15,10 @@ Changelog * Change type parameters from `` to `` +#### :rocket: New Feature + +* Added a new `clone` method + ## v3.50.0 (2021-06-07) #### :rocket: New Feature diff --git a/src/core/cache/restricted/CHANGELOG.md b/src/core/cache/restricted/CHANGELOG.md index 899b9794c..e801887c0 100644 --- a/src/core/cache/restricted/CHANGELOG.md +++ b/src/core/cache/restricted/CHANGELOG.md @@ -15,6 +15,10 @@ Changelog * Change type parameters from `` to `` +#### :rocket: New Feature + +* Added a new `clone` method + ## v3.xx.x (2021-06-01) #### :rocket: New Feature diff --git a/src/core/cache/simple/CHANGELOG.md b/src/core/cache/simple/CHANGELOG.md index 2ee4af74a..e59fc3ef4 100644 --- a/src/core/cache/simple/CHANGELOG.md +++ b/src/core/cache/simple/CHANGELOG.md @@ -15,6 +15,10 @@ Changelog * Change type parameters from `` to `` +#### :rocket: New Feature + +* Added a new `clone` method + ## v3.20.0 (2020-07-05) #### :boom: Breaking Change From d19ca5eff21c97b49b4a4ab9202afe77f3b7dfb6 Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Tue, 23 Aug 2022 13:51:50 +0300 Subject: [PATCH 05/12] fix tests --- src/core/cache/default/spec.js | 30 +++++++++++++++--------------- src/core/cache/restricted/spec.js | 16 ++++++++-------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/core/cache/default/spec.js b/src/core/cache/default/spec.js index abc495618..5f532400c 100644 --- a/src/core/cache/default/spec.js +++ b/src/core/cache/default/spec.js @@ -13,7 +13,7 @@ describe('core/cache/default', () => { const cache = new DefaultCache(Array); - expect(cache.has('foo')).toBeFalse(); + expect(cache.has('foo')).toBe(false); expect(cache.get('foo')).toEqual([]); expect(cache.size).toBe(1); @@ -31,13 +31,13 @@ describe('core/cache/default', () => { const cache = new DefaultCache(); - expect(cache.has('foo')).toBeFalse(); + expect(cache.has('foo')).toBe(false); expect(cache.set('foo', 1)).toBe(1); expect(cache.get('foo')).toBe(1); - expect(cache.has('foo')).toBeTrue(); + expect(cache.has('foo')).toBe(true); expect(cache.size).toBe(1); expect(cache.remove('foo')).toBe(1); - expect(cache.has('foo')).toBeFalse(); + expect(cache.has('foo')).toBe(false); }); it('default iterator', () => { @@ -88,8 +88,8 @@ describe('core/cache/default', () => { cache.set('foo', 1); cache.set('bar', 2); - expect(cache.has('foo')).toBeTrue(); - expect(cache.has('bar')).toBeTrue(); + expect(cache.has('foo')).toBe(true); + expect(cache.has('bar')).toBe(true); expect(cache.clear()).toEqual(new Map([['foo', 1], ['bar', 2]])); }); @@ -101,8 +101,8 @@ describe('core/cache/default', () => { cache.set('foo', 1); cache.set('bar', 2); - expect(cache.has('foo')).toBeTrue(); - expect(cache.has('bar')).toBeTrue(); + expect(cache.has('foo')).toBe(true); + expect(cache.has('bar')).toBe(true); expect(cache.clear((el) => el > 1)).toEqual(new Map([['bar', 2]])); }); @@ -115,15 +115,15 @@ describe('core/cache/default', () => { cache.set('foo', 1); cache.set('bar', obj); - expect(cache.has('foo')).toBeTrue(); - expect(cache.has('bar')).toBeTrue(); + expect(cache.has('foo')).toBe(true); + expect(cache.has('bar')).toBe(true); const newCache = cache.clone(); - expect(cache !== newCache).toBeTrue(); - expect(newCache.has('foo')).toBeTrue(); - expect(newCache.has('bar')).toBeTrue(); - expect(cache.storage !== newCache.storage).toBeTrue(); - expect(cache.get('bla') === newCache.get('bla')).toBeTrue(); + expect(cache !== newCache).toBe(true); + expect(newCache.has('foo')).toBe(true); + expect(newCache.has('bar')).toBe(true); + expect(cache.storage !== newCache.storage).toBe(true); + expect(cache.get('bla') === newCache.get('bla')).toBe(true); }); }); diff --git a/src/core/cache/restricted/spec.js b/src/core/cache/restricted/spec.js index 97ea37862..afc52dcf3 100644 --- a/src/core/cache/restricted/spec.js +++ b/src/core/cache/restricted/spec.js @@ -133,16 +133,16 @@ describe('core/cache/restricted', () => { cache.set('foo', 1); cache.set('bar', obj); - expect(cache.has('foo')).toBeTrue(); - expect(cache.has('bar')).toBeTrue(); + expect(cache.has('foo')).toBe(true); + expect(cache.has('bar')).toBe(true); const newCache = cache.clone(); - expect(cache !== newCache).toBeTrue(); - expect(newCache.has('foo')).toBeTrue(); - expect(newCache.has('bar')).toBeTrue(); - expect(cache.storage !== newCache.storage).toBeTrue(); - expect(cache.queue !== newCache.queue).toBeTrue(); - expect(cache.get('bar') === newCache.get('bar')).toBeTrue(); + expect(cache !== newCache).toBe(true); + expect(newCache.has('foo')).toBe(true); + expect(newCache.has('bar')).toBe(true); + expect(cache.storage !== newCache.storage).toBe(true); + expect(cache.queue !== newCache.queue).toBe(true); + expect(cache.get('bar') === newCache.get('bar')).toBe(true); }); }); From d2a31df10d65d155a7eca660c1bd73b7f55a536c Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Tue, 23 Aug 2022 22:48:19 +0300 Subject: [PATCH 06/12] fix tests --- src/core/cache/never/spec.js | 8 ++++---- src/core/cache/simple/spec.js | 14 +++++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/core/cache/never/spec.js b/src/core/cache/never/spec.js index af480c328..fb1d9b5aa 100644 --- a/src/core/cache/never/spec.js +++ b/src/core/cache/never/spec.js @@ -84,13 +84,13 @@ describe('core/cache/never', () => { cache = new NeverCache(); cache.set('foo', 1); - expect(cache.has('foo')).toBeFalse(); + expect(cache.has('foo')).toBe(false); const newCache = cache.clone(); - expect(cache !== newCache).toBeTrue(); - expect(newCache.has('foo')).toBeFalse(); - expect(cache.storage !== newCache.storage).toBeTrue(); + expect(cache !== newCache).toBe(true); + expect(newCache.has('foo')).toBe(false); + expect(cache.storage !== newCache.storage).toBe(true); }); }); diff --git a/src/core/cache/simple/spec.js b/src/core/cache/simple/spec.js index 77df1f631..fa54b9d69 100644 --- a/src/core/cache/simple/spec.js +++ b/src/core/cache/simple/spec.js @@ -93,15 +93,15 @@ describe('core/cache/simple', () => { cache.set('foo', 1); cache.set('bar', obj); - expect(cache.has('foo')).toBeTrue(); - expect(cache.has('bar')).toBeTrue(); + expect(cache.has('foo')).toBe(true); + expect(cache.has('bar')).toBe(true); const newCache = cache.clone(); - expect(cache !== newCache).toBeTrue(); - expect(newCache.has('foo')).toBeTrue(); - expect(newCache.has('bar')).toBeTrue(); - expect(cache.storage !== newCache.storage).toBeTrue(); - expect(cache.get('bar') === newCache.get('bar')).toBeTrue(); + expect(cache !== newCache).toBe(true); + expect(newCache.has('foo')).toBe(true); + expect(newCache.has('bar')).toBe(true); + expect(cache.storage !== newCache.storage).toBe(true); + expect(cache.get('bar') === newCache.get('bar')).toBe(true); }); }); From 19fa0f28f99d5156dc0b8741afd86ad7b882675b Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Mon, 19 Sep 2022 18:44:13 +0300 Subject: [PATCH 07/12] add clone processing in addTTL --- .../helpers/add-emitter/CHANGELOG.md | 4 + .../decorators/helpers/add-emitter/index.ts | 24 +++- .../helpers/add-emitter/interface.ts | 6 +- src/core/cache/decorators/ttl/CHANGELOG.md | 4 + src/core/cache/decorators/ttl/index.ts | 114 +++++++++++++----- src/core/cache/decorators/ttl/interface.ts | 5 + src/core/cache/decorators/ttl/spec.js | 24 +++- 7 files changed, 149 insertions(+), 32 deletions(-) diff --git a/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md b/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md index 5e95d7a6b..49a6c9415 100644 --- a/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md +++ b/src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md @@ -15,6 +15,10 @@ Changelog * Change type parameters from `` to `` +#### :rocket: New Feature + +* Added a new method `clone` processing + ## v3.59.1 (2021-09-21) #### :bug: Bug Fix diff --git a/src/core/cache/decorators/helpers/add-emitter/index.ts b/src/core/cache/decorators/helpers/add-emitter/index.ts index b175a0651..bc30e0235 100644 --- a/src/core/cache/decorators/helpers/add-emitter/index.ts +++ b/src/core/cache/decorators/helpers/add-emitter/index.ts @@ -66,7 +66,10 @@ const addEmitter: AddEmitter = , K = unknown, V = unknown> originalRemove = cacheWithEmitter.remove, // eslint-disable-next-line @typescript-eslint/unbound-method - originalClear = cacheWithEmitter.clear; + originalClear = cacheWithEmitter.clear, + + // eslint-disable-next-line @typescript-eslint/unbound-method + originalClone = cacheWithEmitter.clone; if (originalSet[eventEmitter] == null) { cacheWithEmitter[$$.set] = originalSet; @@ -131,10 +134,29 @@ const addEmitter: AddEmitter = , K = unknown, V = unknown> originalClear = cacheWithEmitter[$$.clear] ?? originalClear; } + if (originalClone[eventEmitter] == null) { + cacheWithEmitter[$$.clone] = originalClone; + + cacheWithEmitter.clone = function clone(): Cache { + const + result = originalClone.call(this); + + emitter.emit('clone', cacheWithEmitter, {result}); + + return result; + }; + + cacheWithEmitter.clone[eventEmitter] = true; + + } else { + originalClone = cacheWithEmitter[$$.clone] ?? originalClone; + } + return >{ set: originalSet.bind(cacheWithEmitter), remove: originalRemove.bind(cacheWithEmitter), clear: originalClear.bind(cacheWithEmitter), + clone: originalClone.bind(cacheWithEmitter), subscribe: ((method, obj, cb): void => { emitter.on(method, handler); diff --git a/src/core/cache/decorators/helpers/add-emitter/interface.ts b/src/core/cache/decorators/helpers/add-emitter/interface.ts index bb8d3dd08..9a862312f 100644 --- a/src/core/cache/decorators/helpers/add-emitter/interface.ts +++ b/src/core/cache/decorators/helpers/add-emitter/interface.ts @@ -14,7 +14,8 @@ import type Cache from 'core/cache/interface'; export type MethodsToWrap = 'set' | 'remove' | - 'clear'; + 'clear' | + 'clone'; export interface MutationEvent { args: Parameters; @@ -52,6 +53,9 @@ export interface AddEmitterReturn, K = unknown, V = unknow /** @see [[Cache.clear]] */ clear: T['clear']; + /** @see [[Cache.clone]] */ + clone: T['clone']; + /** * Subscribes for mutations of the specified cache object * diff --git a/src/core/cache/decorators/ttl/CHANGELOG.md b/src/core/cache/decorators/ttl/CHANGELOG.md index de45cb774..7470ed9e8 100644 --- a/src/core/cache/decorators/ttl/CHANGELOG.md +++ b/src/core/cache/decorators/ttl/CHANGELOG.md @@ -19,6 +19,10 @@ Changelog * Change type parameters from `` to `` +#### :rocket: New Feature + +* Added a new method `clone` processing + ## v3.47.0 (2021-05-17) #### :bug: Bug Fix diff --git a/src/core/cache/decorators/ttl/index.ts b/src/core/cache/decorators/ttl/index.ts index 21e99e552..389d86ea9 100644 --- a/src/core/cache/decorators/ttl/index.ts +++ b/src/core/cache/decorators/ttl/index.ts @@ -50,43 +50,89 @@ export default function addTTL< remove: originalRemove, set: originalSet, clear: originalClear, + clone: originalClone, subscribe } = addEmitter, K, V>(>cache); const cacheWithTTL: TTLCache = Object.create(cache), - ttlTimers = new Map(); + ttlTimers = new Map>]>(); - cacheWithTTL.set = (key: K, value: V, opts?: TTLDecoratorOptions & Parameters[2]) => { - updateTTL(key, opts?.ttl); - return originalSet(key, value, opts); + const descriptor = { + enumerable: false, + writable: true, + configurable: true }; - cacheWithTTL.remove = (key: K) => { - cacheWithTTL.removeTTLFrom(key); - return originalRemove(key); - }; - - cacheWithTTL.removeTTLFrom = (key: K) => { - if (ttlTimers.has(key)) { - clearTimeout(ttlTimers.get(key)); - ttlTimers.delete(key); - return true; + Object.defineProperties(cacheWithTTL, { + ttlTimers: { + get() { + return new Map(ttlTimers); + }, + enumerable: true + }, + + set: { + value: (key: K, value: V, opts?: TTLDecoratorOptions & Parameters[2]) => { + updateTTL(key, opts?.ttl); + return originalSet(key, value, opts); + }, + ...descriptor + }, + + remove: { + value: (key: K) => { + cacheWithTTL.removeTTLFrom(key); + return originalRemove(key); + }, + ...descriptor + }, + + removeTTLFrom: { + value: (key: K) => { + if (ttlTimers.has(key)) { + clearTimeout(ttlTimers.get(key)?.[0]); + ttlTimers.delete(key); + return true; + } + + return false; + }, + ...descriptor + }, + + clear: { + value: (filter?: ClearFilter) => { + const + removed = originalClear(filter); + + removed.forEach((_, key) => { + cacheWithTTL.removeTTLFrom(key); + }); + + return removed; + }, + ...descriptor + }, + + clone: { + value: () => { + const + cache = addTTL(originalClone(), ttl); + + for (const [key, [,promise]] of ttlTimers) { + void promise.then(() => { + if (!cache.ttlTimers.has(key)) { + cache.remove(key); + } + }); + } + + return cache; + }, + ...descriptor } - - return false; - }; - - cacheWithTTL.clear = (filter?: ClearFilter) => { - const - removed = originalClear(filter); - - removed.forEach((_, key) => { - cacheWithTTL.removeTTLFrom(key); - }); - - return removed; - }; + }); subscribe('remove', cacheWithTTL, ({args}) => cacheWithTTL.removeTTLFrom(args[0])); @@ -98,12 +144,22 @@ export default function addTTL< result.forEach((_, key) => cacheWithTTL.removeTTLFrom(key)); }); + subscribe('clone', cacheWithTTL, () => + cacheWithTTL.clone()); + return cacheWithTTL; function updateTTL(key: K, optionTTL?: number): void { if (optionTTL != null || ttl != null) { const time = optionTTL ?? ttl; - ttlTimers.set(key, setTimeout(() => cacheWithTTL.remove(key), time)); + + let timerId; + + const promise = new Promise>((resolve) => { + timerId = setTimeout(() => resolve(cacheWithTTL.remove(key)), time); + }); + + ttlTimers.set(key, [timerId, promise]); } else { cacheWithTTL.removeTTLFrom(key); diff --git a/src/core/cache/decorators/ttl/interface.ts b/src/core/cache/decorators/ttl/interface.ts index a49925636..ddc7ffbbe 100644 --- a/src/core/cache/decorators/ttl/interface.ts +++ b/src/core/cache/decorators/ttl/interface.ts @@ -13,6 +13,11 @@ export interface TTLCache< V = unknown, T extends CacheWithEmitter = CacheWithEmitter > extends CacheWithEmitter { + /** + * Collection of cache's timers + */ + ttlTimers: Map>]>; + /** * Saves a value to the cache by the specified key * diff --git a/src/core/cache/decorators/ttl/spec.js b/src/core/cache/decorators/ttl/spec.js index bc5edc5e8..26948c5fb 100644 --- a/src/core/cache/decorators/ttl/spec.js +++ b/src/core/cache/decorators/ttl/spec.js @@ -12,7 +12,7 @@ import SimpleCache from 'core/cache/simple'; import RestrictedCache from 'core/cache/restricted'; describe('core/cache/decorators/ttl', () => { - it('should remove items after expiring', (done) => { + it.only('should remove items after expiring', (done) => { const cache = addTTL(new SimpleCache()); cache.set('foo', 1); @@ -118,6 +118,28 @@ describe('core/cache/decorators/ttl', () => { expect(memory).toEqual(['bar']); }); + it('should clone the cache with `ttl`', (done) => { + const + cache = addTTL(new SimpleCache()); + + cache.set('bar', 1, {ttl: 5}); + cache.set('baz', 1, {ttl: 10}); + + const + cloned = cache.clone(); + + expect(cloned.has('bar')).toBe(true); + + cloned.set('baz', 1, {ttl: 20}); + + setTimeout(() => { + expect(cloned.has('bar')).toBe(false); + expect(cloned.has('baz')).toBe(true); + + done(); + }, 15); + }); + it('`clear` caused by a side effect', () => { const originalCache = new SimpleCache(), From 3385966999ae47e2bbb675f5bbfeac10c9c696da Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Sat, 8 Oct 2022 18:59:33 +0300 Subject: [PATCH 08/12] add clone processing in addPersistent --- .../cache/decorators/persistent/CHANGELOG.md | 4 + .../decorators/persistent/engines/active.ts | 4 - .../persistent/engines/interface.ts | 5 + .../decorators/persistent/engines/lazy.ts | 5 +- .../cache/decorators/persistent/interface.ts | 3 + src/core/cache/decorators/persistent/spec.js | 35 ++++- .../cache/decorators/persistent/wrapper.ts | 143 ++++++++++++++---- 7 files changed, 160 insertions(+), 39 deletions(-) diff --git a/src/core/cache/decorators/persistent/CHANGELOG.md b/src/core/cache/decorators/persistent/CHANGELOG.md index bd6bc5017..b3fae323d 100644 --- a/src/core/cache/decorators/persistent/CHANGELOG.md +++ b/src/core/cache/decorators/persistent/CHANGELOG.md @@ -15,6 +15,10 @@ Changelog * Change type parameters from `` to `` +#### :rocket: New Feature + +* Added a new method `clone` processing + ## v3.60.2 (2021-10-04) #### :bug: Bug Fix diff --git a/src/core/cache/decorators/persistent/engines/active.ts b/src/core/cache/decorators/persistent/engines/active.ts index 067b0d4a9..a3dc96ff3 100644 --- a/src/core/cache/decorators/persistent/engines/active.ts +++ b/src/core/cache/decorators/persistent/engines/active.ts @@ -13,10 +13,6 @@ import { INDEX_STORAGE_NAME } from 'core/cache/decorators/persistent/engines/con import { UncheckablePersistentEngine } from 'core/cache/decorators/persistent/engines/interface'; export default class ActivePersistentEngine extends UncheckablePersistentEngine { - /** - * Index with keys and TTL-s of stored values - */ - protected ttlIndex: Dictionary = Object.createDict(); override async initCache(cache: Cache): Promise { if (await this.storage.has(INDEX_STORAGE_NAME)) { diff --git a/src/core/cache/decorators/persistent/engines/interface.ts b/src/core/cache/decorators/persistent/engines/interface.ts index fe7809d8f..a9cdf923b 100644 --- a/src/core/cache/decorators/persistent/engines/interface.ts +++ b/src/core/cache/decorators/persistent/engines/interface.ts @@ -20,6 +20,11 @@ export interface AbstractPersistentEngine { } export abstract class AbstractPersistentEngine { + /** + * Index with keys and TTL-s of stored values + */ + ttlIndex: Dictionary = Object.createDict(); + /** * API for async operations */ diff --git a/src/core/cache/decorators/persistent/engines/lazy.ts b/src/core/cache/decorators/persistent/engines/lazy.ts index 593a19368..9af03524b 100644 --- a/src/core/cache/decorators/persistent/engines/lazy.ts +++ b/src/core/cache/decorators/persistent/engines/lazy.ts @@ -23,8 +23,11 @@ export default class LazyPersistentEngine extends CheckablePersistentEngine>; }; export interface PersistentTTLDecoratorOptions { diff --git a/src/core/cache/decorators/persistent/spec.js b/src/core/cache/decorators/persistent/spec.js index ae5dd1037..116359cff 100644 --- a/src/core/cache/decorators/persistent/spec.js +++ b/src/core/cache/decorators/persistent/spec.js @@ -7,7 +7,7 @@ */ import * as netModule from 'core/net'; -import { asyncLocal } from 'core/kv-storage'; +import { asyncLocal, asyncSession } from 'core/kv-storage'; import addPersistent from 'core/cache/decorators/persistent'; @@ -80,6 +80,39 @@ describe('core/cache/decorators/persistent', () => { expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({bar: Number.MAX_SAFE_INTEGER}); }); + it('should clone the cache', async () => { + const opts = { + loadFromStorage: 'onInit' + }; + + const + persistentCache = await addPersistent(new SimpleCache(), asyncLocal, opts); + + await persistentCache.set('foo', 1, {persistentTTL: 100}); + await persistentCache.set('bar', 1, {persistentTTL: 10}); + + const + fakeClonedCache = persistentCache.clone(), + clonedCache = await persistentCache.cloneTo(asyncSession); + + expect(fakeClonedCache).toBe(undefined); + expect(await clonedCache.get('foo')).toBe(1); + expect(await clonedCache.get('bar')).toBe(1); + + expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({foo: 100, bar: 10}); + expect(await asyncSession.get(INDEX_STORAGE_NAME)).toEqual({foo: 100, bar: 10}); + + Date.now = () => 50; + + const + persistentCache2 = await addPersistent(new SimpleCache(), asyncSession, opts); + + expect(await persistentCache2.get('foo')).toBe(1); + expect(await persistentCache2.get('bar')).toBe(undefined); + + Date.now = () => 0; + }); + it('`clear` caused by a side effect', async () => { const opts = { loadFromStorage: 'onInit' diff --git a/src/core/cache/decorators/persistent/wrapper.ts b/src/core/cache/decorators/persistent/wrapper.ts index 20df3d3bd..ae295a947 100644 --- a/src/core/cache/decorators/persistent/wrapper.ts +++ b/src/core/cache/decorators/persistent/wrapper.ts @@ -7,6 +7,7 @@ */ import SyncPromise from 'core/promise/sync'; +import { unimplement } from 'core/functools'; import type { SyncStorageNamespace, AsyncStorageNamespace } from 'core/kv-storage'; import type Cache from 'core/cache/interface'; @@ -44,6 +45,11 @@ export default class PersistentWrapper, V = unknown> */ protected readonly fetchedItems: Set = new Set(); + /** + * Object with incom + */ + protected readonly opts?: PersistentOptions; + /** * @param cache - cache object to wrap * @param storage - storage object to save cache items @@ -54,6 +60,7 @@ export default class PersistentWrapper, V = unknown> this.cache = cache; this.wrappedCache = Object.create(cache); + this.opts = opts; this.engine = new engines[opts?.loadFromStorage ?? 'onDemand'](storage); } @@ -82,47 +89,115 @@ export default class PersistentWrapper, V = unknown> subscribe } = addEmitter(this.cache); - this.wrappedCache.has = this.getDefaultImplementation('has'); - this.wrappedCache.get = this.getDefaultImplementation('get'); - - this.wrappedCache.set = async (key: string, value: V, opts?: PersistentTTLDecoratorOptions & Parameters[2]) => { - const - ttl = opts?.persistentTTL ?? this.ttl; - - this.fetchedItems.add(key); - - const - res = originalSet(key, value, opts); + const descriptor = { + enumerable: false, + writable: true, + configurable: true + }; - if (this.cache.has(key)) { - await this.engine.set(key, value, ttl); - } + Object.defineProperties(this.wrappedCache, { + has: { + value: this.getDefaultImplementation('has'), + ...descriptor + }, - return res; - }; + get: { + value: this.getDefaultImplementation('get'), + ...descriptor + }, - this.wrappedCache.remove = async (key: string) => { - this.fetchedItems.add(key); - await this.engine.remove(key); - return originalRemove(key); - }; + set: { + value: async (key: string, value: V, opts?: PersistentTTLDecoratorOptions & Parameters[2]) => { + const + ttl = opts?.persistentTTL ?? this.ttl; - this.wrappedCache.keys = () => SyncPromise.resolve(this.cache.keys()); + this.fetchedItems.add(key); - this.wrappedCache.clear = async (filter?: ClearFilter) => { - const - removed = originalClear(filter), - removedKeys: string[] = []; + const + res = originalSet(key, value, opts); - removed.forEach((_, key) => { - removedKeys.push(key); - }); + if (this.cache.has(key)) { + await this.engine.set(key, value, ttl); + } - await Promise.allSettled(removedKeys.map((key) => this.engine.remove(key))); - return removed; - }; + return res; + }, + ...descriptor + }, - this.wrappedCache.removePersistentTTLFrom = (key) => this.engine.removeTTLFrom(key); + remove: { + value: async (key: string) => { + this.fetchedItems.add(key); + await this.engine.remove(key); + return originalRemove(key); + }, + ...descriptor + }, + + keys: { + value: () => SyncPromise.resolve(this.cache.keys()), + ...descriptor + }, + + clear: { + value: async (filter?: ClearFilter) => { + const + removed = originalClear(filter), + removedKeys: string[] = []; + + removed.forEach((_, key) => { + removedKeys.push(key); + }); + + await Promise.allSettled(removedKeys.map((key) => this.engine.remove(key))); + return removed; + }, + ...descriptor + }, + + clone: { + value: () => { + unimplement({ + type: 'function', + alternative: {name: 'cloneTo'} + }, this.wrappedCache.clone); + }, + ...descriptor + }, + + cloneTo: { + value: async ( + storage: SyncStorageNamespace | AsyncStorageNamespace + ): Promise> => { + const + cache = new PersistentWrapper, V>(this.cache.clone(), storage, {...this.opts}); + + Object.defineProperties(cache, { + fetchedItems: { + value: new Set(this.fetchedItems) + } + }); + + for (const [key, value] of this.cache.entries()) { + const + ttl = this.engine.ttlIndex[key] ?? 0, + time = Date.now(); + + if (ttl > time) { + await cache.engine.set(key, value, ttl - time); + } + } + + return cache.getInstance(); + }, + ...descriptor + }, + + removePersistentTTLFrom: { + value: (key) => this.engine.removeTTLFrom(key), + ...descriptor + } + }); subscribe('remove', this.wrappedCache, ({args}) => this.engine.remove(args[0])); @@ -135,6 +210,8 @@ export default class PersistentWrapper, V = unknown> subscribe('clear', this.wrappedCache, ({result}) => { result.forEach((_, key) => this.engine.remove(key)); }); + + subscribe('clone', this.wrappedCache, () => this.wrappedCache.clone()); } /** From d80f48bbb42973d29f549bb059eb4b21c68814d8 Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Sat, 8 Oct 2022 19:00:08 +0300 Subject: [PATCH 09/12] add emitter tests --- .../decorators/helpers/add-emitter/spec.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/core/cache/decorators/helpers/add-emitter/spec.js b/src/core/cache/decorators/helpers/add-emitter/spec.js index 01feff1f0..8f6f630bf 100644 --- a/src/core/cache/decorators/helpers/add-emitter/spec.js +++ b/src/core/cache/decorators/helpers/add-emitter/spec.js @@ -19,6 +19,7 @@ describe('core/cache/decorators/helpers/add-emitter', () => { this.remove = () => null; this.set = () => null; this.clear = () => null; + this.clone = () => null; } const @@ -147,4 +148,24 @@ describe('core/cache/decorators/helpers/add-emitter', () => { expect(memory[1]).toEqual(undefined); }); }); + + describe('clone', () => { + it('clones a cache', () => { + const + cache = new SimpleCache(), + {clone} = addEmitter(cache); + + const memory = []; + + cache[eventEmitter].on('clone', (...args) => { + memory.push(args); + }); + + cache.clone(); + expect(memory[0]).toEqual([cache, {result: new SimpleCache()}]); + + clone(); + expect(memory[1]).toEqual(undefined); + }); + }); }); From 52571ad8fd28084deac4614853af4982e111170a Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Mon, 10 Oct 2022 18:28:14 +0300 Subject: [PATCH 10/12] refactoring --- src/core/cache/decorators/persistent/spec.js | 1 + .../cache/decorators/persistent/wrapper.ts | 20 +++++++++++++------ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/src/core/cache/decorators/persistent/spec.js b/src/core/cache/decorators/persistent/spec.js index 116359cff..f6f8fd692 100644 --- a/src/core/cache/decorators/persistent/spec.js +++ b/src/core/cache/decorators/persistent/spec.js @@ -109,6 +109,7 @@ describe('core/cache/decorators/persistent', () => { expect(await persistentCache2.get('foo')).toBe(1); expect(await persistentCache2.get('bar')).toBe(undefined); + expect(await asyncSession.get(INDEX_STORAGE_NAME)).toEqual({foo: 100}); Date.now = () => 0; }); diff --git a/src/core/cache/decorators/persistent/wrapper.ts b/src/core/cache/decorators/persistent/wrapper.ts index ae295a947..b9477c4e0 100644 --- a/src/core/cache/decorators/persistent/wrapper.ts +++ b/src/core/cache/decorators/persistent/wrapper.ts @@ -17,7 +17,13 @@ import engines from 'core/cache/decorators/persistent/engines'; import addEmitter from 'core/cache/decorators/helpers/add-emitter'; import type { PersistentEngine, CheckablePersistentEngine } from 'core/cache/decorators/persistent/engines/interface'; -import type { PersistentOptions, PersistentCache, PersistentTTLDecoratorOptions } from 'core/cache/decorators/persistent/interface'; +import type { + + PersistentOptions, + PersistentCache, + PersistentTTLDecoratorOptions + +} from 'core/cache/decorators/persistent/interface'; export default class PersistentWrapper, V = unknown> { /** @@ -172,10 +178,11 @@ export default class PersistentWrapper, V = unknown> const cache = new PersistentWrapper, V>(this.cache.clone(), storage, {...this.opts}); - Object.defineProperties(cache, { - fetchedItems: { - value: new Set(this.fetchedItems) - } + Object.defineProperty(cache, 'fetchedItems', { + value: new Set(this.fetchedItems), + enumerable: true, + configurable: true, + writable: true }); for (const [key, value] of this.cache.entries()) { @@ -211,7 +218,8 @@ export default class PersistentWrapper, V = unknown> result.forEach((_, key) => this.engine.remove(key)); }); - subscribe('clone', this.wrappedCache, () => this.wrappedCache.clone()); + subscribe('clone', this.wrappedCache, () => + this.wrappedCache.clone()); } /** From 01a7f54f1e7766243e13900e3af6b2f1bd3eb2ff Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Wed, 12 Oct 2022 22:18:07 +0300 Subject: [PATCH 11/12] fix --- src/core/cache/decorators/persistent/spec.js | 16 ++++------------ src/core/cache/decorators/persistent/wrapper.ts | 3 +++ 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/core/cache/decorators/persistent/spec.js b/src/core/cache/decorators/persistent/spec.js index f6f8fd692..7d19b28af 100644 --- a/src/core/cache/decorators/persistent/spec.js +++ b/src/core/cache/decorators/persistent/spec.js @@ -80,7 +80,7 @@ describe('core/cache/decorators/persistent', () => { expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({bar: Number.MAX_SAFE_INTEGER}); }); - it('should clone the cache', async () => { + it.only('should clone the cache', async () => { const opts = { loadFromStorage: 'onInit' }; @@ -91,24 +91,16 @@ describe('core/cache/decorators/persistent', () => { await persistentCache.set('foo', 1, {persistentTTL: 100}); await persistentCache.set('bar', 1, {persistentTTL: 10}); + Date.now = () => 50; + const fakeClonedCache = persistentCache.clone(), clonedCache = await persistentCache.cloneTo(asyncSession); expect(fakeClonedCache).toBe(undefined); expect(await clonedCache.get('foo')).toBe(1); - expect(await clonedCache.get('bar')).toBe(1); - - expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({foo: 100, bar: 10}); - expect(await asyncSession.get(INDEX_STORAGE_NAME)).toEqual({foo: 100, bar: 10}); - - Date.now = () => 50; - - const - persistentCache2 = await addPersistent(new SimpleCache(), asyncSession, opts); + expect(await clonedCache.get('bar')).toBe(undefined); - expect(await persistentCache2.get('foo')).toBe(1); - expect(await persistentCache2.get('bar')).toBe(undefined); expect(await asyncSession.get(INDEX_STORAGE_NAME)).toEqual({foo: 100}); Date.now = () => 0; diff --git a/src/core/cache/decorators/persistent/wrapper.ts b/src/core/cache/decorators/persistent/wrapper.ts index b9477c4e0..ed6558870 100644 --- a/src/core/cache/decorators/persistent/wrapper.ts +++ b/src/core/cache/decorators/persistent/wrapper.ts @@ -192,6 +192,9 @@ export default class PersistentWrapper, V = unknown> if (ttl > time) { await cache.engine.set(key, value, ttl - time); + + } else { + cache.cache.remove(key); } } From b5f6a494b41eafd093666506619e26336778b5b3 Mon Sep 17 00:00:00 2001 From: Aleksandr Bunin Date: Thu, 13 Oct 2022 00:12:01 +0300 Subject: [PATCH 12/12] refactor --- src/core/cache/decorators/persistent/spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/cache/decorators/persistent/spec.js b/src/core/cache/decorators/persistent/spec.js index 7d19b28af..b51d42feb 100644 --- a/src/core/cache/decorators/persistent/spec.js +++ b/src/core/cache/decorators/persistent/spec.js @@ -80,7 +80,7 @@ describe('core/cache/decorators/persistent', () => { expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({bar: Number.MAX_SAFE_INTEGER}); }); - it.only('should clone the cache', async () => { + it('should clone the cache', async () => { const opts = { loadFromStorage: 'onInit' };