Skip to content
This repository was archived by the owner on Nov 2, 2023. It is now read-only.

Commit 316cd6b

Browse files
committed
feat(core): Allow to provide a custom cacheKeyGenerator
1 parent eeaa47e commit 316cd6b

File tree

7 files changed

+153
-99
lines changed

7 files changed

+153
-99
lines changed

.travis.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
language: node_js
2+
sudo: false
3+
4+
os:
5+
- linux
6+
7+
node_js:
8+
- "node"
9+
10+
script:
11+
- npm test

README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@ RequestRegistry is a generic utility (~1.5kb gziped) to be used as part of your
44

55
## Getting started
66

7-
RequestRegistry works with vanilla javascript.
8-
The optional build in typescript support will allow you to keep your data flow very maintainable.
7+
RequestRegistry works with vanilla javascript.
8+
9+
```ts
10+
import { createGetEndpoint } from "request-registry";
11+
12+
const userEndpoint = createGetEndpoint({
13+
url: keys => `http://example.com/user/${keys.id}`
14+
});
15+
16+
userEndpoint({ id: "4" }).then(data => console.log(data.firstName));
17+
```
18+
19+
The optional build in typescript support will allow you to keep your data flow more maintainable
20+
as it allows you to understand which data has to be send to the backend and which data will be returned.
921

1022
```ts
1123
import { createGetEndpoint } from "request-registry";
@@ -22,8 +34,9 @@ type Output = {
2234
const userEndpoint = createGetEndpoint<Input, Output>({
2335
url: keys => `http://example.com/user/${keys.id}`
2436
});
25-
26-
userEndpoint({ id: "4" }).then(data => console.log(data.firstName));
37+
// The next lines will be fully typesafe without the need of adding any types
38+
const data = await userEndpoint({ id: "4" });
39+
console.log(data.firstName);
2740
```
2841

2942
All CRUD operations are supported, for example a POST request would look like this:
@@ -137,6 +150,22 @@ const userLoader = createGetEndpoint({
137150
});
138151
```
139152

153+
## Connecting Caches
154+
155+
Most of the time mutiation endpoints (POST / PUT / DELTE) influence the validity of data endpoints (GET).
156+
For example setting a users name should invalidate all caches including the old name.
157+
RequestRegistry allows to define those relations for every endpoint using the `afterSuccess` hook:
158+
159+
```ts
160+
const getUserName = createGetEndpoint({
161+
url: keys => `http://example.com/user/${keys.id}/get`,
162+
})
163+
const setUserName = createPostEndpoint({
164+
url: keys => `http://example.com/user/${keys.id}/update`,
165+
afterSuccess: () => getUserName.clearCache();
166+
})
167+
```
168+
140169
## Custom Headers
141170

142171
The `headers` option allows to customize request headers.

core/README.md

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,20 @@ RequestRegistry is a generic utility (~1.5kb gziped) to be used as part of your
44

55
## Getting started
66

7-
RequestRegistry works with vanilla javascript.
8-
The optional build in typescript support will allow you to keep your data flow very maintainable.
7+
RequestRegistry works with vanilla javascript.
8+
9+
```ts
10+
import { createGetEndpoint } from "request-registry";
11+
12+
const userEndpoint = createGetEndpoint({
13+
url: keys => `http://example.com/user/${keys.id}`
14+
});
15+
16+
userEndpoint({ id: "4" }).then(data => console.log(data.firstName));
17+
```
18+
19+
The optional build in typescript support will allow you to keep your data flow more maintainable
20+
as it allows you to understand which data has to be send to the backend and which data will be returned.
921

1022
```ts
1123
import { createGetEndpoint } from "request-registry";
@@ -22,8 +34,9 @@ type Output = {
2234
const userEndpoint = createGetEndpoint<Input, Output>({
2335
url: keys => `http://example.com/user/${keys.id}`
2436
});
25-
26-
userEndpoint({ id: "4" }).then(data => console.log(data.firstName));
37+
// The next lines will be fully typesafe without the need of adding any types
38+
const data = await userEndpoint({ id: "4" });
39+
console.log(data.firstName);
2740
```
2841

2942
All CRUD operations are supported, for example a POST request would look like this:
@@ -137,6 +150,22 @@ const userLoader = createGetEndpoint({
137150
});
138151
```
139152

153+
## Connecting Caches
154+
155+
Most of the time mutiation endpoints (POST / PUT / DELTE) influence the validity of data endpoints (GET).
156+
For example setting a users name should invalidate all caches including the old name.
157+
RequestRegistry allows to define those relations for every endpoint using the `afterSuccess` hook:
158+
159+
```ts
160+
const getUserName = createGetEndpoint({
161+
url: keys => `http://example.com/user/${keys.id}/get`,
162+
})
163+
const setUserName = createPostEndpoint({
164+
url: keys => `http://example.com/user/${keys.id}/update`,
165+
afterSuccess: () => getUserName.clearCache();
166+
})
167+
```
168+
140169
## Custom Headers
141170

142171
The `headers` option allows to customize request headers.

core/src/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,11 +78,6 @@ export interface EndpointGetFunction<TKeys extends {}, TResult extends unknown>
7878
* Cache Key
7979
*/
8080
getCacheKey: (keys: TKeys) => string;
81-
/**
82-
* The time of the first write into the cache
83-
* will be reset on clearCache
84-
*/
85-
state: { cacheCreation?: Date };
8681
/**
8782
* Helper to prevent memory leaks
8883
*

core/src/lib/CacheStore.ts

Lines changed: 32 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface CacheStore<TKeys, TResult> {
2626
* The time of the first write into the cache
2727
* will be reset on clearCache
2828
*/
29-
state: { cacheCreation?: Date };
29+
_state: { _cacheCreation?: Date };
3030
/**
3131
* Helper to prevent memory leaks
3232
*
@@ -78,9 +78,11 @@ export interface EndpointCacheOptions<
7878
cache: Cache<TResult>,
7979
api: any
8080
) => boolean;
81+
/** A custom cacheKey implementation */
82+
cacheKey?: (keys: TKeys) => string;
8183
}
8284

83-
export type Cachable<T = string> = { cacheKey: string; value: T };
85+
export type Cachable<T = string> = { _cacheKey: string; _value: T };
8486

8587
/**
8688
* The createCacheStore provides util functions and events
@@ -94,16 +96,17 @@ export function createCacheStore<
9496
url: (keys: TKeys) => string,
9597
headerTemplate: THeader,
9698
headerKeys: Array<keyof THeader>,
97-
cacheMap?: Cache<TResult>
99+
cacheMap?: Cache<TResult>,
100+
cacheKeyGenerator?: (keys: TKeys, baseCacheKey: string) => string
98101
): CacheStore<TKeys, TResult> {
99102
/** Helper map to track which CacheKeys can be garbage collected */
100103
const keepInCacheTracker = new Map<
101104
/** CacheKey */ string,
102105
{
103106
/** The amount of cache consumers for the given CacheKey */
104-
count: number;
107+
_count: number;
105108
/** The setTimeout to cleanup the given cache once all consumers are gone */
106-
timeout?: number;
109+
_timeout?: number;
107110
}
108111
>();
109112
const cache: Cache<TResult> =
@@ -113,17 +116,12 @@ export function createCacheStore<
113116
}>();
114117

115118
const getUrlCacheKey = (keys: TKeys): string =>
116-
getCacheKey(
117-
getUrl(keys, url),
118-
getHeaders(keys, headerTemplate, headerKeys)
119-
);
119+
getCacheKey(url(keys), getHeaders(keys, headerTemplate, headerKeys));
120120

121-
const cacheState: { cacheCreation?: Date } = {};
122-
/** Clears all cached requests */
123-
const createCacheStore: {
121+
const cacheState: { _cacheCreation?: Date } = {};
122+
const cacheStoreController: {
124123
cache: Cache<TResult>;
125-
state: {};
126-
cacheCreation?: Date;
124+
_state: {};
127125
getCacheKey: (keys: TKeys) => string;
128126
keepInCache: (keys: TKeys, timeout?: number) => () => void;
129127
clearCache: () => Promise<any[]>;
@@ -139,23 +137,24 @@ export function createCacheStore<
139137
) => void;
140138
} = {
141139
cache,
142-
state: cacheState,
143-
getCacheKey: getUrlCacheKey,
140+
_state: cacheState,
141+
getCacheKey: cacheKeyGenerator
142+
? keys => cacheKeyGenerator(keys, getUrlCacheKey(keys))
143+
: getUrlCacheKey,
144144
clearCache: () => {
145-
const previousCacheCreation = cacheState.cacheCreation;
145+
const previousCacheCreation = cacheState._cacheCreation;
146146
if (previousCacheCreation) {
147-
cacheState.cacheCreation = undefined;
147+
cacheState._cacheCreation = undefined;
148+
keepInCacheTracker.clear();
148149
cache.clear();
149150
return Promise.all(
150151
emitter.emit("cacheClear", previousCacheCreation)
151152
);
152153
}
153154
return Promise.resolve([]);
154155
},
155-
on: ((event, callback) =>
156-
emitter.on(event, callback)) as typeof emitter.on,
157-
off: ((event, callback) =>
158-
emitter.off(event, callback)) as typeof emitter.off,
156+
on: emitter.on.bind(emitter),
157+
off: emitter.off.bind(emitter),
159158
/**
160159
* Helper to prevent memory leaks for cached components
161160
*
@@ -165,9 +164,9 @@ export function createCacheStore<
165164
*/
166165
keepInCache: (keys: TKeys, timeout?: number) => {
167166
const cacheKey = getUrlCacheKey(keys);
168-
const consumer = keepInCacheTracker.get(cacheKey) || { count: 0 };
169-
consumer.count++;
170-
clearTimeout(consumer.timeout);
167+
const consumer = keepInCacheTracker.get(cacheKey) || { _count: 0 };
168+
consumer._count++;
169+
clearTimeout(consumer._timeout);
171170
keepInCacheTracker.set(cacheKey, consumer);
172171
let disposed = false;
173172
// Return the release from cache function
@@ -176,37 +175,27 @@ export function createCacheStore<
176175
const consumer = keepInCacheTracker.get(cacheKey);
177176
// If this is the last consumer and it has not been disposed
178177
// start the cleanup timer
179-
if (!disposed && consumer && --consumer.count <= 0) {
178+
if (!disposed && consumer && --consumer._count <= 0) {
180179
disposed = true;
181-
consumer.timeout = setTimeout(() => {
180+
consumer._timeout = setTimeout(() => {
181+
keepInCacheTracker.delete(cacheKey);
182182
cache.delete(cacheKey);
183183
}, timeout || 20000) as any;
184184
}
185185
};
186186
}
187187
};
188-
return createCacheStore;
188+
return cacheStoreController;
189189
}
190190

191191
/**
192192
* Turns the cachable url and header into a cache key
193193
*/
194194
export function getCacheKey(
195-
url: Cachable,
195+
url: string,
196196
header: Cachable<{ [key: string]: string }>
197197
): string {
198-
return header.cacheKey + " - " + url.cacheKey;
199-
}
200-
201-
/**
202-
* Get url for the given keys
203-
*/
204-
export function getUrl<TKeys>(
205-
keys: TKeys,
206-
urlTemplate: (keys: TKeys) => string
207-
): Cachable {
208-
const url = urlTemplate(keys);
209-
return { cacheKey: url, value: url };
198+
return header._cacheKey + " - " + url;
210199
}
211200

212201
/**
@@ -233,7 +222,7 @@ export function getHeaders<
233222
headers[headerKey as string] = header;
234223
});
235224
return {
236-
cacheKey,
237-
value: headers
225+
_cacheKey: cacheKey,
226+
_value: headers
238227
};
239228
}

0 commit comments

Comments
 (0)