@markwharton/liquidplanner 1.8.1 → 1.8.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cache.d.ts +13 -2
- package/dist/cache.js +33 -5
- package/package.json +1 -1
package/dist/cache.d.ts
CHANGED
|
@@ -1,19 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simple in-memory TTL cache
|
|
2
|
+
* Simple in-memory TTL cache with request coalescing
|
|
3
3
|
*
|
|
4
4
|
* Provides per-instance memoization for LPClient API responses.
|
|
5
5
|
* In serverless environments (Azure Functions, Static Web Apps),
|
|
6
6
|
* module-level state persists across warm invocations within the
|
|
7
7
|
* same instance — this cache leverages that behavior.
|
|
8
8
|
*
|
|
9
|
+
* Request coalescing: when multiple concurrent callers request the
|
|
10
|
+
* same expired key, only one factory call is made. All callers
|
|
11
|
+
* receive the same resolved value (or the same rejection).
|
|
12
|
+
*
|
|
9
13
|
* Not a distributed cache: each instance has its own cache.
|
|
10
14
|
* Cold starts and instance recycling naturally clear stale data.
|
|
11
15
|
*/
|
|
12
16
|
export declare class TTLCache {
|
|
13
17
|
private store;
|
|
18
|
+
private inflight;
|
|
14
19
|
/**
|
|
15
20
|
* Get a cached value, or call the factory to populate it.
|
|
16
21
|
*
|
|
22
|
+
* If a factory call is already in progress for this key,
|
|
23
|
+
* returns the existing promise instead of starting a duplicate.
|
|
24
|
+
*
|
|
17
25
|
* @param key - Cache key
|
|
18
26
|
* @param ttlMs - Time-to-live in milliseconds
|
|
19
27
|
* @param factory - Async function to produce the value on cache miss
|
|
@@ -22,11 +30,14 @@ export declare class TTLCache {
|
|
|
22
30
|
/**
|
|
23
31
|
* Invalidate cache entries matching a key prefix.
|
|
24
32
|
*
|
|
33
|
+
* Also cancels any in-flight requests for matching keys,
|
|
34
|
+
* so subsequent calls will start fresh factory invocations.
|
|
35
|
+
*
|
|
25
36
|
* Example: invalidate('timesheet:') clears all timesheet entries.
|
|
26
37
|
*/
|
|
27
38
|
invalidate(prefix: string): void;
|
|
28
39
|
/**
|
|
29
|
-
* Clear all cached data.
|
|
40
|
+
* Clear all cached data and in-flight requests.
|
|
30
41
|
*/
|
|
31
42
|
clear(): void;
|
|
32
43
|
}
|
package/dist/cache.js
CHANGED
|
@@ -1,21 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Simple in-memory TTL cache
|
|
2
|
+
* Simple in-memory TTL cache with request coalescing
|
|
3
3
|
*
|
|
4
4
|
* Provides per-instance memoization for LPClient API responses.
|
|
5
5
|
* In serverless environments (Azure Functions, Static Web Apps),
|
|
6
6
|
* module-level state persists across warm invocations within the
|
|
7
7
|
* same instance — this cache leverages that behavior.
|
|
8
8
|
*
|
|
9
|
+
* Request coalescing: when multiple concurrent callers request the
|
|
10
|
+
* same expired key, only one factory call is made. All callers
|
|
11
|
+
* receive the same resolved value (or the same rejection).
|
|
12
|
+
*
|
|
9
13
|
* Not a distributed cache: each instance has its own cache.
|
|
10
14
|
* Cold starts and instance recycling naturally clear stale data.
|
|
11
15
|
*/
|
|
12
16
|
export class TTLCache {
|
|
13
17
|
constructor() {
|
|
14
18
|
this.store = new Map();
|
|
19
|
+
this.inflight = new Map();
|
|
15
20
|
}
|
|
16
21
|
/**
|
|
17
22
|
* Get a cached value, or call the factory to populate it.
|
|
18
23
|
*
|
|
24
|
+
* If a factory call is already in progress for this key,
|
|
25
|
+
* returns the existing promise instead of starting a duplicate.
|
|
26
|
+
*
|
|
19
27
|
* @param key - Cache key
|
|
20
28
|
* @param ttlMs - Time-to-live in milliseconds
|
|
21
29
|
* @param factory - Async function to produce the value on cache miss
|
|
@@ -25,13 +33,27 @@ export class TTLCache {
|
|
|
25
33
|
if (existing && existing.expiresAt > Date.now()) {
|
|
26
34
|
return existing.data;
|
|
27
35
|
}
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
const pending = this.inflight.get(key);
|
|
37
|
+
if (pending) {
|
|
38
|
+
return pending;
|
|
39
|
+
}
|
|
40
|
+
const promise = factory().then((data) => {
|
|
41
|
+
this.store.set(key, { data, expiresAt: Date.now() + ttlMs });
|
|
42
|
+
this.inflight.delete(key);
|
|
43
|
+
return data;
|
|
44
|
+
}, (err) => {
|
|
45
|
+
this.inflight.delete(key);
|
|
46
|
+
throw err;
|
|
47
|
+
});
|
|
48
|
+
this.inflight.set(key, promise);
|
|
49
|
+
return promise;
|
|
31
50
|
}
|
|
32
51
|
/**
|
|
33
52
|
* Invalidate cache entries matching a key prefix.
|
|
34
53
|
*
|
|
54
|
+
* Also cancels any in-flight requests for matching keys,
|
|
55
|
+
* so subsequent calls will start fresh factory invocations.
|
|
56
|
+
*
|
|
35
57
|
* Example: invalidate('timesheet:') clears all timesheet entries.
|
|
36
58
|
*/
|
|
37
59
|
invalidate(prefix) {
|
|
@@ -40,11 +62,17 @@ export class TTLCache {
|
|
|
40
62
|
this.store.delete(key);
|
|
41
63
|
}
|
|
42
64
|
}
|
|
65
|
+
for (const key of this.inflight.keys()) {
|
|
66
|
+
if (key.startsWith(prefix)) {
|
|
67
|
+
this.inflight.delete(key);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
43
70
|
}
|
|
44
71
|
/**
|
|
45
|
-
* Clear all cached data.
|
|
72
|
+
* Clear all cached data and in-flight requests.
|
|
46
73
|
*/
|
|
47
74
|
clear() {
|
|
48
75
|
this.store.clear();
|
|
76
|
+
this.inflight.clear();
|
|
49
77
|
}
|
|
50
78
|
}
|