Skip to content

Commit 3385966

Browse files
committed
add clone processing in addPersistent
1 parent 19fa0f2 commit 3385966

File tree

7 files changed

+160
-39
lines changed

7 files changed

+160
-39
lines changed

src/core/cache/decorators/persistent/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ Changelog
1515

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

18+
#### :rocket: New Feature
19+
20+
* Added a new method `clone` processing
21+
1822
## v3.60.2 (2021-10-04)
1923

2024
#### :bug: Bug Fix

src/core/cache/decorators/persistent/engines/active.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@ import { INDEX_STORAGE_NAME } from 'core/cache/decorators/persistent/engines/con
1313
import { UncheckablePersistentEngine } from 'core/cache/decorators/persistent/engines/interface';
1414

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

2117
override async initCache(cache: Cache<string, V>): Promise<void> {
2218
if (await this.storage.has(INDEX_STORAGE_NAME)) {

src/core/cache/decorators/persistent/engines/interface.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ export interface AbstractPersistentEngine<V = unknown> {
2020
}
2121

2222
export abstract class AbstractPersistentEngine<V = unknown> {
23+
/**
24+
* Index with keys and TTL-s of stored values
25+
*/
26+
ttlIndex: Dictionary<number> = Object.createDict();
27+
2328
/**
2429
* API for async operations
2530
*/

src/core/cache/decorators/persistent/engines/lazy.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ export default class LazyPersistentEngine<V> extends CheckablePersistentEngine<V
2323

2424
} finally {
2525
if (ttl != null) {
26-
await this.storage.set(key + TTL_POSTFIX, this.normalizeTTL(ttl));
26+
const
27+
normalizedTTL = this.normalizeTTL(ttl);
2728

29+
await this.storage.set(key + TTL_POSTFIX, normalizedTTL);
30+
this.ttlIndex[key] = normalizedTTL;
2831
} else {
2932
await this.removeTTLFrom(key);
3033
}

src/core/cache/decorators/persistent/interface.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
* https://github.com/V4Fire/Core/blob/master/LICENSE
77
*/
88

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

@@ -35,6 +36,8 @@ export type PersistentCache<K = string, V = unknown, T extends CacheWithEmitter<
3536

3637
/** @see [[CacheWithEmitter[eventEmitterSymbol]]] */
3738
eventEmitter: T[typeof eventEmitter];
39+
40+
cloneTo(storage: SyncStorageNamespace | AsyncStorageNamespace): Promise<PersistentCache<K, V>>;
3841
};
3942

4043
export interface PersistentTTLDecoratorOptions {

src/core/cache/decorators/persistent/spec.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import * as netModule from 'core/net';
10-
import { asyncLocal } from 'core/kv-storage';
10+
import { asyncLocal, asyncSession } from 'core/kv-storage';
1111

1212
import addPersistent from 'core/cache/decorators/persistent';
1313

@@ -80,6 +80,39 @@ describe('core/cache/decorators/persistent', () => {
8080
expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({bar: Number.MAX_SAFE_INTEGER});
8181
});
8282

83+
it('should clone the cache', async () => {
84+
const opts = {
85+
loadFromStorage: 'onInit'
86+
};
87+
88+
const
89+
persistentCache = await addPersistent(new SimpleCache(), asyncLocal, opts);
90+
91+
await persistentCache.set('foo', 1, {persistentTTL: 100});
92+
await persistentCache.set('bar', 1, {persistentTTL: 10});
93+
94+
const
95+
fakeClonedCache = persistentCache.clone(),
96+
clonedCache = await persistentCache.cloneTo(asyncSession);
97+
98+
expect(fakeClonedCache).toBe(undefined);
99+
expect(await clonedCache.get('foo')).toBe(1);
100+
expect(await clonedCache.get('bar')).toBe(1);
101+
102+
expect(await asyncLocal.get(INDEX_STORAGE_NAME)).toEqual({foo: 100, bar: 10});
103+
expect(await asyncSession.get(INDEX_STORAGE_NAME)).toEqual({foo: 100, bar: 10});
104+
105+
Date.now = () => 50;
106+
107+
const
108+
persistentCache2 = await addPersistent(new SimpleCache(), asyncSession, opts);
109+
110+
expect(await persistentCache2.get('foo')).toBe(1);
111+
expect(await persistentCache2.get('bar')).toBe(undefined);
112+
113+
Date.now = () => 0;
114+
});
115+
83116
it('`clear` caused by a side effect', async () => {
84117
const opts = {
85118
loadFromStorage: 'onInit'

src/core/cache/decorators/persistent/wrapper.ts

Lines changed: 110 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
*/
88

99
import SyncPromise from 'core/promise/sync';
10+
import { unimplement } from 'core/functools';
1011
import type { SyncStorageNamespace, AsyncStorageNamespace } from 'core/kv-storage';
1112

1213
import type Cache from 'core/cache/interface';
@@ -44,6 +45,11 @@ export default class PersistentWrapper<T extends Cache<string, V>, V = unknown>
4445
*/
4546
protected readonly fetchedItems: Set<string> = new Set();
4647

48+
/**
49+
* Object with incom
50+
*/
51+
protected readonly opts?: PersistentOptions;
52+
4753
/**
4854
* @param cache - cache object to wrap
4955
* @param storage - storage object to save cache items
@@ -54,6 +60,7 @@ export default class PersistentWrapper<T extends Cache<string, V>, V = unknown>
5460

5561
this.cache = cache;
5662
this.wrappedCache = Object.create(cache);
63+
this.opts = opts;
5764

5865
this.engine = new engines[opts?.loadFromStorage ?? 'onDemand']<V>(storage);
5966
}
@@ -82,47 +89,115 @@ export default class PersistentWrapper<T extends Cache<string, V>, V = unknown>
8289
subscribe
8390
} = addEmitter<T, string, V>(this.cache);
8491

85-
this.wrappedCache.has = this.getDefaultImplementation('has');
86-
this.wrappedCache.get = this.getDefaultImplementation('get');
87-
88-
this.wrappedCache.set = async (key: string, value: V, opts?: PersistentTTLDecoratorOptions & Parameters<T['set']>[2]) => {
89-
const
90-
ttl = opts?.persistentTTL ?? this.ttl;
91-
92-
this.fetchedItems.add(key);
93-
94-
const
95-
res = originalSet(key, value, opts);
92+
const descriptor = {
93+
enumerable: false,
94+
writable: true,
95+
configurable: true
96+
};
9697

97-
if (this.cache.has(key)) {
98-
await this.engine.set(key, value, ttl);
99-
}
98+
Object.defineProperties(this.wrappedCache, {
99+
has: {
100+
value: this.getDefaultImplementation('has'),
101+
...descriptor
102+
},
100103

101-
return res;
102-
};
104+
get: {
105+
value: this.getDefaultImplementation('get'),
106+
...descriptor
107+
},
103108

104-
this.wrappedCache.remove = async (key: string) => {
105-
this.fetchedItems.add(key);
106-
await this.engine.remove(key);
107-
return originalRemove(key);
108-
};
109+
set: {
110+
value: async (key: string, value: V, opts?: PersistentTTLDecoratorOptions & Parameters<T['set']>[2]) => {
111+
const
112+
ttl = opts?.persistentTTL ?? this.ttl;
109113

110-
this.wrappedCache.keys = () => SyncPromise.resolve(this.cache.keys());
114+
this.fetchedItems.add(key);
111115

112-
this.wrappedCache.clear = async (filter?: ClearFilter<V>) => {
113-
const
114-
removed = originalClear(filter),
115-
removedKeys: string[] = [];
116+
const
117+
res = originalSet(key, value, opts);
116118

117-
removed.forEach((_, key) => {
118-
removedKeys.push(key);
119-
});
119+
if (this.cache.has(key)) {
120+
await this.engine.set(key, value, ttl);
121+
}
120122

121-
await Promise.allSettled(removedKeys.map((key) => this.engine.remove(key)));
122-
return removed;
123-
};
123+
return res;
124+
},
125+
...descriptor
126+
},
124127

125-
this.wrappedCache.removePersistentTTLFrom = (key) => this.engine.removeTTLFrom(key);
128+
remove: {
129+
value: async (key: string) => {
130+
this.fetchedItems.add(key);
131+
await this.engine.remove(key);
132+
return originalRemove(key);
133+
},
134+
...descriptor
135+
},
136+
137+
keys: {
138+
value: () => SyncPromise.resolve(this.cache.keys()),
139+
...descriptor
140+
},
141+
142+
clear: {
143+
value: async (filter?: ClearFilter<V>) => {
144+
const
145+
removed = originalClear(filter),
146+
removedKeys: string[] = [];
147+
148+
removed.forEach((_, key) => {
149+
removedKeys.push(key);
150+
});
151+
152+
await Promise.allSettled(removedKeys.map((key) => this.engine.remove(key)));
153+
return removed;
154+
},
155+
...descriptor
156+
},
157+
158+
clone: {
159+
value: () => {
160+
unimplement({
161+
type: 'function',
162+
alternative: {name: 'cloneTo'}
163+
}, this.wrappedCache.clone);
164+
},
165+
...descriptor
166+
},
167+
168+
cloneTo: {
169+
value: async (
170+
storage: SyncStorageNamespace | AsyncStorageNamespace
171+
): Promise<PersistentCache<string, V>> => {
172+
const
173+
cache = new PersistentWrapper<Cache<string, V>, V>(this.cache.clone(), storage, {...this.opts});
174+
175+
Object.defineProperties(cache, {
176+
fetchedItems: {
177+
value: new Set(this.fetchedItems)
178+
}
179+
});
180+
181+
for (const [key, value] of this.cache.entries()) {
182+
const
183+
ttl = this.engine.ttlIndex[key] ?? 0,
184+
time = Date.now();
185+
186+
if (ttl > time) {
187+
await cache.engine.set(key, value, ttl - time);
188+
}
189+
}
190+
191+
return cache.getInstance();
192+
},
193+
...descriptor
194+
},
195+
196+
removePersistentTTLFrom: {
197+
value: (key) => this.engine.removeTTLFrom(key),
198+
...descriptor
199+
}
200+
});
126201

127202
subscribe('remove', this.wrappedCache, ({args}) =>
128203
this.engine.remove(args[0]));
@@ -135,6 +210,8 @@ export default class PersistentWrapper<T extends Cache<string, V>, V = unknown>
135210
subscribe('clear', this.wrappedCache, ({result}) => {
136211
result.forEach((_, key) => this.engine.remove(key));
137212
});
213+
214+
subscribe('clone', this.wrappedCache, () => this.wrappedCache.clone());
138215
}
139216

140217
/**

0 commit comments

Comments
 (0)