@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/src/transport.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
/** 传输:把文字发给所有实例(包括自己)、收别的实例发的文字 */
|
|
19
|
+
export interface CacheTransport {
|
|
20
|
+
/** 发给所有实例 */
|
|
21
|
+
send(text: string): Promise<void>;
|
|
22
|
+
/** 开始收;连接断过又连上时调 onReset。返回停止收的函数 */
|
|
23
|
+
listen(onText: (text: string) => void, onReset: () => void): Promise<() => Promise<void>>;
|
|
24
|
+
/** 一条消息最多多少字节(如 PG 的 NOTIFY 不到 8000);超出时切成几条 */
|
|
25
|
+
readonly maxBytes?: number;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 线上的样子:o 是来源标记,k 是删掉的键 */
|
|
29
|
+
interface Wire {
|
|
30
|
+
o: string;
|
|
31
|
+
k: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const encoder = new TextEncoder();
|
|
35
|
+
|
|
36
|
+
/** @internal 把要删的键写成消息,按字节上限切开 */
|
|
37
|
+
export function encodeEviction(
|
|
38
|
+
origin: string,
|
|
39
|
+
keys: readonly string[],
|
|
40
|
+
maxBytes = Infinity,
|
|
41
|
+
): string[] {
|
|
42
|
+
const encode = (k: string[]) => JSON.stringify({ o: origin, k } satisfies Wire);
|
|
43
|
+
const whole = encode([...keys]);
|
|
44
|
+
if (encoder.encode(whole).length <= maxBytes) return [whole];
|
|
45
|
+
const texts: string[] = [];
|
|
46
|
+
let part: string[] = [];
|
|
47
|
+
for (const key of keys) {
|
|
48
|
+
if (part.length > 0 && encoder.encode(encode([...part, key])).length > maxBytes) {
|
|
49
|
+
texts.push(encode(part));
|
|
50
|
+
part = [];
|
|
51
|
+
}
|
|
52
|
+
part.push(key);
|
|
53
|
+
}
|
|
54
|
+
texts.push(encode(part));
|
|
55
|
+
return texts;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** @internal 读消息;解析不了的(通道上别的内容)返回 undefined */
|
|
59
|
+
export function decodeEviction(
|
|
60
|
+
text: string,
|
|
61
|
+
): { readonly origin: string; readonly keys: readonly string[] } | undefined {
|
|
62
|
+
try {
|
|
63
|
+
const wire = JSON.parse(text) as Wire;
|
|
64
|
+
return Array.isArray(wire.k) ? { origin: wire.o, keys: wire.k } : undefined;
|
|
65
|
+
} catch {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 进程内的传输:同一个 memoryTransport 上的几个缓存互相收得到(测试里模拟几个实例)。
|
|
72
|
+
* 发出去的在下一个微任务送达,和真的传输一样是异步的
|
|
73
|
+
*/
|
|
74
|
+
export function memoryTransport(): CacheTransport & { reset(): void } {
|
|
75
|
+
const listeners = new Set<{ onText: (text: string) => void; onReset: () => void }>();
|
|
76
|
+
return {
|
|
77
|
+
send(text) {
|
|
78
|
+
queueMicrotask(() => {
|
|
79
|
+
for (const listener of listeners) listener.onText(text);
|
|
80
|
+
});
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
},
|
|
83
|
+
listen(onText, onReset) {
|
|
84
|
+
const listener = { onText, onReset };
|
|
85
|
+
listeners.add(listener);
|
|
86
|
+
return Promise.resolve(() => {
|
|
87
|
+
listeners.delete(listener);
|
|
88
|
+
return Promise.resolve();
|
|
89
|
+
});
|
|
90
|
+
},
|
|
91
|
+
/** 模拟传输断过又连上(测试用) */
|
|
92
|
+
reset() {
|
|
93
|
+
for (const listener of listeners) listener.onReset();
|
|
94
|
+
},
|
|
95
|
+
};
|
|
96
|
+
}
|