@fluojs/cache-manager 1.0.3 → 1.0.6
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 +94 -15
- package/README.md +94 -15
- package/dist/index.d.ts +10 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -9
- package/dist/interceptor.d.ts.map +1 -1
- package/dist/interceptor.js +9 -1
- package/dist/service.d.ts +3 -0
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +66 -16
- package/dist/types.d.ts +6 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
package/README.ko.md
CHANGED
|
@@ -26,14 +26,16 @@
|
|
|
26
26
|
npm install @fluojs/cache-manager
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
root `@fluojs/cache-manager` import는 memory-only 설치에서도 안전합니다. Redis
|
|
29
|
+
root `@fluojs/cache-manager` import는 memory-only 설치에서도 안전합니다. Redis client는 Redis 저장소 경로를 명시적으로 선택할 때만 필요합니다.
|
|
30
30
|
|
|
31
|
-
Redis 기반 캐싱을 사용하는 경우:
|
|
31
|
+
Lifecycle이 관리되는 `@fluojs/redis` client로 Redis 기반 캐싱을 사용하는 경우:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
npm install @fluojs/cache-manager @fluojs/redis ioredis
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
대신 애플리케이션이 소유하는 compatible client를 `redis.client`로 직접 전달할 수 있습니다. 이 경로에는 `@fluojs/redis`가 필요하지 않습니다. 필수 `get`, `set`, `del`, tuple-returning `scan` operation을 제공하는 client package를 설치하고, 해당 client는 애플리케이션 lifecycle에서 닫으세요.
|
|
38
|
+
|
|
37
39
|
## 사용 시점
|
|
38
40
|
|
|
39
41
|
- 비용이 많이 드는 데이터베이스 쿼리나 외부 API 응답을 캐싱하고 싶을 때 사용합니다.
|
|
@@ -96,16 +98,30 @@ class UserService {
|
|
|
96
98
|
|
|
97
99
|
### Redis 저장소 사용
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
`store: 'redis'`를 설정한 뒤 지원되는 두 client 통합 경로 중 하나를 선택합니다.
|
|
102
|
+
|
|
103
|
+
1. 기본 또는 named raw client를 `@fluojs/redis`로 등록하고 cache module이 DI를 통해 해석하도록 합니다.
|
|
104
|
+
2. 애플리케이션이 소유하는 `RedisCompatibleClient`를 `redis.client`로 직접 전달합니다.
|
|
100
105
|
|
|
101
106
|
memory-only 소비자는 `@fluojs/redis`나 `ioredis`를 설치하지 않아도 `@fluojs/cache-manager`를 계속 import할 수 있습니다. 이 optional peer들은 Redis 저장소 경로를 선택할 때만 해석됩니다.
|
|
102
107
|
|
|
103
108
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
import { Module } from '@fluojs/core';
|
|
110
|
+
import { CacheModule } from '@fluojs/cache-manager';
|
|
111
|
+
import { RedisModule } from '@fluojs/redis';
|
|
112
|
+
|
|
113
|
+
@Module({
|
|
114
|
+
imports: [
|
|
115
|
+
RedisModule.forRoot({ name: 'cache', host: 'localhost', port: 6379 }),
|
|
116
|
+
CacheModule.forRoot({
|
|
117
|
+
store: 'redis',
|
|
118
|
+
ttl: 600,
|
|
119
|
+
keyPrefix: 'myapp:cache:',
|
|
120
|
+
redis: { clientName: 'cache' },
|
|
121
|
+
}),
|
|
122
|
+
],
|
|
108
123
|
})
|
|
124
|
+
class AppModule {}
|
|
109
125
|
```
|
|
110
126
|
|
|
111
127
|
여러 Redis 클라이언트를 등록했다면 `redis.clientName`으로 사용할 `@fluojs/redis` 연결을 지정할 수 있습니다.
|
|
@@ -119,7 +135,26 @@ CacheModule.forRoot({
|
|
|
119
135
|
})
|
|
120
136
|
```
|
|
121
137
|
|
|
122
|
-
`redis.client`는
|
|
138
|
+
`redis.client`는 가장 높은 우선순위의 override이며 DI 기반 client 선택을 완전히 우회합니다. Export된 `RedisCompatibleClient` 계약을 만족하는 모든 client를 받을 수 있고, 이 경로에서는 `@fluojs/redis`를 load하거나 요구하지 않습니다. 직접 전달한 client의 connection startup과 shutdown은 애플리케이션이 소유합니다.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import Redis from 'ioredis';
|
|
142
|
+
import { Module } from '@fluojs/core';
|
|
143
|
+
import { CacheModule } from '@fluojs/cache-manager';
|
|
144
|
+
|
|
145
|
+
const cacheClient = new Redis({ host: 'localhost', port: 6379 });
|
|
146
|
+
|
|
147
|
+
@Module({
|
|
148
|
+
imports: [
|
|
149
|
+
CacheModule.forRoot({
|
|
150
|
+
store: 'redis',
|
|
151
|
+
keyPrefix: 'myapp:cache:',
|
|
152
|
+
redis: { client: cacheClient },
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
})
|
|
156
|
+
class AppModule {}
|
|
157
|
+
```
|
|
123
158
|
|
|
124
159
|
내장 `RedisStore`는 엔트리를 `JSON.stringify(...)`로 저장합니다. 따라서 캐시 값은 JSON 호환 형태여야 합니다. 일반 객체, 배열, 문자열, 숫자, 불리언, `null`은 안정적으로 round-trip 되지만, `Date`는 JSON 결과(예: ISO 문자열)로 돌아오고, 함수/`undefined`/`symbol`은 유지되지 않으며, `bigint`나 순환 그래프처럼 직렬화 불가능한 값은 캐싱 전에 정규화해야 합니다.
|
|
125
160
|
|
|
@@ -140,13 +175,37 @@ CacheModule.forRoot({
|
|
|
140
175
|
})
|
|
141
176
|
```
|
|
142
177
|
|
|
143
|
-
완전히 다른 키 전략이 필요하다면 `httpKeyStrategy`에 함수를 전달하거나, literal key 또는 key factory를 받는 `@CacheKey(...)`를 사용하세요.
|
|
178
|
+
완전히 다른 키 전략이 필요하다면 `httpKeyStrategy`에 함수를 전달하거나, literal key 또는 key factory를 받는 `@CacheKey(...)`를 사용하세요. 요청을 인식하는 cache key를 만들 때 지원되는 확장 경로는 이러한 function-based hook이며, cache key 생성만 바꾸기 위해 `CacheInterceptor`를 subclass하지 않습니다.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
CacheModule.forRoot({
|
|
182
|
+
store: 'memory',
|
|
183
|
+
httpKeyStrategy: (context) => {
|
|
184
|
+
const path = context.requestContext.request.path;
|
|
185
|
+
const query = context.requestContext.request.query;
|
|
186
|
+
const q = String(query.q ?? '').trim().toLowerCase();
|
|
187
|
+
|
|
188
|
+
return q ? `${path}?q=${encodeURIComponent(q)}` : path;
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
특정 handler 하나만 custom 동작이 필요하다면 handler-level key를 route 가까이에 둘 수 있습니다.
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
@CacheKey((context) => {
|
|
197
|
+
const tenant = context.requestContext.principal?.subject ?? 'anonymous';
|
|
198
|
+
const slug = String(context.requestContext.request.query.slug ?? 'index');
|
|
199
|
+
|
|
200
|
+
return `tenant:${tenant}:page:${slug}`;
|
|
201
|
+
})
|
|
202
|
+
```
|
|
144
203
|
|
|
145
204
|
HTTP 인터셉터는 나중에 재사용할 수 있는 값이 있는 성공한, 아직 commit되지 않은 GET 핸들러 결과만 캐싱합니다. `undefined`, `SseResponse` 스트림, 이미 commit된 응답, 그리고 status code가 `2xx` 범위를 벗어난 응답은 건너뛰므로 redirect와 error 응답은 cache hit로 저장되지 않습니다.
|
|
146
205
|
|
|
147
206
|
### 캐시 소유권과 reset 범위
|
|
148
207
|
|
|
149
|
-
`CacheService.reset()`은 관련 없는 애플리케이션 상태가 아니라 설정된 store가 소유한 엔트리만 삭제합니다. 또한 진행 중인 `remember(...)`
|
|
208
|
+
`CacheService.reset()`은 관련 없는 애플리케이션 상태가 아니라 설정된 store가 소유한 엔트리만 삭제합니다. 또한 reset 경계에서 store read/write를 직렬화하고 진행 중인 `remember(...)` loader를 무효화하므로, reset 전에 시작된 loader가 reset 완료 후 stale 엔트리를 다시 채우지 못합니다. 내장 메모리 저장소에서는 해당 store 인스턴스가 보유한 in-process 엔트리를 의미합니다. Redis에서는 설정된 `keyPrefix` namespace가 소유권 경계입니다. 공유 Redis 배포에서는 기본 `fluo:cache:`를 유지하거나 `myapp:cache:`처럼 전용 prefix를 선택하세요.
|
|
150
209
|
|
|
151
210
|
```typescript
|
|
152
211
|
CacheModule.forRoot({
|
|
@@ -157,7 +216,7 @@ CacheModule.forRoot({
|
|
|
157
216
|
|
|
158
217
|
Redis cache prefix를 cache가 아닌 데이터와 공유하지 마세요. `del(key)`은 이 패키지가 해석한 정확한 캐시 키를 삭제하고, `reset()`은 위에서 설명한 store 소유 캐시 namespace만 삭제합니다.
|
|
159
218
|
|
|
160
|
-
애플리케이션이 종료될 때 `CacheService`는 `close()` 또는 `dispose()`를 노출하는 custom store로 shutdown을 전달합니다. store가 socket, pool, timer 또는 기타 외부 리소스를 소유한다면 이 optional hook 중 하나를 사용하세요.
|
|
219
|
+
애플리케이션이 종료될 때 `CacheService`는 새 store read/write를 중단하고 이미 시작된 store 작업을 기다린 뒤, `close()` 또는 `dispose()`를 노출하는 custom store로 shutdown을 전달합니다. store가 socket, pool, timer 또는 기타 외부 리소스를 소유한다면 이 optional hook 중 하나를 사용하세요.
|
|
161
220
|
|
|
162
221
|
`CacheStore` 계약을 구현한 custom store는 `store` 옵션에 직접 전달할 수 있습니다. in-process LRU store, Redis 외 원격 캐시, 또는 cache operation을 관찰해야 하는 테스트 더블에 적합합니다.
|
|
163
222
|
|
|
@@ -187,7 +246,24 @@ defineModule(ManualCacheModule, {
|
|
|
187
246
|
|
|
188
247
|
### 지연 삭제 시점
|
|
189
248
|
|
|
190
|
-
`@CacheEvict(...)
|
|
249
|
+
`@CacheEvict(...)`는 범용 service-method decorator가 아니라 HTTP route metadata입니다. `CacheInterceptor`가 non-GET controller handler를 감싸 실행될 때만 이 metadata를 소비합니다. Service method나 HTTP interceptor pipeline 밖의 호출에서는 `CacheService`를 주입하고 `del(...)`을 명시적으로 호출하세요.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { CacheEvict, CacheInterceptor } from '@fluojs/cache-manager';
|
|
253
|
+
import { Controller, Post, UseInterceptors } from '@fluojs/http';
|
|
254
|
+
|
|
255
|
+
@Controller('/products')
|
|
256
|
+
@UseInterceptors(CacheInterceptor)
|
|
257
|
+
class ProductController {
|
|
258
|
+
@Post('/refresh')
|
|
259
|
+
@CacheEvict('/products')
|
|
260
|
+
refresh() {
|
|
261
|
+
return { refreshed: true };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
이렇게 지원되는 HTTP 경로에서는 응답이 성공적으로 commit된 뒤에 캐시를 삭제합니다. `response.send(...)`가 reject되면 지연 eviction을 취소하여 실패한 commit이 이전 캐시된 읽기 결과를 삭제하지 않도록 합니다. 어댑터 경로가 `response.send(...)`를 호출하지 않더라도, 인터셉터는 bounded fallback timer를 통해 성공한 쓰기 이후 stale 엔트리가 무기한 남지 않도록 보장합니다. fallback timer는 response commit 경로가 호출되지 않았을 때만 eviction을 수행하며, `response.send(...)`가 여전히 pending 상태인 동안에는 send 경로가 eviction(성공 시) 또는 취소(실패 시)를 소유하므로 fallback timer가 pending send 하에서 eviction을 실행하지 않습니다. 또한 지연 eviction 실패는 인터셉터 내부에 containment되어 cache key factory나 cache store 삭제 오류가 응답 이후 unhandled promise rejection으로 노출되지 않습니다.
|
|
191
267
|
|
|
192
268
|
## 공개 API 개요
|
|
193
269
|
|
|
@@ -195,6 +271,9 @@ defineModule(ManualCacheModule, {
|
|
|
195
271
|
- `CacheModule.forRoot(options)`: 캐시 저장소(memory/redis/custom), 기본 TTL, 키 전략, `global`, `principalScopeResolver`, Redis namespace `keyPrefix`, `redis.scanCount` 같은 Redis 옵션을 설정합니다.
|
|
196
272
|
애플리케이션 모듈에서 사용하는 기본 패키지 진입점입니다.
|
|
197
273
|
|
|
274
|
+
### 공개 타입
|
|
275
|
+
- `CacheModuleOptions`: `CacheModule.forRoot(...)`가 받는 애플리케이션-facing 설정입니다.
|
|
276
|
+
- `NormalizedCacheModuleOptions`: 기본값이 적용된 정규화 설정 모양과 일치하는 compatibility-only type export입니다. 애플리케이션 코드에서는 `CacheModuleOptions`를 우선 사용하세요. 이 타입은 이전에 배포된 declaration surface를 참조한 소비자가 계속 컴파일되도록 공개 상태를 유지합니다.
|
|
198
277
|
|
|
199
278
|
### 서비스
|
|
200
279
|
- `CacheService`: 수동 캐시 작업(`get`, `set`, `del`, `remember`, `reset`, `close`)을 위한 기본 API입니다. 애플리케이션 shutdown은 같은 `close()` 경로를 호출하며, 이 경로는 `close()` 또는 `dispose()`를 노출하는 custom store로 teardown을 전달합니다.
|
|
@@ -202,11 +281,11 @@ defineModule(ManualCacheModule, {
|
|
|
202
281
|
### 데코레이터
|
|
203
282
|
- `@CacheTTL(seconds)`: 특정 핸들러의 TTL을 설정합니다.
|
|
204
283
|
- `@CacheKey(key)`: 특정 핸들러의 custom cache key 또는 key factory를 설정합니다.
|
|
205
|
-
- `@CacheEvict(key)`: 성공적인 non-GET
|
|
284
|
+
- `@CacheEvict(key)`: 성공적인 non-GET controller handler가 완료된 뒤 `CacheInterceptor`가 소비하는 HTTP route metadata를 저장합니다. 임의의 service call을 intercept하지 않습니다.
|
|
206
285
|
- `cacheRouteMetadataKey`, `getCacheKeyMetadata(...)`, `getCacheTtlMetadata(...)`, `getCacheEvictMetadata(...)`: 캐시 데코레이터 metadata key를 다시 구현하지 않고 cache decorator metadata를 검사해야 하는 first-party interceptor 통합, 진단, 고급 tooling을 위해 공개된 low-level metadata helper입니다.
|
|
207
286
|
|
|
208
287
|
### 인터셉터
|
|
209
|
-
- `CacheInterceptor`: 자동 GET 응답
|
|
288
|
+
- `CacheInterceptor`: 자동 GET 응답 캐싱을 처리하고 non-GET HTTP handler에서 `@CacheEvict(...)` metadata를 소비합니다.
|
|
210
289
|
|
|
211
290
|
### 저장소와 status helper
|
|
212
291
|
- `MemoryStore`, `RedisStore`: 내장 store 구현입니다.
|
|
@@ -215,7 +294,7 @@ defineModule(ManualCacheModule, {
|
|
|
215
294
|
|
|
216
295
|
## 관련 패키지
|
|
217
296
|
|
|
218
|
-
- `@fluojs/redis`: Redis
|
|
297
|
+
- `@fluojs/redis`: Lifecycle-managed Redis client를 위한 optional 통합입니다. `redis.client`로 애플리케이션 소유 `RedisCompatibleClient`를 직접 전달하면 필요하지 않습니다.
|
|
219
298
|
- `@fluojs/http`: HTTP 인터셉터 및 데코레이터 사용 시 필요합니다.
|
|
220
299
|
|
|
221
300
|
## 예제 소스
|
package/README.md
CHANGED
|
@@ -26,14 +26,16 @@ General-purpose cache manager for fluo with pluggable memory, Redis, and custom
|
|
|
26
26
|
npm install @fluojs/cache-manager
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
The root `@fluojs/cache-manager` import stays safe for memory-only installs. You only need Redis
|
|
29
|
+
The root `@fluojs/cache-manager` import stays safe for memory-only installs. You only need a Redis client when you explicitly select the Redis-backed store path.
|
|
30
30
|
|
|
31
|
-
For Redis-backed caching:
|
|
31
|
+
For Redis-backed caching with a lifecycle-managed `@fluojs/redis` client:
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
npm install @fluojs/cache-manager @fluojs/redis ioredis
|
|
35
35
|
```
|
|
36
36
|
|
|
37
|
+
You can instead pass an application-owned compatible client through `redis.client`. That path does not require `@fluojs/redis`; install whichever client package provides the required `get`, `set`, `del`, and tuple-returning `scan` operations, and close that client from the application lifecycle.
|
|
38
|
+
|
|
37
39
|
## When to Use
|
|
38
40
|
|
|
39
41
|
- When you want to cache expensive database queries or external API responses.
|
|
@@ -96,16 +98,30 @@ class UserService {
|
|
|
96
98
|
|
|
97
99
|
### Redis Storage
|
|
98
100
|
|
|
99
|
-
|
|
101
|
+
Set `store: 'redis'`, then choose one of two supported client integration paths:
|
|
102
|
+
|
|
103
|
+
1. Register a default or named raw client with `@fluojs/redis` and let the cache module resolve it through DI.
|
|
104
|
+
2. Pass an application-owned `RedisCompatibleClient` directly through `redis.client`.
|
|
100
105
|
|
|
101
106
|
Memory-only consumers can keep importing from `@fluojs/cache-manager` without installing `@fluojs/redis` or `ioredis`; those optional peers are resolved only when the Redis store path is selected.
|
|
102
107
|
|
|
103
108
|
```typescript
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
109
|
+
import { Module } from '@fluojs/core';
|
|
110
|
+
import { CacheModule } from '@fluojs/cache-manager';
|
|
111
|
+
import { RedisModule } from '@fluojs/redis';
|
|
112
|
+
|
|
113
|
+
@Module({
|
|
114
|
+
imports: [
|
|
115
|
+
RedisModule.forRoot({ name: 'cache', host: 'localhost', port: 6379 }),
|
|
116
|
+
CacheModule.forRoot({
|
|
117
|
+
store: 'redis',
|
|
118
|
+
ttl: 600,
|
|
119
|
+
keyPrefix: 'myapp:cache:',
|
|
120
|
+
redis: { clientName: 'cache' },
|
|
121
|
+
}),
|
|
122
|
+
],
|
|
108
123
|
})
|
|
124
|
+
class AppModule {}
|
|
109
125
|
```
|
|
110
126
|
|
|
111
127
|
If you registered multiple Redis clients, set `redis.clientName` to target a named `@fluojs/redis` connection.
|
|
@@ -119,7 +135,26 @@ CacheModule.forRoot({
|
|
|
119
135
|
})
|
|
120
136
|
```
|
|
121
137
|
|
|
122
|
-
`redis.client`
|
|
138
|
+
`redis.client` is the highest-precedence override and bypasses DI-based client selection entirely. It accepts any client that satisfies the exported `RedisCompatibleClient` contract; `@fluojs/redis` is not loaded or required on this path. The application owns connection startup and shutdown for a directly supplied client.
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import Redis from 'ioredis';
|
|
142
|
+
import { Module } from '@fluojs/core';
|
|
143
|
+
import { CacheModule } from '@fluojs/cache-manager';
|
|
144
|
+
|
|
145
|
+
const cacheClient = new Redis({ host: 'localhost', port: 6379 });
|
|
146
|
+
|
|
147
|
+
@Module({
|
|
148
|
+
imports: [
|
|
149
|
+
CacheModule.forRoot({
|
|
150
|
+
store: 'redis',
|
|
151
|
+
keyPrefix: 'myapp:cache:',
|
|
152
|
+
redis: { client: cacheClient },
|
|
153
|
+
}),
|
|
154
|
+
],
|
|
155
|
+
})
|
|
156
|
+
class AppModule {}
|
|
157
|
+
```
|
|
123
158
|
|
|
124
159
|
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.
|
|
125
160
|
|
|
@@ -140,13 +175,37 @@ CacheModule.forRoot({
|
|
|
140
175
|
})
|
|
141
176
|
```
|
|
142
177
|
|
|
143
|
-
For fully custom keying, pass a function as `httpKeyStrategy` or use `@CacheKey(...)` with either a literal key or a key factory.
|
|
178
|
+
For fully custom keying, pass a function as `httpKeyStrategy` or use `@CacheKey(...)` with either a literal key or a key factory. These function-based hooks are the supported extension path for request-aware keys; do not subclass `CacheInterceptor` just to replace cache-key generation.
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
CacheModule.forRoot({
|
|
182
|
+
store: 'memory',
|
|
183
|
+
httpKeyStrategy: (context) => {
|
|
184
|
+
const path = context.requestContext.request.path;
|
|
185
|
+
const query = context.requestContext.request.query;
|
|
186
|
+
const q = String(query.q ?? '').trim().toLowerCase();
|
|
187
|
+
|
|
188
|
+
return q ? `${path}?q=${encodeURIComponent(q)}` : path;
|
|
189
|
+
},
|
|
190
|
+
})
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Handler-level keys can stay local to the route when only one endpoint needs custom behavior:
|
|
194
|
+
|
|
195
|
+
```typescript
|
|
196
|
+
@CacheKey((context) => {
|
|
197
|
+
const tenant = context.requestContext.principal?.subject ?? 'anonymous';
|
|
198
|
+
const slug = String(context.requestContext.request.query.slug ?? 'index');
|
|
199
|
+
|
|
200
|
+
return `tenant:${tenant}:page:${slug}`;
|
|
201
|
+
})
|
|
202
|
+
```
|
|
144
203
|
|
|
145
204
|
The HTTP interceptor caches only successful, uncommitted GET handler results with a value that can be replayed later. It skips `undefined`, `SseResponse` streams, already committed responses, and responses whose status code is outside the `2xx` range, so redirects and error responses are not stored as cache hits.
|
|
146
205
|
|
|
147
206
|
### Cache Ownership and Reset Scope
|
|
148
207
|
|
|
149
|
-
`CacheService.reset()` clears entries owned by the configured store, not unrelated application state. It also
|
|
208
|
+
`CacheService.reset()` clears entries owned by the configured store, not unrelated application state. It also serializes store reads/writes across the reset boundary and invalidates in-flight `remember(...)` loaders so loaders that started before the reset cannot repopulate stale entries after the reset completes. 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.
|
|
150
209
|
|
|
151
210
|
```typescript
|
|
152
211
|
CacheModule.forRoot({
|
|
@@ -157,7 +216,7 @@ CacheModule.forRoot({
|
|
|
157
216
|
|
|
158
217
|
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.
|
|
159
218
|
|
|
160
|
-
When the application closes, `CacheService` forwards shutdown to custom stores that expose `close()` or `dispose()`. Use one of those optional hooks when a store owns sockets, pools, timers, or other external resources.
|
|
219
|
+
When the application closes, `CacheService` stops new store reads/writes, waits for already-started store operations, and then forwards shutdown to custom stores that expose `close()` or `dispose()`. Use one of those optional hooks when a store owns sockets, pools, timers, or other external resources.
|
|
161
220
|
|
|
162
221
|
Custom stores can be passed directly through `store` when they implement the `CacheStore` contract. This is the right option for in-process LRU stores, remote caches other than Redis, or test doubles that need to observe cache operations.
|
|
163
222
|
|
|
@@ -187,7 +246,24 @@ The built-in memory store is designed for single-process, bounded caching:
|
|
|
187
246
|
|
|
188
247
|
### Deferred eviction timing
|
|
189
248
|
|
|
190
|
-
|
|
249
|
+
`@CacheEvict(...)` is HTTP route metadata, not a general service-method decorator. `CacheInterceptor` consumes it only when that interceptor runs around a non-GET controller handler. For service methods and other calls outside the HTTP interceptor pipeline, inject `CacheService` and call `del(...)` explicitly.
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
import { CacheEvict, CacheInterceptor } from '@fluojs/cache-manager';
|
|
253
|
+
import { Controller, Post, UseInterceptors } from '@fluojs/http';
|
|
254
|
+
|
|
255
|
+
@Controller('/products')
|
|
256
|
+
@UseInterceptors(CacheInterceptor)
|
|
257
|
+
class ProductController {
|
|
258
|
+
@Post('/refresh')
|
|
259
|
+
@CacheEvict('/products')
|
|
260
|
+
refresh() {
|
|
261
|
+
return { refreshed: true };
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
On that supported HTTP path, eviction is deferred until the response successfully commits. If `response.send(...)` rejects, the deferred eviction is cancelled so a failed commit does not drop the previous cached read result. If an adapter path never calls `response.send(...)`, the interceptor still runs a bounded fallback timer so successful writes do not leave stale entries behind indefinitely. The fallback timer evicts only when no response commit path was invoked; while `response.send(...)` is still pending, the send path owns eviction (on success) or cancellation (on failure), so the fallback timer does not evict under a pending send. Deferred eviction failures stay contained inside the interceptor, so cache-key factories or cache-store deletes cannot surface as post-response unhandled promise rejections.
|
|
191
267
|
|
|
192
268
|
## Public API Overview
|
|
193
269
|
|
|
@@ -195,6 +271,9 @@ For non-GET handlers decorated with `@CacheEvict(...)`, eviction is deferred unt
|
|
|
195
271
|
- `CacheModule.forRoot(options)`: Configures the cache store (memory/redis/custom), default TTL, key strategies, `global`, `principalScopeResolver`, the Redis namespace `keyPrefix`, and Redis options such as `redis.scanCount`.
|
|
196
272
|
This is the primary package entrypoint for application modules.
|
|
197
273
|
|
|
274
|
+
### Public types
|
|
275
|
+
- `CacheModuleOptions`: Application-facing configuration accepted by `CacheModule.forRoot(...)`.
|
|
276
|
+
- `NormalizedCacheModuleOptions`: Compatibility-only type export matching the normalized configuration shape after defaults are applied. Prefer `CacheModuleOptions` for application code; this type remains public so consumers that referenced the previously shipped declaration surface can keep compiling.
|
|
198
277
|
|
|
199
278
|
### Services
|
|
200
279
|
- `CacheService`: Main API for manual cache operations (`get`, `set`, `del`, `remember`, `reset`, `close`). Application shutdown calls the same `close()` path, which forwards teardown to custom stores exposing `close()` or `dispose()`.
|
|
@@ -202,11 +281,11 @@ For non-GET handlers decorated with `@CacheEvict(...)`, eviction is deferred unt
|
|
|
202
281
|
### Decorators
|
|
203
282
|
- `@CacheTTL(seconds)`: Sets the TTL for a specific handler.
|
|
204
283
|
- `@CacheKey(key)`: Sets a custom cache key or key factory for a specific handler.
|
|
205
|
-
- `@CacheEvict(key)`:
|
|
284
|
+
- `@CacheEvict(key)`: Stores HTTP route metadata that `CacheInterceptor` consumes after a successful non-GET controller handler completes; it does not intercept arbitrary service calls.
|
|
206
285
|
- `cacheRouteMetadataKey`, `getCacheKeyMetadata(...)`, `getCacheTtlMetadata(...)`, and `getCacheEvictMetadata(...)`: Low-level metadata helpers exported for first-party interceptor integration, diagnostics, and advanced tooling that needs to inspect cache decorator metadata without reimplementing the metadata keys.
|
|
207
286
|
|
|
208
287
|
### Interceptors
|
|
209
|
-
- `CacheInterceptor`: Handles automatic GET response caching and
|
|
288
|
+
- `CacheInterceptor`: Handles automatic GET response caching and consumes `@CacheEvict(...)` metadata for non-GET HTTP handlers.
|
|
210
289
|
|
|
211
290
|
### Stores and status helpers
|
|
212
291
|
- `MemoryStore` and `RedisStore`: Built-in store implementations.
|
|
@@ -215,7 +294,7 @@ For non-GET handlers decorated with `@CacheEvict(...)`, eviction is deferred unt
|
|
|
215
294
|
|
|
216
295
|
## Related Packages
|
|
217
296
|
|
|
218
|
-
- `@fluojs/redis`:
|
|
297
|
+
- `@fluojs/redis`: Optional lifecycle-managed Redis client integration. It is not required when `redis.client` supplies an application-owned `RedisCompatibleClient` directly.
|
|
219
298
|
- `@fluojs/http`: Required for HTTP interceptors and decorators.
|
|
220
299
|
|
|
221
300
|
## Example Sources
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export
|
|
1
|
+
export { CacheEvict, CacheKey, cacheRouteMetadataKey, CacheTTL, getCacheEvictMetadata, getCacheKeyMetadata, getCacheTtlMetadata, } from './decorators.js';
|
|
2
|
+
export { CacheInterceptor } from './interceptor.js';
|
|
3
|
+
export { CacheModule } from './module.js';
|
|
4
|
+
export { CacheService } from './service.js';
|
|
5
|
+
export { createCacheManagerPlatformDiagnosticIssues, createCacheManagerPlatformStatusSnapshot, } from './status.js';
|
|
6
|
+
export { MemoryStore } from './stores/memory-store.js';
|
|
7
|
+
export { RedisStore, type RedisStoreOptions } from './stores/redis-store.js';
|
|
8
|
+
export { CACHE_OPTIONS, CACHE_STORE } from './tokens.js';
|
|
9
|
+
export type { CacheEvictDecoratorValue, CacheEvictFactory, CacheKeyDecoratorValue, CacheKeyFactory, CacheKeyStrategy, CacheModuleOptions, CacheStore, NormalizedCacheModuleOptions, PrincipalScopeResolver, RedisCacheOptions, RedisCompatibleClient, } from './types.js';
|
|
10
|
+
export type { CacheManagerPlatformStatusSnapshot, CacheManagerStatusAdapterInput, CacheManagerStoreKind, CacheManagerStoreOwnershipMode, } from './status.js';
|
|
10
11
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,UAAU,EACV,QAAQ,EACR,qBAAqB,EACrB,QAAQ,EACR,qBAAqB,EACrB,mBAAmB,EACnB,mBAAmB,GACpB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EACL,0CAA0C,EAC1C,wCAAwC,GACzC,MAAM,aAAa,CAAC;AACrB,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACzD,YAAY,EACV,wBAAwB,EACxB,iBAAiB,EACjB,sBAAsB,EACtB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,UAAU,EACV,4BAA4B,EAC5B,sBAAsB,EACtB,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,kCAAkC,EAClC,8BAA8B,EAC9B,qBAAqB,EACrB,8BAA8B,GAC/B,MAAM,aAAa,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
export
|
|
2
|
-
export
|
|
3
|
-
export
|
|
4
|
-
export
|
|
5
|
-
export
|
|
6
|
-
export
|
|
7
|
-
export
|
|
8
|
-
export
|
|
9
|
-
export * from './types.js';
|
|
1
|
+
export { CacheEvict, CacheKey, cacheRouteMetadataKey, CacheTTL, getCacheEvictMetadata, getCacheKeyMetadata, getCacheTtlMetadata } from './decorators.js';
|
|
2
|
+
export { CacheInterceptor } from './interceptor.js';
|
|
3
|
+
export { CacheModule } from './module.js';
|
|
4
|
+
export { CacheService } from './service.js';
|
|
5
|
+
export { createCacheManagerPlatformDiagnosticIssues, createCacheManagerPlatformStatusSnapshot } from './status.js';
|
|
6
|
+
export { MemoryStore } from './stores/memory-store.js';
|
|
7
|
+
export { RedisStore } from './stores/redis-store.js';
|
|
8
|
+
export { CACHE_OPTIONS, CACHE_STORE } from './tokens.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,kBAAkB,EAAe,MAAM,cAAc,CAAC;AAGxG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAsE,4BAA4B,EAA0B,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"interceptor.d.ts","sourceRoot":"","sources":["../src/interceptor.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,kBAAkB,EAAe,MAAM,cAAc,CAAC;AAGxG,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,KAAK,EAAsE,4BAA4B,EAA0B,MAAM,YAAY,CAAC;AAsM3J;;GAEG;AACH,qBACa,gBAAiB,YAAW,WAAW;IAEhD,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;gBADP,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,4BAA4B;IAGlD,SAAS,CAAC,OAAO,EAAE,kBAAkB,EAAE,IAAI,EAAE,WAAW,GAAG,OAAO,CAAC,OAAO,CAAC;YAanE,YAAY;YA2BZ,eAAe;YA2Bf,gBAAgB;IAY9B,OAAO,CAAC,gBAAgB;IAUxB,OAAO,CAAC,gBAAgB;YAkBV,OAAO;YAQP,OAAO;YAOP,OAAO;CAMtB"}
|
package/dist/interceptor.js
CHANGED
|
@@ -98,6 +98,7 @@ function installDeferredEviction(response, evict) {
|
|
|
98
98
|
const originalSend = response.send.bind(response);
|
|
99
99
|
let restored = false;
|
|
100
100
|
let completed = false;
|
|
101
|
+
let sendInvoked = false;
|
|
101
102
|
const runEviction = () => {
|
|
102
103
|
if (completed) {
|
|
103
104
|
return;
|
|
@@ -114,10 +115,17 @@ function installDeferredEviction(response, evict) {
|
|
|
114
115
|
restored = true;
|
|
115
116
|
};
|
|
116
117
|
const fallbackTimer = setTimeout(() => {
|
|
117
|
-
|
|
118
|
+
// Run fallback eviction only when no response commit path was invoked.
|
|
119
|
+
// If response.send(...) is still pending or already completed, the send
|
|
120
|
+
// path owns eviction (on success) or cancellation (on failure), so the
|
|
121
|
+
// fallback timer must not evict under a pending send.
|
|
122
|
+
if (!sendInvoked) {
|
|
123
|
+
runEviction();
|
|
124
|
+
}
|
|
118
125
|
restore();
|
|
119
126
|
}, EVICTION_FALLBACK_TIMEOUT_MS);
|
|
120
127
|
response.send = async body => {
|
|
128
|
+
sendInvoked = true;
|
|
121
129
|
try {
|
|
122
130
|
await originalSend(body);
|
|
123
131
|
runEviction();
|
package/dist/service.d.ts
CHANGED
|
@@ -11,6 +11,8 @@ export declare class CacheService {
|
|
|
11
11
|
private readonly invalidatedInflight;
|
|
12
12
|
private closed;
|
|
13
13
|
private resetVersion;
|
|
14
|
+
private storeOperationTail;
|
|
15
|
+
private runStoreOperation;
|
|
14
16
|
private beginPendingLoad;
|
|
15
17
|
private endPendingLoad;
|
|
16
18
|
constructor(store: CacheStore, options: NormalizedCacheModuleOptions);
|
|
@@ -58,6 +60,7 @@ export declare class CacheService {
|
|
|
58
60
|
* @returns A promise that resolves after store teardown completes.
|
|
59
61
|
*/
|
|
60
62
|
close(): Promise<void>;
|
|
63
|
+
private deleteFromStore;
|
|
61
64
|
/**
|
|
62
65
|
* Runtime shutdown hook that releases resource-owning stores during application close.
|
|
63
66
|
*
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAQ3E;;GAEG;AACH,qBACa,YAAY;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,4BAA4B,EAAE,MAAM,YAAY,CAAC;AAQ3E;;GAEG;AACH,qBACa,YAAY;IAiDrB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAjD1B,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAmC;IAC5D,OAAO,CAAC,QAAQ,CAAC,YAAY,CAA0C;IACvE,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAA6B;IAClE,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAqB;IACzD,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,kBAAkB,CAAoC;YAEhD,iBAAiB;IAW/B,OAAO,CAAC,gBAAgB;IAOxB,OAAO,CAAC,cAAc;gBAsBH,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,4BAA4B;IAGxD;;;;;OAKG;IACH,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAcrD;;;;;;;OAOG;IACG,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBjF;;;;;;;OAOG;IACG,QAAQ,CAAC,CAAC,GAAG,OAAO,EACxB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EACxB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,CAAC,CAAC;IAkEb;;;;;OAKG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBrC;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAmB5B;;;;OAIG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAwBd,eAAe;IAU7B;;;;OAIG;IACH,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;CAGjC"}
|
package/dist/service.js
CHANGED
|
@@ -20,6 +20,12 @@ class CacheService {
|
|
|
20
20
|
invalidatedInflight = new Set();
|
|
21
21
|
closed = false;
|
|
22
22
|
resetVersion = 0;
|
|
23
|
+
storeOperationTail = Promise.resolve();
|
|
24
|
+
async runStoreOperation(operation) {
|
|
25
|
+
const result = this.storeOperationTail.then(operation, operation);
|
|
26
|
+
this.storeOperationTail = result.then(() => undefined, () => undefined);
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
23
29
|
beginPendingLoad(key, generation) {
|
|
24
30
|
const generations = this.pendingLoads.get(key) ?? new Map();
|
|
25
31
|
generations.set(generation, (generations.get(generation) ?? 0) + 1);
|
|
@@ -52,7 +58,15 @@ class CacheService {
|
|
|
52
58
|
* @returns The cached value, or `undefined` when the key is missing or expired.
|
|
53
59
|
*/
|
|
54
60
|
get(key) {
|
|
55
|
-
|
|
61
|
+
if (this.closed) {
|
|
62
|
+
return Promise.resolve(undefined);
|
|
63
|
+
}
|
|
64
|
+
return this.runStoreOperation(() => {
|
|
65
|
+
if (this.closed) {
|
|
66
|
+
return undefined;
|
|
67
|
+
}
|
|
68
|
+
return this.store.get(key);
|
|
69
|
+
});
|
|
56
70
|
}
|
|
57
71
|
|
|
58
72
|
/**
|
|
@@ -65,10 +79,15 @@ class CacheService {
|
|
|
65
79
|
*/
|
|
66
80
|
async set(key, value, ttlSeconds) {
|
|
67
81
|
const resolvedTtl = ttlSeconds ?? this.options.ttl;
|
|
68
|
-
if (!Number.isFinite(resolvedTtl) || resolvedTtl < 0) {
|
|
82
|
+
if (this.closed || !Number.isFinite(resolvedTtl) || resolvedTtl < 0) {
|
|
69
83
|
return;
|
|
70
84
|
}
|
|
71
|
-
await this.
|
|
85
|
+
await this.runStoreOperation(async () => {
|
|
86
|
+
if (this.closed) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
await this.store.set(key, value, resolvedTtl);
|
|
90
|
+
});
|
|
72
91
|
}
|
|
73
92
|
|
|
74
93
|
/**
|
|
@@ -80,6 +99,9 @@ class CacheService {
|
|
|
80
99
|
* @returns The cached or freshly loaded value.
|
|
81
100
|
*/
|
|
82
101
|
async remember(key, loader, ttlSeconds) {
|
|
102
|
+
if (this.closed) {
|
|
103
|
+
return loader();
|
|
104
|
+
}
|
|
83
105
|
const resetVersion = this.resetVersion;
|
|
84
106
|
this.beginPendingLoad(key, resetVersion);
|
|
85
107
|
try {
|
|
@@ -87,22 +109,28 @@ class CacheService {
|
|
|
87
109
|
if (cached !== undefined) {
|
|
88
110
|
return cached;
|
|
89
111
|
}
|
|
112
|
+
if (this.closed || this.resetVersion !== resetVersion) {
|
|
113
|
+
return loader();
|
|
114
|
+
}
|
|
90
115
|
const existing = this.inflight.get(key);
|
|
91
|
-
if (existing) {
|
|
116
|
+
if (existing && existing.generation === resetVersion) {
|
|
92
117
|
return existing.promise;
|
|
93
118
|
}
|
|
119
|
+
if (existing) {
|
|
120
|
+
this.inflight.delete(key);
|
|
121
|
+
}
|
|
94
122
|
const entry = {
|
|
95
123
|
generation: resetVersion,
|
|
96
124
|
invalidated: this.pendingInvalidations.get(key) === resetVersion,
|
|
97
125
|
promise: Promise.resolve(undefined)
|
|
98
126
|
};
|
|
99
127
|
const promise = loader().then(async value => {
|
|
100
|
-
if (entry.invalidated || this.resetVersion !== resetVersion) {
|
|
128
|
+
if (this.closed || entry.invalidated || this.resetVersion !== resetVersion) {
|
|
101
129
|
return value;
|
|
102
130
|
}
|
|
103
131
|
await this.set(key, value, ttlSeconds);
|
|
104
|
-
if (entry.invalidated || this.resetVersion !== resetVersion) {
|
|
105
|
-
await this.
|
|
132
|
+
if (!this.closed && (entry.invalidated || this.resetVersion !== resetVersion)) {
|
|
133
|
+
await this.deleteFromStore(key);
|
|
106
134
|
}
|
|
107
135
|
return value;
|
|
108
136
|
}).finally(() => {
|
|
@@ -129,6 +157,9 @@ class CacheService {
|
|
|
129
157
|
* @returns A promise that resolves after the entry is removed.
|
|
130
158
|
*/
|
|
131
159
|
async del(key) {
|
|
160
|
+
if (this.closed) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
132
163
|
const entry = this.inflight.get(key);
|
|
133
164
|
if (entry) {
|
|
134
165
|
entry.invalidated = true;
|
|
@@ -137,7 +168,7 @@ class CacheService {
|
|
|
137
168
|
this.pendingInvalidations.set(key, this.resetVersion);
|
|
138
169
|
this.invalidatedInflight.add(key);
|
|
139
170
|
}
|
|
140
|
-
await this.
|
|
171
|
+
await this.deleteFromStore(key);
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
/**
|
|
@@ -146,12 +177,20 @@ class CacheService {
|
|
|
146
177
|
* @returns A promise that resolves after the store reset completes.
|
|
147
178
|
*/
|
|
148
179
|
async reset() {
|
|
180
|
+
if (this.closed) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
149
183
|
this.resetVersion += 1;
|
|
150
184
|
this.inflight.clear();
|
|
151
185
|
this.pendingLoads.clear();
|
|
152
186
|
this.pendingInvalidations.clear();
|
|
153
187
|
this.invalidatedInflight.clear();
|
|
154
|
-
await this.
|
|
188
|
+
await this.runStoreOperation(async () => {
|
|
189
|
+
if (this.closed) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
await this.store.reset();
|
|
193
|
+
});
|
|
155
194
|
}
|
|
156
195
|
|
|
157
196
|
/**
|
|
@@ -164,17 +203,28 @@ class CacheService {
|
|
|
164
203
|
return;
|
|
165
204
|
}
|
|
166
205
|
this.closed = true;
|
|
206
|
+
this.resetVersion += 1;
|
|
167
207
|
this.inflight.clear();
|
|
168
208
|
this.pendingLoads.clear();
|
|
169
209
|
this.pendingInvalidations.clear();
|
|
170
210
|
this.invalidatedInflight.clear();
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
211
|
+
await this.runStoreOperation(async () => {
|
|
212
|
+
if (this.store.close) {
|
|
213
|
+
await this.store.close();
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
if (this.store.dispose) {
|
|
217
|
+
await this.store.dispose();
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
async deleteFromStore(key) {
|
|
222
|
+
await this.runStoreOperation(async () => {
|
|
223
|
+
if (this.closed) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
await this.store.del(key);
|
|
227
|
+
});
|
|
178
228
|
}
|
|
179
229
|
|
|
180
230
|
/**
|
package/dist/types.d.ts
CHANGED
|
@@ -54,7 +54,12 @@ export interface CacheModuleOptions extends CacheModuleInternalOptions {
|
|
|
54
54
|
principalScopeResolver?: PrincipalScopeResolver;
|
|
55
55
|
}
|
|
56
56
|
/**
|
|
57
|
-
*
|
|
57
|
+
* Compatibility-only public type for normalized cache-module configuration after defaults are applied.
|
|
58
|
+
*
|
|
59
|
+
* @remarks
|
|
60
|
+
* Application configuration should use `CacheModuleOptions` with `CacheModule.forRoot(...)`.
|
|
61
|
+
* This type remains exported so consumers that referenced the previously shipped declaration
|
|
62
|
+
* surface can keep compiling, but runtime module registration still normalizes options internally.
|
|
58
63
|
*/
|
|
59
64
|
export interface NormalizedCacheModuleOptions {
|
|
60
65
|
global: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;OAEG;IACH,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,OAAO,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC9D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1H,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC9F;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,0BAA0B;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;AAEzF;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,0BAA0B;IACpE,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACjD;AAED
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEvD,KAAK,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;AAEnC;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC,CAAC;IAC5C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB;;OAEG;IACH,KAAK,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;IAC1B;;OAEG;IACH,OAAO,CAAC,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC;IAC9D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1H,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,IAAI,EAAE,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC;CAC9F;AAED;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IAC/B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,0BAA0B;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,iBAAiB,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,MAAM,GAAG,SAAS,CAAC;AAEzF;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,0BAA0B;IACpE,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;IACxC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,eAAe,CAAC,EAAE,gBAAgB,CAAC;IACnC,sBAAsB,CAAC,EAAE,sBAAsB,CAAC;CACjD;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,4BAA4B;IAC3C,MAAM,EAAE,OAAO,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B,KAAK,EAAE,QAAQ,GAAG,OAAO,GAAG,UAAU,CAAC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,eAAe,EAAE,gBAAgB,CAAC;IAClC,sBAAsB,EAAE,sBAAsB,GAAG,SAAS,CAAC;CAC5D;AAED;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,EAAE,kBAAkB,KAAK,SAAS,CAAC,MAAM,CAAC,CAAC;AAEjF;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,eAAe,CAAC;AAE9D;;GAEG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,kBAAkB,EAC3B,KAAK,EAAE,OAAO,KACX,SAAS,CAAC,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC;AAE3C;;GAEG;AACH,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,iBAAiB,CAAC;AAEtF;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,aAAa,GAAG,MAAM,GAAG,CAAC,CAAC,OAAO,EAAE,kBAAkB,KAAK,MAAM,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"memory-store",
|
|
11
11
|
"decorator"
|
|
12
12
|
],
|
|
13
|
-
"version": "1.0.
|
|
13
|
+
"version": "1.0.6",
|
|
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/core": "^1.0
|
|
41
|
-
"@fluojs/di": "^
|
|
42
|
-
"@fluojs/http": "^
|
|
43
|
-
"@fluojs/runtime": "^
|
|
40
|
+
"@fluojs/core": "^1.1.0",
|
|
41
|
+
"@fluojs/di": "^2.0.0",
|
|
42
|
+
"@fluojs/http": "^2.0.1",
|
|
43
|
+
"@fluojs/runtime": "^2.0.1"
|
|
44
44
|
},
|
|
45
45
|
"peerDependencies": {
|
|
46
46
|
"ioredis": "^5.0.0",
|
|
47
|
-
"@fluojs/redis": "^1.
|
|
47
|
+
"@fluojs/redis": "^1.1.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependenciesMeta": {
|
|
50
50
|
"@fluojs/redis": {
|