@keq-request/cache 5.0.0-alpha.10
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/CHANGELOG.md +166 -0
- package/LICENSE +21 -0
- package/dist/cache-entry/cache-entry.d.ts +17 -0
- package/dist/cache-entry/cache-entry.d.ts.map +1 -0
- package/dist/cache-entry/index.d.ts +4 -0
- package/dist/cache-entry/index.d.ts.map +1 -0
- package/dist/cache-entry/types/cache-entry-build-options.d.ts +8 -0
- package/dist/cache-entry/types/cache-entry-build-options.d.ts.map +1 -0
- package/dist/cache-entry/types/cache-entry-options.d.ts +7 -0
- package/dist/cache-entry/types/cache-entry-options.d.ts.map +1 -0
- package/dist/cache.d.ts +30 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/constants/eviction.enum.d.ts +7 -0
- package/dist/constants/eviction.enum.d.ts.map +1 -0
- package/dist/constants/strategy.enum.d.ts +7 -0
- package/dist/constants/strategy.enum.d.ts.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1102 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +1059 -0
- package/dist/index.mjs.map +1 -0
- package/dist/storage/index.d.ts +6 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/base-indexed-db-storage.d.ts +24 -0
- package/dist/storage/indexed-db-storage/base-indexed-db-storage.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/constants/default-table-name.d.ts +2 -0
- package/dist/storage/indexed-db-storage/constants/default-table-name.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/indexed-db-storage.d.ts +12 -0
- package/dist/storage/indexed-db-storage/indexed-db-storage.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/lfu-indexed-db-storage.d.ts +11 -0
- package/dist/storage/indexed-db-storage/lfu-indexed-db-storage.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/lru-indexed-db-storage.d.ts +11 -0
- package/dist/storage/indexed-db-storage/lru-indexed-db-storage.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/random-indexed-db-storage.d.ts +11 -0
- package/dist/storage/indexed-db-storage/random-indexed-db-storage.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/ttl-indexed-db-storage.d.ts +11 -0
- package/dist/storage/indexed-db-storage/ttl-indexed-db-storage.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-entry-metadata.d.ts +12 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-entry-metadata.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-entry-response.d.ts +8 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-entry-response.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-entry-visits.d.ts +14 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-entry-visits.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-schema.d.ts +29 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-schema.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-storage-options.d.ts +14 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-storage-options.d.ts.map +1 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-storage-size.d.ts +13 -0
- package/dist/storage/indexed-db-storage/types/indexed-db-storage-size.d.ts.map +1 -0
- package/dist/storage/internal-storage/internal-storage.d.ts +16 -0
- package/dist/storage/internal-storage/internal-storage.d.ts.map +1 -0
- package/dist/storage/internal-storage/types/events.d.ts +16 -0
- package/dist/storage/internal-storage/types/events.d.ts.map +1 -0
- package/dist/storage/internal-storage/types/storage-options.d.ts +20 -0
- package/dist/storage/internal-storage/types/storage-options.d.ts.map +1 -0
- package/dist/storage/keq-cache-storage.d.ts +20 -0
- package/dist/storage/keq-cache-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/base-memory-storage.d.ts +26 -0
- package/dist/storage/memory-storage/base-memory-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/lfu-memory-storage.d.ts +11 -0
- package/dist/storage/memory-storage/lfu-memory-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/lru-memory-storage.d.ts +11 -0
- package/dist/storage/memory-storage/lru-memory-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/memory-storage.d.ts +12 -0
- package/dist/storage/memory-storage/memory-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/random-memory-storage.d.ts +11 -0
- package/dist/storage/memory-storage/random-memory-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/ttl-memory-storage.d.ts +11 -0
- package/dist/storage/memory-storage/ttl-memory-storage.d.ts.map +1 -0
- package/dist/storage/memory-storage/types/memory-storage-options.d.ts +9 -0
- package/dist/storage/memory-storage/types/memory-storage-options.d.ts.map +1 -0
- package/dist/storage/memory-storage/types/memory-storage-size.d.ts +13 -0
- package/dist/storage/memory-storage/types/memory-storage-size.d.ts.map +1 -0
- package/dist/storage/multi-tier-storage/index.d.ts +2 -0
- package/dist/storage/multi-tier-storage/index.d.ts.map +1 -0
- package/dist/storage/multi-tier-storage/multi-tier-storage.d.ts +36 -0
- package/dist/storage/multi-tier-storage/multi-tier-storage.d.ts.map +1 -0
- package/dist/storage/multi-tier-storage/types/multi-tier-storage-options.d.ts +5 -0
- package/dist/storage/multi-tier-storage/types/multi-tier-storage-options.d.ts.map +1 -0
- package/dist/storage/tier-storage/index.d.ts +3 -0
- package/dist/storage/tier-storage/index.d.ts.map +1 -0
- package/dist/storage/tier-storage/tier-storage.d.ts +15 -0
- package/dist/storage/tier-storage/tier-storage.d.ts.map +1 -0
- package/dist/storage/tier-storage/types/tier-storage-options.d.ts +21 -0
- package/dist/storage/tier-storage/types/tier-storage-options.d.ts.map +1 -0
- package/dist/strategies/cache-first.d.ts +3 -0
- package/dist/strategies/cache-first.d.ts.map +1 -0
- package/dist/strategies/network-first.d.ts +3 -0
- package/dist/strategies/network-first.d.ts.map +1 -0
- package/dist/strategies/network-only.d.ts +3 -0
- package/dist/strategies/network-only.d.ts.map +1 -0
- package/dist/strategies/stale-while-revalidate.d.ts +3 -0
- package/dist/strategies/stale-while-revalidate.d.ts.map +1 -0
- package/dist/strategies/utils/cache-context.d.ts +5 -0
- package/dist/strategies/utils/cache-context.d.ts.map +1 -0
- package/dist/strategies/utils/index.d.ts +2 -0
- package/dist/strategies/utils/index.d.ts.map +1 -0
- package/dist/types/keq-cache-key.d.ts +3 -0
- package/dist/types/keq-cache-key.d.ts.map +1 -0
- package/dist/types/keq-cache-option.d.ts +24 -0
- package/dist/types/keq-cache-option.d.ts.map +1 -0
- package/dist/types/keq-cache-parameters.d.ts +12 -0
- package/dist/types/keq-cache-parameters.d.ts.map +1 -0
- package/dist/types/keq-cache-rule.d.ts +6 -0
- package/dist/types/keq-cache-rule.d.ts.map +1 -0
- package/dist/types/keq-cache-strategy.d.ts +6 -0
- package/dist/types/keq-cache-strategy.d.ts.map +1 -0
- package/dist/types/strategies-options.d.ts +14 -0
- package/dist/types/strategies-options.d.ts.map +1 -0
- package/dist/utils/debug.d.ts +2 -0
- package/dist/utils/debug.d.ts.map +1 -0
- package/dist/utils/get-response-bytes.d.ts +2 -0
- package/dist/utils/get-response-bytes.d.ts.map +1 -0
- package/dist/utils/random.d.ts +2 -0
- package/dist/utils/random.d.ts.map +1 -0
- package/package.json +58 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
CacheStorage: () => KeqCacheStorage,
|
|
34
|
+
Eviction: () => Eviction,
|
|
35
|
+
IndexedDBStorage: () => IndexedDBStorage,
|
|
36
|
+
MemoryStorage: () => MemoryStorage,
|
|
37
|
+
Strategy: () => Strategy,
|
|
38
|
+
TierStorage: () => TierStorage,
|
|
39
|
+
cache: () => cache
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/cache.ts
|
|
44
|
+
var R = __toESM(require("ramda"));
|
|
45
|
+
var import_keq = require("keq");
|
|
46
|
+
function cache(opts) {
|
|
47
|
+
const storage = opts.storage;
|
|
48
|
+
const rules = opts?.rules || [];
|
|
49
|
+
return async function cache2(ctx, next) {
|
|
50
|
+
let cOpt = ctx.options.cache;
|
|
51
|
+
const rule = rules.find((rule2) => {
|
|
52
|
+
if (rule2.pattern === void 0 || rule2.pattern === true) return true;
|
|
53
|
+
if (typeof rule2.pattern === "function") return rule2.pattern(ctx);
|
|
54
|
+
return rule2.pattern.test(ctx.request.__url__.href);
|
|
55
|
+
});
|
|
56
|
+
if (rule) cOpt = R.mergeRight(rule, cOpt || {});
|
|
57
|
+
if (!cOpt || R.isEmpty(cOpt)) {
|
|
58
|
+
await next();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
let key = ctx.locationId;
|
|
62
|
+
if (cOpt.key) {
|
|
63
|
+
if (typeof cOpt.key === "function") key = cOpt.key(ctx);
|
|
64
|
+
else key = cOpt.key;
|
|
65
|
+
} else if (opts?.keyFactory) {
|
|
66
|
+
key = opts.keyFactory(ctx);
|
|
67
|
+
}
|
|
68
|
+
if (!key) throw new import_keq.Exception("Cache key is required");
|
|
69
|
+
const strategy = cOpt.strategy;
|
|
70
|
+
const opt = {
|
|
71
|
+
key,
|
|
72
|
+
storage,
|
|
73
|
+
ttl: cOpt.ttl,
|
|
74
|
+
exclude: cOpt.exclude
|
|
75
|
+
};
|
|
76
|
+
await strategy(opt)(ctx, next);
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/utils/get-response-bytes.ts
|
|
81
|
+
async function getResponseBytes(response) {
|
|
82
|
+
const contentLength = response.headers.get("content-length");
|
|
83
|
+
if (contentLength) {
|
|
84
|
+
return parseInt(contentLength);
|
|
85
|
+
}
|
|
86
|
+
const arrayBuffer = await response.clone().arrayBuffer();
|
|
87
|
+
return arrayBuffer.byteLength;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/cache-entry/cache-entry.ts
|
|
91
|
+
var CacheEntry = class _CacheEntry {
|
|
92
|
+
key;
|
|
93
|
+
response;
|
|
94
|
+
/**
|
|
95
|
+
* @en bytes
|
|
96
|
+
* @zh 字节数
|
|
97
|
+
*/
|
|
98
|
+
size;
|
|
99
|
+
expiredAt;
|
|
100
|
+
constructor(options) {
|
|
101
|
+
this.key = options.key;
|
|
102
|
+
this.response = options.response;
|
|
103
|
+
this.size = options.size;
|
|
104
|
+
this.expiredAt = options.expiredAt ?? /* @__PURE__ */ new Date(864e13);
|
|
105
|
+
}
|
|
106
|
+
static async build(options) {
|
|
107
|
+
const expiredAt = "expiredAt" in options ? options.expiredAt : "ttl" in options && typeof options.ttl === "number" && options.ttl > 0 ? new Date(Date.now() + options.ttl * 1e3) : /* @__PURE__ */ new Date(864e13);
|
|
108
|
+
const response = options.response.clone();
|
|
109
|
+
return new _CacheEntry({
|
|
110
|
+
key: options.key,
|
|
111
|
+
response,
|
|
112
|
+
size: options.size ?? await getResponseBytes(response),
|
|
113
|
+
expiredAt
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
clone() {
|
|
117
|
+
return new _CacheEntry({
|
|
118
|
+
key: this.key,
|
|
119
|
+
response: this.response.clone(),
|
|
120
|
+
size: this.size,
|
|
121
|
+
expiredAt: this.expiredAt
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
assign(another) {
|
|
125
|
+
this.key = another.key;
|
|
126
|
+
this.response = another.response.clone();
|
|
127
|
+
this.size = another.size;
|
|
128
|
+
this.expiredAt = another.expiredAt;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
// src/strategies/utils/cache-context.ts
|
|
134
|
+
async function cacheContext(opts, context) {
|
|
135
|
+
if (!context.response) return;
|
|
136
|
+
if (opts.exclude && await opts.exclude(context.response)) return;
|
|
137
|
+
const key = opts.key;
|
|
138
|
+
const storage = opts.storage;
|
|
139
|
+
const entry = await CacheEntry.build({
|
|
140
|
+
key,
|
|
141
|
+
response: context.response,
|
|
142
|
+
ttl: opts.ttl
|
|
143
|
+
});
|
|
144
|
+
storage.set(entry);
|
|
145
|
+
return entry;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/strategies/cache-first.ts
|
|
149
|
+
var cacheFirst = function(opts) {
|
|
150
|
+
const { storage, key } = opts;
|
|
151
|
+
return async function(context, next) {
|
|
152
|
+
const cache2 = await storage.get(key);
|
|
153
|
+
if (cache2) {
|
|
154
|
+
context.emitter.emit("cache:hit", { key, response: cache2.response, context });
|
|
155
|
+
context.res = cache2.response;
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
context.emitter.emit("cache:miss", { key, context });
|
|
159
|
+
await next();
|
|
160
|
+
const entry = await cacheContext(opts, context);
|
|
161
|
+
if (entry) {
|
|
162
|
+
context.emitter.emit("cache:update", {
|
|
163
|
+
key,
|
|
164
|
+
oldResponse: void 0,
|
|
165
|
+
newResponse: entry.response,
|
|
166
|
+
context
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
};
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
// src/strategies/network-first.ts
|
|
173
|
+
var networkFirst = function(opts) {
|
|
174
|
+
const { key, storage } = opts;
|
|
175
|
+
return async function(context, next) {
|
|
176
|
+
try {
|
|
177
|
+
await next();
|
|
178
|
+
const cache2 = await storage.get(key);
|
|
179
|
+
const entry = await cacheContext(opts, context);
|
|
180
|
+
if (entry) {
|
|
181
|
+
context.emitter.emit("cache:update", {
|
|
182
|
+
key,
|
|
183
|
+
oldResponse: cache2?.response,
|
|
184
|
+
newResponse: entry.response,
|
|
185
|
+
context
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
const cache2 = await storage.get(key);
|
|
190
|
+
if (!cache2) {
|
|
191
|
+
context.emitter.emit("cache:miss", { key, context });
|
|
192
|
+
throw err;
|
|
193
|
+
}
|
|
194
|
+
context.emitter.emit("cache:hit", { key, response: cache2.response, context });
|
|
195
|
+
context.res = cache2.response;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
// src/strategies/network-only.ts
|
|
201
|
+
var networkOnly = function(opts) {
|
|
202
|
+
return async function(ctx, next) {
|
|
203
|
+
await next();
|
|
204
|
+
};
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/strategies/stale-while-revalidate.ts
|
|
208
|
+
var staleWhileRevalidate = function(opts) {
|
|
209
|
+
const { key, storage } = opts;
|
|
210
|
+
return async function(context, next) {
|
|
211
|
+
const cache2 = await storage.get(key);
|
|
212
|
+
if (cache2) {
|
|
213
|
+
context.emitter.emit("cache:hit", { key, response: cache2.response, context });
|
|
214
|
+
const orchestrator = context.orchestration.fork();
|
|
215
|
+
context.res = cache2.response;
|
|
216
|
+
setTimeout(async () => {
|
|
217
|
+
try {
|
|
218
|
+
await orchestrator.execute();
|
|
219
|
+
const context2 = orchestrator.context;
|
|
220
|
+
const entry = await cacheContext(opts, context2);
|
|
221
|
+
if (entry) {
|
|
222
|
+
context2.emitter.emit("cache:update", {
|
|
223
|
+
key,
|
|
224
|
+
oldResponse: cache2.response,
|
|
225
|
+
newResponse: entry.response,
|
|
226
|
+
context: context2
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
} catch (err) {
|
|
230
|
+
}
|
|
231
|
+
}, 1);
|
|
232
|
+
} else {
|
|
233
|
+
context.emitter.emit("cache:miss", { key, context });
|
|
234
|
+
await next();
|
|
235
|
+
const entry = await cacheContext(opts, context);
|
|
236
|
+
if (entry) {
|
|
237
|
+
context.emitter.emit("cache:update", {
|
|
238
|
+
key,
|
|
239
|
+
oldResponse: void 0,
|
|
240
|
+
newResponse: entry.response,
|
|
241
|
+
context
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
// src/constants/strategy.enum.ts
|
|
249
|
+
var Strategy = {
|
|
250
|
+
STALE_WHILE_REVALIDATE: staleWhileRevalidate,
|
|
251
|
+
NETWORK_FIRST: networkFirst,
|
|
252
|
+
NETWORK_ONLY: networkOnly,
|
|
253
|
+
CACHE_FIRST: cacheFirst
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/constants/eviction.enum.ts
|
|
257
|
+
var Eviction = /* @__PURE__ */ ((Eviction2) => {
|
|
258
|
+
Eviction2["LRU"] = "lru";
|
|
259
|
+
Eviction2["LFU"] = "lfu";
|
|
260
|
+
Eviction2["RANDOM"] = "random";
|
|
261
|
+
Eviction2["TTL"] = "ttl";
|
|
262
|
+
return Eviction2;
|
|
263
|
+
})(Eviction || {});
|
|
264
|
+
|
|
265
|
+
// src/storage/memory-storage/ttl-memory-storage.ts
|
|
266
|
+
var import_dayjs2 = __toESM(require("dayjs"));
|
|
267
|
+
var R3 = __toESM(require("ramda"));
|
|
268
|
+
|
|
269
|
+
// src/storage/memory-storage/base-memory-storage.ts
|
|
270
|
+
var import_dayjs = __toESM(require("dayjs"));
|
|
271
|
+
var R2 = __toESM(require("ramda"));
|
|
272
|
+
|
|
273
|
+
// src/utils/debug.ts
|
|
274
|
+
function debug(...args) {
|
|
275
|
+
console.debug("[@keq-cache] [DEBUG] ", ...args);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/storage/keq-cache-storage.ts
|
|
279
|
+
var KeqCacheStorage = class {
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
// src/storage/internal-storage/internal-storage.ts
|
|
283
|
+
var InternalStorage = class extends KeqCacheStorage {
|
|
284
|
+
__id__ = Math.random().toString(36).slice(2);
|
|
285
|
+
__size__;
|
|
286
|
+
__debug__;
|
|
287
|
+
__onCacheGet__;
|
|
288
|
+
__onCacheSet__;
|
|
289
|
+
__onCacheRemove__;
|
|
290
|
+
__onCacheEvict__;
|
|
291
|
+
__onCacheExpired__;
|
|
292
|
+
constructor(options) {
|
|
293
|
+
super();
|
|
294
|
+
if (options?.size && (typeof options?.size !== "number" || options.size <= 0)) {
|
|
295
|
+
throw TypeError(`Invalid size: ${String(options?.size)}`);
|
|
296
|
+
}
|
|
297
|
+
this.__size__ = options?.size ?? Infinity;
|
|
298
|
+
this.__debug__ = !!options?.debug;
|
|
299
|
+
this.__onCacheGet__ = options?.onCacheGet;
|
|
300
|
+
this.__onCacheSet__ = options?.onCacheSet;
|
|
301
|
+
this.__onCacheRemove__ = options?.onCacheRemove;
|
|
302
|
+
this.__onCacheEvict__ = options?.onCacheEvict;
|
|
303
|
+
this.debug((log) => log("Storage Created: ", this));
|
|
304
|
+
}
|
|
305
|
+
debug(fn) {
|
|
306
|
+
if (this.__debug__) {
|
|
307
|
+
fn((...args) => {
|
|
308
|
+
debug(`[Storage(${this.__id__})]`, ...args);
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// src/storage/memory-storage/base-memory-storage.ts
|
|
315
|
+
var BaseMemoryStorage = class extends InternalStorage {
|
|
316
|
+
storage = /* @__PURE__ */ new Map();
|
|
317
|
+
visitTimeRecords = /* @__PURE__ */ new Map();
|
|
318
|
+
visitCountRecords = /* @__PURE__ */ new Map();
|
|
319
|
+
get size() {
|
|
320
|
+
const used = R2.sum(R2.pluck("size", [...this.storage.values()]));
|
|
321
|
+
const free = this.__size__ > used ? this.__size__ - used : 0;
|
|
322
|
+
return {
|
|
323
|
+
used,
|
|
324
|
+
free
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
get(key) {
|
|
328
|
+
this.evictExpired();
|
|
329
|
+
const entry = this.storage.get(key);
|
|
330
|
+
this.visitCountRecords.set(key, (this.visitCountRecords.get(key) ?? 0) + 1);
|
|
331
|
+
this.visitTimeRecords.set(key, /* @__PURE__ */ new Date());
|
|
332
|
+
if (!entry) this.debug((log) => log(`Entry(${key}) Not Found`));
|
|
333
|
+
else this.debug((log) => log(`Entry(${key}) Found: `, entry));
|
|
334
|
+
return entry?.clone();
|
|
335
|
+
}
|
|
336
|
+
set(value) {
|
|
337
|
+
if (!this.evict(value.size)) {
|
|
338
|
+
this.debug((log) => log("Storage Size Not Enough: ", this.size.free, " < ", value.size));
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
this.storage.set(value.key, value);
|
|
342
|
+
this.visitTimeRecords.set(value.key, /* @__PURE__ */ new Date());
|
|
343
|
+
this.visitCountRecords.set(value.key, this.visitCountRecords.get(value.key) ?? 0);
|
|
344
|
+
this.debug((log) => log("Entry Added: ", value));
|
|
345
|
+
this.debug((log) => log("Storage Size: ", this.size));
|
|
346
|
+
}
|
|
347
|
+
__remove__(keys) {
|
|
348
|
+
for (const key of keys) {
|
|
349
|
+
const entry = this.storage.get(key);
|
|
350
|
+
if (!entry) return;
|
|
351
|
+
this.storage.delete(key);
|
|
352
|
+
this.visitCountRecords.delete(key);
|
|
353
|
+
this.visitTimeRecords.delete(key);
|
|
354
|
+
this.debug((log) => log("Entry Removed: ", entry));
|
|
355
|
+
this.debug((log) => log("Storage Size: ", this.size));
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
remove(key) {
|
|
359
|
+
this.__remove__([key]);
|
|
360
|
+
}
|
|
361
|
+
lastEvictExpiredTime = (0, import_dayjs.default)();
|
|
362
|
+
/**
|
|
363
|
+
* @zh 清除过期的缓存
|
|
364
|
+
*/
|
|
365
|
+
evictExpired() {
|
|
366
|
+
const now = (0, import_dayjs.default)();
|
|
367
|
+
if (now.diff(this.lastEvictExpiredTime, "second") < 1) return;
|
|
368
|
+
const keys = [];
|
|
369
|
+
for (const [key, entry] of this.storage.entries()) {
|
|
370
|
+
if (entry.expiredAt && now.isAfter(entry.expiredAt)) {
|
|
371
|
+
keys.push(key);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
this.__remove__(keys);
|
|
375
|
+
this.__onCacheExpired__?.({ keys });
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* @en Evict the storage to make sure the size is enough
|
|
379
|
+
* @zh 清除缓存以确保有足够的空间
|
|
380
|
+
*
|
|
381
|
+
* @return {boolean} - is evicted successfully
|
|
382
|
+
*/
|
|
383
|
+
evict(expectSize) {
|
|
384
|
+
this.evictExpired();
|
|
385
|
+
const size = this.size;
|
|
386
|
+
return size.free >= expectSize;
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
// src/storage/memory-storage/ttl-memory-storage.ts
|
|
391
|
+
var TTLMemoryStorage = class extends BaseMemoryStorage {
|
|
392
|
+
constructor(options) {
|
|
393
|
+
super(options);
|
|
394
|
+
}
|
|
395
|
+
get(key) {
|
|
396
|
+
const entry = super.get(key);
|
|
397
|
+
this.__onCacheGet__?.({ key });
|
|
398
|
+
return entry;
|
|
399
|
+
}
|
|
400
|
+
set(value) {
|
|
401
|
+
super.set(value);
|
|
402
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
403
|
+
}
|
|
404
|
+
remove(key) {
|
|
405
|
+
super.remove(key);
|
|
406
|
+
this.__onCacheRemove__?.({ key });
|
|
407
|
+
}
|
|
408
|
+
evict(expectSize) {
|
|
409
|
+
if (expectSize > this.__size__) {
|
|
410
|
+
this.debug((log) => log("Storage Size Not Enough: ", this.__size__, " < ", expectSize));
|
|
411
|
+
return false;
|
|
412
|
+
}
|
|
413
|
+
this.evictExpired();
|
|
414
|
+
let deficitSize = expectSize - this.size.free;
|
|
415
|
+
if (deficitSize <= 0) return true;
|
|
416
|
+
const entries = [...this.storage.values()].sort((a, b) => {
|
|
417
|
+
const aExpiredAt = (0, import_dayjs2.default)(a.expiredAt);
|
|
418
|
+
const bExpiredAt = (0, import_dayjs2.default)(b.expiredAt);
|
|
419
|
+
return aExpiredAt.isBefore(bExpiredAt) ? 1 : -1;
|
|
420
|
+
});
|
|
421
|
+
if (R3.sum(R3.pluck("size", entries)) < deficitSize) {
|
|
422
|
+
this.debug((log) => log("Storage Size Not Enough: ", this.size.free, " < ", deficitSize));
|
|
423
|
+
return false;
|
|
424
|
+
}
|
|
425
|
+
const keys = [];
|
|
426
|
+
while (deficitSize > 0 && entries.length) {
|
|
427
|
+
const entry = entries.pop();
|
|
428
|
+
deficitSize -= entry.size;
|
|
429
|
+
keys.push(entry.key);
|
|
430
|
+
}
|
|
431
|
+
this.__remove__(keys);
|
|
432
|
+
this.__onCacheEvict__?.({ keys });
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
// src/utils/random.ts
|
|
438
|
+
function random(min, max) {
|
|
439
|
+
return Math.floor(Math.random() * (max - min)) + min;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// src/storage/memory-storage/random-memory-storage.ts
|
|
443
|
+
var RandomMemoryStorage = class extends BaseMemoryStorage {
|
|
444
|
+
constructor(options) {
|
|
445
|
+
super(options);
|
|
446
|
+
}
|
|
447
|
+
get(key) {
|
|
448
|
+
const entry = super.get(key);
|
|
449
|
+
this.__onCacheGet__?.({ key });
|
|
450
|
+
return entry;
|
|
451
|
+
}
|
|
452
|
+
set(value) {
|
|
453
|
+
super.set(value);
|
|
454
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
455
|
+
}
|
|
456
|
+
remove(key) {
|
|
457
|
+
super.remove(key);
|
|
458
|
+
this.__onCacheRemove__?.({ key });
|
|
459
|
+
}
|
|
460
|
+
evict(expectSize) {
|
|
461
|
+
if (expectSize > this.__size__) {
|
|
462
|
+
this.debug((log) => log("Storage Size Not Enough: ", this.__size__, " < ", expectSize));
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
this.evictExpired();
|
|
466
|
+
let deficitSize = expectSize - this.size.free;
|
|
467
|
+
if (deficitSize <= 0) return true;
|
|
468
|
+
const entries = [...this.storage.values()];
|
|
469
|
+
const keys = [];
|
|
470
|
+
while (deficitSize > 0 && entries.length) {
|
|
471
|
+
const index = random(0, entries.length - 1);
|
|
472
|
+
const entry = entries[index];
|
|
473
|
+
deficitSize -= entry.size;
|
|
474
|
+
entries.splice(index, 1);
|
|
475
|
+
keys.push(entry.key);
|
|
476
|
+
}
|
|
477
|
+
this.__remove__(keys);
|
|
478
|
+
this.__onCacheEvict__?.({ keys });
|
|
479
|
+
return true;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
// src/storage/memory-storage/lru-memory-storage.ts
|
|
484
|
+
var import_dayjs3 = __toESM(require("dayjs"));
|
|
485
|
+
var LRUMemoryStorage = class extends BaseMemoryStorage {
|
|
486
|
+
constructor(options) {
|
|
487
|
+
super(options);
|
|
488
|
+
}
|
|
489
|
+
get(key) {
|
|
490
|
+
const entry = super.get(key);
|
|
491
|
+
this.__onCacheGet__?.({ key });
|
|
492
|
+
return entry;
|
|
493
|
+
}
|
|
494
|
+
set(value) {
|
|
495
|
+
super.set(value);
|
|
496
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
497
|
+
}
|
|
498
|
+
remove(key) {
|
|
499
|
+
super.remove(key);
|
|
500
|
+
this.__onCacheRemove__?.({ key });
|
|
501
|
+
}
|
|
502
|
+
evict(expectSize) {
|
|
503
|
+
if (expectSize > this.__size__) {
|
|
504
|
+
this.debug((log) => log("Storage Size Not Enough: ", this.__size__, " < ", expectSize));
|
|
505
|
+
return false;
|
|
506
|
+
}
|
|
507
|
+
this.evictExpired();
|
|
508
|
+
let deficitSize = expectSize - this.size.free;
|
|
509
|
+
if (deficitSize <= 0) return true;
|
|
510
|
+
const entries = [...this.storage.values()].sort((a, b) => {
|
|
511
|
+
const aVisitAt = this.visitTimeRecords.get(a.key);
|
|
512
|
+
const bVisitAt = this.visitTimeRecords.get(b.key);
|
|
513
|
+
if (aVisitAt === bVisitAt) return 0;
|
|
514
|
+
if (!aVisitAt) return 1;
|
|
515
|
+
if (!bVisitAt) return -1;
|
|
516
|
+
return (0, import_dayjs3.default)(aVisitAt).isBefore((0, import_dayjs3.default)(bVisitAt)) ? 1 : -1;
|
|
517
|
+
});
|
|
518
|
+
const keys = [];
|
|
519
|
+
while (deficitSize > 0 && entries.length) {
|
|
520
|
+
const entry = entries.pop();
|
|
521
|
+
deficitSize -= entry.size;
|
|
522
|
+
keys.push(entry.key);
|
|
523
|
+
}
|
|
524
|
+
this.__remove__(keys);
|
|
525
|
+
this.__onCacheEvict__?.({ keys });
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
// src/storage/memory-storage/lfu-memory-storage.ts
|
|
531
|
+
var LFUMemoryStorage = class extends BaseMemoryStorage {
|
|
532
|
+
constructor(options) {
|
|
533
|
+
super(options);
|
|
534
|
+
}
|
|
535
|
+
get(key) {
|
|
536
|
+
const entry = super.get(key);
|
|
537
|
+
this.__onCacheGet__?.({ key });
|
|
538
|
+
return entry;
|
|
539
|
+
}
|
|
540
|
+
set(value) {
|
|
541
|
+
super.set(value);
|
|
542
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
543
|
+
}
|
|
544
|
+
remove(key) {
|
|
545
|
+
super.remove(key);
|
|
546
|
+
this.__onCacheRemove__?.({ key });
|
|
547
|
+
}
|
|
548
|
+
evict(expectSize) {
|
|
549
|
+
if (expectSize > this.__size__) {
|
|
550
|
+
this.debug((log) => log("Storage Size Not Enough: ", this.__size__, " < ", expectSize));
|
|
551
|
+
return false;
|
|
552
|
+
}
|
|
553
|
+
this.evictExpired();
|
|
554
|
+
let deficitSize = expectSize - this.size.free;
|
|
555
|
+
if (deficitSize <= 0) return true;
|
|
556
|
+
const entries = [...this.storage.values()].sort((a, b) => {
|
|
557
|
+
const aVisitCount = this.visitCountRecords.get(a.key) || 0;
|
|
558
|
+
const bVisitCount = this.visitCountRecords.get(b.key) || 0;
|
|
559
|
+
return bVisitCount - aVisitCount;
|
|
560
|
+
});
|
|
561
|
+
const keys = [];
|
|
562
|
+
while (deficitSize > 0 && entries.length) {
|
|
563
|
+
const entry = entries.pop();
|
|
564
|
+
deficitSize -= entry.size;
|
|
565
|
+
keys.push(entry.key);
|
|
566
|
+
}
|
|
567
|
+
this.__remove__(keys);
|
|
568
|
+
this.__onCacheEvict__?.({ keys });
|
|
569
|
+
return true;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
// src/storage/memory-storage/memory-storage.ts
|
|
574
|
+
var MemoryStorage = class extends KeqCacheStorage {
|
|
575
|
+
storage;
|
|
576
|
+
constructor(options) {
|
|
577
|
+
super();
|
|
578
|
+
const eviction = options?.eviction || "lru" /* LRU */;
|
|
579
|
+
if (eviction === "ttl" /* TTL */) {
|
|
580
|
+
this.storage = new TTLMemoryStorage(options);
|
|
581
|
+
} else if (eviction === "random" /* RANDOM */) {
|
|
582
|
+
this.storage = new RandomMemoryStorage(options);
|
|
583
|
+
} else if (eviction === "lru" /* LRU */) {
|
|
584
|
+
this.storage = new LRUMemoryStorage(options);
|
|
585
|
+
} else if (eviction === "lfu" /* LFU */) {
|
|
586
|
+
this.storage = new LFUMemoryStorage(options);
|
|
587
|
+
} else {
|
|
588
|
+
throw new TypeError(`Invalid eviction: ${String(eviction)}`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
set(entry) {
|
|
592
|
+
return this.storage.set(entry);
|
|
593
|
+
}
|
|
594
|
+
get(key) {
|
|
595
|
+
return this.storage.get(key);
|
|
596
|
+
}
|
|
597
|
+
remove(key) {
|
|
598
|
+
return this.storage.remove(key);
|
|
599
|
+
}
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
// src/storage/indexed-db-storage/random-indexed-db-storage.ts
|
|
603
|
+
var R5 = __toESM(require("ramda"));
|
|
604
|
+
|
|
605
|
+
// src/storage/indexed-db-storage/base-indexed-db-storage.ts
|
|
606
|
+
var R4 = __toESM(require("ramda"));
|
|
607
|
+
var import_idb = require("idb");
|
|
608
|
+
var import_dayjs4 = __toESM(require("dayjs"));
|
|
609
|
+
|
|
610
|
+
// src/storage/indexed-db-storage/constants/default-table-name.ts
|
|
611
|
+
var DEFAULT_TABLE_NAME = "keq_cache_indexed_db_storage";
|
|
612
|
+
|
|
613
|
+
// src/storage/indexed-db-storage/base-indexed-db-storage.ts
|
|
614
|
+
var BaseIndexedDBStorage = class extends InternalStorage {
|
|
615
|
+
tableName = DEFAULT_TABLE_NAME;
|
|
616
|
+
db;
|
|
617
|
+
constructor(options) {
|
|
618
|
+
super(options);
|
|
619
|
+
if (options?.tableName === DEFAULT_TABLE_NAME) {
|
|
620
|
+
throw new TypeError(`[keq-cache] IndexedDBStorage name cannot be "${DEFAULT_TABLE_NAME}"`);
|
|
621
|
+
}
|
|
622
|
+
this.tableName = options?.tableName || DEFAULT_TABLE_NAME;
|
|
623
|
+
}
|
|
624
|
+
async openDB() {
|
|
625
|
+
if (this.db) return this.db;
|
|
626
|
+
const tableName = this.tableName;
|
|
627
|
+
const db = await (0, import_idb.openDB)(tableName, 2, {
|
|
628
|
+
upgrade(db2) {
|
|
629
|
+
if (!db2.objectStoreNames.contains("metadata")) {
|
|
630
|
+
const entriesStore = db2.createObjectStore("metadata", { keyPath: "key" });
|
|
631
|
+
entriesStore.createIndex("expiredAt", "expiredAt");
|
|
632
|
+
}
|
|
633
|
+
if (!db2.objectStoreNames.contains("response")) {
|
|
634
|
+
const responsesStore = db2.createObjectStore("response", { keyPath: "key" });
|
|
635
|
+
responsesStore.createIndex("responseStatus", "responseStatus");
|
|
636
|
+
}
|
|
637
|
+
if (!db2.objectStoreNames.contains("visits")) {
|
|
638
|
+
const visitsStore = db2.createObjectStore("visits", { keyPath: "key" });
|
|
639
|
+
visitsStore.createIndex("visitCount", "visitCount");
|
|
640
|
+
visitsStore.createIndex("lastVisitedAt", "lastVisitedAt");
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
blocked() {
|
|
644
|
+
console.error(`IndexedDB Table ${tableName} is blocked`);
|
|
645
|
+
},
|
|
646
|
+
blocking() {
|
|
647
|
+
console.error(`IndexedDB Table ${tableName} is blocking`);
|
|
648
|
+
},
|
|
649
|
+
terminated() {
|
|
650
|
+
console.error(`IndexedDB Table ${tableName} is terminated`);
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
this.db = db;
|
|
654
|
+
return db;
|
|
655
|
+
}
|
|
656
|
+
async getSize() {
|
|
657
|
+
const db = await this.openDB();
|
|
658
|
+
const items = await db.getAll("metadata");
|
|
659
|
+
const used = R4.sum(items.map((entry) => entry.size));
|
|
660
|
+
const free = this.__size__ - used;
|
|
661
|
+
return { used, free };
|
|
662
|
+
}
|
|
663
|
+
async get(key) {
|
|
664
|
+
await this.evictExpired();
|
|
665
|
+
try {
|
|
666
|
+
const db = await this.openDB();
|
|
667
|
+
const dbMetadata = await db.get("metadata", key);
|
|
668
|
+
const dbResponse = await db.get("response", key);
|
|
669
|
+
const dbVisits = await db.get("visits", key);
|
|
670
|
+
if (!dbMetadata || !dbResponse) return;
|
|
671
|
+
await db.put("visits", {
|
|
672
|
+
key: dbMetadata.key,
|
|
673
|
+
visitCount: dbVisits ? dbVisits.visitCount + 1 : 1,
|
|
674
|
+
lastVisitedAt: /* @__PURE__ */ new Date()
|
|
675
|
+
});
|
|
676
|
+
const response = new Response(dbResponse.responseBody, {
|
|
677
|
+
status: dbResponse.responseStatus,
|
|
678
|
+
headers: new Headers(dbResponse.responseHeaders),
|
|
679
|
+
statusText: dbResponse.responseStatusText
|
|
680
|
+
});
|
|
681
|
+
return await CacheEntry.build({
|
|
682
|
+
key: dbMetadata.key,
|
|
683
|
+
expiredAt: dbMetadata.expiredAt,
|
|
684
|
+
response,
|
|
685
|
+
size: dbMetadata.size
|
|
686
|
+
});
|
|
687
|
+
} catch (error) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
async set(entry) {
|
|
692
|
+
try {
|
|
693
|
+
if (!await this.evict(entry.size)) {
|
|
694
|
+
const size = await this.getSize();
|
|
695
|
+
this.debug((log) => log(`Storage Size Not Enough: ${size.free} < ${entry.size}`));
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
const dbMetadata = {
|
|
699
|
+
key: entry.key,
|
|
700
|
+
size: entry.size,
|
|
701
|
+
expiredAt: entry.expiredAt,
|
|
702
|
+
visitedAt: /* @__PURE__ */ new Date(),
|
|
703
|
+
visitCount: 0
|
|
704
|
+
};
|
|
705
|
+
const response = entry.response.clone();
|
|
706
|
+
const dbResponse = {
|
|
707
|
+
key: entry.key,
|
|
708
|
+
responseBody: await response.arrayBuffer(),
|
|
709
|
+
responseHeaders: [...response.headers.entries()],
|
|
710
|
+
responseStatus: response.status,
|
|
711
|
+
responseStatusText: response.statusText
|
|
712
|
+
};
|
|
713
|
+
const db = await this.openDB();
|
|
714
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
715
|
+
const metadataStore = await tx.objectStore("metadata");
|
|
716
|
+
const responseStore = await tx.objectStore("response");
|
|
717
|
+
const visitsStore = await tx.objectStore("visits");
|
|
718
|
+
const dbVisits = await visitsStore.get(entry.key) || {
|
|
719
|
+
key: entry.key,
|
|
720
|
+
visitCount: 0,
|
|
721
|
+
lastVisitedAt: /* @__PURE__ */ new Date()
|
|
722
|
+
};
|
|
723
|
+
await Promise.all([
|
|
724
|
+
metadataStore.put(dbMetadata),
|
|
725
|
+
responseStore.put(dbResponse),
|
|
726
|
+
visitsStore.put(dbVisits)
|
|
727
|
+
]);
|
|
728
|
+
await tx.done;
|
|
729
|
+
} catch (error) {
|
|
730
|
+
return;
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
async __remove__(tx, keys) {
|
|
734
|
+
await Promise.all(
|
|
735
|
+
R4.unnest(
|
|
736
|
+
keys.map((key) => [
|
|
737
|
+
tx.objectStore("metadata").delete(key),
|
|
738
|
+
tx.objectStore("response").delete(key),
|
|
739
|
+
tx.objectStore("visits").delete(key)
|
|
740
|
+
])
|
|
741
|
+
)
|
|
742
|
+
);
|
|
743
|
+
}
|
|
744
|
+
async remove(key) {
|
|
745
|
+
try {
|
|
746
|
+
const db = await this.openDB();
|
|
747
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
748
|
+
await this.__remove__(tx, [key]);
|
|
749
|
+
await tx.done;
|
|
750
|
+
} catch (error) {
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
lastEvictExpiredTime = (0, import_dayjs4.default)();
|
|
755
|
+
/**
|
|
756
|
+
* @zh 清除过期的缓存
|
|
757
|
+
*/
|
|
758
|
+
async evictExpired() {
|
|
759
|
+
const now = (0, import_dayjs4.default)();
|
|
760
|
+
if (now.diff(this.lastEvictExpiredTime, "second") < 1) return;
|
|
761
|
+
try {
|
|
762
|
+
const now2 = (0, import_dayjs4.default)();
|
|
763
|
+
const db = await this.openDB();
|
|
764
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
765
|
+
const metadataStore = tx.objectStore("metadata");
|
|
766
|
+
let cursor = await metadataStore.index("expiredAt").openCursor(IDBKeyRange.upperBound(now2.valueOf()));
|
|
767
|
+
const expiredKeys = [];
|
|
768
|
+
while (cursor) {
|
|
769
|
+
if ((0, import_dayjs4.default)(cursor.value.expiredAt).isBefore(now2)) {
|
|
770
|
+
expiredKeys.push(cursor.value.key);
|
|
771
|
+
cursor = await cursor.continue();
|
|
772
|
+
} else {
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
await this.__remove__(tx, expiredKeys);
|
|
777
|
+
await tx.done;
|
|
778
|
+
this.__onCacheExpired__?.({ keys: expiredKeys });
|
|
779
|
+
} catch (error) {
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
};
|
|
784
|
+
|
|
785
|
+
// src/storage/indexed-db-storage/random-indexed-db-storage.ts
|
|
786
|
+
var RandomIndexedDBStorage = class extends BaseIndexedDBStorage {
|
|
787
|
+
constructor(options) {
|
|
788
|
+
super(options);
|
|
789
|
+
}
|
|
790
|
+
async get(key) {
|
|
791
|
+
const entry = await super.get(key);
|
|
792
|
+
this.__onCacheGet__?.({ key });
|
|
793
|
+
return entry;
|
|
794
|
+
}
|
|
795
|
+
async set(value) {
|
|
796
|
+
await super.set(value);
|
|
797
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
798
|
+
}
|
|
799
|
+
async remove(key) {
|
|
800
|
+
await super.remove(key);
|
|
801
|
+
this.__onCacheRemove__?.({ key });
|
|
802
|
+
}
|
|
803
|
+
async evict(expectSize) {
|
|
804
|
+
await this.evictExpired();
|
|
805
|
+
const size = await this.getSize();
|
|
806
|
+
let deficitSize = expectSize - size.free;
|
|
807
|
+
if (deficitSize <= 0) return true;
|
|
808
|
+
const db = await this.openDB();
|
|
809
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
810
|
+
const metadataStore = tx.objectStore("metadata");
|
|
811
|
+
const metadatas = await metadataStore.getAll();
|
|
812
|
+
const totalSize = R5.sum(metadatas.map((m) => m.size));
|
|
813
|
+
if (totalSize < deficitSize) {
|
|
814
|
+
this.debug((log) => log(`Storage Size Not Enough, deficit size: ${deficitSize - totalSize}`));
|
|
815
|
+
await tx.abort();
|
|
816
|
+
return false;
|
|
817
|
+
}
|
|
818
|
+
const keys = [];
|
|
819
|
+
while (deficitSize > 0 && metadatas.length) {
|
|
820
|
+
const index = random(0, metadatas.length - 1);
|
|
821
|
+
const metadata = metadatas[index];
|
|
822
|
+
deficitSize -= metadata.size;
|
|
823
|
+
keys.push(metadata.key);
|
|
824
|
+
metadatas.splice(index, 1);
|
|
825
|
+
}
|
|
826
|
+
await this.__remove__(tx, keys);
|
|
827
|
+
await tx.done;
|
|
828
|
+
await this.__onCacheEvict__?.({ keys });
|
|
829
|
+
return true;
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
|
|
833
|
+
// src/storage/indexed-db-storage/lfu-indexed-db-storage.ts
|
|
834
|
+
var LFUIndexedDBStorage = class extends BaseIndexedDBStorage {
|
|
835
|
+
constructor(options) {
|
|
836
|
+
super(options);
|
|
837
|
+
}
|
|
838
|
+
async get(key) {
|
|
839
|
+
const entry = await super.get(key);
|
|
840
|
+
this.__onCacheGet__?.({ key });
|
|
841
|
+
return entry;
|
|
842
|
+
}
|
|
843
|
+
async set(value) {
|
|
844
|
+
await super.set(value);
|
|
845
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
846
|
+
}
|
|
847
|
+
async remove(key) {
|
|
848
|
+
await super.remove(key);
|
|
849
|
+
this.__onCacheRemove__?.({ key });
|
|
850
|
+
}
|
|
851
|
+
async evict(expectSize) {
|
|
852
|
+
await this.evictExpired();
|
|
853
|
+
const size = await this.getSize();
|
|
854
|
+
let deficitSize = expectSize - size.free;
|
|
855
|
+
if (deficitSize <= 0) return true;
|
|
856
|
+
const db = await this.openDB();
|
|
857
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
858
|
+
const metadataStore = tx.objectStore("metadata");
|
|
859
|
+
const visitsStore = tx.objectStore("visits");
|
|
860
|
+
let cursor = await visitsStore.index("visitCount").openCursor();
|
|
861
|
+
const keys = [];
|
|
862
|
+
while (deficitSize > 0 && cursor) {
|
|
863
|
+
const metadata = await metadataStore.get(cursor.value.key);
|
|
864
|
+
if (!metadata) {
|
|
865
|
+
await cursor.delete();
|
|
866
|
+
cursor = await cursor.continue();
|
|
867
|
+
continue;
|
|
868
|
+
}
|
|
869
|
+
deficitSize -= metadata.size;
|
|
870
|
+
keys.push(cursor.value.key);
|
|
871
|
+
cursor = await cursor.continue();
|
|
872
|
+
}
|
|
873
|
+
if (deficitSize > 0) {
|
|
874
|
+
this.debug((log) => log(`Storage Size Not Enough, deficit size: ${deficitSize}`));
|
|
875
|
+
await tx.abort;
|
|
876
|
+
return false;
|
|
877
|
+
}
|
|
878
|
+
await this.__remove__(tx, keys);
|
|
879
|
+
await tx.done;
|
|
880
|
+
this.__onCacheEvict__?.({ keys });
|
|
881
|
+
return true;
|
|
882
|
+
}
|
|
883
|
+
};
|
|
884
|
+
|
|
885
|
+
// src/storage/indexed-db-storage/lru-indexed-db-storage.ts
|
|
886
|
+
var LRUIndexedDBStorage = class extends BaseIndexedDBStorage {
|
|
887
|
+
constructor(options) {
|
|
888
|
+
super(options);
|
|
889
|
+
}
|
|
890
|
+
async get(key) {
|
|
891
|
+
const entry = await super.get(key);
|
|
892
|
+
this.__onCacheGet__?.({ key });
|
|
893
|
+
return entry;
|
|
894
|
+
}
|
|
895
|
+
async set(value) {
|
|
896
|
+
await super.set(value);
|
|
897
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
898
|
+
}
|
|
899
|
+
async remove(key) {
|
|
900
|
+
await super.remove(key);
|
|
901
|
+
this.__onCacheRemove__?.({ key });
|
|
902
|
+
}
|
|
903
|
+
async evict(expectSize) {
|
|
904
|
+
await this.evictExpired();
|
|
905
|
+
const size = await this.getSize();
|
|
906
|
+
let deficitSize = expectSize - size.free;
|
|
907
|
+
if (deficitSize <= 0) return true;
|
|
908
|
+
const db = await this.openDB();
|
|
909
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
910
|
+
const metadataStore = tx.objectStore("metadata");
|
|
911
|
+
const visitsStore = tx.objectStore("visits");
|
|
912
|
+
const keys = [];
|
|
913
|
+
let cursor = await visitsStore.index("lastVisitedAt").openCursor();
|
|
914
|
+
while (deficitSize > 0 && cursor) {
|
|
915
|
+
const metadata = await metadataStore.get(cursor.value.key);
|
|
916
|
+
if (!metadata) {
|
|
917
|
+
await cursor.delete();
|
|
918
|
+
cursor = await cursor.continue();
|
|
919
|
+
continue;
|
|
920
|
+
}
|
|
921
|
+
deficitSize -= metadata.size;
|
|
922
|
+
keys.push(cursor.value.key);
|
|
923
|
+
cursor = await cursor.continue();
|
|
924
|
+
}
|
|
925
|
+
if (deficitSize > 0) {
|
|
926
|
+
this.debug((log) => log(`Storage Size Not Enough, deficit size: ${deficitSize}`));
|
|
927
|
+
await tx.abort();
|
|
928
|
+
return false;
|
|
929
|
+
}
|
|
930
|
+
await this.__remove__(tx, keys);
|
|
931
|
+
await tx.done;
|
|
932
|
+
await this.__onCacheEvict__?.({ keys });
|
|
933
|
+
return true;
|
|
934
|
+
}
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
// src/storage/indexed-db-storage/ttl-indexed-db-storage.ts
|
|
938
|
+
var TTLIndexedDBStorage = class extends BaseIndexedDBStorage {
|
|
939
|
+
constructor(options) {
|
|
940
|
+
super(options);
|
|
941
|
+
}
|
|
942
|
+
async get(key) {
|
|
943
|
+
const entry = await super.get(key);
|
|
944
|
+
this.__onCacheGet__?.({ key });
|
|
945
|
+
return entry;
|
|
946
|
+
}
|
|
947
|
+
async set(value) {
|
|
948
|
+
await super.set(value);
|
|
949
|
+
this.__onCacheSet__?.({ key: value.key });
|
|
950
|
+
}
|
|
951
|
+
async remove(key) {
|
|
952
|
+
await super.remove(key);
|
|
953
|
+
this.__onCacheRemove__?.({ key });
|
|
954
|
+
}
|
|
955
|
+
async evict(expectSize) {
|
|
956
|
+
await this.evictExpired();
|
|
957
|
+
const size = await this.getSize();
|
|
958
|
+
let deficitSize = expectSize - size.free;
|
|
959
|
+
if (deficitSize <= 0) return true;
|
|
960
|
+
const db = await this.openDB();
|
|
961
|
+
const tx = db.transaction(["metadata", "response", "visits"], "readwrite");
|
|
962
|
+
const metadataStore = tx.objectStore("metadata");
|
|
963
|
+
const keys = [];
|
|
964
|
+
let cursor = await metadataStore.index("expiredAt").openCursor();
|
|
965
|
+
while (deficitSize > 0 && cursor) {
|
|
966
|
+
const metadata = await metadataStore.get(cursor.value.key);
|
|
967
|
+
if (!metadata) {
|
|
968
|
+
await cursor.delete();
|
|
969
|
+
cursor = await cursor.continue();
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
972
|
+
deficitSize -= metadata.size;
|
|
973
|
+
keys.push(cursor.value.key);
|
|
974
|
+
cursor = await cursor.continue();
|
|
975
|
+
}
|
|
976
|
+
if (deficitSize > 0) {
|
|
977
|
+
this.debug((log) => log(`Storage Size Not Enough, deficit size: ${deficitSize}`));
|
|
978
|
+
await tx.abort();
|
|
979
|
+
return false;
|
|
980
|
+
}
|
|
981
|
+
await this.__remove__(tx, keys);
|
|
982
|
+
await tx.done;
|
|
983
|
+
await this.__onCacheEvict__?.({ keys });
|
|
984
|
+
return true;
|
|
985
|
+
}
|
|
986
|
+
};
|
|
987
|
+
|
|
988
|
+
// src/storage/indexed-db-storage/indexed-db-storage.ts
|
|
989
|
+
var IndexedDBStorage = class extends KeqCacheStorage {
|
|
990
|
+
storage;
|
|
991
|
+
constructor(options) {
|
|
992
|
+
super();
|
|
993
|
+
const eviction = options?.eviction || "lru" /* LRU */;
|
|
994
|
+
if (eviction === "random" /* RANDOM */) {
|
|
995
|
+
this.storage = new RandomIndexedDBStorage(options);
|
|
996
|
+
} else if (eviction === "lfu" /* LFU */) {
|
|
997
|
+
this.storage = new LFUIndexedDBStorage(options);
|
|
998
|
+
} else if (eviction === "lru" /* LRU */) {
|
|
999
|
+
this.storage = new LRUIndexedDBStorage(options);
|
|
1000
|
+
} else if (eviction === "ttl" /* TTL */) {
|
|
1001
|
+
this.storage = new TTLIndexedDBStorage(options);
|
|
1002
|
+
} else {
|
|
1003
|
+
throw TypeError(`Not Supported Eviction: ${String(options?.eviction)}`);
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
set(entry) {
|
|
1007
|
+
return this.storage.set(entry);
|
|
1008
|
+
}
|
|
1009
|
+
get(key) {
|
|
1010
|
+
return this.storage.get(key);
|
|
1011
|
+
}
|
|
1012
|
+
remove(key) {
|
|
1013
|
+
return this.storage.remove(key);
|
|
1014
|
+
}
|
|
1015
|
+
};
|
|
1016
|
+
|
|
1017
|
+
// src/storage/multi-tier-storage/multi-tier-storage.ts
|
|
1018
|
+
var MultiTierStorage = class extends KeqCacheStorage {
|
|
1019
|
+
storages;
|
|
1020
|
+
/**
|
|
1021
|
+
* @param storages Array of storage instances ordered by performance (fastest first)
|
|
1022
|
+
* @zh 按性价比排序的存储实例数组,排在前面的成本低,排在后面的成本高
|
|
1023
|
+
*/
|
|
1024
|
+
constructor(options) {
|
|
1025
|
+
super();
|
|
1026
|
+
if (!options.tiers || options.tiers.length === 0) {
|
|
1027
|
+
throw new Error("At least one storage instance is required");
|
|
1028
|
+
}
|
|
1029
|
+
this.storages = [...options.tiers];
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* @en Get cache entry, searching from lowest to highest tier
|
|
1033
|
+
* @zh 获取缓存条目,从最底层到高层依次搜索
|
|
1034
|
+
*/
|
|
1035
|
+
async get(key) {
|
|
1036
|
+
for (let i = 0; i < this.storages.length; i++) {
|
|
1037
|
+
const storage = this.storages[i];
|
|
1038
|
+
const entry = await storage.get(key);
|
|
1039
|
+
if (entry) {
|
|
1040
|
+
if (i > 0) {
|
|
1041
|
+
await this.syncToLowerTiers(entry, i);
|
|
1042
|
+
}
|
|
1043
|
+
return entry;
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
return void 0;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* @en Set cache entry to all tiers concurrently
|
|
1050
|
+
* @zh 并发写入所有层的缓存
|
|
1051
|
+
*/
|
|
1052
|
+
async set(entry) {
|
|
1053
|
+
const promises = this.storages.map((storage) => storage.set(entry));
|
|
1054
|
+
await Promise.all(promises);
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* @en Remove cache entry from all tiers
|
|
1058
|
+
* @zh 从所有层删除缓存条目
|
|
1059
|
+
*/
|
|
1060
|
+
async remove(key) {
|
|
1061
|
+
const promises = this.storages.map((storage) => storage.remove(key));
|
|
1062
|
+
await Promise.all(promises);
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* @en Sync cache entry to all lower tiers (tiers with index < currentTierIndex)
|
|
1066
|
+
* @zh 将缓存条目同步到所有低层存储(索引小于当前层的存储)
|
|
1067
|
+
*/
|
|
1068
|
+
async syncToLowerTiers(entry, currentTierIndex) {
|
|
1069
|
+
const lowerTierStorages = this.storages.slice(0, currentTierIndex);
|
|
1070
|
+
if (lowerTierStorages.length === 0) {
|
|
1071
|
+
return;
|
|
1072
|
+
}
|
|
1073
|
+
const promises = lowerTierStorages.map((storage) => storage.set(entry.clone()));
|
|
1074
|
+
try {
|
|
1075
|
+
await Promise.all(promises);
|
|
1076
|
+
} catch (error) {
|
|
1077
|
+
console.warn("Failed to sync cache entry to lower tiers:", error);
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
|
|
1082
|
+
// src/storage/tier-storage/tier-storage.ts
|
|
1083
|
+
var TierStorage = class extends MultiTierStorage {
|
|
1084
|
+
constructor(options) {
|
|
1085
|
+
const memoryStorage = options?.memory instanceof MemoryStorage ? options.memory : new MemoryStorage(options?.memory);
|
|
1086
|
+
const indexedDBStorage = options?.indexedDB instanceof IndexedDBStorage ? options.indexedDB : new IndexedDBStorage(options?.indexedDB);
|
|
1087
|
+
super({
|
|
1088
|
+
tiers: [memoryStorage, indexedDBStorage]
|
|
1089
|
+
});
|
|
1090
|
+
}
|
|
1091
|
+
};
|
|
1092
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
1093
|
+
0 && (module.exports = {
|
|
1094
|
+
CacheStorage,
|
|
1095
|
+
Eviction,
|
|
1096
|
+
IndexedDBStorage,
|
|
1097
|
+
MemoryStorage,
|
|
1098
|
+
Strategy,
|
|
1099
|
+
TierStorage,
|
|
1100
|
+
cache
|
|
1101
|
+
});
|
|
1102
|
+
//# sourceMappingURL=index.js.map
|