@jintianxiayu/cache-decorator 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -11
- package/README.md +364 -270
- package/dist/core/cache-error.d.ts +75 -0
- package/dist/core/cache-error.d.ts.map +1 -0
- package/dist/core/cache-error.js +168 -0
- package/dist/core/cache-error.js.map +1 -0
- package/dist/core/cache-logger.d.ts +3 -2
- package/dist/core/cache-logger.d.ts.map +1 -1
- package/dist/core/cache-logger.js +2 -0
- package/dist/core/cache-logger.js.map +1 -1
- package/dist/decorators/cache-evict.d.ts.map +1 -1
- package/dist/decorators/cache-evict.js +15 -10
- package/dist/decorators/cache-evict.js.map +1 -1
- package/dist/decorators/cache.d.ts +6 -0
- package/dist/decorators/cache.d.ts.map +1 -1
- package/dist/decorators/cache.js +113 -36
- package/dist/decorators/cache.js.map +1 -1
- package/jest.config.js +11 -11
- package/package.json +1 -1
- package/src/adapters/ioredis-cache-client.ts +89 -89
- package/src/adapters/node-redis-cache-client.ts +95 -95
- package/src/adapters/redis-key-prefix.ts +38 -38
- package/src/core/cache-error.ts +233 -0
- package/src/core/cache-logger.ts +116 -105
- package/src/core/key-builder.ts +33 -33
- package/src/core/native-cache.ts +55 -55
- package/src/core/pending-cache.ts +29 -29
- package/src/core/redis-cache-client.ts +160 -160
- package/src/core/redis-cache.ts +104 -104
- package/src/decorators/cache-evict.ts +137 -129
- package/src/decorators/cache.ts +323 -203
- package/src/index.ts +11 -11
- package/test/cache-evict-logging.test.ts +398 -362
- package/test/cache-logger.integration.test.ts +159 -129
- package/test/cache-logger.test.ts +156 -153
- package/test/cache-logging.test.ts +929 -544
- package/test/cache.test.ts +1017 -255
- package/test/fixtures/cache-logger-child.mjs +108 -108
- package/test/fixtures/cache-provider-failure-child.mjs +70 -0
- package/test/helpers/legacy-redis-cache.ts +41 -22
- package/test/helpers/package-consumer.ts +231 -231
- package/test/helpers/redis-fixture.ts +142 -142
- package/test/ioredis-cache-client.test.ts +143 -143
- package/test/legacy-redis-cache.test.ts +51 -0
- package/test/native-cache.test.ts +77 -77
- package/test/node-redis-cache-client.test.ts +149 -149
- package/test/pending-cache.test.ts +69 -69
- package/test/redis-cache-client-lifecycle.test.ts +112 -112
- package/test/redis-cache-client-types.test.ts +184 -184
- package/test/redis-cache-client.integration.test.ts +355 -327
- package/test/redis-cache-decorator.test.ts +601 -201
- package/test/redis-cache-provider.test.ts +269 -269
- package/test/type-contract/contract.ts +119 -64
- package/test/type-contract/tsconfig.json +12 -12
- package/tsconfig.json +8 -8
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/** 异常缓存使用的固定 envelope 类型标识。 */
|
|
2
|
+
declare const CACHE_ERROR_KIND = "@jintianxiayu/cache-decorator/error";
|
|
3
|
+
/** 当前异常缓存 envelope 版本。 */
|
|
4
|
+
declare const CACHE_ERROR_VERSION = 1;
|
|
5
|
+
/**
|
|
6
|
+
* 异常缓存编解码器,将业务异常转换为可持久化 payload,并在命中时恢复异常语义。
|
|
7
|
+
*/
|
|
8
|
+
export interface CacheErrorCodec {
|
|
9
|
+
/**
|
|
10
|
+
* 编码本次业务异常。
|
|
11
|
+
* @param error 被装饰业务方法抛出或拒绝的值。
|
|
12
|
+
* @returns 可 JSON 往返的 payload。
|
|
13
|
+
*/
|
|
14
|
+
encode(error: unknown): unknown;
|
|
15
|
+
/**
|
|
16
|
+
* 解码缓存中的 payload。
|
|
17
|
+
* @param payload 当前版本 envelope 中的 JSON payload。
|
|
18
|
+
* @returns 命中时应向调用方抛出的值。
|
|
19
|
+
*/
|
|
20
|
+
decode(payload: unknown): unknown;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 业务异常持久化策略;提供该对象即显式启用异常缓存。
|
|
24
|
+
*/
|
|
25
|
+
export interface CacheErrorPolicy {
|
|
26
|
+
/** 独立于正常结果 TTL 的正整数秒级异常 TTL。 */
|
|
27
|
+
ttl: number;
|
|
28
|
+
/** 返回 true 时允许缓存本次业务异常;省略时接受全部业务异常。 */
|
|
29
|
+
shouldCache?: (error: unknown) => boolean;
|
|
30
|
+
/** 成对提供的异常编解码器;省略时使用标准 Error/JSON 值 codec。 */
|
|
31
|
+
codec?: CacheErrorCodec;
|
|
32
|
+
}
|
|
33
|
+
/** 装饰器求值时完成校验并绑定默认 codec 的内部策略。 */
|
|
34
|
+
export interface NormalizedCacheErrorPolicy {
|
|
35
|
+
readonly ttl: number;
|
|
36
|
+
readonly shouldCache: ((error: unknown) => boolean) | undefined;
|
|
37
|
+
readonly codec: CacheErrorCodec;
|
|
38
|
+
}
|
|
39
|
+
/** Provider 中异常条目的版本化内容。 */
|
|
40
|
+
export interface VersionedCacheErrorPayload {
|
|
41
|
+
readonly kind: typeof CACHE_ERROR_KIND;
|
|
42
|
+
readonly version: typeof CACHE_ERROR_VERSION;
|
|
43
|
+
readonly payload: unknown;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* 在 legacy decorator 求值阶段校验外部异常策略并绑定默认 codec。
|
|
47
|
+
* @param policy 调用方传入的可选异常策略。
|
|
48
|
+
* @returns 禁用状态返回 undefined;启用状态返回不可变的内部策略快照。
|
|
49
|
+
* @throws TTL 非正有限整数时抛 RangeError;筛选器或 codec 结构非法时抛 TypeError。
|
|
50
|
+
*/
|
|
51
|
+
export declare function normalizeCacheErrorPolicy(policy: CacheErrorPolicy | undefined): NormalizedCacheErrorPolicy | undefined;
|
|
52
|
+
/**
|
|
53
|
+
* 将业务异常编码为当前版本且已经 JSON 规范化的 envelope。
|
|
54
|
+
* @param error 本次业务方法产生的原始异常。
|
|
55
|
+
* @param policy 已在 decorator 求值阶段归一化的策略。
|
|
56
|
+
* @returns 可直接交给任意 CacheProvider 的版本化异常内容。
|
|
57
|
+
* @throws codec 或 JSON 往返失败时传播错误。
|
|
58
|
+
*/
|
|
59
|
+
export declare function encodeCacheError(error: unknown, policy: NormalizedCacheErrorPolicy): VersionedCacheErrorPayload;
|
|
60
|
+
/**
|
|
61
|
+
* 判断 Provider 返回的 error 字段是否为当前版本 envelope。
|
|
62
|
+
* @param value Provider 返回的 error 字段。
|
|
63
|
+
* @returns kind、version 与 payload 完整匹配当前协议时返回 true。
|
|
64
|
+
*/
|
|
65
|
+
export declare function isCurrentCacheErrorPayload(value: unknown): value is VersionedCacheErrorPayload;
|
|
66
|
+
/**
|
|
67
|
+
* 使用当前策略解码异常,并拒绝异步或不能安全表示的非 Error 结果。
|
|
68
|
+
* @param envelope 已识别为当前版本的异常内容。
|
|
69
|
+
* @param policy 当前装饰器启用的异常策略。
|
|
70
|
+
* @returns codec 产生的同一个异常结果。
|
|
71
|
+
* @throws codec 抛错或返回不受支持的结果时抛出。
|
|
72
|
+
*/
|
|
73
|
+
export declare function decodeCacheError(envelope: VersionedCacheErrorPayload, policy: NormalizedCacheErrorPolicy): unknown;
|
|
74
|
+
export {};
|
|
75
|
+
//# sourceMappingURL=cache-error.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-error.d.ts","sourceRoot":"","sources":["../../src/core/cache-error.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,QAAA,MAAM,gBAAgB,wCAAwC,CAAC;AAE/D,0BAA0B;AAC1B,QAAA,MAAM,mBAAmB,IAAI,CAAC;AAE9B;;GAEG;AACH,MAAM,WAAW,eAAe;IAC5B;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC;IAEhC;;;;OAIG;IACH,MAAM,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC;CACrC;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC7B,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IAEZ,uCAAuC;IACvC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC;IAE1C,8CAA8C;IAC9C,KAAK,CAAC,EAAE,eAAe,CAAC;CAC3B;AAED,mCAAmC;AACnC,MAAM,WAAW,0BAA0B;IACvC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,WAAW,EAAE,CAAC,CAAC,KAAK,EAAE,OAAO,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;IAChE,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;CACnC;AAED,4BAA4B;AAC5B,MAAM,WAAW,0BAA0B;IACvC,QAAQ,CAAC,IAAI,EAAE,OAAO,gBAAgB,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,OAAO,mBAAmB,CAAC;IAC7C,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC7B;AA0GD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACrC,MAAM,EAAE,gBAAgB,GAAG,SAAS,GACrC,0BAA0B,GAAG,SAAS,CAqBxC;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,0BAA0B,GAAG,0BAA0B,CAO/G;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,0BAA0B,CAO9F;AAED;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,0BAA0B,EAAE,MAAM,EAAE,0BAA0B,GAAG,OAAO,CAOlH"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.normalizeCacheErrorPolicy = normalizeCacheErrorPolicy;
|
|
4
|
+
exports.encodeCacheError = encodeCacheError;
|
|
5
|
+
exports.isCurrentCacheErrorPayload = isCurrentCacheErrorPayload;
|
|
6
|
+
exports.decodeCacheError = decodeCacheError;
|
|
7
|
+
/** 异常缓存使用的固定 envelope 类型标识。 */
|
|
8
|
+
const CACHE_ERROR_KIND = '@jintianxiayu/cache-decorator/error';
|
|
9
|
+
/** 当前异常缓存 envelope 版本。 */
|
|
10
|
+
const CACHE_ERROR_VERSION = 1;
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null;
|
|
13
|
+
}
|
|
14
|
+
function isPlainObject(value) {
|
|
15
|
+
const prototype = Object.getPrototypeOf(value);
|
|
16
|
+
return prototype === Object.prototype || prototype === null;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* 递归校验值是否可以无损表示为 JSON,拒绝 JSON.stringify 会静默丢弃或改写的结构。
|
|
20
|
+
* @param value 待校验的 payload 或 envelope 节点。
|
|
21
|
+
* @param ancestors 当前递归路径上的对象,用于识别循环引用而不拒绝普通重复引用。
|
|
22
|
+
* @returns 无返回值。
|
|
23
|
+
* @throws 值包含非 JSON 类型、非有限数、稀疏数组、symbol key、特殊对象或循环引用时抛出 TypeError。
|
|
24
|
+
*/
|
|
25
|
+
function validateJsonValue(value, ancestors) {
|
|
26
|
+
if (value === null || typeof value === 'string' || typeof value === 'boolean') {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === 'number') {
|
|
30
|
+
if (!Number.isFinite(value)) {
|
|
31
|
+
throw new TypeError('Cache error payload numbers must be finite');
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (!isRecord(value)) {
|
|
36
|
+
throw new TypeError('Cache error payload must contain only JSON-compatible values');
|
|
37
|
+
}
|
|
38
|
+
if ((!Array.isArray(value) && !isPlainObject(value)) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
39
|
+
throw new TypeError('Cache error payload must use JSON objects and arrays');
|
|
40
|
+
}
|
|
41
|
+
if (ancestors.has(value)) {
|
|
42
|
+
throw new TypeError('Cache error payload must not contain circular references');
|
|
43
|
+
}
|
|
44
|
+
ancestors.add(value);
|
|
45
|
+
try {
|
|
46
|
+
if (Array.isArray(value)) {
|
|
47
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
48
|
+
if (!Object.prototype.hasOwnProperty.call(value, index)) {
|
|
49
|
+
throw new TypeError('Cache error payload arrays must not be sparse');
|
|
50
|
+
}
|
|
51
|
+
validateJsonValue(value[index], ancestors);
|
|
52
|
+
}
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
for (const child of Object.values(value)) {
|
|
56
|
+
validateJsonValue(child, ancestors);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
ancestors.delete(value);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 校验并执行一次 JSON stringify/parse,以统一 Memory、Redis 与自定义 Provider 得到的表示。
|
|
65
|
+
* @param value codec 产生的完整版本化 envelope。
|
|
66
|
+
* @returns JSON 往返后的独立规范化值。
|
|
67
|
+
* @throws 无法安全 JSON 往返时抛出 TypeError 或底层序列化错误。
|
|
68
|
+
*/
|
|
69
|
+
function normalizeJsonValue(value) {
|
|
70
|
+
validateJsonValue(value, new Set());
|
|
71
|
+
const serialized = JSON.stringify(value);
|
|
72
|
+
if (typeof serialized !== 'string') {
|
|
73
|
+
throw new TypeError('Cache error payload must serialize to JSON');
|
|
74
|
+
}
|
|
75
|
+
return JSON.parse(serialized);
|
|
76
|
+
}
|
|
77
|
+
const defaultCacheErrorCodec = Object.freeze({
|
|
78
|
+
encode(error) {
|
|
79
|
+
if (error instanceof Error) {
|
|
80
|
+
return { type: 'error', name: error.name, message: error.message };
|
|
81
|
+
}
|
|
82
|
+
return { type: 'value', value: error };
|
|
83
|
+
},
|
|
84
|
+
decode(payload) {
|
|
85
|
+
if (!isRecord(payload)) {
|
|
86
|
+
throw new TypeError('Default cache error payload must be an object');
|
|
87
|
+
}
|
|
88
|
+
if (payload.type === 'error' && typeof payload.name === 'string' && typeof payload.message === 'string') {
|
|
89
|
+
const error = new Error(payload.message);
|
|
90
|
+
error.name = payload.name;
|
|
91
|
+
return error;
|
|
92
|
+
}
|
|
93
|
+
if (payload.type === 'value' && Object.prototype.hasOwnProperty.call(payload, 'value')) {
|
|
94
|
+
return payload.value;
|
|
95
|
+
}
|
|
96
|
+
throw new TypeError('Default cache error payload has an unsupported shape');
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
/**
|
|
100
|
+
* 在 legacy decorator 求值阶段校验外部异常策略并绑定默认 codec。
|
|
101
|
+
* @param policy 调用方传入的可选异常策略。
|
|
102
|
+
* @returns 禁用状态返回 undefined;启用状态返回不可变的内部策略快照。
|
|
103
|
+
* @throws TTL 非正有限整数时抛 RangeError;筛选器或 codec 结构非法时抛 TypeError。
|
|
104
|
+
*/
|
|
105
|
+
function normalizeCacheErrorPolicy(policy) {
|
|
106
|
+
if (policy === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (!isRecord(policy)) {
|
|
110
|
+
throw new TypeError('Cache error policy must be an object');
|
|
111
|
+
}
|
|
112
|
+
const ttl = policy.ttl;
|
|
113
|
+
const shouldCache = policy.shouldCache;
|
|
114
|
+
const configuredCodec = policy.codec;
|
|
115
|
+
if (typeof ttl !== 'number' || !Number.isFinite(ttl) || !Number.isInteger(ttl) || ttl < 1) {
|
|
116
|
+
throw new RangeError('Cache error TTL must be a positive finite integer');
|
|
117
|
+
}
|
|
118
|
+
if (shouldCache !== undefined && typeof shouldCache !== 'function') {
|
|
119
|
+
throw new TypeError('Cache error shouldCache must be a function');
|
|
120
|
+
}
|
|
121
|
+
const codec = configuredCodec === undefined ? defaultCacheErrorCodec : configuredCodec;
|
|
122
|
+
if (!isRecord(codec) || typeof codec.encode !== 'function' || typeof codec.decode !== 'function') {
|
|
123
|
+
throw new TypeError('Cache error codec must provide encode and decode functions');
|
|
124
|
+
}
|
|
125
|
+
return Object.freeze({ ttl, shouldCache, codec });
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 将业务异常编码为当前版本且已经 JSON 规范化的 envelope。
|
|
129
|
+
* @param error 本次业务方法产生的原始异常。
|
|
130
|
+
* @param policy 已在 decorator 求值阶段归一化的策略。
|
|
131
|
+
* @returns 可直接交给任意 CacheProvider 的版本化异常内容。
|
|
132
|
+
* @throws codec 或 JSON 往返失败时传播错误。
|
|
133
|
+
*/
|
|
134
|
+
function encodeCacheError(error, policy) {
|
|
135
|
+
const envelope = {
|
|
136
|
+
kind: CACHE_ERROR_KIND,
|
|
137
|
+
version: CACHE_ERROR_VERSION,
|
|
138
|
+
payload: policy.codec.encode(error),
|
|
139
|
+
};
|
|
140
|
+
return normalizeJsonValue(envelope);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 判断 Provider 返回的 error 字段是否为当前版本 envelope。
|
|
144
|
+
* @param value Provider 返回的 error 字段。
|
|
145
|
+
* @returns kind、version 与 payload 完整匹配当前协议时返回 true。
|
|
146
|
+
*/
|
|
147
|
+
function isCurrentCacheErrorPayload(value) {
|
|
148
|
+
return (isRecord(value) &&
|
|
149
|
+
value.kind === CACHE_ERROR_KIND &&
|
|
150
|
+
value.version === CACHE_ERROR_VERSION &&
|
|
151
|
+
Object.prototype.hasOwnProperty.call(value, 'payload'));
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* 使用当前策略解码异常,并拒绝异步或不能安全表示的非 Error 结果。
|
|
155
|
+
* @param envelope 已识别为当前版本的异常内容。
|
|
156
|
+
* @param policy 当前装饰器启用的异常策略。
|
|
157
|
+
* @returns codec 产生的同一个异常结果。
|
|
158
|
+
* @throws codec 抛错或返回不受支持的结果时抛出。
|
|
159
|
+
*/
|
|
160
|
+
function decodeCacheError(envelope, policy) {
|
|
161
|
+
const decoded = policy.codec.decode(envelope.payload);
|
|
162
|
+
if (decoded instanceof Error) {
|
|
163
|
+
return decoded;
|
|
164
|
+
}
|
|
165
|
+
validateJsonValue(decoded, new Set());
|
|
166
|
+
return decoded;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=cache-error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache-error.js","sourceRoot":"","sources":["../../src/core/cache-error.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type CacheLogEvent = 'cache.pending_hit' | 'cache.hit' | 'cache.miss' | 'cache.write_dispatched' | 'cache.evict_dispatched' | 'cache.evict_completed' | 'cache.key_fallback' | 'cache.evict_skipped' | 'cache.operation_failed';
|
|
1
|
+
export type CacheLogEvent = 'cache.pending_hit' | 'cache.hit' | 'cache.miss' | 'cache.write_dispatched' | 'cache.error_cache_skipped' | 'cache.error_cache_failed' | 'cache.evict_dispatched' | 'cache.evict_completed' | 'cache.key_fallback' | 'cache.evict_skipped' | 'cache.operation_failed';
|
|
2
2
|
/** 缓存日志只接受与决策相关的稳定字段,避免业务数据和 cache key 被意外带入输出。 */
|
|
3
3
|
export interface CacheLogContext {
|
|
4
4
|
readonly cacheName: string;
|
|
@@ -6,7 +6,8 @@ export interface CacheLogContext {
|
|
|
6
6
|
readonly providerName: string;
|
|
7
7
|
readonly entryType?: 'value' | 'error';
|
|
8
8
|
readonly scope?: 'key' | 'allEntries';
|
|
9
|
-
readonly reason?: 'resolver_error' | 'business_error';
|
|
9
|
+
readonly reason?: 'resolver_error' | 'business_error' | 'disabled' | 'predicate_rejected' | 'legacy_entry' | 'disabled_entry';
|
|
10
|
+
readonly phase?: 'predicate' | 'encode' | 'decode';
|
|
10
11
|
readonly operation?: 'provider_resolution' | 'read' | 'write' | 'evict';
|
|
11
12
|
readonly error?: unknown;
|
|
12
13
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-logger.d.ts","sourceRoot":"","sources":["../../src/core/cache-logger.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GACnB,mBAAmB,GACnB,WAAW,GACX,YAAY,GACZ,wBAAwB,GACxB,wBAAwB,GACxB,uBAAuB,GACvB,oBAAoB,GACpB,qBAAqB,GACrB,wBAAwB,CAAC;AAS/B,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,YAAY,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,
|
|
1
|
+
{"version":3,"file":"cache-logger.d.ts","sourceRoot":"","sources":["../../src/core/cache-logger.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GACnB,mBAAmB,GACnB,WAAW,GACX,YAAY,GACZ,wBAAwB,GACxB,2BAA2B,GAC3B,0BAA0B,GAC1B,wBAAwB,GACxB,uBAAuB,GACvB,oBAAoB,GACpB,qBAAqB,GACrB,wBAAwB,CAAC;AAS/B,mDAAmD;AACnD,MAAM,WAAW,eAAe;IAC5B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,SAAS,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC;IACvC,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,GAAG,YAAY,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EACV,gBAAgB,GAChB,gBAAgB,GAChB,UAAU,GACV,oBAAoB,GACpB,cAAc,GACd,gBAAgB,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,GAAG,QAAQ,GAAG,QAAQ,CAAC;IACnD,QAAQ,CAAC,SAAS,CAAC,EAAE,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC5B;AAgDD;;;;;;GAMG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,GAAG,IAAI,CAMlF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,CAK3E"}
|
|
@@ -10,6 +10,8 @@ const CACHE_LOG_DEFINITIONS = {
|
|
|
10
10
|
'cache.hit': { level: 'debug', message: 'Cache entry hit' },
|
|
11
11
|
'cache.miss': { level: 'debug', message: 'Cache entry missed' },
|
|
12
12
|
'cache.write_dispatched': { level: 'debug', message: 'Cache write dispatched' },
|
|
13
|
+
'cache.error_cache_skipped': { level: 'debug', message: 'Cache error entry skipped' },
|
|
14
|
+
'cache.error_cache_failed': { level: 'warn', message: 'Cache error policy failed' },
|
|
13
15
|
'cache.evict_dispatched': { level: 'debug', message: 'Cache eviction dispatched' },
|
|
14
16
|
'cache.evict_completed': { level: 'debug', message: 'Cache eviction completed' },
|
|
15
17
|
'cache.key_fallback': { level: 'warn', message: 'Cache key resolver failed; using default key' },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-logger.js","sourceRoot":"","sources":["../../src/core/cache-logger.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"cache-logger.js","sourceRoot":"","sources":["../../src/core/cache-logger.ts"],"names":[],"mappings":";;AAgGA,sCAMC;AAQD,gDAKC;AAnHD,iDAA2E;AAE3E,MAAM,iBAAiB,GAAG,+BAA+B,CAAC;AAyC1D,2CAA2C;AAC3C,MAAM,qBAAqB,GAAwD;IAC/E,mBAAmB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,8BAA8B,EAAE;IAChF,WAAW,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE;IAC3D,YAAY,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,oBAAoB,EAAE;IAC/D,wBAAwB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE;IAC/E,2BAA2B,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,2BAA2B,EAAE;IACrF,0BAA0B,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,2BAA2B,EAAE;IACnF,wBAAwB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,2BAA2B,EAAE;IAClF,uBAAuB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,0BAA0B,EAAE;IAChF,oBAAoB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,8CAA8C,EAAE;IAChG,qBAAqB,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,+CAA+C,EAAE;IAClG,wBAAwB,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,EAAE;CAClF,CAAC;AAEF,IAAI,WAAwC,CAAC;AAE7C,SAAS,cAAc;IACnB,WAAW,KAAK,sBAAa,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;IAC3D,OAAO,WAAW,CAAC;AACvB,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,aAAa,CAClB,MAAuB,EACvB,UAA8B,EAC9B,QAA6D;IAE7D,IAAI,UAAU,CAAC,KAAK,KAAK,OAAO,EAAE,CAAC;QAC/B,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC3C,OAAO;IACX,CAAC;IACD,IAAI,UAAU,CAAC,KAAK,KAAK,MAAM,EAAE,CAAC;QAC9B,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;QAC1C,OAAO;IACX,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,aAAa,CAAC,KAAoB,EAAE,OAAwB;IACxE,IAAI,CAAC;QACD,aAAa,CAAC,cAAc,EAAE,EAAE,qBAAqB,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,OAAO,EAAE,CAAC,CAAC;IACzF,CAAC;IAAC,OAAO,MAAM,EAAE,CAAC;QACd,OAAO;IACX,CAAC;AACL,CAAC;AAED;;;;;GAKG;AACH,SAAgB,kBAAkB,CAAC,YAAgC;IAC/D,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1D,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,OAAO,YAAY,CAAC;AACxB,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-evict.d.ts","sourceRoot":"","sources":["../../src/decorators/cache-evict.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,SAAS,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC;IAChE;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;
|
|
1
|
+
{"version":3,"file":"cache-evict.d.ts","sourceRoot":"","sources":["../../src/decorators/cache-evict.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAoB,YAAY,EAAE,MAAM,SAAS,CAAC;AAE9D;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAAC,YAAY,EAAE,KAAK,CAAC;IAChE;;OAEG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACzB;AAmED;;;;;GAKG;AACH,wBAAgB,UAAU,CACtB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,iBAAiB,GAC5B,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,KAAK,IAAI,CAuCjF;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC"}
|
|
@@ -28,11 +28,10 @@ function resolveCacheKey(keyResolver, args, logContext) {
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
/**
|
|
31
|
-
* 获取淘汰操作使用的 Provider
|
|
31
|
+
* 获取淘汰操作使用的 Provider,并把解析失败转换为缓存旁路。
|
|
32
32
|
* @param providerName decorator 配置中的 Provider 名称。
|
|
33
33
|
* @param logContext 当前方法的稳定日志上下文。
|
|
34
|
-
* @returns 已注册的缓存 Provider。
|
|
35
|
-
* @throws Provider 注册表抛出的原始错误。
|
|
34
|
+
* @returns 已注册的缓存 Provider;解析失败时返回 undefined。
|
|
36
35
|
*/
|
|
37
36
|
function resolveCacheProvider(providerName, logContext) {
|
|
38
37
|
try {
|
|
@@ -40,26 +39,29 @@ function resolveCacheProvider(providerName, logContext) {
|
|
|
40
39
|
}
|
|
41
40
|
catch (error) {
|
|
42
41
|
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...logContext, operation: 'provider_resolution', error });
|
|
43
|
-
|
|
42
|
+
return undefined;
|
|
44
43
|
}
|
|
45
44
|
}
|
|
46
45
|
/**
|
|
47
|
-
* 保持 fire-and-forget 语义发起单 key
|
|
46
|
+
* 保持 fire-and-forget 语义发起单 key 删除,并消费同步或异步 Provider 失败。
|
|
48
47
|
* @param provider 当前淘汰使用的 Provider。
|
|
49
48
|
* @param cacheKey 待删除的完整 key;不会进入日志元数据。
|
|
50
49
|
* @param logContext 当前方法的稳定日志上下文。
|
|
51
|
-
* @returns
|
|
52
|
-
* @throws Provider delete 同步抛出的原始错误。
|
|
50
|
+
* @returns 无返回值;删除失败仅记录日志,不影响业务结果。
|
|
53
51
|
*/
|
|
54
52
|
function dispatchCacheDelete(provider, cacheKey, logContext) {
|
|
53
|
+
let operation;
|
|
55
54
|
try {
|
|
56
|
-
provider.delete(cacheKey);
|
|
55
|
+
operation = provider.delete(cacheKey);
|
|
57
56
|
}
|
|
58
57
|
catch (error) {
|
|
59
58
|
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...logContext, operation: 'evict', error });
|
|
60
|
-
|
|
59
|
+
return;
|
|
61
60
|
}
|
|
62
61
|
(0, cache_logger_1.logCacheEvent)('cache.evict_dispatched', { ...logContext, scope: 'key' });
|
|
62
|
+
void Promise.resolve(operation).catch((error) => {
|
|
63
|
+
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...logContext, operation: 'evict', error });
|
|
64
|
+
});
|
|
63
65
|
}
|
|
64
66
|
/**
|
|
65
67
|
* 缓存清除装饰器
|
|
@@ -85,13 +87,16 @@ function CacheEvict(cacheName, options) {
|
|
|
85
87
|
throw error;
|
|
86
88
|
}
|
|
87
89
|
const provider = resolveCacheProvider(options?.providerName, logContext);
|
|
90
|
+
if (provider === undefined) {
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
88
93
|
if (options?.allEntries) {
|
|
89
94
|
try {
|
|
90
95
|
await provider.deleteByPattern(cacheName + '*');
|
|
91
96
|
}
|
|
92
97
|
catch (error) {
|
|
93
98
|
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...logContext, operation: 'evict', error });
|
|
94
|
-
|
|
99
|
+
return result;
|
|
95
100
|
}
|
|
96
101
|
(0, cache_logger_1.logCacheEvent)('cache.evict_completed', { ...logContext, scope: 'allEntries' });
|
|
97
102
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache-evict.js","sourceRoot":"","sources":["../../src/decorators/cache-evict.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"cache-evict.js","sourceRoot":"","sources":["../../src/decorators/cache-evict.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import 'reflect-metadata';
|
|
2
|
+
import { type CacheErrorCodec, type CacheErrorPolicy } from '../core/cache-error';
|
|
2
3
|
/**
|
|
3
4
|
* 缓存 key 解析器类型
|
|
4
5
|
* - null: 使用自动生成逻辑
|
|
@@ -6,6 +7,7 @@ import 'reflect-metadata';
|
|
|
6
7
|
* - function: 接收方法参数数组,返回自定义字符串
|
|
7
8
|
*/
|
|
8
9
|
export type CacheKeyResolver = null | string | ((...args: unknown[]) => string);
|
|
10
|
+
export type { CacheErrorCodec, CacheErrorPolicy };
|
|
9
11
|
/**
|
|
10
12
|
* @Cache 装饰器配置项
|
|
11
13
|
*/
|
|
@@ -25,6 +27,10 @@ export interface CacheOptions {
|
|
|
25
27
|
* - function: 调用函数后使用 KeyBuilder.build(cacheName, [result])
|
|
26
28
|
*/
|
|
27
29
|
key?: CacheKeyResolver;
|
|
30
|
+
/**
|
|
31
|
+
* 业务异常缓存策略;省略时不向 Provider 持久化业务异常。
|
|
32
|
+
*/
|
|
33
|
+
errorCache?: CacheErrorPolicy;
|
|
28
34
|
}
|
|
29
35
|
/**
|
|
30
36
|
* 缓存装饰器
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/decorators/cache.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/decorators/cache.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAKH,KAAK,eAAe,EACpB,KAAK,gBAAgB,EAExB,MAAM,qBAAqB,CAAC;AAO7B;;;;;GAKG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,GAAG,MAAM,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,KAAK,MAAM,CAAC,CAAC;AAEhF,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,CAAC;AAElD;;GAEG;AACH,MAAM,WAAW,YAAY;IACzB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IAEb;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;;OAKG;IACH,GAAG,CAAC,EAAE,gBAAgB,CAAC;IAEvB;;OAEG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAC;CACjC;AA6LD;;;;;GAKG;AACH,wBAAgB,KAAK,CACjB,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,YAAY,GACvB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,EAAE,kBAAkB,KAAK,IAAI,CAsEjF;AAED,OAAO,EAAE,qBAAqB,EAAE,MAAM,iCAAiC,CAAC"}
|
package/dist/decorators/cache.js
CHANGED
|
@@ -3,17 +3,17 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.CacheProviderRegistry = void 0;
|
|
4
4
|
exports.Cache = Cache;
|
|
5
5
|
require("reflect-metadata");
|
|
6
|
+
const cache_error_1 = require("../core/cache-error");
|
|
6
7
|
const cache_logger_1 = require("../core/cache-logger");
|
|
7
8
|
const cache_provider_registry_1 = require("../core/cache-provider-registry");
|
|
8
9
|
const key_builder_1 = require("../core/key-builder");
|
|
9
10
|
const pending_cache_1 = require("../core/pending-cache");
|
|
10
11
|
const pendingCache = new pending_cache_1.PendingCache();
|
|
11
12
|
/**
|
|
12
|
-
* 获取配置指向的缓存 Provider
|
|
13
|
+
* 获取配置指向的缓存 Provider,并把解析失败转换为缓存旁路。
|
|
13
14
|
* @param providerName decorator 配置中的 Provider 名称。
|
|
14
15
|
* @param logContext 当前方法的稳定日志上下文。
|
|
15
|
-
* @returns 已注册的缓存 Provider。
|
|
16
|
-
* @throws Provider 注册表抛出的原始错误。
|
|
16
|
+
* @returns 已注册的缓存 Provider;解析失败时返回 undefined。
|
|
17
17
|
*/
|
|
18
18
|
function resolveCacheProvider(providerName, logContext) {
|
|
19
19
|
try {
|
|
@@ -21,24 +21,98 @@ function resolveCacheProvider(providerName, logContext) {
|
|
|
21
21
|
}
|
|
22
22
|
catch (error) {
|
|
23
23
|
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...logContext, operation: 'provider_resolution', error });
|
|
24
|
-
|
|
24
|
+
return undefined;
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
/**
|
|
28
|
-
* 保持 fire-and-forget
|
|
28
|
+
* 保持 fire-and-forget 语义提交缓存写入,并消费同步或异步 Provider 失败。
|
|
29
29
|
* @param request 写入所需 Provider、entry 和无业务数据日志上下文。
|
|
30
|
-
* @returns
|
|
31
|
-
* @throws Provider set 同步抛出的原始错误。
|
|
30
|
+
* @returns 无返回值;写入失败仅记录日志,不影响业务结果。
|
|
32
31
|
*/
|
|
33
32
|
function dispatchCacheWrite(request) {
|
|
33
|
+
let operation;
|
|
34
34
|
try {
|
|
35
|
-
request.provider.set(request.cacheKey, request.entry, request.ttl);
|
|
35
|
+
operation = request.provider.set(request.cacheKey, request.entry, request.ttl);
|
|
36
36
|
}
|
|
37
37
|
catch (error) {
|
|
38
38
|
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...request.logContext, operation: 'write', error });
|
|
39
|
-
|
|
39
|
+
return;
|
|
40
40
|
}
|
|
41
41
|
(0, cache_logger_1.logCacheEvent)('cache.write_dispatched', { ...request.logContext, entryType: request.entryType });
|
|
42
|
+
void Promise.resolve(operation).catch((error) => {
|
|
43
|
+
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...request.logContext, operation: 'write', error });
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
function isErrorCacheEntry(entry) {
|
|
47
|
+
return typeof entry === 'object' && entry !== null && 'error' in entry;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* 按当前异常策略分类 Provider 条目,并只把可成功解码的当前版本异常视为命中。
|
|
51
|
+
* @param entry Provider 返回的缓存联合条目。
|
|
52
|
+
* @param errorPolicy 当前装饰器归一化后的异常策略。
|
|
53
|
+
* @param logContext 当前方法的稳定日志上下文。
|
|
54
|
+
* @returns value/error 命中结果,或需要继续业务流程的 miss。
|
|
55
|
+
*/
|
|
56
|
+
function resolveCachedEntry(entry, errorPolicy, logContext) {
|
|
57
|
+
if (!isErrorCacheEntry(entry)) {
|
|
58
|
+
(0, cache_logger_1.logCacheEvent)('cache.hit', { ...logContext, entryType: 'value' });
|
|
59
|
+
return { type: 'value', value: entry.value };
|
|
60
|
+
}
|
|
61
|
+
if (!(0, cache_error_1.isCurrentCacheErrorPayload)(entry.error)) {
|
|
62
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_skipped', { ...logContext, reason: 'legacy_entry' });
|
|
63
|
+
return { type: 'miss' };
|
|
64
|
+
}
|
|
65
|
+
if (errorPolicy === undefined) {
|
|
66
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_skipped', { ...logContext, reason: 'disabled_entry' });
|
|
67
|
+
return { type: 'miss' };
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const error = (0, cache_error_1.decodeCacheError)(entry.error, errorPolicy);
|
|
71
|
+
(0, cache_logger_1.logCacheEvent)('cache.hit', { ...logContext, entryType: 'error' });
|
|
72
|
+
return { type: 'error', error };
|
|
73
|
+
}
|
|
74
|
+
catch (_error) {
|
|
75
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_failed', { ...logContext, phase: 'decode' });
|
|
76
|
+
return { type: 'miss' };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 按固定顺序评估筛选器、编码异常并发起独立 TTL 写入,任何策略失败都保留原业务异常。
|
|
81
|
+
* @param request 本次业务异常、当前策略、Provider、完整 key 与稳定日志上下文。
|
|
82
|
+
* @returns 无返回值。
|
|
83
|
+
*/
|
|
84
|
+
function handleBusinessError(request) {
|
|
85
|
+
const { error, errorPolicy, provider, cacheKey, logContext } = request;
|
|
86
|
+
if (errorPolicy === undefined) {
|
|
87
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_skipped', { ...logContext, reason: 'disabled' });
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (errorPolicy.shouldCache !== undefined) {
|
|
91
|
+
let accepted;
|
|
92
|
+
try {
|
|
93
|
+
accepted = errorPolicy.shouldCache(error);
|
|
94
|
+
if (typeof accepted !== 'boolean') {
|
|
95
|
+
throw new TypeError('Cache error shouldCache must return a boolean');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (_error) {
|
|
99
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_failed', { ...logContext, phase: 'predicate' });
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
if (!accepted) {
|
|
103
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_skipped', { ...logContext, reason: 'predicate_rejected' });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
let entry;
|
|
108
|
+
try {
|
|
109
|
+
entry = { error: (0, cache_error_1.encodeCacheError)(error, errorPolicy) };
|
|
110
|
+
}
|
|
111
|
+
catch (_error) {
|
|
112
|
+
(0, cache_logger_1.logCacheEvent)('cache.error_cache_failed', { ...logContext, phase: 'encode' });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
dispatchCacheWrite({ provider, cacheKey, entry, entryType: 'error', ttl: errorPolicy.ttl, logContext });
|
|
42
116
|
}
|
|
43
117
|
/**
|
|
44
118
|
* 解析缓存 key
|
|
@@ -69,6 +143,7 @@ function resolveCacheKey(keyResolver, args, logContext) {
|
|
|
69
143
|
* @param options 配置项
|
|
70
144
|
*/
|
|
71
145
|
function Cache(cacheName, options) {
|
|
146
|
+
const errorPolicy = (0, cache_error_1.normalizeCacheErrorPolicy)(options?.errorCache);
|
|
72
147
|
return function (_target, propertyKey, descriptor) {
|
|
73
148
|
const originalMethod = descriptor.value;
|
|
74
149
|
const logContext = {
|
|
@@ -85,46 +160,48 @@ function Cache(cacheName, options) {
|
|
|
85
160
|
}
|
|
86
161
|
const promise = (async () => {
|
|
87
162
|
const provider = resolveCacheProvider(options?.providerName, logContext);
|
|
88
|
-
|
|
163
|
+
if (provider === undefined) {
|
|
164
|
+
return (await originalMethod.apply(this, args));
|
|
165
|
+
}
|
|
166
|
+
let lookupResult;
|
|
89
167
|
try {
|
|
90
|
-
|
|
168
|
+
const entry = await provider.get(cacheKey);
|
|
169
|
+
lookupResult = entry === undefined ? { type: 'miss', provider } : { type: 'hit', provider, entry };
|
|
91
170
|
}
|
|
92
171
|
catch (error) {
|
|
93
172
|
(0, cache_logger_1.logCacheEvent)('cache.operation_failed', { ...logContext, operation: 'read', error });
|
|
94
|
-
|
|
173
|
+
lookupResult = { type: 'bypass' };
|
|
174
|
+
}
|
|
175
|
+
if (lookupResult.type === 'bypass') {
|
|
176
|
+
return (await originalMethod.apply(this, args));
|
|
95
177
|
}
|
|
96
|
-
if (
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
178
|
+
if (lookupResult.type === 'hit') {
|
|
179
|
+
const cachedResult = resolveCachedEntry(lookupResult.entry, errorPolicy, logContext);
|
|
180
|
+
if (cachedResult.type === 'value') {
|
|
181
|
+
return cachedResult.value;
|
|
182
|
+
}
|
|
183
|
+
if (cachedResult.type === 'error') {
|
|
184
|
+
throw cachedResult.error;
|
|
100
185
|
}
|
|
101
|
-
(0, cache_logger_1.logCacheEvent)('cache.hit', { ...logContext, entryType: 'value' });
|
|
102
|
-
return cached.value;
|
|
103
186
|
}
|
|
104
187
|
(0, cache_logger_1.logCacheEvent)('cache.miss', logContext);
|
|
188
|
+
let result;
|
|
105
189
|
try {
|
|
106
|
-
|
|
107
|
-
dispatchCacheWrite({
|
|
108
|
-
provider,
|
|
109
|
-
cacheKey,
|
|
110
|
-
entry: { value: result },
|
|
111
|
-
entryType: 'value',
|
|
112
|
-
ttl: options?.ttl,
|
|
113
|
-
logContext,
|
|
114
|
-
});
|
|
115
|
-
return result;
|
|
190
|
+
result = (await originalMethod.apply(this, args));
|
|
116
191
|
}
|
|
117
192
|
catch (error) {
|
|
118
|
-
|
|
119
|
-
provider,
|
|
120
|
-
cacheKey,
|
|
121
|
-
entry: { error },
|
|
122
|
-
entryType: 'error',
|
|
123
|
-
ttl: options?.ttl,
|
|
124
|
-
logContext,
|
|
125
|
-
});
|
|
193
|
+
handleBusinessError({ error, errorPolicy, provider, cacheKey, logContext });
|
|
126
194
|
throw error;
|
|
127
195
|
}
|
|
196
|
+
dispatchCacheWrite({
|
|
197
|
+
provider,
|
|
198
|
+
cacheKey,
|
|
199
|
+
entry: { value: result },
|
|
200
|
+
entryType: 'value',
|
|
201
|
+
ttl: options?.ttl,
|
|
202
|
+
logContext,
|
|
203
|
+
});
|
|
204
|
+
return result;
|
|
128
205
|
})();
|
|
129
206
|
pendingCache.set(cacheKey, promise);
|
|
130
207
|
return promise;
|