@hyperttp/cache 1.1.1 → 1.1.6
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/README.md +0 -5
- package/package.json +10 -6
- package/plugin.d.ts +38 -10
- package/plugin.d.ts.map +1 -1
- package/plugin.js +73 -126
- package/plugin.js.map +1 -1
- package/types/cache.d.ts +60 -8
- package/types/cache.d.ts.map +1 -1
- package/utils/CacheManager.d.ts +37 -44
- package/utils/CacheManager.d.ts.map +1 -1
- package/utils/CacheManager.js +102 -54
- package/utils/CacheManager.js.map +1 -1
- package/utils/createHttpResponse.d.ts +21 -0
- package/utils/createHttpResponse.d.ts.map +1 -0
- package/utils/createHttpResponse.js +60 -0
- package/utils/createHttpResponse.js.map +1 -0
package/README.md
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
```markdown
|
|
2
1
|
# hyperttp-cache
|
|
3
2
|
|
|
4
3
|
> [Русский](https://github.com/IT-IF-OR/hyperttp-cache/tree/main/lang/ru) | English
|
|
@@ -136,7 +135,3 @@ from the map to ensure subsequent retries targeting the failed endpoint are neve
|
|
|
136
135
|
## 📄 License
|
|
137
136
|
|
|
138
137
|
MIT
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
|
|
142
|
-
```
|
package/package.json
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperttp/cache",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.6",
|
|
4
4
|
"description": "High-performance LRU caching plugin for Hyperttp client",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./index.js",
|
|
7
7
|
"types": "./index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./index.d.ts",
|
|
11
|
+
"import": "./index.js",
|
|
12
|
+
"require": "./index.js"
|
|
13
|
+
},
|
|
14
|
+
"./package.json": "./package.json"
|
|
15
|
+
},
|
|
8
16
|
"keywords": [
|
|
9
17
|
"hyperttp"
|
|
10
18
|
],
|
|
@@ -14,12 +22,8 @@
|
|
|
14
22
|
"type": "git",
|
|
15
23
|
"url": "git+https://github.com/IT-IF-OR/hyperttp-cache.git"
|
|
16
24
|
},
|
|
17
|
-
"dependencies": {
|
|
18
|
-
"@hyperttp/types": "^0.1.1",
|
|
19
|
-
"lru-cache": "^11.5.1"
|
|
20
|
-
},
|
|
21
25
|
"peerDependencies": {
|
|
22
|
-
"@hyperttp/
|
|
26
|
+
"@hyperttp/types": "^0.1.5"
|
|
23
27
|
},
|
|
24
28
|
"private": false
|
|
25
29
|
}
|
package/plugin.d.ts
CHANGED
|
@@ -1,24 +1,52 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { CacheManagerOptions } from "./types/cache.js";
|
|
1
|
+
import { CacheManagerOptions, LightweightResponse } from "./types/cache.js";
|
|
3
2
|
import { CacheManager } from "./utils/CacheManager.js";
|
|
3
|
+
import type { HyperPlugin } from "@hyperttp/types";
|
|
4
|
+
/**
|
|
5
|
+
* @en Extends the PluginContext to include the cache manager instance.
|
|
6
|
+
* @ru Расширяет PluginContext, добавляя экземпляр менеджера кэша.
|
|
7
|
+
*/
|
|
4
8
|
declare module "@hyperttp/types" {
|
|
5
9
|
interface PluginContext {
|
|
6
|
-
|
|
10
|
+
/**
|
|
11
|
+
* @en The active cache manager instance.
|
|
12
|
+
* @ru Активный экземпляр менеджера кэша.
|
|
13
|
+
*/
|
|
14
|
+
cache?: CacheManager<LightweightResponse>;
|
|
7
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* @en Extends HyperttpPluginsExtension to include cache configuration options.
|
|
18
|
+
* @ru Расширяет HyperttpPluginsExtension, добавляя опции конфигурации кэша.
|
|
19
|
+
*/
|
|
8
20
|
interface HyperttpPluginsExtension {
|
|
21
|
+
/**
|
|
22
|
+
* @en Cache configuration and enablement flags.
|
|
23
|
+
* @ru Конфигурация кэша и флаги включения.
|
|
24
|
+
*/
|
|
9
25
|
cache?: CacheManagerOptions & {
|
|
26
|
+
/**
|
|
27
|
+
* @en Enables or disables the caching plugin.
|
|
28
|
+
* @ru Включает или отключает плагин кэширования.
|
|
29
|
+
*/
|
|
10
30
|
enabled: boolean;
|
|
31
|
+
/**
|
|
32
|
+
* @en List of HTTP methods allowed for caching (e.g., ["GET"]).
|
|
33
|
+
* @ru Список HTTP-методов, разрешенных для кэширования (например, ["GET"]).
|
|
34
|
+
*/
|
|
35
|
+
methods?: string[];
|
|
11
36
|
};
|
|
12
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* @en Extends IHyperCore with cache management methods.
|
|
40
|
+
* @ru Расширяет IHyperCore методами управления кэшем.
|
|
41
|
+
*/
|
|
13
42
|
interface IHyperCore {
|
|
43
|
+
/**
|
|
44
|
+
* @en Clears the cache. If a key is provided, only that entry is removed.
|
|
45
|
+
* @ru Очищает кэш. Если указан ключ, удаляется только соответствующая запись.
|
|
46
|
+
* @param key - Optional cache key to delete.
|
|
47
|
+
*/
|
|
14
48
|
clearCache(key?: string): void;
|
|
15
|
-
getStats?(): Record<string, any>;
|
|
16
49
|
}
|
|
17
50
|
}
|
|
18
|
-
|
|
19
|
-
* @ru Фабрика плагина кэширования и дедупликации конкурентных запросов для HyperCore.
|
|
20
|
-
* @en Cache management and concurrent request deduplication plugin factory for HyperCore.
|
|
21
|
-
* @returns Модуль расширения ядра в рамках линейного жизненного цикла. / Configured extension plugin container.
|
|
22
|
-
*/
|
|
23
|
-
export declare function withCache(): HyperPlugin;
|
|
51
|
+
export declare function withCache(options?: CacheManagerOptions): HyperPlugin;
|
|
24
52
|
//# sourceMappingURL=plugin.d.ts.map
|
package/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAC5E,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AAEvD,OAAO,KAAK,EAGV,WAAW,EAEZ,MAAM,iBAAiB,CAAC;AAEzB;;;GAGG;AACH,OAAO,QAAQ,iBAAiB,CAAC;IAC/B,UAAU,aAAa;QACrB;;;WAGG;QACH,KAAK,CAAC,EAAE,YAAY,CAAC,mBAAmB,CAAC,CAAC;KAC3C;IACD;;;OAGG;IACH,UAAU,wBAAwB;QAChC;;;WAGG;QACH,KAAK,CAAC,EAAE,mBAAmB,GAAG;YAC5B;;;eAGG;YACH,OAAO,EAAE,OAAO,CAAC;YACjB;;;eAGG;YACH,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;SACpB,CAAC;KACH;IAED;;;OAGG;IACH,UAAU,UAAU;QAClB;;;;WAIG;QACH,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAChC;CACF;AAUD,wBAAgB,SAAS,CAAC,OAAO,CAAC,EAAE,mBAAmB,GAAG,WAAW,CAiIpE"}
|
package/plugin.js
CHANGED
|
@@ -1,156 +1,103 @@
|
|
|
1
1
|
import { CacheManager } from "./utils/CacheManager.js";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
export function withCache() {
|
|
8
|
-
/**
|
|
9
|
-
* @private
|
|
10
|
-
* @ru Изолированный инстанс менеджера кэша для текущего клиента.
|
|
11
|
-
* @en Isolated cache manager orchestration instance allocated for the target client.
|
|
12
|
-
*/
|
|
2
|
+
import { createHttpResponse } from "./utils/createHttpResponse.js";
|
|
3
|
+
export function withCache(options) {
|
|
4
|
+
if (options?.enabled === false) {
|
|
5
|
+
return { name: "hyperttp-cache" };
|
|
6
|
+
}
|
|
13
7
|
let cache;
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* @en Set of HTTP request methods authorized for downstream response caching.
|
|
18
|
-
*/
|
|
19
|
-
let allowedMethods;
|
|
20
|
-
/**
|
|
21
|
-
* @private
|
|
22
|
-
* @ru Карта активных сетевых запросов для предотвращения каскадного заваливания бэкенда (Cache Stampede).
|
|
23
|
-
* @en Registry of concurrent requests in execution to prevent backend server breakdown (Cache Stampede).
|
|
24
|
-
*/
|
|
8
|
+
const allowedMethods = new Set(options?.methods
|
|
9
|
+
? options.methods.map((m) => m.toUpperCase())
|
|
10
|
+
: ["GET", "HEAD"]);
|
|
25
11
|
const inFlight = new Map();
|
|
26
12
|
return {
|
|
27
13
|
name: "hyperttp-cache",
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
enabled: (config) => {
|
|
35
|
-
return !!config.cache?.enabled;
|
|
36
|
-
},
|
|
37
|
-
/**
|
|
38
|
-
* @ru Однократный хук инициализации. Настраивает менеджер кэша и внедряет методы отладки/очистки в ядро.
|
|
39
|
-
* @en One-time setup context hook. Orchestrates the cache manager and extends telemetry/purge interfaces inside the core.
|
|
40
|
-
* @param ctx - Общий контекст окружения плагина. / Shared plugin execution context metadata.
|
|
41
|
-
*/
|
|
42
|
-
setup(ctx) {
|
|
43
|
-
const { core, config } = ctx;
|
|
44
|
-
cache = new CacheManager(config?.cache);
|
|
45
|
-
ctx.cache = cache;
|
|
46
|
-
const methods = config?.cache?.methods ?? ["GET"];
|
|
47
|
-
allowedMethods = new Set(methods.map((m) => m.toUpperCase()));
|
|
48
|
-
if (core && typeof core.getStats === "function") {
|
|
49
|
-
const originalGetStats = core.getStats;
|
|
50
|
-
core.getStats = function () {
|
|
51
|
-
const stats = originalGetStats.call(this);
|
|
52
|
-
if (stats) {
|
|
53
|
-
stats.cacheSize = cache.size;
|
|
54
|
-
}
|
|
55
|
-
return stats;
|
|
56
|
-
};
|
|
57
|
-
}
|
|
58
|
-
core.clearCache = (key) => {
|
|
59
|
-
return key ? cache.delete(key) : cache.clear();
|
|
60
|
-
};
|
|
14
|
+
setup() {
|
|
15
|
+
cache = new CacheManager({
|
|
16
|
+
maxSize: options?.maxSize ?? 1000,
|
|
17
|
+
ttl: options?.ttl ?? 300_000,
|
|
18
|
+
updateAgeOnGet: options?.updateAgeOnGet ?? true,
|
|
19
|
+
});
|
|
61
20
|
},
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
* @en Request phase interceptor. Evaluates local cache hits, appends conditional headers, or hooks into active in-flight targets.
|
|
65
|
-
* @param req - Сконфигурированный внутренний объект запроса. / Contextual internal request parameters.
|
|
66
|
-
* @param ctx - Общий контекст окружения плагина. / Shared plugin execution context metadata.
|
|
67
|
-
* @returns Мгновенный изолированный ответ из кэша, обертку гонки или void для продолжения сетевого цикла. / Short-circuit response clone, matching follower promise tracker, or void execution.
|
|
68
|
-
*/
|
|
69
|
-
onRequest(req,
|
|
70
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
71
|
-
_ctx) {
|
|
72
|
-
const method = req.method.toUpperCase();
|
|
73
|
-
if (!allowedMethods.has(method)) {
|
|
21
|
+
onRequest(req) {
|
|
22
|
+
if (!allowedMethods.has(req.method.toUpperCase()))
|
|
74
23
|
return;
|
|
75
|
-
}
|
|
76
24
|
const cacheKey = req.url;
|
|
77
25
|
const cachedEntry = cache.getWithMetadata(cacheKey);
|
|
78
|
-
if (cachedEntry
|
|
79
|
-
|
|
80
|
-
!cachedEntry.lastModified) {
|
|
81
|
-
return cachedEntry.data.clone();
|
|
26
|
+
if (cachedEntry && !cachedEntry.etag && !cachedEntry.lastModified) {
|
|
27
|
+
return createHttpResponse(cachedEntry.data);
|
|
82
28
|
}
|
|
83
|
-
|
|
84
|
-
if (cachedEntry !== undefined) {
|
|
29
|
+
if (cachedEntry) {
|
|
85
30
|
req.headers = { ...req.headers };
|
|
86
31
|
if (cachedEntry.etag)
|
|
87
32
|
req.headers["if-none-match"] = cachedEntry.etag;
|
|
88
|
-
if (cachedEntry.lastModified)
|
|
33
|
+
if (cachedEntry.lastModified) {
|
|
89
34
|
req.headers["if-modified-since"] = cachedEntry.lastModified;
|
|
35
|
+
}
|
|
90
36
|
}
|
|
91
37
|
const currentInFlight = inFlight.get(cacheKey);
|
|
92
38
|
if (currentInFlight) {
|
|
93
|
-
return
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
currentInFlight.listeners.push({ resolve, reject });
|
|
41
|
+
});
|
|
94
42
|
}
|
|
95
|
-
|
|
96
|
-
let rejectFn;
|
|
97
|
-
const promise = new Promise((res, rej) => {
|
|
98
|
-
resolveFn = res;
|
|
99
|
-
rejectFn = rej;
|
|
100
|
-
});
|
|
101
|
-
promise.catch(() => { });
|
|
102
|
-
inFlight.set(cacheKey, { promise, resolve: resolveFn, reject: rejectFn });
|
|
43
|
+
inFlight.set(cacheKey, { listeners: [], timestamp: Date.now() });
|
|
103
44
|
},
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*/
|
|
108
|
-
onResponse(res, req,
|
|
109
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
110
|
-
_ctx) {
|
|
45
|
+
onResponse(res, req) {
|
|
46
|
+
if (!req)
|
|
47
|
+
return;
|
|
111
48
|
const cacheKey = req.url;
|
|
112
49
|
const trigger = inFlight.get(cacheKey);
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
50
|
+
if (res.status === 304) {
|
|
51
|
+
if (res.body &&
|
|
52
|
+
typeof res.body.cancel === "function") {
|
|
53
|
+
res.body.cancel().catch(() => { });
|
|
54
|
+
}
|
|
55
|
+
const cachedEntry = cache.getWithMetadata(cacheKey);
|
|
56
|
+
if (cachedEntry) {
|
|
57
|
+
const restoredRes = createHttpResponse(cachedEntry.data);
|
|
58
|
+
res.status = restoredRes.status;
|
|
59
|
+
res.headers = restoredRes.headers;
|
|
60
|
+
res.body = restoredRes.body;
|
|
61
|
+
if (trigger) {
|
|
62
|
+
inFlight.delete(cacheKey);
|
|
63
|
+
trigger.listeners.forEach((l) => l.resolve(restoredRes.clone()));
|
|
64
|
+
}
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
const err = new Error("304 status received, but cache entry is missing");
|
|
68
|
+
if (trigger) {
|
|
69
|
+
inFlight.delete(cacheKey);
|
|
70
|
+
trigger.listeners.forEach((l) => l.reject(err));
|
|
71
|
+
}
|
|
72
|
+
throw err;
|
|
124
73
|
}
|
|
125
74
|
if (res.status >= 200 && res.status < 300) {
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
75
|
+
const cleanHeaders = { ...res.headers };
|
|
76
|
+
cache.setWithMetadata(cacheKey, {
|
|
77
|
+
body: res.body,
|
|
78
|
+
status: res.status,
|
|
79
|
+
headers: cleanHeaders,
|
|
80
|
+
url: res.url ?? "",
|
|
81
|
+
}, {
|
|
82
|
+
etag: cleanHeaders["etag"] ?? cleanHeaders["ETag"],
|
|
83
|
+
lastModified: cleanHeaders["last-modified"] ?? cleanHeaders["Last-Modified"],
|
|
133
84
|
});
|
|
134
|
-
trigger.resolve(res.clone());
|
|
135
|
-
return;
|
|
136
85
|
}
|
|
137
|
-
trigger.resolve(res.clone());
|
|
138
|
-
},
|
|
139
|
-
/**
|
|
140
|
-
* @ru Перехватчик фазы критических сбоев. Разрывает пул ожидания дедупликации, транслируя ошибку всем подписчикам.
|
|
141
|
-
* @en Error phase interceptor. Purges matching metadata maps and forwards pipeline processing exceptions to all waiting threads.
|
|
142
|
-
* @param err - Специфичный объект ошибки сетевого клиента. / Normalized client-level error framework tracking details.
|
|
143
|
-
* @param req - Сконфигурированный внутренний объект запроса. / Contextual internal request parameters.
|
|
144
|
-
* @param ctx - Общий контекст окружения плагина. / Shared plugin execution context metadata.
|
|
145
|
-
*/
|
|
146
|
-
onError(err, req,
|
|
147
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
148
|
-
_ctx) {
|
|
149
|
-
const cacheKey = req.url;
|
|
150
|
-
const trigger = inFlight.get(cacheKey);
|
|
151
86
|
if (trigger) {
|
|
152
87
|
inFlight.delete(cacheKey);
|
|
153
|
-
trigger.
|
|
88
|
+
if (trigger.listeners.length > 0) {
|
|
89
|
+
const clonedForDependents = res.clone();
|
|
90
|
+
trigger.listeners.forEach((l) => l.resolve(clonedForDependents));
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
onError(err, req) {
|
|
95
|
+
if (!req)
|
|
96
|
+
return;
|
|
97
|
+
const trigger = inFlight.get(req.url);
|
|
98
|
+
if (trigger) {
|
|
99
|
+
inFlight.delete(req.url);
|
|
100
|
+
trigger.listeners.forEach((l) => l.reject(err));
|
|
154
101
|
}
|
|
155
102
|
},
|
|
156
103
|
};
|
package/plugin.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,yBAAyB,CAAC;AACvD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAiEnE,MAAM,UAAU,SAAS,CAAC,OAA6B;IACrD,IAAI,OAAO,EAAE,OAAO,KAAK,KAAK,EAAE,CAAC;QAC/B,OAAO,EAAE,IAAI,EAAE,gBAAgB,EAAE,CAAC;IACpC,CAAC;IAED,IAAI,KAAiD,CAAC;IAEtD,MAAM,cAAc,GAAG,IAAI,GAAG,CAC5B,OAAO,EAAE,OAAO;QACd,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7C,CAAC,CAAC,CAAC,KAAK,EAAE,MAAM,CAAC,CACpB,CAAC;IAEF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEpD,OAAO;QACL,IAAI,EAAE,gBAAgB;QAEtB,KAAK;YACH,KAAK,GAAG,IAAI,YAAY,CAA+B;gBACrD,OAAO,EAAE,OAAO,EAAE,OAAO,IAAI,IAAI;gBACjC,GAAG,EAAE,OAAO,EAAE,GAAG,IAAI,OAAO;gBAC5B,cAAc,EAAE,OAAO,EAAE,cAAc,IAAI,IAAI;aAChD,CAAC,CAAC;QACL,CAAC;QAED,SAAS,CAAC,GAAoB;YAC5B,IAAI,CAAC,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;gBAAE,OAAO;YAE1D,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC;YACzB,MAAM,WAAW,GAAG,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;YAEpD,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,IAAI,IAAI,CAAC,WAAW,CAAC,YAAY,EAAE,CAAC;gBAClE,OAAO,kBAAkB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;YAC9C,CAAC;YAED,IAAI,WAAW,EAAE,CAAC;gBAChB,GAAG,CAAC,OAAO,GAAG,EAAE,GAAG,GAAG,CAAC,OAAO,EAAE,CAAC;gBACjC,IAAI,WAAW,CAAC,IAAI;oBAAE,GAAG,CAAC,OAAO,CAAC,eAAe,CAAC,GAAG,WAAW,CAAC,IAAI,CAAC;gBACtE,IAAI,WAAW,CAAC,YAAY,EAAE,CAAC;oBAC7B,GAAG,CAAC,OAAO,CAAC,mBAAmB,CAAC,GAAG,WAAW,CAAC,YAAY,CAAC;gBAC9D,CAAC;YACH,CAAC;YAED,MAAM,eAAe,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,eAAe,EAAE,CAAC;gBACpB,OAAO,IAAI,OAAO,CAAwB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;oBAC5D,eAAe,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;gBACtD,CAAC,CAAC,CAAC;YACL,CAAC;YAED,QAAQ,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACnE,CAAC;QAED,UAAU,CAAC,GAA0B,EAAE,GAAqB;YAC1D,IAAI,CAAC,GAAG;gBAAE,OAAO;YACjB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC;YACzB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAEvC,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBACvB,IACE,GAAG,CAAC,IAAI;oBACR,OAAQ,GAAG,CAAC,IAAgC,CAAC,MAAM,KAAK,UAAU,EAClE,CAAC;oBACA,GAAG,CAAC,IAAuB,CAAC,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;gBACxD,CAAC;gBAED,MAAM,WAAW,GAAG,KAAK,CAAC,eAAe,CAAC,QAAQ,CAAC,CAAC;gBACpD,IAAI,WAAW,EAAE,CAAC;oBAChB,MAAM,WAAW,GAAG,kBAAkB,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC;oBAEzD,GAAG,CAAC,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC;oBAChC,GAAG,CAAC,OAAO,GAAG,WAAW,CAAC,OAAO,CAAC;oBAClC,GAAG,CAAC,IAAI,GAAG,WAAW,CAAC,IAAI,CAAC;oBAE5B,IAAI,OAAO,EAAE,CAAC;wBACZ,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;wBAC1B,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,WAAW,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;oBACnE,CAAC;oBACD,OAAO;gBACT,CAAC;gBAED,MAAM,GAAG,GAAG,IAAI,KAAK,CACnB,iDAAiD,CAClD,CAAC;gBACF,IAAI,OAAO,EAAE,CAAC;oBACZ,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;oBAC1B,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;gBAClD,CAAC;gBACD,MAAM,GAAG,CAAC;YACZ,CAAC;YAED,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG,EAAE,CAAC;gBAC1C,MAAM,YAAY,GAAG,EAAE,GAAG,GAAG,CAAC,OAAO,EAA4B,CAAC;gBAElE,KAAK,CAAC,eAAe,CACnB,QAAQ,EACR;oBACE,IAAI,EAAE,GAAG,CAAC,IAAI;oBACd,MAAM,EAAE,GAAG,CAAC,MAAM;oBAClB,OAAO,EAAE,YAAY;oBACrB,GAAG,EAAE,GAAG,CAAC,GAAG,IAAI,EAAE;iBACnB,EACD;oBACE,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,IAAI,YAAY,CAAC,MAAM,CAAC;oBAClD,YAAY,EACV,YAAY,CAAC,eAAe,CAAC,IAAI,YAAY,CAAC,eAAe,CAAC;iBACjE,CACF,CAAC;YACJ,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACZ,QAAQ,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;gBAC1B,IAAI,OAAO,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;oBACjC,MAAM,mBAAmB,GAAG,GAAG,CAAC,KAAK,EAAE,CAAC;oBACxC,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC,CAAC;gBACnE,CAAC;YACH,CAAC;QACH,CAAC;QAED,OAAO,CAAC,GAAkB,EAAE,GAAqB;YAC/C,IAAI,CAAC,GAAG;gBAAE,OAAO;YACjB,MAAM,OAAO,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACtC,IAAI,OAAO,EAAE,CAAC;gBACZ,QAAQ,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;gBACzB,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/types/cache.d.ts
CHANGED
|
@@ -1,29 +1,81 @@
|
|
|
1
1
|
import { Method } from "@hyperttp/types";
|
|
2
|
+
/**
|
|
3
|
+
* @en Structure representing a cached item with optional HTTP validation metadata.
|
|
4
|
+
* @ru Структура, представляющая элемент кэша с опциональными метаданными для HTTP-валидации.
|
|
5
|
+
* @template T - Type of the cached data.
|
|
6
|
+
*/
|
|
2
7
|
export interface CacheEntry<T> {
|
|
8
|
+
/**
|
|
9
|
+
* @en The cached data payload.
|
|
10
|
+
* @ru Сохраненные данные.
|
|
11
|
+
*/
|
|
3
12
|
data: T;
|
|
13
|
+
/**
|
|
14
|
+
* @en HTTP ETag header value for conditional requests.
|
|
15
|
+
* @ru Значение заголовка HTTP ETag для условных запросов.
|
|
16
|
+
*/
|
|
4
17
|
etag?: string;
|
|
18
|
+
/**
|
|
19
|
+
* @en HTTP Last-Modified header value for conditional requests.
|
|
20
|
+
* @ru Значение заголовка HTTP Last-Modified для условных запросов.
|
|
21
|
+
*/
|
|
5
22
|
lastModified?: string;
|
|
6
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* @en Configuration options for the CacheManager.
|
|
26
|
+
* @ru Конфигурационные опции для CacheManager.
|
|
27
|
+
*/
|
|
7
28
|
export interface CacheManagerOptions {
|
|
8
29
|
/**
|
|
9
|
-
* @
|
|
10
|
-
* @
|
|
30
|
+
* @en Enable cache functionality.
|
|
31
|
+
* @ru Включить кэш.
|
|
11
32
|
*/
|
|
12
33
|
enabled?: boolean;
|
|
13
34
|
/**
|
|
14
|
-
* @
|
|
15
|
-
* @
|
|
35
|
+
* @en Cache time-to-live in milliseconds.
|
|
36
|
+
* @ru Время жизни кэша (мс).
|
|
16
37
|
*/
|
|
17
38
|
ttl?: number;
|
|
18
39
|
/**
|
|
19
|
-
* @
|
|
20
|
-
* @
|
|
40
|
+
* @en Maximum number of entries in the cache.
|
|
41
|
+
* @ru Максимальный размер кэша.
|
|
21
42
|
*/
|
|
22
43
|
maxSize?: number;
|
|
23
44
|
/**
|
|
24
|
-
* @
|
|
25
|
-
* @
|
|
45
|
+
* @en HTTP methods allowed to be cached.
|
|
46
|
+
* @ru HTTP методы, которые можно кэшировать.
|
|
26
47
|
*/
|
|
27
48
|
methods?: readonly Method[];
|
|
49
|
+
/**
|
|
50
|
+
* @en Update the expiration time when an entry is accessed.
|
|
51
|
+
* @ru Обновлять время истечения при доступе к записи.
|
|
52
|
+
*/
|
|
53
|
+
updateAgeOnGet?: boolean;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* @en A simplified response object used for efficient cache storage.
|
|
57
|
+
* @ru Упрощенный объект ответа, используемый для эффективного хранения в кэше.
|
|
58
|
+
*/
|
|
59
|
+
export interface LightweightResponse<T = unknown> {
|
|
60
|
+
/**
|
|
61
|
+
* @en The response body content.
|
|
62
|
+
* @ru Содержимое тела ответа.
|
|
63
|
+
*/
|
|
64
|
+
body: T;
|
|
65
|
+
/**
|
|
66
|
+
* @en The HTTP status code.
|
|
67
|
+
* @ru HTTP-код статуса.
|
|
68
|
+
*/
|
|
69
|
+
status: number;
|
|
70
|
+
/**
|
|
71
|
+
* @en The response headers.
|
|
72
|
+
* @ru Заголовки ответа.
|
|
73
|
+
*/
|
|
74
|
+
headers: Record<string, string | string[]>;
|
|
75
|
+
/**
|
|
76
|
+
* @en The final URL of the response.
|
|
77
|
+
* @ru Финальный URL ответа.
|
|
78
|
+
*/
|
|
79
|
+
url: string;
|
|
28
80
|
}
|
|
29
81
|
//# sourceMappingURL=cache.d.ts.map
|
package/types/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/types/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,IAAI,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/types/cache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AAEzC;;;;GAIG;AACH,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B;;;OAGG;IACH,IAAI,EAAE,CAAC,CAAC;IAER;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAElB;;;OAGG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAE5B;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB,CAAC,CAAC,GAAG,OAAO;IAC9C;;;OAGG;IACH,IAAI,EAAE,CAAC,CAAC;IAER;;;OAGG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;IAE3C;;;OAGG;IACH,GAAG,EAAE,MAAM,CAAC;CACb"}
|
package/utils/CacheManager.d.ts
CHANGED
|
@@ -1,78 +1,71 @@
|
|
|
1
1
|
import type { CacheEntry, CacheManagerOptions } from "../types/cache.ts";
|
|
2
2
|
/**
|
|
3
|
-
* @
|
|
4
|
-
*
|
|
5
|
-
*
|
|
3
|
+
* @en High-performance inline in-memory cache with no external dependencies.
|
|
4
|
+
* Uses native Map and lazy invalidation to ensure maximum RPS.
|
|
5
|
+
* @ru Высокопроизводительный инлайновый in-memory кэш без внешних зависимостей.
|
|
6
|
+
* Использует нативный Map и ленивую инвалидацию для обеспечения максимального RPS.
|
|
6
7
|
*
|
|
7
|
-
* @
|
|
8
|
-
* Поддерживает TTL, хранение метаданных (etag, lastModified) и быстрый доступ по ключу.
|
|
8
|
+
* @template T - Type of the data stored in the cache. Defaults to unknown.
|
|
9
9
|
*/
|
|
10
|
-
export declare class CacheManager {
|
|
11
|
-
private readonly
|
|
10
|
+
export declare class CacheManager<T = unknown> {
|
|
11
|
+
private readonly storage;
|
|
12
|
+
private readonly maxSize;
|
|
13
|
+
private readonly ttl;
|
|
14
|
+
private readonly updateAgeOnGet;
|
|
12
15
|
constructor(options?: CacheManagerOptions);
|
|
13
16
|
/**
|
|
14
|
-
* @en Retrieves
|
|
17
|
+
* @en Retrieves a value from the cache by key.
|
|
15
18
|
* @ru Получает значение из кэша по ключу.
|
|
16
|
-
*
|
|
17
|
-
* @
|
|
18
|
-
* @param key Cache key
|
|
19
|
-
* @returns Cached value or undefined if not found
|
|
19
|
+
* @param key - The cache key.
|
|
20
|
+
* @returns The cached value or undefined if not found or expired.
|
|
20
21
|
*/
|
|
21
|
-
get
|
|
22
|
+
get(key: string): T | undefined;
|
|
22
23
|
/**
|
|
23
|
-
* @en Stores value in cache.
|
|
24
|
+
* @en Stores a value in the cache.
|
|
24
25
|
* @ru Сохраняет значение в кэш.
|
|
25
|
-
*
|
|
26
|
-
* @
|
|
27
|
-
* @param key Cache key
|
|
28
|
-
* @param value Value to store
|
|
26
|
+
* @param key - The cache key.
|
|
27
|
+
* @param value - The data to store.
|
|
29
28
|
*/
|
|
30
|
-
set
|
|
29
|
+
set(key: string, value: T): void;
|
|
31
30
|
/**
|
|
32
|
-
* @en Retrieves
|
|
31
|
+
* @en Retrieves a cache entry along with its metadata (etag, lastModified).
|
|
33
32
|
* @ru Получает запись кэша вместе с метаданными (etag, lastModified).
|
|
34
|
-
*
|
|
35
|
-
* @
|
|
36
|
-
* @param key Cache key
|
|
37
|
-
* @returns Cached entry with metadata or undefined
|
|
33
|
+
* @param key - The cache key.
|
|
34
|
+
* @returns The cache entry with metadata or undefined if not found or expired.
|
|
38
35
|
*/
|
|
39
|
-
getWithMetadata
|
|
36
|
+
getWithMetadata(key: string): CacheEntry<T> | undefined;
|
|
40
37
|
/**
|
|
41
|
-
* @en Stores value with
|
|
42
|
-
* @ru Сохраняет значение с дополнительными HTTP
|
|
43
|
-
*
|
|
44
|
-
* @
|
|
45
|
-
* @param
|
|
46
|
-
* @param data Value to store
|
|
47
|
-
* @param meta Optional HTTP metadata
|
|
38
|
+
* @en Stores a value with additional HTTP metadata.
|
|
39
|
+
* @ru Сохраняет значение с дополнительными HTTP метаданными.
|
|
40
|
+
* @param key - The cache key.
|
|
41
|
+
* @param data - The response data to store.
|
|
42
|
+
* @param meta - Optional HTTP metadata (etag, lastModified).
|
|
48
43
|
*/
|
|
49
|
-
setWithMetadata
|
|
44
|
+
setWithMetadata(key: string, data: T, meta?: {
|
|
50
45
|
etag?: string;
|
|
51
46
|
lastModified?: string;
|
|
52
47
|
}): void;
|
|
53
48
|
/**
|
|
54
|
-
* @en Checks
|
|
55
|
-
* @ru Проверяет наличие ключа в
|
|
56
|
-
*
|
|
57
|
-
* @
|
|
58
|
-
* @returns true if exists, otherwise false
|
|
49
|
+
* @en Checks for the existence of a key in the cache without updating its TTL.
|
|
50
|
+
* @ru Проверяет наличие ключа в кэше без обновления его времени жизни.
|
|
51
|
+
* @param key - The cache key.
|
|
52
|
+
* @returns True if the key exists and is not expired.
|
|
59
53
|
*/
|
|
60
54
|
has(key: string): boolean;
|
|
61
55
|
/**
|
|
62
|
-
* @en
|
|
56
|
+
* @en Removes a value from the cache by key.
|
|
63
57
|
* @ru Удаляет значение из кэша по ключу.
|
|
64
|
-
*
|
|
65
|
-
* @
|
|
66
|
-
* @returns true if value was removed
|
|
58
|
+
* @param key - The cache key.
|
|
59
|
+
* @returns True if the element was removed, false otherwise.
|
|
67
60
|
*/
|
|
68
61
|
delete(key: string): boolean;
|
|
69
62
|
/**
|
|
70
|
-
* @en Clears entire cache.
|
|
63
|
+
* @en Clears the entire cache.
|
|
71
64
|
* @ru Очищает весь кэш.
|
|
72
65
|
*/
|
|
73
66
|
clear(): void;
|
|
74
67
|
/**
|
|
75
|
-
* @en Returns current cache
|
|
68
|
+
* @en Returns the current number of items in the cache.
|
|
76
69
|
* @ru Возвращает текущий размер кэша.
|
|
77
70
|
*/
|
|
78
71
|
get size(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/utils/CacheManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CacheManager.d.ts","sourceRoot":"","sources":["../../src/utils/CacheManager.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAczE;;;;;;;GAOG;AACH,qBAAa,YAAY,CAAC,CAAC,GAAG,OAAO;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4C;IACpE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAU;gBAE7B,OAAO,CAAC,EAAE,mBAAmB;IAMzC;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS;IAoB/B;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAgBhC;;;;;OAKG;IACH,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,GAAG,SAAS;IAuBvD;;;;;;OAMG;IACH,eAAe,CACb,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,CAAC,EACP,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE,GAC9C,IAAI;IAoBP;;;;;OAKG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAWzB;;;;;OAKG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAI5B;;;OAGG;IACH,KAAK,IAAI,IAAI;IAIb;;;OAGG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
|
package/utils/CacheManager.js
CHANGED
|
@@ -1,106 +1,154 @@
|
|
|
1
|
-
import { LRUCache } from "lru-cache";
|
|
2
1
|
/**
|
|
3
|
-
* @
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* @en High-performance inline in-memory cache with no external dependencies.
|
|
3
|
+
* Uses native Map and lazy invalidation to ensure maximum RPS.
|
|
4
|
+
* @ru Высокопроизводительный инлайновый in-memory кэш без внешних зависимостей.
|
|
5
|
+
* Использует нативный Map и ленивую инвалидацию для обеспечения максимального RPS.
|
|
6
6
|
*
|
|
7
|
-
* @
|
|
8
|
-
* Поддерживает TTL, хранение метаданных (etag, lastModified) и быстрый доступ по ключу.
|
|
7
|
+
* @template T - Type of the data stored in the cache. Defaults to unknown.
|
|
9
8
|
*/
|
|
10
9
|
export class CacheManager {
|
|
11
|
-
|
|
10
|
+
storage = new Map();
|
|
11
|
+
maxSize;
|
|
12
|
+
ttl;
|
|
13
|
+
updateAgeOnGet;
|
|
12
14
|
constructor(options) {
|
|
13
|
-
this.
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
updateAgeOnGet: true,
|
|
17
|
-
});
|
|
15
|
+
this.maxSize = options?.maxSize ?? 500;
|
|
16
|
+
this.ttl = options?.ttl ?? 300_000;
|
|
17
|
+
this.updateAgeOnGet = options?.updateAgeOnGet ?? true;
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
20
|
-
* @en Retrieves
|
|
20
|
+
* @en Retrieves a value from the cache by key.
|
|
21
21
|
* @ru Получает значение из кэша по ключу.
|
|
22
|
-
*
|
|
23
|
-
* @
|
|
24
|
-
* @param key Cache key
|
|
25
|
-
* @returns Cached value or undefined if not found
|
|
22
|
+
* @param key - The cache key.
|
|
23
|
+
* @returns The cached value or undefined if not found or expired.
|
|
26
24
|
*/
|
|
27
25
|
get(key) {
|
|
28
|
-
|
|
26
|
+
const entry = this.storage.get(key);
|
|
27
|
+
if (!entry)
|
|
28
|
+
return undefined;
|
|
29
|
+
const now = Date.now();
|
|
30
|
+
if (now > entry.expiresAt) {
|
|
31
|
+
this.storage.delete(key);
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
if (this.updateAgeOnGet) {
|
|
35
|
+
entry.expiresAt = now + this.ttl;
|
|
36
|
+
// Move to end for LRU
|
|
37
|
+
this.storage.delete(key);
|
|
38
|
+
this.storage.set(key, entry);
|
|
39
|
+
}
|
|
40
|
+
return entry.data;
|
|
29
41
|
}
|
|
30
42
|
/**
|
|
31
|
-
* @en Stores value in cache.
|
|
43
|
+
* @en Stores a value in the cache.
|
|
32
44
|
* @ru Сохраняет значение в кэш.
|
|
33
|
-
*
|
|
34
|
-
* @
|
|
35
|
-
* @param key Cache key
|
|
36
|
-
* @param value Value to store
|
|
45
|
+
* @param key - The cache key.
|
|
46
|
+
* @param value - The data to store.
|
|
37
47
|
*/
|
|
38
48
|
set(key, value) {
|
|
39
|
-
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
if (this.storage.has(key)) {
|
|
51
|
+
this.storage.delete(key);
|
|
52
|
+
}
|
|
53
|
+
else if (this.storage.size >= this.maxSize) {
|
|
54
|
+
const oldestKey = this.storage.keys().next().value;
|
|
55
|
+
if (oldestKey !== undefined)
|
|
56
|
+
this.storage.delete(oldestKey);
|
|
57
|
+
}
|
|
58
|
+
this.storage.set(key, {
|
|
40
59
|
data: value,
|
|
60
|
+
expiresAt: now + this.ttl,
|
|
41
61
|
});
|
|
42
62
|
}
|
|
43
63
|
/**
|
|
44
|
-
* @en Retrieves
|
|
64
|
+
* @en Retrieves a cache entry along with its metadata (etag, lastModified).
|
|
45
65
|
* @ru Получает запись кэша вместе с метаданными (etag, lastModified).
|
|
46
|
-
*
|
|
47
|
-
* @
|
|
48
|
-
* @param key Cache key
|
|
49
|
-
* @returns Cached entry with metadata or undefined
|
|
66
|
+
* @param key - The cache key.
|
|
67
|
+
* @returns The cache entry with metadata or undefined if not found or expired.
|
|
50
68
|
*/
|
|
51
69
|
getWithMetadata(key) {
|
|
52
|
-
const entry = this.
|
|
53
|
-
|
|
70
|
+
const entry = this.storage.get(key);
|
|
71
|
+
if (!entry)
|
|
72
|
+
return undefined;
|
|
73
|
+
const now = Date.now();
|
|
74
|
+
if (now > entry.expiresAt) {
|
|
75
|
+
this.storage.delete(key);
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
if (this.updateAgeOnGet) {
|
|
79
|
+
entry.expiresAt = now + this.ttl;
|
|
80
|
+
this.storage.delete(key);
|
|
81
|
+
this.storage.set(key, entry);
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
data: entry.data,
|
|
85
|
+
etag: entry.etag,
|
|
86
|
+
lastModified: entry.lastModified,
|
|
87
|
+
};
|
|
54
88
|
}
|
|
55
89
|
/**
|
|
56
|
-
* @en Stores value with
|
|
57
|
-
* @ru Сохраняет значение с дополнительными HTTP
|
|
58
|
-
*
|
|
59
|
-
* @
|
|
60
|
-
* @param
|
|
61
|
-
* @param data Value to store
|
|
62
|
-
* @param meta Optional HTTP metadata
|
|
90
|
+
* @en Stores a value with additional HTTP metadata.
|
|
91
|
+
* @ru Сохраняет значение с дополнительными HTTP метаданными.
|
|
92
|
+
* @param key - The cache key.
|
|
93
|
+
* @param data - The response data to store.
|
|
94
|
+
* @param meta - Optional HTTP metadata (etag, lastModified).
|
|
63
95
|
*/
|
|
64
96
|
setWithMetadata(key, data, meta) {
|
|
65
|
-
|
|
97
|
+
const now = Date.now();
|
|
98
|
+
if (this.storage.has(key)) {
|
|
99
|
+
this.storage.delete(key);
|
|
100
|
+
}
|
|
101
|
+
else if (this.storage.size >= this.maxSize) {
|
|
102
|
+
const oldestKey = this.storage.keys().next().value;
|
|
103
|
+
if (oldestKey !== undefined) {
|
|
104
|
+
this.storage.delete(oldestKey);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
this.storage.set(key, {
|
|
66
108
|
data,
|
|
67
109
|
etag: meta?.etag,
|
|
68
110
|
lastModified: meta?.lastModified,
|
|
111
|
+
expiresAt: now + this.ttl,
|
|
69
112
|
});
|
|
70
113
|
}
|
|
71
114
|
/**
|
|
72
|
-
* @en Checks
|
|
73
|
-
* @ru Проверяет наличие ключа в
|
|
74
|
-
*
|
|
75
|
-
* @
|
|
76
|
-
* @returns true if exists, otherwise false
|
|
115
|
+
* @en Checks for the existence of a key in the cache without updating its TTL.
|
|
116
|
+
* @ru Проверяет наличие ключа в кэше без обновления его времени жизни.
|
|
117
|
+
* @param key - The cache key.
|
|
118
|
+
* @returns True if the key exists and is not expired.
|
|
77
119
|
*/
|
|
78
120
|
has(key) {
|
|
79
|
-
|
|
121
|
+
const entry = this.storage.get(key);
|
|
122
|
+
if (!entry)
|
|
123
|
+
return false;
|
|
124
|
+
if (Date.now() > entry.expiresAt) {
|
|
125
|
+
this.storage.delete(key);
|
|
126
|
+
return false;
|
|
127
|
+
}
|
|
128
|
+
return true;
|
|
80
129
|
}
|
|
81
130
|
/**
|
|
82
|
-
* @en
|
|
131
|
+
* @en Removes a value from the cache by key.
|
|
83
132
|
* @ru Удаляет значение из кэша по ключу.
|
|
84
|
-
*
|
|
85
|
-
* @
|
|
86
|
-
* @returns true if value was removed
|
|
133
|
+
* @param key - The cache key.
|
|
134
|
+
* @returns True if the element was removed, false otherwise.
|
|
87
135
|
*/
|
|
88
136
|
delete(key) {
|
|
89
|
-
return this.
|
|
137
|
+
return this.storage.delete(key);
|
|
90
138
|
}
|
|
91
139
|
/**
|
|
92
|
-
* @en Clears entire cache.
|
|
140
|
+
* @en Clears the entire cache.
|
|
93
141
|
* @ru Очищает весь кэш.
|
|
94
142
|
*/
|
|
95
143
|
clear() {
|
|
96
|
-
this.
|
|
144
|
+
this.storage.clear();
|
|
97
145
|
}
|
|
98
146
|
/**
|
|
99
|
-
* @en Returns current cache
|
|
147
|
+
* @en Returns the current number of items in the cache.
|
|
100
148
|
* @ru Возвращает текущий размер кэша.
|
|
101
149
|
*/
|
|
102
150
|
get size() {
|
|
103
|
-
return this.
|
|
151
|
+
return this.storage.size;
|
|
104
152
|
}
|
|
105
153
|
}
|
|
106
154
|
//# sourceMappingURL=CacheManager.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CacheManager.js","sourceRoot":"","sources":["../../src/utils/CacheManager.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"CacheManager.js","sourceRoot":"","sources":["../../src/utils/CacheManager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { HttpResponse } from "@hyperttp/types";
|
|
2
|
+
import { LightweightResponse } from "../types/cache.js";
|
|
3
|
+
/**
|
|
4
|
+
* @en Reconstructs a full HttpResponse object from a lightweight cache response.
|
|
5
|
+
* @ru Реконструирует полный объект HttpResponse из легковесного ответа кэша.
|
|
6
|
+
*
|
|
7
|
+
* @param snapshot - Lightweight response object containing cached data.
|
|
8
|
+
* @returns A fully compatible HttpResponse object with methods like .text(), .json(), and .clone().
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* const cached = {
|
|
12
|
+
* status: 200,
|
|
13
|
+
* headers: { 'content-type': 'application/json' },
|
|
14
|
+
* url: 'https://api.example.com/data',
|
|
15
|
+
* body: { id: 1, name: 'John' }
|
|
16
|
+
* };
|
|
17
|
+
* const response = createHttpResponse(cached);
|
|
18
|
+
* const data = await response.json(); // { id: 1, name: 'John' }
|
|
19
|
+
*/
|
|
20
|
+
export declare function createHttpResponse(snapshot: LightweightResponse<unknown>): HttpResponse<unknown>;
|
|
21
|
+
//# sourceMappingURL=createHttpResponse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createHttpResponse.d.ts","sourceRoot":"","sources":["../../src/utils/createHttpResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,mBAAmB,CAAC,OAAO,CAAC,GACrC,YAAY,CAAC,OAAO,CAAC,CA6CvB"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @en Reconstructs a full HttpResponse object from a lightweight cache response.
|
|
3
|
+
* @ru Реконструирует полный объект HttpResponse из легковесного ответа кэша.
|
|
4
|
+
*
|
|
5
|
+
* @param snapshot - Lightweight response object containing cached data.
|
|
6
|
+
* @returns A fully compatible HttpResponse object with methods like .text(), .json(), and .clone().
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* const cached = {
|
|
10
|
+
* status: 200,
|
|
11
|
+
* headers: { 'content-type': 'application/json' },
|
|
12
|
+
* url: 'https://api.example.com/data',
|
|
13
|
+
* body: { id: 1, name: 'John' }
|
|
14
|
+
* };
|
|
15
|
+
* const response = createHttpResponse(cached);
|
|
16
|
+
* const data = await response.json(); // { id: 1, name: 'John' }
|
|
17
|
+
*/
|
|
18
|
+
export function createHttpResponse(snapshot) {
|
|
19
|
+
return {
|
|
20
|
+
status: snapshot.status,
|
|
21
|
+
headers: { ...snapshot.headers },
|
|
22
|
+
url: snapshot.url,
|
|
23
|
+
body: snapshot.body,
|
|
24
|
+
text() {
|
|
25
|
+
if (typeof snapshot.body === "string") {
|
|
26
|
+
return Promise.resolve(snapshot.body);
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
return Promise.resolve(JSON.stringify(snapshot.body));
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return Promise.resolve("");
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
json() {
|
|
36
|
+
return Promise.resolve(snapshot.body);
|
|
37
|
+
},
|
|
38
|
+
dump() {
|
|
39
|
+
return Promise.resolve();
|
|
40
|
+
},
|
|
41
|
+
clone() {
|
|
42
|
+
let clonedBody = snapshot.body;
|
|
43
|
+
if (snapshot.body && typeof snapshot.body === "object") {
|
|
44
|
+
if (!(snapshot.body instanceof ReadableStream)) {
|
|
45
|
+
try {
|
|
46
|
+
clonedBody = structuredClone(snapshot.body);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
clonedBody = JSON.parse(JSON.stringify(snapshot.body));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return createHttpResponse({
|
|
54
|
+
...snapshot,
|
|
55
|
+
body: clonedBody,
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
//# sourceMappingURL=createHttpResponse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createHttpResponse.js","sourceRoot":"","sources":["../../src/utils/createHttpResponse.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,kBAAkB,CAChC,QAAsC;IAEtC,OAAO;QACL,MAAM,EAAE,QAAQ,CAAC,MAAM;QACvB,OAAO,EAAE,EAAE,GAAG,QAAQ,CAAC,OAAO,EAAE;QAChC,GAAG,EAAE,QAAQ,CAAC,GAAG;QACjB,IAAI,EAAE,QAAQ,CAAC,IAAI;QAEnB,IAAI;YACF,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACtC,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;YACD,IAAI,CAAC;gBACH,OAAO,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;YACxD,CAAC;YAAC,MAAM,CAAC;gBACP,OAAO,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAED,IAAI;YACF,OAAO,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC,IAAS,CAAC,CAAC;QAC7C,CAAC;QAED,IAAI;YACF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;QAC3B,CAAC;QAED,KAAK;YACH,IAAI,UAAU,GAAG,QAAQ,CAAC,IAAI,CAAC;YAE/B,IAAI,QAAQ,CAAC,IAAI,IAAI,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACvD,IAAI,CAAC,CAAC,QAAQ,CAAC,IAAI,YAAY,cAAc,CAAC,EAAE,CAAC;oBAC/C,IAAI,CAAC;wBACH,UAAU,GAAG,eAAe,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC;oBAC9C,CAAC;oBAAC,MAAM,CAAC;wBACP,UAAU,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;oBACzD,CAAC;gBACH,CAAC;YACH,CAAC;YAED,OAAO,kBAAkB,CAAC;gBACxB,GAAG,QAAQ;gBACX,IAAI,EAAE,UAAU;aACjB,CAAC,CAAC;QACL,CAAC;KACF,CAAC;AACJ,CAAC"}
|