Skip to content

Commit 1b3a942

Browse files
committed
Refactor: Move cache storage logic from base class to concrete implementations
Move entries map and put/get/forget/flush methods from ODataBaseCache to ODataInMemoryCache. Rewrite ODataInStorageCache to use storage directly per-key instead of serializing the entire cache. Rename 'name' to 'prefix' in ODataInStorageCache for clarity.
1 parent 35898fa commit 1b3a942

3 files changed

Lines changed: 120 additions & 109 deletions

File tree

projects/angular-odata/src/lib/cache/cache.ts

Lines changed: 14 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ export interface ODataCacheEntry<T> {
1818

1919
export abstract class ODataBaseCache implements ODataCache {
2020
maxAge: number;
21-
entries: Map<string, ODataCacheEntry<any>>;
2221

2322
constructor({ maxAge = DEFAULT_MAXAGE }: { maxAge?: number }) {
2423
this.maxAge = maxAge;
25-
this.entries = new Map<string, ODataCacheEntry<any>>();
2624
}
2725

28-
abstract getResponse(req: ODataRequest<any>): ODataResponse<any> | undefined;
29-
abstract putResponse(req: ODataRequest<any>, res: ODataResponse<any>): void;
26+
/**
27+
* Check if the entry is expired
28+
* @param entry The cache entry
29+
* @returns Boolean indicating if the entry is expired
30+
*/
31+
isExpired(entry: ODataCacheEntry<any>) {
32+
return entry.date < (Date.now() - entry.maxAge);
33+
}
3034

3135
/**
3236
* Using the resource on the request build an array of string to identify the scope of the request
@@ -87,71 +91,11 @@ export abstract class ODataBaseCache implements ODataCache {
8791
return names.join(CACHE_KEY_SEPARATOR);
8892
}
8993

90-
/**
91-
* Put some payload in the cache
92-
* @param name The name for the entry
93-
* @param payload The payload to store in the cache
94-
* @param maxAge The maximum age for the entry
95-
* @param scope The scope for the entry
96-
* @param tags The tags for the entry
97-
*/
98-
put<T>(
99-
name: string,
100-
payload: T,
101-
{ maxAge, scope, tags }: { maxAge?: number; scope?: string[]; tags?: string[] } = {},
102-
) {
103-
const entry = this.buildEntry<T>(payload, { maxAge, tags });
104-
const key = this.buildKey([...(scope ?? []), name]);
105-
this.entries.set(key, entry);
106-
}
107-
108-
/**
109-
* Return the payload from the cache if it exists and is not expired
110-
* @param name The name of the entry
111-
* @param scope The scope of the entry
112-
* @returns The payload of the entry
113-
*/
114-
get<T>(name: string, { scope }: { scope?: string[] } = {}): T | undefined {
115-
const key = this.buildKey([...(scope || []), name]);
116-
const entry = this.entries.get(key);
117-
return entry !== undefined && !this.isExpired(entry) ? entry.payload : undefined;
118-
}
119-
120-
/**
121-
* Remove all cache entries that are matching with the given options
122-
* @param options The options to forget
123-
*/
124-
forget({
125-
name,
126-
scope = [],
127-
tags = [],
128-
}: { name?: string; scope?: string[]; tags?: string[] } = {}) {
129-
if (name !== undefined) scope.push(name);
130-
const key = scope.length > 0 ? this.buildKey(scope) : undefined;
131-
this.entries.forEach((entry, k) => {
132-
if (
133-
this.isExpired(entry) || // Expired
134-
(key !== undefined && k.startsWith(key)) || // Key
135-
(tags.length > 0 && tags.some((t) => entry.tags.indexOf(t) !== -1)) // Tags
136-
) {
137-
this.entries.delete(k);
138-
}
139-
});
140-
}
141-
142-
/**
143-
* Remove all cache entries
144-
*/
145-
flush() {
146-
this.entries = new Map<string, ODataCacheEntry<any>>();
147-
}
94+
abstract put<T>(name: string, payload: T, { maxAge, scope, tags }: { maxAge?: number; scope?: string[]; tags?: string[] }): void;
95+
abstract get<T>(name: string, { scope }: { scope?: string[] }): T | undefined;
96+
abstract getResponse(req: ODataRequest<any>): ODataResponse<any> | undefined;
97+
abstract putResponse(req: ODataRequest<any>, res: ODataResponse<any>): void;
98+
abstract forget({ name, scope, tags}: { name?: string; scope?: string[]; tags?: string[] }): void;
99+
abstract flush(): void;
148100

149-
/**
150-
* Check if the entry is expired
151-
* @param entry The cache entry
152-
* @returns Boolean indicating if the entry is expired
153-
*/
154-
isExpired(entry: ODataCacheEntry<any>) {
155-
return entry.date < Date.now() - entry.maxAge;
156-
}
157101
}

projects/angular-odata/src/lib/cache/memory.ts

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,41 @@
11
import { ODataRequest, ODataResponse } from '../resources';
2-
import { ODataBaseCache } from './cache';
2+
import { ODataBaseCache, ODataCacheEntry } from './cache';
33

44
export class ODataInMemoryCache extends ODataBaseCache {
5+
entries: Map<string, ODataCacheEntry<any>>;
56
constructor({ maxAge }: { maxAge?: number } = {}) {
67
super({ maxAge });
8+
this.entries = new Map<string, ODataCacheEntry<any>>();
9+
}
10+
11+
/**
12+
* Put some payload in the cache
13+
* @param name The name for the entry
14+
* @param payload The payload to store in the cache
15+
* @param maxAge The maximum age for the entry
16+
* @param scope The scope for the entry
17+
* @param tags The tags for the entry
18+
*/
19+
override put<T>(
20+
name: string,
21+
payload: T,
22+
{ maxAge, scope, tags }: { maxAge?: number; scope?: string[]; tags?: string[] } = {},
23+
) {
24+
const entry = this.buildEntry<T>(payload, { maxAge, tags });
25+
const key = this.buildKey([...(scope ?? []), name]);
26+
this.entries.set(key, entry);
27+
}
28+
29+
/**
30+
* Return the payload from the cache if it exists and is not expired
31+
* @param name The name of the entry
32+
* @param scope The scope of the entry
33+
* @returns The payload of the entry
34+
*/
35+
override get<T>(name: string, { scope }: { scope?: string[] } = {}): T | undefined {
36+
const key = this.buildKey([...(scope || []), name]);
37+
const entry = this.entries.get(key);
38+
return entry !== undefined && !this.isExpired(entry) ? entry.payload : undefined;
739
}
840

941
/**
@@ -30,4 +62,33 @@ export class ODataInMemoryCache extends ODataBaseCache {
3062
let scope = this.scope(req);
3163
return this.get(req.cacheKey, { scope });
3264
}
65+
66+
/**
67+
* Remove all cache entries that are matching with the given options
68+
* @param options The options to forget
69+
*/
70+
override forget({
71+
name,
72+
scope = [],
73+
tags = [],
74+
}: { name?: string, scope?: string[]; tags?: string[] } = {}) {
75+
if (name) scope.push(name);
76+
const key = scope.length > 0 ? this.buildKey(scope) : undefined;
77+
this.entries.forEach((entry, k) => {
78+
if (
79+
this.isExpired(entry) || // Expired
80+
(key !== undefined && k.startsWith(key)) || // Key
81+
(tags.length > 0 && tags.some((t) => entry.tags.indexOf(t) !== -1)) // Tags
82+
) {
83+
this.entries.delete(k);
84+
}
85+
});
86+
}
87+
88+
/**
89+
* Remove all cache entries
90+
*/
91+
override flush() {
92+
this.entries = new Map<string, ODataCacheEntry<any>>();
93+
}
3394
}

projects/angular-odata/src/lib/cache/storage.ts

Lines changed: 44 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -2,59 +2,69 @@ import { ODataRequest, ODataResponse, ODataResponseJson } from '../resources';
22
import { ODataBaseCache, ODataCacheEntry } from './cache';
33

44
export class ODataInStorageCache extends ODataBaseCache {
5-
name: string;
5+
prefix: string;
66
storage: Storage;
77

88
constructor({
9-
name,
9+
prefix,
1010
storage = sessionStorage,
1111
maxAge,
1212
}: {
1313
maxAge?: number;
14-
name: string;
14+
prefix: string;
1515
storage?: Storage;
1616
}) {
1717
super({ maxAge });
18-
this.name = name;
18+
this.prefix = prefix;
1919
this.storage = storage;
20-
this.restore();
21-
window.addEventListener('beforeunload', () => this.store());
2220
}
2321

24-
/**
25-
* Store the cache in the storage
26-
*/
27-
store() {
28-
this.storage.setItem(
29-
this.name,
30-
JSON.stringify(
31-
Array.from(this.entries.entries()).map(([key, entry]) => [
32-
key,
33-
{
34-
...entry,
35-
payload:
36-
entry.payload instanceof ODataResponse ? entry.payload.toJson() : entry.payload,
37-
} as ODataCacheEntry<any>,
38-
]),
39-
),
40-
);
22+
override buildKey(names: string[]): string {
23+
return super.buildKey([this.prefix, ...names]);
4124
}
4225

43-
/**
44-
* Restore the cache from the storage
45-
*/
46-
restore() {
47-
this.entries = new Map<string, ODataCacheEntry<any>>(
48-
JSON.parse(this.storage.getItem(this.name) || '[]'),
49-
);
26+
override put<T>(
27+
name: string,
28+
payload: T,
29+
{ maxAge, scope, tags }: { maxAge?: number; scope?: string[]; tags?: string[] } = {},
30+
) {
31+
const entry = this.buildEntry<T>(payload, { maxAge, tags });
32+
const key = this.buildKey([...(scope ?? []), name]);
33+
this.storage.setItem(key, JSON.stringify(entry));
34+
}
35+
36+
override get<T>(name: string, { scope }: { scope?: string[] } = {}): T | undefined {
37+
const key = this.buildKey([...(scope || []), name]);
38+
const entry = JSON.parse(this.storage.getItem(key) ?? "{}");
39+
return entry !== undefined && !this.isExpired(entry) ? entry.payload : undefined;
40+
}
41+
42+
override forget({
43+
name,
44+
scope = [],
45+
tags = [],
46+
}: { name?: string, scope?: string[]; tags?: string[] }) {
47+
if (name) scope.push(name);
48+
const key = scope.length > 0 ? this.buildKey(scope) : undefined;
49+
Object.keys(this.storage).filter(k => k.startsWith(this.prefix)).forEach(k => {
50+
const entry = JSON.parse(this.storage.getItem(k) ?? "{}");
51+
if (
52+
this.isExpired(entry) || // Expired
53+
(key !== undefined && k.startsWith(key)) || // Key
54+
(tags.length > 0 && tags.some((t) => entry.tags.indexOf(t) !== -1)) // Tags
55+
) {
56+
this.storage.removeItem(k);
57+
}
58+
});
5059
}
5160

5261
/**
5362
* Flush the cache and clean the storage
5463
*/
5564
override flush() {
56-
super.flush();
57-
this.store();
65+
Object.keys(this.storage).filter(k => k.startsWith(this.prefix)).forEach(k => {
66+
this.storage.removeItem(k);
67+
});
5868
}
5969

6070
/**
@@ -65,7 +75,7 @@ export class ODataInStorageCache extends ODataBaseCache {
6575
override putResponse(req: ODataRequest<any>, res: ODataResponse<any>) {
6676
const scope = this.scope(req);
6777
const tags = this.tags(res);
68-
this.put<ODataResponse<any>>(req.cacheKey, res, {
78+
this.put<ODataResponseJson<any>>(req.cacheKey, res.toJson(), {
6979
maxAge: req.maxAge ?? res.options.maxAge,
7080
scope,
7181
tags,
@@ -81,10 +91,6 @@ export class ODataInStorageCache extends ODataBaseCache {
8191
const scope = this.scope(req);
8292
const data = this.get<ODataResponseJson<any>>(req.cacheKey, { scope });
8393

84-
return data instanceof ODataResponse
85-
? data
86-
: data !== undefined
87-
? ODataResponse.fromJson(req, data)
88-
: undefined;
94+
return data !== undefined ? ODataResponse.fromJson(req, data) : undefined;
8995
}
9096
}

0 commit comments

Comments
 (0)