Skip to content

Tune cache #318

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 12 commits into
base: master
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions src/core/cache/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,21 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.0 (2022-0?-??)

#### :boom: Breaking Change
* Changed typed parameters of `Cache` interface from `<V, K>` to `<K, V>`
* Change a default `Cache` interface key type

#### :rocket: New Feature

* Added new cache `DefaultCache`
* Added a new method `clone`

#### :bug: Bug Fix

* Fixed type inference in decorator

## v3.50.0 (2021-06-07)

#### :rocket: New Feature
Expand Down
26 changes: 26 additions & 0 deletions src/core/cache/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
```
10 changes: 10 additions & 0 deletions src/core/cache/decorators/helpers/add-emitter/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.? (2022-0?-??)

#### :boom: Breaking Change

* Change type parameters from `<V, K>` to `<K, V>`

#### :rocket: New Feature

* Added a new method `clone` processing

## v3.59.1 (2021-09-21)

#### :bug: Bug Fix
Expand Down
30 changes: 26 additions & 4 deletions src/core/cache/decorators/helpers/add-emitter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,12 @@ export const
*
* @param cache
*/
const addEmitter: AddEmitter = <T extends Cache<V, K>, V = unknown, K extends string = string>(cache) => {
const addEmitter: AddEmitter = <T extends Cache<K, V>, K = unknown, V = unknown>(cache: T) => {
const
expandedCache = <Overwrite<CacheWithEmitter<V, K, T>, {[eventEmitter]?: EventEmitter}>><unknown>cache;
expandedCache = <Overwrite<CacheWithEmitter<K, V, T>, {[eventEmitter]?: EventEmitter}>><unknown>cache;

const
cacheWithEmitter = <CacheWithEmitter<V, K, T>>expandedCache;
cacheWithEmitter = <CacheWithEmitter<K, V, T>>expandedCache;

let
emitter;
Expand All @@ -66,7 +66,10 @@ const addEmitter: AddEmitter = <T extends Cache<V, K>, V = unknown, K extends st
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;
Expand Down Expand Up @@ -131,10 +134,29 @@ const addEmitter: AddEmitter = <T extends Cache<V, K>, V = unknown, K extends st
originalClear = cacheWithEmitter[$$.clear] ?? originalClear;
}

if (originalClone[eventEmitter] == null) {
cacheWithEmitter[$$.clone] = originalClone;

cacheWithEmitter.clone = function clone(): Cache<K, V> {
const
result = originalClone.call(this);

emitter.emit('clone', cacheWithEmitter, {result});

return result;
};

cacheWithEmitter.clone[eventEmitter] = true;

} else {
originalClone = cacheWithEmitter[$$.clone] ?? originalClone;
}

return <AddEmitterReturn<T>>{
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);
Expand Down
16 changes: 12 additions & 4 deletions src/core/cache/decorators/helpers/add-emitter/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import type Cache from 'core/cache/interface';
export type MethodsToWrap =
'set' |
'remove' |
'clear';
'clear' |
'clone';

export interface MutationEvent<M extends AnyFunction = AnyFunction> {
args: Parameters<M>;
Expand All @@ -25,7 +26,11 @@ export interface MutationHandler<M extends AnyFunction = AnyFunction> {
(e: MutationEvent<M>): void;
}

export interface CacheWithEmitter<V = unknown, K = string, T extends Cache<V, K> = Cache<V, K>> extends Cache<V, K> {
export interface CacheWithEmitter<
K = unknown,
V = unknown,
T extends Cache<K, V> = Cache<K, V>
> extends Cache<K, V> {
/** @override */
set(key: K, value: V, opts?: Parameters<T['set']>[2]): V;

Expand All @@ -36,9 +41,9 @@ export interface CacheWithEmitter<V = unknown, K = string, T extends Cache<V, K>
}

export type AddEmitter =
<T extends Cache<V, K>, V = unknown, K extends string = string>(cache: T) => AddEmitterReturn<T>;
<T extends Cache<K, V>, K = unknown, V = unknown>(cache: T) => AddEmitterReturn<T>;

export interface AddEmitterReturn<T extends Cache<V, K>, V = unknown, K extends string = string> {
export interface AddEmitterReturn<T extends Cache<K, V>, K = unknown, V = unknown> {
/** @see [[Cache.set]] */
set: T['set'];

Expand All @@ -48,6 +53,9 @@ export interface AddEmitterReturn<T extends Cache<V, K>, V = unknown, K extends
/** @see [[Cache.clear]] */
clear: T['clear'];

/** @see [[Cache.clone]] */
clone: T['clone'];

/**
* Subscribes for mutations of the specified cache object
*
Expand Down
21 changes: 21 additions & 0 deletions src/core/cache/decorators/helpers/add-emitter/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ describe('core/cache/decorators/helpers/add-emitter', () => {
this.remove = () => null;
this.set = () => null;
this.clear = () => null;
this.clone = () => null;
}

const
Expand Down Expand Up @@ -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);
});
});
});
10 changes: 10 additions & 0 deletions src/core/cache/decorators/persistent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ Changelog
> - :house: [Internal]
> - :nail_care: [Polish]

## v3.??.? (2022-0?-??)

#### :boom: Breaking Change

* Change type parameters from `<V, K>` to `<K, V>`

#### :rocket: New Feature

* Added a new method `clone` processing

## v3.60.2 (2021-10-04)

#### :bug: Bug Fix
Expand Down
6 changes: 1 addition & 5 deletions src/core/cache/decorators/persistent/engines/active.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,8 @@ import { INDEX_STORAGE_NAME } from 'core/cache/decorators/persistent/engines/con
import { UncheckablePersistentEngine } from 'core/cache/decorators/persistent/engines/interface';

export default class ActivePersistentEngine<V> extends UncheckablePersistentEngine<V> {
/**
* Index with keys and TTL-s of stored values
*/
protected ttlIndex: Dictionary<number> = Object.createDict();

override async initCache(cache: Cache<V>): Promise<void> {
override async initCache(cache: Cache<string, V>): Promise<void> {
if (await this.storage.has(INDEX_STORAGE_NAME)) {
this.ttlIndex = (await this.storage.get<Dictionary<number>>(INDEX_STORAGE_NAME))!;

Expand Down
7 changes: 6 additions & 1 deletion src/core/cache/decorators/persistent/engines/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ export interface AbstractPersistentEngine<V = unknown> {
* Initializes a new cache instance from the past one
* @param cache
*/
initCache?(cache: Cache<V>): CanPromise<void>;
initCache?(cache: Cache<unknown, V>): CanPromise<void>;
}

export abstract class AbstractPersistentEngine<V = unknown> {
/**
* Index with keys and TTL-s of stored values
*/
ttlIndex: Dictionary<number> = Object.createDict();

/**
* API for async operations
*/
Expand Down
5 changes: 4 additions & 1 deletion src/core/cache/decorators/persistent/engines/lazy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ export default class LazyPersistentEngine<V> extends CheckablePersistentEngine<V

} finally {
if (ttl != null) {
await this.storage.set(key + TTL_POSTFIX, this.normalizeTTL(ttl));
const
normalizedTTL = this.normalizeTTL(ttl);

await this.storage.set(key + TTL_POSTFIX, normalizedTTL);
this.ttlIndex[key] = normalizedTTL;
} else {
await this.removeTTLFrom(key);
}
Expand Down
5 changes: 3 additions & 2 deletions src/core/cache/decorators/persistent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,10 @@ export * from 'core/cache/decorators/persistent/interface';
* ```
*/
const addPersistent = <V>(
cache: Cache<V, string>,
cache: Cache<string, V>,
storage: SyncStorageNamespace | AsyncStorageNamespace,
opts?: PersistentOptions
): Promise<PersistentCache<V>> => new PersistentWrapper<Cache<V, string>, V>(cache, storage, opts).getInstance();
): Promise<PersistentCache<string, V>> =>
new PersistentWrapper<Cache<string, V>, V>(cache, storage, opts).getInstance();

