@fluojs/cache-manager 1.0.0-beta.1 → 1.0.0-beta.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +16 -0
- package/README.md +16 -0
- package/dist/interceptor.js +2 -2
- package/dist/stores/redis-store.d.ts +14 -0
- package/dist/stores/redis-store.d.ts.map +1 -1
- package/dist/stores/redis-store.js +34 -3
- package/package.json +6 -6
package/README.ko.md
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
- [공통 패턴](#공통-패턴)
|
|
15
15
|
- [Redis 저장소 사용](#redis-저장소-사용)
|
|
16
16
|
- [쿼리 매개변수 기반 캐싱](#쿼리-매개변수-기반-캐싱)
|
|
17
|
+
- [캐시 소유권과 reset 범위](#캐시-소유권과-reset-범위)
|
|
17
18
|
- [수동 모듈 조합](#수동-모듈-조합)
|
|
18
19
|
- [공개 API 개요](#공개-api-개요)
|
|
19
20
|
- [관련 패키지](#관련-패키지)
|
|
@@ -120,6 +121,8 @@ CacheModule.forRoot({
|
|
|
120
121
|
|
|
121
122
|
내장 `RedisStore`는 엔트리를 `JSON.stringify(...)`로 저장합니다. 따라서 캐시 값은 JSON 호환 형태여야 합니다. 일반 객체, 배열, 문자열, 숫자, 불리언, `null`은 안정적으로 round-trip 되지만, `Date`는 JSON 결과(예: ISO 문자열)로 돌아오고, 함수/`undefined`/`symbol`은 유지되지 않으며, `bigint`나 순환 그래프처럼 직렬화 불가능한 값은 캐싱 전에 정규화해야 합니다.
|
|
122
123
|
|
|
124
|
+
Redis reset 소유권은 기본값이 `fluo:cache:`인 `keyPrefix`로 제한됩니다. Redis 기반 저장소에서 `CacheService.reset()`은 해당 prefix 아래의 키만 삭제하므로, cache prefix 밖의 애플리케이션 소유 Redis 데이터는 유지됩니다. 의도적으로 빈 `keyPrefix`를 설정하면 reset은 `*`를 scan하지 않고 현재 `RedisStore` 인스턴스가 쓴 키로만 제한됩니다. 재시작 이후나 여러 프로세스에 걸친 캐시 엔트리까지 reset해야 한다면 비어 있지 않은 애플리케이션 전용 prefix를 사용하세요.
|
|
125
|
+
|
|
123
126
|
### 쿼리 매개변수 기반 캐싱
|
|
124
127
|
|
|
125
128
|
기본적으로 캐시 키는 쿼리 매개변수를 무시합니다. 검색 조건 등에 따라 다른 응답을 캐싱하려면 `httpKeyStrategy: 'route+query'`를 활성화하세요.
|
|
@@ -131,6 +134,19 @@ CacheModule.forRoot({
|
|
|
131
134
|
})
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
### 캐시 소유권과 reset 범위
|
|
138
|
+
|
|
139
|
+
`CacheService.reset()`은 관련 없는 애플리케이션 상태가 아니라 설정된 store가 소유한 엔트리만 삭제합니다. 내장 메모리 저장소에서는 해당 store 인스턴스가 보유한 in-process 엔트리를 의미합니다. Redis에서는 설정된 `keyPrefix` namespace가 소유권 경계입니다. 공유 Redis 배포에서는 기본 `fluo:cache:`를 유지하거나 `myapp:cache:`처럼 전용 prefix를 선택하세요.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
CacheModule.forRoot({
|
|
143
|
+
store: 'redis',
|
|
144
|
+
keyPrefix: 'myapp:cache:',
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Redis cache prefix를 cache가 아닌 데이터와 공유하지 마세요. `del(key)`은 이 패키지가 해석한 정확한 캐시 키를 삭제하고, `reset()`은 위에서 설명한 store 소유 캐시 namespace만 삭제합니다.
|
|
149
|
+
|
|
134
150
|
### 수동 모듈 조합
|
|
135
151
|
|
|
136
152
|
일반적인 애플리케이션 설정과 커스텀 `defineModule(...)` 조합에서는 `CacheModule.forRoot(...)`를 사용합니다.
|
package/README.md
CHANGED
|
@@ -14,6 +14,7 @@ General-purpose cache manager for fluo with pluggable memory and Redis stores. P
|
|
|
14
14
|
- [Common Patterns](#common-patterns)
|
|
15
15
|
- [Redis Storage](#redis-storage)
|
|
16
16
|
- [Query-Sensitive Caching](#query-sensitive-caching)
|
|
17
|
+
- [Cache Ownership and Reset Scope](#cache-ownership-and-reset-scope)
|
|
17
18
|
- [Manual Module Composition](#manual-module-composition)
|
|
18
19
|
- [Public API Overview](#public-api-overview)
|
|
19
20
|
- [Related Packages](#related-packages)
|
|
@@ -120,6 +121,8 @@ CacheModule.forRoot({
|
|
|
120
121
|
|
|
121
122
|
The built-in `RedisStore` persists entries with `JSON.stringify(...)`. Cache values therefore need to be JSON-compatible: plain objects, arrays, strings, numbers, booleans, and `null` round-trip cleanly, while values such as `Date` come back as JSON output (for example ISO strings), functions/`undefined`/symbols do not survive, and non-serializable values like `bigint` or cyclic graphs should be normalized before caching.
|
|
122
123
|
|
|
124
|
+
Redis reset ownership is scoped by `keyPrefix`, which defaults to `fluo:cache:`. `CacheService.reset()` deletes only keys under that prefix for Redis-backed stores, so application-owned Redis data outside the cache prefix is preserved. If you intentionally configure an empty `keyPrefix`, reset is limited to keys written by the current `RedisStore` instance instead of scanning `*`; use a non-empty, application-specific prefix when you need reset to cover cache entries across restarts or multiple processes.
|
|
125
|
+
|
|
123
126
|
### Query-Sensitive Caching
|
|
124
127
|
|
|
125
128
|
By default, the cache key ignores query parameters. Enable `httpKeyStrategy: 'route+query'` to cache different responses for different search parameters.
|
|
@@ -131,6 +134,19 @@ CacheModule.forRoot({
|
|
|
131
134
|
})
|
|
132
135
|
```
|
|
133
136
|
|
|
137
|
+
### Cache Ownership and Reset Scope
|
|
138
|
+
|
|
139
|
+
`CacheService.reset()` clears entries owned by the configured store, not unrelated application state. For the built-in memory store that means the in-process entries held by that store instance. For Redis, ownership is the configured `keyPrefix` namespace; keep the default `fluo:cache:` or choose a dedicated prefix such as `myapp:cache:` for shared Redis deployments.
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
CacheModule.forRoot({
|
|
143
|
+
store: 'redis',
|
|
144
|
+
keyPrefix: 'myapp:cache:',
|
|
145
|
+
})
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Avoid sharing a Redis cache prefix with non-cache data. `del(key)` removes the exact cache key resolved by this package, while `reset()` removes only the store-owned cache namespace described above.
|
|
149
|
+
|
|
134
150
|
### Manual Module Composition
|
|
135
151
|
|
|
136
152
|
Use `CacheModule.forRoot(...)` for normal application setup, including custom `defineModule(...)` composition.
|
package/dist/interceptor.js
CHANGED
|
@@ -5,7 +5,7 @@ function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e =
|
|
|
5
5
|
function _setFunctionName(e, t, n) { "symbol" == typeof t && (t = (t = t.description) ? "[" + t + "]" : ""); try { Object.defineProperty(e, "name", { configurable: !0, value: n ? n + " " + t : t }); } catch (e) {} return e; }
|
|
6
6
|
function _checkInRHS(e) { if (Object(e) !== e) throw TypeError("right-hand side of 'in' should be an object, got " + (null !== e ? typeof e : "null")); return e; }
|
|
7
7
|
import { Inject } from '@fluojs/core';
|
|
8
|
-
import {
|
|
8
|
+
import { getStandardMetadataBag } from '@fluojs/core/internal';
|
|
9
9
|
import { SseResponse } from '@fluojs/http';
|
|
10
10
|
import { cacheRouteMetadataKey, getCacheEvictMetadata, getCacheKeyMetadata, getCacheTtlMetadata } from './decorators.js';
|
|
11
11
|
import { CacheService } from './service.js';
|
|
@@ -14,7 +14,7 @@ function isMetadataBag(value) {
|
|
|
14
14
|
return typeof value === 'object' && value !== null;
|
|
15
15
|
}
|
|
16
16
|
function getMethodMetadataBag(controllerToken, methodName) {
|
|
17
|
-
const classBag =
|
|
17
|
+
const classBag = getStandardMetadataBag(controllerToken);
|
|
18
18
|
if (!isMetadataBag(classBag)) {
|
|
19
19
|
return undefined;
|
|
20
20
|
}
|
|
@@ -1,16 +1,30 @@
|
|
|
1
1
|
import type { CacheStore, RedisCompatibleClient } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Redis store configuration for key ownership and reset scanning.
|
|
4
|
+
*/
|
|
2
5
|
export interface RedisStoreOptions {
|
|
6
|
+
/**
|
|
7
|
+
* Prefix used to scope keys owned by this cache store when deleting entries or resetting the store.
|
|
8
|
+
*/
|
|
3
9
|
keyPrefix?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Maximum number of keys requested per Redis SCAN iteration during reset.
|
|
12
|
+
*/
|
|
4
13
|
scanCount?: number;
|
|
5
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Cache store implementation backed by a Redis-compatible client.
|
|
17
|
+
*/
|
|
6
18
|
export declare class RedisStore implements CacheStore {
|
|
7
19
|
private readonly client;
|
|
20
|
+
private readonly ownedKeys;
|
|
8
21
|
private readonly keyPrefix;
|
|
9
22
|
private readonly scanCount;
|
|
10
23
|
constructor(client: RedisCompatibleClient, options?: RedisStoreOptions);
|
|
11
24
|
private toRedisKey;
|
|
12
25
|
get<T = unknown>(key: string): Promise<T | undefined>;
|
|
13
26
|
set<T = unknown>(key: string, value: T, ttlSeconds?: number): Promise<void>;
|
|
27
|
+
private trackOwnedKey;
|
|
14
28
|
del(key: string): Promise<void>;
|
|
15
29
|
reset(): Promise<void>;
|
|
16
30
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-store.d.ts","sourceRoot":"","sources":["../../src/stores/redis-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAErE,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAwCD,qBAAa,UAAW,YAAW,UAAU;
|
|
1
|
+
{"version":3,"file":"redis-store.d.ts","sourceRoot":"","sources":["../../src/stores/redis-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAErE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAwCD;;GAEG;AACH,qBAAa,UAAW,YAAW,UAAU;IAMzC,OAAO,CAAC,QAAQ,CAAC,MAAM;IALzB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqB;IAC/C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGhB,MAAM,EAAE,qBAAqB,EAC9C,OAAO,GAAE,iBAAsB;IAMjC,OAAO,CAAC,UAAU;IAIZ,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAiBrD,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,SAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAoB5E,OAAO,CAAC,aAAa;IAMf,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/B,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAoC7B"}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis store configuration for key ownership and reset scanning.
|
|
3
|
+
*/
|
|
4
|
+
|
|
1
5
|
const DEFAULT_KEY_PREFIX = 'fluo:cache:';
|
|
2
6
|
const DEFAULT_SCAN_COUNT = 100;
|
|
3
7
|
function parseEntry(raw) {
|
|
@@ -24,7 +28,12 @@ function normalizeScanResponse(result) {
|
|
|
24
28
|
keys
|
|
25
29
|
};
|
|
26
30
|
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Cache store implementation backed by a Redis-compatible client.
|
|
34
|
+
*/
|
|
27
35
|
export class RedisStore {
|
|
36
|
+
ownedKeys = new Set();
|
|
28
37
|
keyPrefix;
|
|
29
38
|
scanCount;
|
|
30
39
|
constructor(client, options = {}) {
|
|
@@ -49,6 +58,7 @@ export class RedisStore {
|
|
|
49
58
|
}
|
|
50
59
|
async set(key, value, ttlSeconds = 0) {
|
|
51
60
|
const now = Date.now();
|
|
61
|
+
const redisKey = this.toRedisKey(key);
|
|
52
62
|
const entry = {
|
|
53
63
|
value
|
|
54
64
|
};
|
|
@@ -56,15 +66,35 @@ export class RedisStore {
|
|
|
56
66
|
const ttlMilliseconds = Math.max(1, Math.floor(ttlSeconds * 1000));
|
|
57
67
|
entry.expiresAt = now + ttlMilliseconds;
|
|
58
68
|
const ttlSecondsRounded = Math.max(1, Math.ceil(ttlMilliseconds / 1000));
|
|
59
|
-
await this.client.set(
|
|
69
|
+
await this.client.set(redisKey, JSON.stringify(entry), 'EX', ttlSecondsRounded);
|
|
70
|
+
this.trackOwnedKey(redisKey);
|
|
60
71
|
return;
|
|
61
72
|
}
|
|
62
|
-
await this.client.set(
|
|
73
|
+
await this.client.set(redisKey, JSON.stringify(entry));
|
|
74
|
+
this.trackOwnedKey(redisKey);
|
|
75
|
+
}
|
|
76
|
+
trackOwnedKey(redisKey) {
|
|
77
|
+
if (this.keyPrefix.length === 0) {
|
|
78
|
+
this.ownedKeys.add(redisKey);
|
|
79
|
+
}
|
|
63
80
|
}
|
|
64
81
|
async del(key) {
|
|
65
|
-
|
|
82
|
+
const redisKey = this.toRedisKey(key);
|
|
83
|
+
await this.client.del(redisKey);
|
|
84
|
+
this.ownedKeys.delete(redisKey);
|
|
66
85
|
}
|
|
67
86
|
async reset() {
|
|
87
|
+
if (this.keyPrefix.length === 0) {
|
|
88
|
+
const keys = Array.from(this.ownedKeys);
|
|
89
|
+
if (keys.length > 0) {
|
|
90
|
+
const [firstKey, ...restKeys] = keys;
|
|
91
|
+
if (firstKey !== undefined) {
|
|
92
|
+
await this.client.del(firstKey, ...restKeys);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
this.ownedKeys.clear();
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
68
98
|
let cursor = '0';
|
|
69
99
|
const pattern = `${this.keyPrefix}*`;
|
|
70
100
|
do {
|
|
@@ -77,5 +107,6 @@ export class RedisStore {
|
|
|
77
107
|
}
|
|
78
108
|
}
|
|
79
109
|
} while (cursor !== '0');
|
|
110
|
+
this.ownedKeys.clear();
|
|
80
111
|
}
|
|
81
112
|
}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"memory-store",
|
|
11
11
|
"decorator"
|
|
12
12
|
],
|
|
13
|
-
"version": "1.0.0-beta.
|
|
13
|
+
"version": "1.0.0-beta.3",
|
|
14
14
|
"private": false,
|
|
15
15
|
"license": "MIT",
|
|
16
16
|
"repository": {
|
|
@@ -37,14 +37,14 @@
|
|
|
37
37
|
"dist"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@fluojs/
|
|
41
|
-
"@fluojs/
|
|
42
|
-
"@fluojs/
|
|
43
|
-
"@fluojs/runtime": "^1.0.0-beta.
|
|
40
|
+
"@fluojs/core": "^1.0.0-beta.2",
|
|
41
|
+
"@fluojs/di": "^1.0.0-beta.3",
|
|
42
|
+
"@fluojs/http": "^1.0.0-beta.2",
|
|
43
|
+
"@fluojs/runtime": "^1.0.0-beta.3"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"ioredis": "^5.0.0",
|
|
47
|
-
"@fluojs/redis": "^1.0.0-beta.
|
|
47
|
+
"@fluojs/redis": "^1.0.0-beta.2"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
50
50
|
"@fluojs/redis": {
|