@zerodep-dev/easy-query-ts-cache 0.0.0-stage → 0.2.0
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/LICENSE +201 -0
- package/README.md +11 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/index.d.ts +148 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +358 -0
- package/dist/index.js.map +1 -0
- package/dist/stores.d.ts +54 -0
- package/dist/stores.d.ts.map +1 -0
- package/dist/stores.js +90 -0
- package/dist/stores.js.map +1 -0
- package/dist/transport.d.ts +40 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +75 -0
- package/dist/transport.js.map +1 -0
- package/package.json +44 -3
- package/src/index.ts +457 -0
- package/src/stores.ts +119 -0
- package/src/transport.ts +96 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 二级缓存(对应 Java 版的 easy-cache):kv 模式按主键缓存单行,all 模式缓存整张表(适合配置、省市这类小表)。
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* const cache = createCache({ entities: [User, Province] })
|
|
6
|
+
* const db = createDb({ schema, adapter, interceptors: [cache.interceptor] }) // 写入这些实体时失效
|
|
7
|
+
* const users = cache.of(db)
|
|
8
|
+
* await users.kv(User).find(1) // 先查缓存,没有再查库并写回
|
|
9
|
+
* await users.kv(User).where((u) => u.enabled.eq(true)).find(1) // 条件是缓存字段的一部分
|
|
10
|
+
* await users.all(Province).where((p) => p.name.startsWith('浙')).toPage(1, 20)
|
|
11
|
+
* ```
|
|
12
|
+
*
|
|
13
|
+
* 三种部署(见 stores.ts):单实例用默认的本机缓存;多实例只用外部 Redis;多实例本机 + 外部 Redis,再加失效通知。
|
|
14
|
+
* 本库不带 Redis 与 MQ 的驱动,后两种由使用方实现 CacheStore 与 CacheTransport。
|
|
15
|
+
*
|
|
16
|
+
* 一致性(对应官方的"延迟删除 + 逻辑删除"方案):
|
|
17
|
+
* - 失效走拦截器:insert 按插入的主键,update / delete 按改之前的行(拦截器对缓存的实体声明 before,核心先按同样条件读出受影响的行),
|
|
18
|
+
* 所以表达式更新也知道该删哪些键——官方要自己实现 CacheKeysProvider 按更新时间回查
|
|
19
|
+
* - 写入时删一次,事务提交后再删一次(事务里别的请求可能把旧值读回缓存);回滚了第二次也不删,第一次删掉的只是少命中一次
|
|
20
|
+
* - 绕过本库的写入(直接改库、别的服务)不会失效,靠过期时间兜底,或接 CDC 调用 evict
|
|
21
|
+
* - 两级存储时:提交后删本机与共享层、发通知,别的实例收到后只删本机层;通知断过时清空本机层。读库前记下代数,
|
|
22
|
+
* 期间这个键被失效过(本机的或收到的)就不写回;lateEvictMs 在提交后隔一会儿再删一次、再发一次
|
|
23
|
+
* (挡住从库延迟与慢请求把旧值写回)。通知丢了靠本机层的过期时间兜底
|
|
24
|
+
* 读写拦截器(租户等)照常生效,它们加的条件是缓存字段的一部分:不同租户各缓存各的。
|
|
25
|
+
*/
|
|
26
|
+
import { flattenRow, nestRow, } from '@zerodep-dev/easy-query-ts';
|
|
27
|
+
import { memoryStore } from './stores.js';
|
|
28
|
+
import { decodeEviction, encodeEviction } from './transport.js';
|
|
29
|
+
export { memoryStore, tieredStore, } from './stores.js';
|
|
30
|
+
export { memoryTransport } from './transport.js';
|
|
31
|
+
/** 缓存里表示"库里没有"的值 */
|
|
32
|
+
const MISSING = 'null';
|
|
33
|
+
export class EasyCache {
|
|
34
|
+
/** 放进 createDb 的 interceptors:写入缓存的实体时失效 */
|
|
35
|
+
interceptor;
|
|
36
|
+
store;
|
|
37
|
+
tables;
|
|
38
|
+
ttlMs;
|
|
39
|
+
nullTtlMs;
|
|
40
|
+
prefix;
|
|
41
|
+
transport;
|
|
42
|
+
lateEvictMs;
|
|
43
|
+
/** 通知的来源标记:传输把消息也发给自己,收到时跳过自己发的 */
|
|
44
|
+
origin = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
45
|
+
/** 失效通知的订阅:ready() 等它,close() 退订 */
|
|
46
|
+
listening;
|
|
47
|
+
/** 写回守卫:每次失效加一;evicted 记下每个键最后一次失效时的代数 */
|
|
48
|
+
generation = 0;
|
|
49
|
+
evicted = new Map();
|
|
50
|
+
/** evicted 太大时清空,这之前开始的读一律不写回 */
|
|
51
|
+
floor = 0;
|
|
52
|
+
constructor(options) {
|
|
53
|
+
this.store = options.store ?? memoryStore();
|
|
54
|
+
this.tables = new Set(options.entities.map((e) => e.table));
|
|
55
|
+
this.ttlMs = options.ttlMs ?? 300_000;
|
|
56
|
+
this.nullTtlMs = options.nullTtlMs ?? 10_000;
|
|
57
|
+
this.prefix = options.keyPrefix ?? 'CACHE';
|
|
58
|
+
this.transport = options.transport;
|
|
59
|
+
this.lateEvictMs = options.lateEvictMs ?? 0;
|
|
60
|
+
this.listening = this.transport
|
|
61
|
+
? this.transport.listen((text) => {
|
|
62
|
+
const message = decodeEviction(text);
|
|
63
|
+
if (message && message.origin !== this.origin)
|
|
64
|
+
void this.applyRemote(message.keys);
|
|
65
|
+
}, () => void this.resetLocal())
|
|
66
|
+
: Promise.resolve(undefined);
|
|
67
|
+
// 订阅失败由 ready() 报出;没人等时不当作未处理的拒绝
|
|
68
|
+
this.listening.catch(() => undefined);
|
|
69
|
+
this.interceptor = {
|
|
70
|
+
name: 'cache',
|
|
71
|
+
before: (entity) => this.tables.has(entity.table),
|
|
72
|
+
afterWrite: async (event) => {
|
|
73
|
+
if (!this.tables.has(event.entity.table))
|
|
74
|
+
return;
|
|
75
|
+
const rows = event.kind === 'insert' ? event.rows : (event.before ?? []);
|
|
76
|
+
const keys = [this.allKey(event.entity), ...this.keysOf(event.entity, rows)];
|
|
77
|
+
// 延迟双删:现在删一次,提交后再删一次
|
|
78
|
+
this.markEvicted(keys);
|
|
79
|
+
await this.store.delete(keys);
|
|
80
|
+
event.afterCommit(() => this.remove(keys));
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
/** 绑定一个 Db(事务里的 tx 也行):缓存没命中时用它查库 */
|
|
85
|
+
of(db) {
|
|
86
|
+
return new CacheClient(db, this);
|
|
87
|
+
}
|
|
88
|
+
/** 手动失效(CDC、别的服务改了库):给主键删这几行,不给删这个实体的全部 kv 之外的 all 缓存 */
|
|
89
|
+
async evict(entity, keys = []) {
|
|
90
|
+
await this.remove([this.allKey(entity), ...keys.map((k) => this.kvKey(entity, k))]);
|
|
91
|
+
}
|
|
92
|
+
/** 等失效通知订阅好(部署时启动阶段调用;没有 transport 时立即完成) */
|
|
93
|
+
async ready() {
|
|
94
|
+
await this.listening;
|
|
95
|
+
}
|
|
96
|
+
/** 退订失效通知(停机、测试结束) */
|
|
97
|
+
async close() {
|
|
98
|
+
const stop = await this.listening.catch(() => undefined);
|
|
99
|
+
await stop?.();
|
|
100
|
+
}
|
|
101
|
+
/** 本实例的失效:删本机与共享层、发通知;配了 lateEvictMs 时过一会儿再来一次 */
|
|
102
|
+
async remove(keys) {
|
|
103
|
+
this.markEvicted(keys);
|
|
104
|
+
await this.store.delete(keys);
|
|
105
|
+
await this.notify(keys);
|
|
106
|
+
if (this.lateEvictMs > 0) {
|
|
107
|
+
const timer = setTimeout(() => {
|
|
108
|
+
this.markEvicted(keys);
|
|
109
|
+
void this.store
|
|
110
|
+
.delete(keys)
|
|
111
|
+
.then(() => this.notify(keys))
|
|
112
|
+
.catch(() => undefined);
|
|
113
|
+
}, this.lateEvictMs);
|
|
114
|
+
// 不让这个定时器拖住进程退出
|
|
115
|
+
timer.unref?.();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async notify(keys) {
|
|
119
|
+
if (!this.transport)
|
|
120
|
+
return;
|
|
121
|
+
for (const text of encodeEviction(this.origin, keys, this.transport.maxBytes)) {
|
|
122
|
+
await this.transport.send(text);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/** 别的实例的失效:只删本机层 */
|
|
126
|
+
async applyRemote(keys) {
|
|
127
|
+
this.markEvicted(keys);
|
|
128
|
+
if (this.store.evictLocal)
|
|
129
|
+
await this.store.evictLocal(keys);
|
|
130
|
+
else
|
|
131
|
+
await this.store.delete(keys);
|
|
132
|
+
}
|
|
133
|
+
/** 通知断过:中间的可能丢了,清空本机层,这之前开始的读不写回 */
|
|
134
|
+
async resetLocal() {
|
|
135
|
+
this.floor = ++this.generation;
|
|
136
|
+
this.evicted.clear();
|
|
137
|
+
await this.store.evictLocal?.(undefined);
|
|
138
|
+
}
|
|
139
|
+
markEvicted(keys) {
|
|
140
|
+
this.generation++;
|
|
141
|
+
if (this.evicted.size > 10_000) {
|
|
142
|
+
this.evicted.clear();
|
|
143
|
+
this.floor = this.generation;
|
|
144
|
+
}
|
|
145
|
+
for (const key of keys)
|
|
146
|
+
this.evicted.set(key, this.generation);
|
|
147
|
+
}
|
|
148
|
+
/** @internal 读库前调用:记下现在的代数,write 时带回来 */
|
|
149
|
+
since() {
|
|
150
|
+
return this.generation;
|
|
151
|
+
}
|
|
152
|
+
/** @internal 缓存里的值:undefined 是没缓存,null 是缓存了"库里没有" */
|
|
153
|
+
async read(key, field) {
|
|
154
|
+
const cached = await this.store.get(key, field);
|
|
155
|
+
return cached === MISSING ? null : cached;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* @internal 写回缓存;value 为 undefined 表示库里没有(较短的过期时间)。
|
|
159
|
+
* since 是读库前的代数:期间这个键被失效过(读到的可能是旧值)就不写
|
|
160
|
+
*/
|
|
161
|
+
async write(key, field, value, since) {
|
|
162
|
+
if (since < this.floor || (this.evicted.get(key) ?? 0) > since)
|
|
163
|
+
return;
|
|
164
|
+
await this.store.set(key, field, value ?? MISSING, value === undefined ? this.nullTtlMs : this.ttlMs);
|
|
165
|
+
}
|
|
166
|
+
/** @internal */
|
|
167
|
+
check(entity, mode) {
|
|
168
|
+
if (!this.tables.has(entity.table))
|
|
169
|
+
throw new Error(`cache.${mode}(${entity.name}): list ${entity.name} in createCache({ entities }) so that writes evict it`);
|
|
170
|
+
}
|
|
171
|
+
/** @internal */
|
|
172
|
+
kvKey(entity, key) {
|
|
173
|
+
return `${this.prefix}:${entity.table}:${String(key)}`;
|
|
174
|
+
}
|
|
175
|
+
/** @internal */
|
|
176
|
+
allKey(entity) {
|
|
177
|
+
return `${this.prefix}:${entity.table}:all`;
|
|
178
|
+
}
|
|
179
|
+
keysOf(entity, rows) {
|
|
180
|
+
const [pk] = entity.primaryKey;
|
|
181
|
+
if (pk === undefined || entity.primaryKey.length > 1)
|
|
182
|
+
return [];
|
|
183
|
+
return rows
|
|
184
|
+
.map((row) => row[pk])
|
|
185
|
+
.filter((v) => v !== undefined && v !== null)
|
|
186
|
+
.map((v) => this.kvKey(entity, v));
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/** 建一个缓存;它的 interceptor 要放进 createDb */
|
|
190
|
+
export function createCache(options) {
|
|
191
|
+
return new EasyCache(options);
|
|
192
|
+
}
|
|
193
|
+
/** 绑定了 Db 的缓存入口 */
|
|
194
|
+
export class CacheClient {
|
|
195
|
+
db;
|
|
196
|
+
cache;
|
|
197
|
+
constructor(db, cache) {
|
|
198
|
+
this.db = db;
|
|
199
|
+
this.cache = cache;
|
|
200
|
+
}
|
|
201
|
+
/** kv 模式(对应官方 kvStorage):按单列主键缓存单行 */
|
|
202
|
+
kv(entity) {
|
|
203
|
+
this.cache.check(entity, 'kv');
|
|
204
|
+
if (entity.primaryKey.length !== 1)
|
|
205
|
+
throw new Error(`cache.kv(${entity.name}) needs a single-column primary key`);
|
|
206
|
+
return new KvCache(this.db, this.cache, entity, []);
|
|
207
|
+
}
|
|
208
|
+
/** all 模式(对应官方 allStorage):整张表按条件缓存成一个列表,toList / toPage 在内存里取 */
|
|
209
|
+
all(entity) {
|
|
210
|
+
this.cache.check(entity, 'all');
|
|
211
|
+
return new AllCache(this.db, this.cache, entity, []);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
export class KvCache {
|
|
215
|
+
db;
|
|
216
|
+
cache;
|
|
217
|
+
entity;
|
|
218
|
+
filters;
|
|
219
|
+
constructor(db, cache, entity, filters) {
|
|
220
|
+
this.db = db;
|
|
221
|
+
this.cache = cache;
|
|
222
|
+
this.entity = entity;
|
|
223
|
+
this.filters = filters;
|
|
224
|
+
}
|
|
225
|
+
/** 加条件(对应官方 kvStorage 的 where):不满足条件的行当作没有;条件连同拦截器加的条件是缓存字段 */
|
|
226
|
+
where(filter) {
|
|
227
|
+
return new KvCache(this.db, this.cache, this.entity, [...this.filters, filter]);
|
|
228
|
+
}
|
|
229
|
+
async find(key) {
|
|
230
|
+
return (await this.findMany([key]))[0] ?? null;
|
|
231
|
+
}
|
|
232
|
+
/** 几个主键一起取:没命中的用一条 IN 查询补上;返回按给出的顺序,库里没有的跳过 */
|
|
233
|
+
async findMany(keys) {
|
|
234
|
+
const field = this.field();
|
|
235
|
+
const found = new Map();
|
|
236
|
+
const missing = [];
|
|
237
|
+
for (const key of keys) {
|
|
238
|
+
const cached = await this.cache.read(this.cache.kvKey(this.entity, key), field);
|
|
239
|
+
if (cached === undefined)
|
|
240
|
+
missing.push(key);
|
|
241
|
+
else if (cached !== null)
|
|
242
|
+
found.set(String(key), deserialize(this.entity, cached));
|
|
243
|
+
}
|
|
244
|
+
if (missing.length > 0) {
|
|
245
|
+
const since = this.cache.since();
|
|
246
|
+
const pk = this.entity.primaryKey[0];
|
|
247
|
+
const rows = (await this.query()
|
|
248
|
+
.where((r) => r[pk].in(missing))
|
|
249
|
+
.toList());
|
|
250
|
+
const byKey = new Map(rows.map((row) => [String(row[pk]), row]));
|
|
251
|
+
for (const key of missing) {
|
|
252
|
+
const row = byKey.get(String(key));
|
|
253
|
+
await this.cache.write(this.cache.kvKey(this.entity, key), field, row && serialize(this.entity, row), since);
|
|
254
|
+
if (row)
|
|
255
|
+
found.set(String(key), row);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
return keys
|
|
259
|
+
.map((key) => found.get(String(key)))
|
|
260
|
+
.filter((row) => row !== undefined);
|
|
261
|
+
}
|
|
262
|
+
query() {
|
|
263
|
+
const q = this.db.queryable(this.entity);
|
|
264
|
+
return this.filters.reduce((acc, f) => acc.where(f), q);
|
|
265
|
+
}
|
|
266
|
+
/** 缓存字段:条件(含拦截器加的)的 SQL 与参数 */
|
|
267
|
+
field() {
|
|
268
|
+
const { sql, params } = this.query().toSQL();
|
|
269
|
+
return fingerprint(sql, params);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
export class AllCache {
|
|
273
|
+
db;
|
|
274
|
+
cache;
|
|
275
|
+
entity;
|
|
276
|
+
filters;
|
|
277
|
+
constructor(db, cache, entity, filters) {
|
|
278
|
+
this.db = db;
|
|
279
|
+
this.cache = cache;
|
|
280
|
+
this.entity = entity;
|
|
281
|
+
this.filters = filters;
|
|
282
|
+
}
|
|
283
|
+
where(filter) {
|
|
284
|
+
return new AllCache(this.db, this.cache, this.entity, [...this.filters, filter]);
|
|
285
|
+
}
|
|
286
|
+
async toList() {
|
|
287
|
+
const query = this.filters.reduce((acc, f) => acc.where(f), this.db.queryable(this.entity));
|
|
288
|
+
const pk = this.entity.primaryKey;
|
|
289
|
+
const ordered = pk.length > 0 ? query.orderBy((r) => pk.map((k) => r[k])) : query;
|
|
290
|
+
const { sql, params } = ordered.toSQL();
|
|
291
|
+
const key = this.cache.allKey(this.entity);
|
|
292
|
+
const field = fingerprint(sql, params);
|
|
293
|
+
let text = await this.cache.read(key, field);
|
|
294
|
+
if (typeof text !== 'string') {
|
|
295
|
+
const since = this.cache.since();
|
|
296
|
+
const rows = (await ordered.toList());
|
|
297
|
+
text = JSON.stringify(rows.map((row) => serialize(this.entity, row)));
|
|
298
|
+
await this.cache.write(key, field, text, since);
|
|
299
|
+
}
|
|
300
|
+
const list = JSON.parse(text);
|
|
301
|
+
return list.map((item) => deserialize(this.entity, item));
|
|
302
|
+
}
|
|
303
|
+
/** 在缓存的整表上分页(page 从 1 开始) */
|
|
304
|
+
async toPage(page, size) {
|
|
305
|
+
const rows = await this.toList();
|
|
306
|
+
return { rows: rows.slice((page - 1) * size, page * size), total: rows.length, page, size };
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/** 一行按列的编码写成文字(日期、大整数、二进制都能原样读回) */
|
|
310
|
+
function serialize(entity, row) {
|
|
311
|
+
const flat = flattenRow(entity, row);
|
|
312
|
+
const encoded = Object.fromEntries(Object.entries(entity.columns).map(([p, column]) => {
|
|
313
|
+
const value = flat[p];
|
|
314
|
+
return [
|
|
315
|
+
p,
|
|
316
|
+
value === null || value === undefined ? null : column.codec.encode(value),
|
|
317
|
+
];
|
|
318
|
+
}));
|
|
319
|
+
return JSON.stringify(encoded, (_key, value) => {
|
|
320
|
+
if (typeof value === 'bigint')
|
|
321
|
+
return { $bigint: value.toString() };
|
|
322
|
+
if (value instanceof Uint8Array)
|
|
323
|
+
return { $bytes: Buffer.from(value).toString('base64') };
|
|
324
|
+
return value;
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
function deserialize(entity, text) {
|
|
328
|
+
const raw = JSON.parse(text, (_key, value) => {
|
|
329
|
+
if (value && typeof value === 'object') {
|
|
330
|
+
const tagged = value;
|
|
331
|
+
if (tagged.$bigint !== undefined)
|
|
332
|
+
return BigInt(tagged.$bigint);
|
|
333
|
+
if (tagged.$bytes !== undefined)
|
|
334
|
+
return new Uint8Array(Buffer.from(tagged.$bytes, 'base64'));
|
|
335
|
+
}
|
|
336
|
+
return value;
|
|
337
|
+
});
|
|
338
|
+
const flat = Object.fromEntries(Object.entries(entity.columns).map(([p, column]) => {
|
|
339
|
+
const value = raw[p];
|
|
340
|
+
return [p, value === null || value === undefined ? null : column.codec.decode(value)];
|
|
341
|
+
}));
|
|
342
|
+
return nestRow(flat);
|
|
343
|
+
}
|
|
344
|
+
/** 条件的指纹:SQL 与参数(cyrb53,够用来区分缓存字段) */
|
|
345
|
+
function fingerprint(sql, params) {
|
|
346
|
+
const text = JSON.stringify([sql, params], (_k, v) => typeof v === 'bigint' ? v.toString() : v);
|
|
347
|
+
let h1 = 0xdeadbeef;
|
|
348
|
+
let h2 = 0x41c6ce57;
|
|
349
|
+
for (let i = 0; i < text.length; i++) {
|
|
350
|
+
const c = text.charCodeAt(i);
|
|
351
|
+
h1 = Math.imul(h1 ^ c, 2654435761);
|
|
352
|
+
h2 = Math.imul(h2 ^ c, 1597334677);
|
|
353
|
+
}
|
|
354
|
+
h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507) ^ Math.imul(h2 ^ (h2 >>> 13), 3266489909);
|
|
355
|
+
h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507) ^ Math.imul(h1 ^ (h1 >>> 13), 3266489909);
|
|
356
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);
|
|
357
|
+
}
|
|
358
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/stores.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存的存储。三种部署各用一种:
|
|
3
|
+
*
|
|
4
|
+
* createCache({ entities }); // 单实例:默认的 memoryStore,本机缓存
|
|
5
|
+
* createCache({ entities, store: redisStore }); // 多实例:只用外部 Redis,实现 CacheStore
|
|
6
|
+
* createCache({ // 多实例:本机 + 外部 Redis,再加失效通知
|
|
7
|
+
* entities,
|
|
8
|
+
* store: tieredStore({ local: memoryStore(), shared: redisStore }),
|
|
9
|
+
* transport,
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* 本库不带 Redis 驱动,CacheStore 由使用方按自己的客户端实现(完整示例在仓库的 packages/test/src/redis-cache.ts)。
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* 缓存的存储(对应官方的 CacheManager):一个键下多个字段(字段是查询条件),按键删除。
|
|
16
|
+
* Redis 用 HGET / HSET + PEXPIRE / DEL 实现
|
|
17
|
+
*/
|
|
18
|
+
export interface CacheStore {
|
|
19
|
+
/** 读一个字段;没有或过期时返回 undefined */
|
|
20
|
+
get(key: string, field: string): Promise<string | undefined>;
|
|
21
|
+
/** 写一个字段,ttlMs 毫秒后过期 */
|
|
22
|
+
set(key: string, field: string, value: string, ttlMs: number): Promise<void>;
|
|
23
|
+
/** 删掉这些键(连同它们所有的字段) */
|
|
24
|
+
delete(keys: readonly string[]): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* 别的实例的失效通知到了:只删本机这一层(共享层写入的那个实例已经删过);keys 为 undefined 时清空本机层(通知断过)。
|
|
27
|
+
* 没有这个方法的存储收到通知时按 delete 删
|
|
28
|
+
*/
|
|
29
|
+
evictLocal?(keys: readonly string[] | undefined): Promise<void>;
|
|
30
|
+
}
|
|
31
|
+
/** memoryStore 的选项 */
|
|
32
|
+
export interface MemoryStoreOptions {
|
|
33
|
+
/** 最多存多少个键,默认 10000:超出时删最久没用过的。过期的键没人再读就不会被清掉,靠这个上限兜底 */
|
|
34
|
+
readonly maxKeys?: number;
|
|
35
|
+
/** 当前时间,可以换成测试用的时钟 */
|
|
36
|
+
readonly now?: () => number;
|
|
37
|
+
}
|
|
38
|
+
/** 进程内的存储:每个字段各自过期,键数有上限 */
|
|
39
|
+
export declare function memoryStore(options?: MemoryStoreOptions): CacheStore;
|
|
40
|
+
/** tieredStore 的选项 */
|
|
41
|
+
export interface TieredStoreOptions {
|
|
42
|
+
/** 本机层,通常是 memoryStore() */
|
|
43
|
+
readonly local: CacheStore;
|
|
44
|
+
/** 共享层:使用方实现的 Redis 存储 */
|
|
45
|
+
readonly shared: CacheStore;
|
|
46
|
+
/** 本机层最多存多久(毫秒),默认 30 秒:失效通知丢了时本机层最多旧这么久 */
|
|
47
|
+
readonly localTtlMs?: number;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 两级存储:读先本机、再共享,共享命中时回填本机;写两层都写(本机层的过期时间取较短的);删两层都删;
|
|
51
|
+
* 别的实例的失效通知只删本机层
|
|
52
|
+
*/
|
|
53
|
+
export declare function tieredStore(options: TieredStoreOptions): CacheStore;
|
|
54
|
+
//# sourceMappingURL=stores.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stores.d.ts","sources":["../src/stores.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH;;;GAGG;AACH,MAAM,WAAW,UAAU;IACzB,+BAA+B;IAC/B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC7D,wBAAwB;IACxB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,uBAAuB;IACvB,MAAM,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C;;;OAGG;IACH,UAAU,CAAC,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACjE;AAED,sBAAsB;AACtB,MAAM,WAAW,kBAAkB;IACjC,yDAAyD;IACzD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,sBAAsB;IACtB,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED,4BAA4B;AAC5B,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,UAAU,CAsCxE;AAED,sBAAsB;AACtB,MAAM,WAAW,kBAAkB;IACjC,4BAA4B;IAC5B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,0BAA0B;IAC1B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,4CAA4C;IAC5C,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU,CAuBnE"}
|
package/dist/stores.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存的存储。三种部署各用一种:
|
|
3
|
+
*
|
|
4
|
+
* createCache({ entities }); // 单实例:默认的 memoryStore,本机缓存
|
|
5
|
+
* createCache({ entities, store: redisStore }); // 多实例:只用外部 Redis,实现 CacheStore
|
|
6
|
+
* createCache({ // 多实例:本机 + 外部 Redis,再加失效通知
|
|
7
|
+
* entities,
|
|
8
|
+
* store: tieredStore({ local: memoryStore(), shared: redisStore }),
|
|
9
|
+
* transport,
|
|
10
|
+
* });
|
|
11
|
+
*
|
|
12
|
+
* 本库不带 Redis 驱动,CacheStore 由使用方按自己的客户端实现(完整示例在仓库的 packages/test/src/redis-cache.ts)。
|
|
13
|
+
*/
|
|
14
|
+
/** 进程内的存储:每个字段各自过期,键数有上限 */
|
|
15
|
+
export function memoryStore(options = {}) {
|
|
16
|
+
const { maxKeys = 10_000, now = Date.now } = options;
|
|
17
|
+
// Map 按插入顺序遍历:用过的键挪到末尾,最前面的就是最久没用过的
|
|
18
|
+
const keys = new Map();
|
|
19
|
+
const touch = (key, fields) => {
|
|
20
|
+
keys.delete(key);
|
|
21
|
+
keys.set(key, fields);
|
|
22
|
+
};
|
|
23
|
+
const store = {
|
|
24
|
+
get(key, field) {
|
|
25
|
+
const fields = keys.get(key);
|
|
26
|
+
const item = fields?.get(field);
|
|
27
|
+
if (!fields || !item)
|
|
28
|
+
return Promise.resolve(undefined);
|
|
29
|
+
if (item.expires <= now()) {
|
|
30
|
+
fields.delete(field);
|
|
31
|
+
return Promise.resolve(undefined);
|
|
32
|
+
}
|
|
33
|
+
touch(key, fields);
|
|
34
|
+
return Promise.resolve(item.value);
|
|
35
|
+
},
|
|
36
|
+
set(key, field, value, ttlMs) {
|
|
37
|
+
const fields = keys.get(key) ?? new Map();
|
|
38
|
+
fields.set(field, { value, expires: now() + ttlMs });
|
|
39
|
+
touch(key, fields);
|
|
40
|
+
if (keys.size > maxKeys)
|
|
41
|
+
keys.delete(keys.keys().next().value);
|
|
42
|
+
return Promise.resolve();
|
|
43
|
+
},
|
|
44
|
+
delete(list) {
|
|
45
|
+
for (const key of list)
|
|
46
|
+
keys.delete(key);
|
|
47
|
+
return Promise.resolve();
|
|
48
|
+
},
|
|
49
|
+
evictLocal(list) {
|
|
50
|
+
if (list)
|
|
51
|
+
return store.delete(list);
|
|
52
|
+
keys.clear();
|
|
53
|
+
return Promise.resolve();
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
return store;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* 两级存储:读先本机、再共享,共享命中时回填本机;写两层都写(本机层的过期时间取较短的);删两层都删;
|
|
60
|
+
* 别的实例的失效通知只删本机层
|
|
61
|
+
*/
|
|
62
|
+
export function tieredStore(options) {
|
|
63
|
+
const { local, shared, localTtlMs = 30_000 } = options;
|
|
64
|
+
return {
|
|
65
|
+
async get(key, field) {
|
|
66
|
+
const near = await local.get(key, field);
|
|
67
|
+
if (near !== undefined)
|
|
68
|
+
return near;
|
|
69
|
+
const far = await shared.get(key, field);
|
|
70
|
+
if (far !== undefined)
|
|
71
|
+
await local.set(key, field, far, localTtlMs);
|
|
72
|
+
return far;
|
|
73
|
+
},
|
|
74
|
+
async set(key, field, value, ttlMs) {
|
|
75
|
+
await shared.set(key, field, value, ttlMs);
|
|
76
|
+
await local.set(key, field, value, Math.min(ttlMs, localTtlMs));
|
|
77
|
+
},
|
|
78
|
+
async delete(keys) {
|
|
79
|
+
await shared.delete(keys);
|
|
80
|
+
await local.delete(keys);
|
|
81
|
+
},
|
|
82
|
+
async evictLocal(keys) {
|
|
83
|
+
if (local.evictLocal)
|
|
84
|
+
await local.evictLocal(keys);
|
|
85
|
+
else if (keys)
|
|
86
|
+
await local.delete(keys);
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=stores.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stores.js","sources":["../src/stores.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多实例之间的失效通知:一个实例写入后,让别的实例删掉本机层里的缓存。本库不带 Redis、MQ 的驱动,传输由使用方实现两个方法:
|
|
3
|
+
*
|
|
4
|
+
* const transport: CacheTransport = {
|
|
5
|
+
* send: (text) => producer.send({ topic: 'cache-evict', messages: [{ value: text }] }).then(() => {}),
|
|
6
|
+
* listen: async (onText, onReset) => {
|
|
7
|
+
* // Kafka 每个实例一个消费组才是广播;重平衡、重连时调 onReset
|
|
8
|
+
* await consumer.subscribe({ topic: 'cache-evict' });
|
|
9
|
+
* await consumer.run({ eachMessage: async ({ message }) => onText(String(message.value)) });
|
|
10
|
+
* return () => consumer.disconnect();
|
|
11
|
+
* },
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* 消息至少送达一次就够:收到后做的都是删除,重复无害;传输断过又连上时调 onReset(中间的可能丢了),收的一方清空本机层。
|
|
15
|
+
* 接 Redis 发布订阅的完整示例在仓库的 packages/test/src/redis-cache.ts。
|
|
16
|
+
*/
|
|
17
|
+
/** 传输:把文字发给所有实例(包括自己)、收别的实例发的文字 */
|
|
18
|
+
export interface CacheTransport {
|
|
19
|
+
/** 发给所有实例 */
|
|
20
|
+
send(text: string): Promise<void>;
|
|
21
|
+
/** 开始收;连接断过又连上时调 onReset。返回停止收的函数 */
|
|
22
|
+
listen(onText: (text: string) => void, onReset: () => void): Promise<() => Promise<void>>;
|
|
23
|
+
/** 一条消息最多多少字节(如 PG 的 NOTIFY 不到 8000);超出时切成几条 */
|
|
24
|
+
readonly maxBytes?: number;
|
|
25
|
+
}
|
|
26
|
+
/** @internal 把要删的键写成消息,按字节上限切开 */
|
|
27
|
+
export declare function encodeEviction(origin: string, keys: readonly string[], maxBytes?: number): string[];
|
|
28
|
+
/** @internal 读消息;解析不了的(通道上别的内容)返回 undefined */
|
|
29
|
+
export declare function decodeEviction(text: string): {
|
|
30
|
+
readonly origin: string;
|
|
31
|
+
readonly keys: readonly string[];
|
|
32
|
+
} | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* 进程内的传输:同一个 memoryTransport 上的几个缓存互相收得到(测试里模拟几个实例)。
|
|
35
|
+
* 发出去的在下一个微任务送达,和真的传输一样是异步的
|
|
36
|
+
*/
|
|
37
|
+
export declare function memoryTransport(): CacheTransport & {
|
|
38
|
+
reset(): void;
|
|
39
|
+
};
|
|
40
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,mCAAmC;AACnC,MAAM,WAAW,cAAc;IAC7B,aAAa;IACb,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClC,qCAAqC;IACrC,MAAM,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,EAAE,OAAO,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1F,gDAAgD;IAChD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAUD,kCAAkC;AAClC,wBAAgB,cAAc,CAC5B,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,SAAW,GAClB,MAAM,EAAE,CAeV;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAC5B,IAAI,EAAE,MAAM,GACX;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CAAE,GAAG,SAAS,CAO3E;AAED;;;GAGG;AACH,wBAAgB,eAAe,IAAI,cAAc,GAAG;IAAE,KAAK,IAAI,IAAI,CAAA;CAAE,CAsBpE"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 多实例之间的失效通知:一个实例写入后,让别的实例删掉本机层里的缓存。本库不带 Redis、MQ 的驱动,传输由使用方实现两个方法:
|
|
3
|
+
*
|
|
4
|
+
* const transport: CacheTransport = {
|
|
5
|
+
* send: (text) => producer.send({ topic: 'cache-evict', messages: [{ value: text }] }).then(() => {}),
|
|
6
|
+
* listen: async (onText, onReset) => {
|
|
7
|
+
* // Kafka 每个实例一个消费组才是广播;重平衡、重连时调 onReset
|
|
8
|
+
* await consumer.subscribe({ topic: 'cache-evict' });
|
|
9
|
+
* await consumer.run({ eachMessage: async ({ message }) => onText(String(message.value)) });
|
|
10
|
+
* return () => consumer.disconnect();
|
|
11
|
+
* },
|
|
12
|
+
* };
|
|
13
|
+
*
|
|
14
|
+
* 消息至少送达一次就够:收到后做的都是删除,重复无害;传输断过又连上时调 onReset(中间的可能丢了),收的一方清空本机层。
|
|
15
|
+
* 接 Redis 发布订阅的完整示例在仓库的 packages/test/src/redis-cache.ts。
|
|
16
|
+
*/
|
|
17
|
+
const encoder = new TextEncoder();
|
|
18
|
+
/** @internal 把要删的键写成消息,按字节上限切开 */
|
|
19
|
+
export function encodeEviction(origin, keys, maxBytes = Infinity) {
|
|
20
|
+
const encode = (k) => JSON.stringify({ o: origin, k });
|
|
21
|
+
const whole = encode([...keys]);
|
|
22
|
+
if (encoder.encode(whole).length <= maxBytes)
|
|
23
|
+
return [whole];
|
|
24
|
+
const texts = [];
|
|
25
|
+
let part = [];
|
|
26
|
+
for (const key of keys) {
|
|
27
|
+
if (part.length > 0 && encoder.encode(encode([...part, key])).length > maxBytes) {
|
|
28
|
+
texts.push(encode(part));
|
|
29
|
+
part = [];
|
|
30
|
+
}
|
|
31
|
+
part.push(key);
|
|
32
|
+
}
|
|
33
|
+
texts.push(encode(part));
|
|
34
|
+
return texts;
|
|
35
|
+
}
|
|
36
|
+
/** @internal 读消息;解析不了的(通道上别的内容)返回 undefined */
|
|
37
|
+
export function decodeEviction(text) {
|
|
38
|
+
try {
|
|
39
|
+
const wire = JSON.parse(text);
|
|
40
|
+
return Array.isArray(wire.k) ? { origin: wire.o, keys: wire.k } : undefined;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* 进程内的传输:同一个 memoryTransport 上的几个缓存互相收得到(测试里模拟几个实例)。
|
|
48
|
+
* 发出去的在下一个微任务送达,和真的传输一样是异步的
|
|
49
|
+
*/
|
|
50
|
+
export function memoryTransport() {
|
|
51
|
+
const listeners = new Set();
|
|
52
|
+
return {
|
|
53
|
+
send(text) {
|
|
54
|
+
queueMicrotask(() => {
|
|
55
|
+
for (const listener of listeners)
|
|
56
|
+
listener.onText(text);
|
|
57
|
+
});
|
|
58
|
+
return Promise.resolve();
|
|
59
|
+
},
|
|
60
|
+
listen(onText, onReset) {
|
|
61
|
+
const listener = { onText, onReset };
|
|
62
|
+
listeners.add(listener);
|
|
63
|
+
return Promise.resolve(() => {
|
|
64
|
+
listeners.delete(listener);
|
|
65
|
+
return Promise.resolve();
|
|
66
|
+
});
|
|
67
|
+
},
|
|
68
|
+
/** 模拟传输断过又连上(测试用) */
|
|
69
|
+
reset() {
|
|
70
|
+
for (const listener of listeners)
|
|
71
|
+
listener.onReset();
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
//# sourceMappingURL=transport.js.map
|