export default addPersistent;
7 changes: 5 additions & 2 deletions src/core/cache/decorators/persistent/interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
* https://github.com/V4Fire/Core/blob/master/LICENSE
*/

import type { AsyncStorageNamespace, SyncStorageNamespace } from 'core/kv-storage';
import type { CacheWithEmitter } from 'core/cache/decorators/helpers/add-emitter/interface';
import type { eventEmitter } from 'core/cache/decorators/helpers/add-emitter';

export type PersistentCache<V = unknown, K = string, T extends CacheWithEmitter<V, K> = CacheWithEmitter<V, K>> = {
[key in Exclude<(keyof CacheWithEmitter<V, K>), 'set' | 'size' | typeof eventEmitter>]: ReturnPromise<CacheWithEmitter<V, K>[key]>
export type PersistentCache<K = string, V = unknown, T extends CacheWithEmitter<K, V> = CacheWithEmitter<K, V>> = {
[key in Exclude<(keyof CacheWithEmitter<K, V>), 'set' | 'size' | typeof eventEmitter>]: ReturnPromise<CacheWithEmitter<K, V>[key]>
} & {
/** @see [[Cache.size]] */
size: [T['size']];
Expand All @@ -35,6 +36,8 @@ export type PersistentCache<V = unknown, K = string, T extends CacheWithEmitter<

/** @see [[CacheWithEmitter[eventEmitterSymbol]]] */
eventEmitter: T[typeof eventEmitter];

cloneTo(storage: SyncStorageNamespace | AsyncStorageNamespace): Promise<PersistentCache<K, V>>;
};

export interface PersistentTTLDecoratorOptions {
Expand Down
28 changes: 27 additions & 1 deletion src/core/cache/decorators/persistent/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -80,6 +80,32 @@ 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});

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(undefined);

expect(await asyncSession.get(INDEX_STORAGE_NAME)).toEqual({foo: 100});

Date.now = () => 0;
});

it('`clear` caused by a side effect', async () => {
const opts = {
loadFromStorage: 'onInit'
Expand Down
Loading