-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathConfigCatCache.ts
More file actions
129 lines (105 loc) · 4.22 KB
/
ConfigCatCache.ts
File metadata and controls
129 lines (105 loc) · 4.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
import type { LoggerWrapper } from "./ConfigCatLogger";
import { ProjectConfig } from "./ProjectConfig";
import { isPromiseLike } from "./Utils";
/** Defines the interface used by the ConfigCat SDK to store and retrieve downloaded config data. */
export interface IConfigCatCache {
/**
* Stores a data item into the cache.
* @param key A string identifying the data item.
* @param value The data item to cache.
*/
set(key: string, value: string): Promise<void> | void;
/**
* Retrieves a data item from the cache.
* @param key A string identifying the value.
* @returns The cached data item or `null` or `undefined` if there is none.
*/
get(key: string): Promise<string | null | undefined> | string | null | undefined;
}
/** @remarks Unchanged config is returned as is, changed config is wrapped in an array so we can distinguish between the two cases. */
export type CacheSyncResult = ProjectConfig | [changedConfig: ProjectConfig];
export interface IConfigCache {
set(key: string, config: ProjectConfig): Promise<void> | void;
get(key: string): Promise<CacheSyncResult> | CacheSyncResult;
getInMemory(): ProjectConfig;
}
export class InMemoryConfigCache implements IConfigCache {
private cachedConfig: ProjectConfig = ProjectConfig.empty;
set(_key: string, config: ProjectConfig): void {
this.cachedConfig = config;
}
get(_key: string): ProjectConfig {
return this.cachedConfig;
}
getInMemory(): ProjectConfig {
return this.cachedConfig;
}
}
export class ExternalConfigCache implements IConfigCache {
private cachedConfig: ProjectConfig = ProjectConfig.empty;
private cachedSerializedConfig: string | undefined;
constructor(
private readonly cache: IConfigCatCache,
private readonly logger: LoggerWrapper) {
}
async set(key: string, config: ProjectConfig): Promise<void> {
try {
if (!config.isEmpty) {
this.cachedSerializedConfig = ProjectConfig.serialize(config);
this.cachedConfig = config;
}
else {
// We may have empty entries with timestamp > 0 (see the flooding prevention logic in ConfigServiceBase.fetchAsync).
// In such cases we want to preserve the timestamp locally but don't want to store those entries into the external cache.
this.cachedSerializedConfig = void 0;
this.cachedConfig = config;
return;
}
await this.cache.set(key, this.cachedSerializedConfig);
}
catch (err) {
this.logger.configServiceCacheWriteError(err);
}
}
private updateCachedConfig(externalSerializedConfig: string | null | undefined): CacheSyncResult {
if (externalSerializedConfig == null || externalSerializedConfig === this.cachedSerializedConfig) {
return this.cachedConfig;
}
const externalConfig = ProjectConfig.deserialize(externalSerializedConfig);
const hasChanged = !ProjectConfig.contentEquals(externalConfig, this.cachedConfig);
this.cachedConfig = externalConfig;
this.cachedSerializedConfig = externalSerializedConfig;
return hasChanged ? [this.cachedConfig] : this.cachedConfig;
}
get(key: string): Promise<CacheSyncResult> | CacheSyncResult {
let cacheSyncResult: CacheSyncResult;
try {
const cacheGetResult = this.cache.get(key);
// Take the async path only when the IConfigCatCache.get operation is asynchronous.
if (isPromiseLike(cacheGetResult)) {
return (async (cacheGetPromise) => {
let cacheSyncResult: CacheSyncResult;
try {
cacheSyncResult = this.updateCachedConfig(await cacheGetPromise);
}
catch (err) {
cacheSyncResult = this.cachedConfig;
this.logger.configServiceCacheReadError(err);
}
return cacheSyncResult;
})(cacheGetResult);
}
// Otherwise, keep the code flow synchronous so the config services can sync up
// with the cache in their ctors synchronously (see ConfigServiceBase.syncUpWithCache).
cacheSyncResult = this.updateCachedConfig(cacheGetResult);
}
catch (err) {
cacheSyncResult = this.cachedConfig;
this.logger.configServiceCacheReadError(err);
}
return cacheSyncResult;
}
getInMemory(): ProjectConfig {
return this.cachedConfig;
}
}