@fluojs/cache-manager 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.ko.md +189 -0
- package/README.md +189 -0
- package/dist/clone.d.ts +2 -0
- package/dist/clone.d.ts.map +1 -0
- package/dist/clone.js +3 -0
- package/dist/decorators.d.ts +70 -0
- package/dist/decorators.d.ts.map +1 -0
- package/dist/decorators.js +120 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +9 -0
- package/dist/interceptor.d.ts +20 -0
- package/dist/interceptor.d.ts.map +1 -0
- package/dist/interceptor.js +216 -0
- package/dist/module.d.ts +32 -0
- package/dist/module.d.ts.map +1 -0
- package/dist/module.js +166 -0
- package/dist/service.d.ts +54 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +130 -0
- package/dist/status.d.ts +45 -0
- package/dist/status.d.ts.map +1 -0
- package/dist/status.js +115 -0
- package/dist/stores/memory-store.d.ts +10 -0
- package/dist/stores/memory-store.d.ts.map +1 -0
- package/dist/stores/memory-store.js +71 -0
- package/dist/stores/redis-store.d.ts +17 -0
- package/dist/stores/redis-store.d.ts.map +1 -0
- package/dist/stores/redis-store.js +81 -0
- package/dist/tokens.d.ts +9 -0
- package/dist/tokens.d.ts.map +1 -0
- package/dist/tokens.js +8 -0
- package/dist/types.d.ts +80 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +1 -0
- package/package.json +67 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fluo contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.ko.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# @fluojs/cache-manager
|
|
2
|
+
|
|
3
|
+
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
|
+
|
|
5
|
+
메모리(Memory) 및 Redis 저장소 어댑터를 지원하는 fluo 애플리케이션용 범용 캐시 관리 패키지입니다. 데코레이터 기반의 HTTP 응답 캐싱과 프로그래밍 방식의 애플리케이션 레벨 캐시 API를 모두 제공합니다.
|
|
6
|
+
|
|
7
|
+
## 목차
|
|
8
|
+
|
|
9
|
+
- [설치](#설치)
|
|
10
|
+
- [사용 시점](#사용-시점)
|
|
11
|
+
- [빠른 시작](#빠른-시작)
|
|
12
|
+
- [HTTP 응답 캐싱](#http-응답-캐싱)
|
|
13
|
+
- [애플리케이션 레벨 캐싱](#애플리케이션-레벨-캐싱)
|
|
14
|
+
- [공통 패턴](#공통-패턴)
|
|
15
|
+
- [Redis 저장소 사용](#redis-저장소-사용)
|
|
16
|
+
- [쿼리 매개변수 기반 캐싱](#쿼리-매개변수-기반-캐싱)
|
|
17
|
+
- [수동 모듈 조합](#수동-모듈-조합)
|
|
18
|
+
- [공개 API 개요](#공개-api-개요)
|
|
19
|
+
- [관련 패키지](#관련-패키지)
|
|
20
|
+
- [예제 소스](#예제-소스)
|
|
21
|
+
|
|
22
|
+
## 설치
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @fluojs/cache-manager
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
root `@fluojs/cache-manager` import는 memory-only 설치에서도 안전합니다. Redis peer는 Redis 저장소 경로를 명시적으로 선택할 때만 필요합니다.
|
|
29
|
+
|
|
30
|
+
Redis 기반 캐싱을 사용하는 경우:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @fluojs/cache-manager @fluojs/redis ioredis
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## 사용 시점
|
|
37
|
+
|
|
38
|
+
- 비용이 많이 드는 데이터베이스 쿼리나 외부 API 응답을 캐싱하고 싶을 때 사용합니다.
|
|
39
|
+
- GET 응답을 캐싱하여 HTTP 성능을 향상시키고 싶을 때 적합합니다.
|
|
40
|
+
- 여러 인스턴스 간에 캐시 상태를 공유해야 할 때(Redis 사용) 사용합니다.
|
|
41
|
+
- "Remember" 패턴(값이 없으면 조회 후 캐싱)을 간편하게 구현하고 싶을 때 사용합니다.
|
|
42
|
+
|
|
43
|
+
## 빠른 시작
|
|
44
|
+
|
|
45
|
+
### HTTP 응답 캐싱
|
|
46
|
+
|
|
47
|
+
`CacheModule`을 등록하고 컨트롤러에 `CacheInterceptor`를 사용합니다.
|
|
48
|
+
|
|
49
|
+
내장 메모리 경로는 기본적으로 안전한 상한을 갖습니다. `ttl`을 생략하면 fluo는 기본 TTL 300초를 적용하고, 메모리 저장소의 live 엔트리가 1,000개를 넘으면 가장 오래된 키부터 제거합니다.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Module } from '@fluojs/core';
|
|
53
|
+
import { Controller, Get, UseInterceptors } from '@fluojs/http';
|
|
54
|
+
import { CacheModule, CacheInterceptor, CacheTTL } from '@fluojs/cache-manager';
|
|
55
|
+
|
|
56
|
+
@Controller('/products')
|
|
57
|
+
class ProductController {
|
|
58
|
+
@Get('/')
|
|
59
|
+
@UseInterceptors(CacheInterceptor)
|
|
60
|
+
@CacheTTL(60) // 60초 동안 캐싱
|
|
61
|
+
list() {
|
|
62
|
+
return [{ id: 1, name: 'Product A' }];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Module({
|
|
67
|
+
imports: [CacheModule.forRoot({ store: 'memory' })],
|
|
68
|
+
controllers: [ProductController],
|
|
69
|
+
})
|
|
70
|
+
class AppModule {}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### 애플리케이션 레벨 캐싱
|
|
74
|
+
|
|
75
|
+
`CacheService`를 주입받아 프로그래밍 방식으로 캐시를 관리합니다.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { Inject } from '@fluojs/core';
|
|
79
|
+
import { CacheService } from '@fluojs/cache-manager';
|
|
80
|
+
|
|
81
|
+
class UserService {
|
|
82
|
+
constructor(@Inject(CacheService) private readonly cache: CacheService) {}
|
|
83
|
+
|
|
84
|
+
async getProfile(userId: string) {
|
|
85
|
+
return this.cache.remember(`user:${userId}`, async () => {
|
|
86
|
+
// 캐시에 값이 없을 때만 이 로직이 실행됩니다.
|
|
87
|
+
return fetchUserProfile(userId);
|
|
88
|
+
}, 300); // 5분
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 공통 패턴
|
|
94
|
+
|
|
95
|
+
### Redis 저장소 사용
|
|
96
|
+
|
|
97
|
+
Redis를 사용하려면 `@fluojs/redis`가 설정되어 있어야 하며, `store` 옵션을 `'redis'`로 설정합니다.
|
|
98
|
+
|
|
99
|
+
memory-only 소비자는 `@fluojs/redis`나 `ioredis`를 설치하지 않아도 `@fluojs/cache-manager`를 계속 import할 수 있습니다. 이 optional peer들은 Redis 저장소 경로를 선택할 때만 해석됩니다.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
CacheModule.forRoot({
|
|
103
|
+
store: 'redis',
|
|
104
|
+
ttl: 600,
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
여러 Redis 클라이언트를 등록했다면 `redis.clientName`으로 사용할 `@fluojs/redis` 연결을 지정할 수 있습니다.
|
|
109
|
+
|
|
110
|
+
`redis.clientName`을 생략하면 `REDIS_CLIENT`를 통해 해석되는 기본 Redis 클라이언트를 계속 사용합니다.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
CacheModule.forRoot({
|
|
114
|
+
store: 'redis',
|
|
115
|
+
redis: { clientName: 'cache' },
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`redis.client`는 여전히 가장 높은 우선순위의 명시적 override입니다. DI 기반 선택을 완전히 우회해야 할 때만 사용하세요.
|
|
120
|
+
|
|
121
|
+
내장 `RedisStore`는 엔트리를 `JSON.stringify(...)`로 저장합니다. 따라서 캐시 값은 JSON 호환 형태여야 합니다. 일반 객체, 배열, 문자열, 숫자, 불리언, `null`은 안정적으로 round-trip 되지만, `Date`는 JSON 결과(예: ISO 문자열)로 돌아오고, 함수/`undefined`/`symbol`은 유지되지 않으며, `bigint`나 순환 그래프처럼 직렬화 불가능한 값은 캐싱 전에 정규화해야 합니다.
|
|
122
|
+
|
|
123
|
+
### 쿼리 매개변수 기반 캐싱
|
|
124
|
+
|
|
125
|
+
기본적으로 캐시 키는 쿼리 매개변수를 무시합니다. 검색 조건 등에 따라 다른 응답을 캐싱하려면 `httpKeyStrategy: 'route+query'`를 활성화하세요.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
CacheModule.forRoot({
|
|
129
|
+
store: 'memory',
|
|
130
|
+
httpKeyStrategy: 'route+query',
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 수동 모듈 조합
|
|
135
|
+
|
|
136
|
+
일반적인 애플리케이션 설정과 커스텀 `defineModule(...)` 조합에서는 `CacheModule.forRoot(...)`를 사용합니다.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { defineModule } from '@fluojs/runtime';
|
|
140
|
+
import { CacheInterceptor, CacheModule, CacheService } from '@fluojs/cache-manager';
|
|
141
|
+
|
|
142
|
+
class ManualCacheModule {}
|
|
143
|
+
|
|
144
|
+
defineModule(ManualCacheModule, {
|
|
145
|
+
exports: [CacheService, CacheInterceptor],
|
|
146
|
+
imports: [CacheModule.forRoot({ store: 'memory', ttl: 60 })],
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### 메모리 저장소 운영 한계
|
|
151
|
+
|
|
152
|
+
내장 메모리 저장소는 단일 프로세스의 bounded cache 용도로 설계되어 있습니다.
|
|
153
|
+
|
|
154
|
+
- 기본 메모리 경로에서 `ttl`을 생략하면 `CacheModule.forRoot()`는 300초 TTL을 사용합니다.
|
|
155
|
+
- `ttl: 0`은 만료 없는 엔트리로 계속 지원되지만, 메모리 저장소는 가장 최근의 live 키 1,000개만 유지합니다.
|
|
156
|
+
- 키 종류가 매우 많거나 여러 인스턴스가 캐시를 공유해야 한다면 프로세스 로컬 메모리 대신 Redis 저장소를 사용하세요.
|
|
157
|
+
|
|
158
|
+
### 지연 삭제 시점
|
|
159
|
+
|
|
160
|
+
`@CacheEvict(...)`가 붙은 non-GET 핸들러는 응답이 성공적으로 commit된 뒤에 캐시를 삭제합니다. 어댑터 경로가 `response.send(...)`를 호출하지 않더라도, 인터셉터는 bounded fallback timer를 통해 성공한 쓰기 이후 stale 엔트리가 무기한 남지 않도록 보장합니다. 또한 지연 eviction 실패는 인터셉터 내부에 containment되어 cache key factory나 cache store 삭제 오류가 응답 이후 unhandled promise rejection으로 노출되지 않습니다.
|
|
161
|
+
|
|
162
|
+
## 공개 API 개요
|
|
163
|
+
|
|
164
|
+
### 모듈
|
|
165
|
+
- `CacheModule.forRoot(options)`: 캐시 저장소(memory/redis), 기본 TTL, 키 전략 등을 설정합니다.
|
|
166
|
+
애플리케이션 모듈에서 사용하는 기본 패키지 진입점입니다.
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
### 서비스
|
|
170
|
+
- `CacheService`: 수동 캐시 작업(`get`, `set`, `del`, `remember`, `reset`)을 위한 기본 API입니다.
|
|
171
|
+
|
|
172
|
+
### 데코레이터
|
|
173
|
+
- `@CacheTTL(seconds)`: 특정 핸들러의 TTL을 설정합니다.
|
|
174
|
+
- `@CacheKey(key)`: 특정 핸들러의 커스텀 캐시 키를 설정합니다.
|
|
175
|
+
- `@CacheEvict(key)`: 성공적인 데이터 변경(POST/PUT/DELETE) 후 특정 캐시 키를 삭제합니다.
|
|
176
|
+
|
|
177
|
+
### 인터셉터
|
|
178
|
+
- `CacheInterceptor`: 자동 GET 응답 캐싱 및 삭제 로직을 처리합니다.
|
|
179
|
+
|
|
180
|
+
## 관련 패키지
|
|
181
|
+
|
|
182
|
+
- `@fluojs/redis`: Redis 저장소 사용 시 필요합니다.
|
|
183
|
+
- `@fluojs/http`: HTTP 인터셉터 및 데코레이터 사용 시 필요합니다.
|
|
184
|
+
|
|
185
|
+
## 예제 소스
|
|
186
|
+
|
|
187
|
+
- `packages/cache-manager/src/module.test.ts`: 모듈 설정 및 프로바이더 테스트.
|
|
188
|
+
- `packages/cache-manager/src/interceptor.test.ts`: HTTP 캐싱 및 삭제 테스트.
|
|
189
|
+
- `packages/cache-manager/src/service.ts`: 코어 `CacheService` 구현.
|
package/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# @fluojs/cache-manager
|
|
2
|
+
|
|
3
|
+
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
|
+
|
|
5
|
+
General-purpose cache manager for fluo with pluggable memory and Redis stores. Provides both decorator-driven HTTP response caching and a standalone cache API for application-level caching.
|
|
6
|
+
|
|
7
|
+
## Table of Contents
|
|
8
|
+
|
|
9
|
+
- [Installation](#installation)
|
|
10
|
+
- [When to Use](#when-to-use)
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [HTTP Response Caching](#http-response-caching)
|
|
13
|
+
- [Application-Level Caching](#application-level-caching)
|
|
14
|
+
- [Common Patterns](#common-patterns)
|
|
15
|
+
- [Redis Storage](#redis-storage)
|
|
16
|
+
- [Query-Sensitive Caching](#query-sensitive-caching)
|
|
17
|
+
- [Manual Module Composition](#manual-module-composition)
|
|
18
|
+
- [Public API Overview](#public-api-overview)
|
|
19
|
+
- [Related Packages](#related-packages)
|
|
20
|
+
- [Example Sources](#example-sources)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm install @fluojs/cache-manager
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The root `@fluojs/cache-manager` import stays safe for memory-only installs. You only need Redis peers when you explicitly select the Redis-backed store path.
|
|
29
|
+
|
|
30
|
+
For Redis-backed caching:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npm install @fluojs/cache-manager @fluojs/redis ioredis
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## When to Use
|
|
37
|
+
|
|
38
|
+
- When you want to cache expensive database queries or external API responses.
|
|
39
|
+
- When you need to improve HTTP performance by caching GET responses.
|
|
40
|
+
- When you need to share cache state across multiple instances (using Redis).
|
|
41
|
+
- When you need a simple "remember" pattern (fetch if missing, then cache).
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
### HTTP Response Caching
|
|
46
|
+
|
|
47
|
+
Register the `CacheModule` and use the `CacheInterceptor` on your controllers.
|
|
48
|
+
|
|
49
|
+
The built-in memory path is intentionally bounded by default: when you omit `ttl`, fluo applies a 300-second default TTL and keeps at most 1,000 live memory-store entries before evicting the oldest keys.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { Module } from '@fluojs/core';
|
|
53
|
+
import { Controller, Get, UseInterceptors } from '@fluojs/http';
|
|
54
|
+
import { CacheModule, CacheInterceptor, CacheTTL } from '@fluojs/cache-manager';
|
|
55
|
+
|
|
56
|
+
@Controller('/products')
|
|
57
|
+
class ProductController {
|
|
58
|
+
@Get('/')
|
|
59
|
+
@UseInterceptors(CacheInterceptor)
|
|
60
|
+
@CacheTTL(60) // Cache for 60 seconds
|
|
61
|
+
list() {
|
|
62
|
+
return [{ id: 1, name: 'Product A' }];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
@Module({
|
|
67
|
+
imports: [CacheModule.forRoot({ store: 'memory' })],
|
|
68
|
+
controllers: [ProductController],
|
|
69
|
+
})
|
|
70
|
+
class AppModule {}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Application-Level Caching
|
|
74
|
+
|
|
75
|
+
Inject `CacheService` to manage cache programmatically.
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { Inject } from '@fluojs/core';
|
|
79
|
+
import { CacheService } from '@fluojs/cache-manager';
|
|
80
|
+
|
|
81
|
+
class UserService {
|
|
82
|
+
constructor(@Inject(CacheService) private readonly cache: CacheService) {}
|
|
83
|
+
|
|
84
|
+
async getProfile(userId: string) {
|
|
85
|
+
return this.cache.remember(`user:${userId}`, async () => {
|
|
86
|
+
// This runs only if the key is missing from cache
|
|
87
|
+
return fetchUserProfile(userId);
|
|
88
|
+
}, 300); // 5 minutes
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Common Patterns
|
|
94
|
+
|
|
95
|
+
### Redis Storage
|
|
96
|
+
|
|
97
|
+
To use Redis, ensure `@fluojs/redis` is configured and set the store to `'redis'`.
|
|
98
|
+
|
|
99
|
+
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.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
CacheModule.forRoot({
|
|
103
|
+
store: 'redis',
|
|
104
|
+
ttl: 600,
|
|
105
|
+
})
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
If you registered multiple Redis clients, set `redis.clientName` to target a named `@fluojs/redis` connection.
|
|
109
|
+
|
|
110
|
+
Leave `redis.clientName` unset to keep using the default Redis client resolved through `REDIS_CLIENT`.
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
CacheModule.forRoot({
|
|
114
|
+
store: 'redis',
|
|
115
|
+
redis: { clientName: 'cache' },
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`redis.client` remains the highest-precedence override. Use it only when you need to bypass DI-based client selection entirely.
|
|
120
|
+
|
|
121
|
+
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
|
+
### Query-Sensitive Caching
|
|
124
|
+
|
|
125
|
+
By default, the cache key ignores query parameters. Enable `httpKeyStrategy: 'route+query'` to cache different responses for different search parameters.
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
CacheModule.forRoot({
|
|
129
|
+
store: 'memory',
|
|
130
|
+
httpKeyStrategy: 'route+query',
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Manual Module Composition
|
|
135
|
+
|
|
136
|
+
Use `CacheModule.forRoot(...)` for normal application setup, including custom `defineModule(...)` composition.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
import { defineModule } from '@fluojs/runtime';
|
|
140
|
+
import { CacheInterceptor, CacheModule, CacheService } from '@fluojs/cache-manager';
|
|
141
|
+
|
|
142
|
+
class ManualCacheModule {}
|
|
143
|
+
|
|
144
|
+
defineModule(ManualCacheModule, {
|
|
145
|
+
exports: [CacheService, CacheInterceptor],
|
|
146
|
+
imports: [CacheModule.forRoot({ store: 'memory', ttl: 60 })],
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Memory Store Operational Limits
|
|
151
|
+
|
|
152
|
+
The built-in memory store is designed for single-process, bounded caching:
|
|
153
|
+
|
|
154
|
+
- If you omit `ttl` on the default memory path, `CacheModule.forRoot()` uses a 300-second TTL.
|
|
155
|
+
- `ttl: 0` is still supported for no-expiry entries, but the memory store keeps only the most recent 1,000 live keys.
|
|
156
|
+
- High-cardinality or multi-instance deployments should use the Redis store instead of relying on process-local memory.
|
|
157
|
+
|
|
158
|
+
### Deferred eviction timing
|
|
159
|
+
|
|
160
|
+
For non-GET handlers decorated with `@CacheEvict(...)`, eviction is deferred until the response successfully commits. 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. Deferred eviction failures stay contained inside the interceptor, so cache-key factories or cache-store deletes cannot surface as post-response unhandled promise rejections.
|
|
161
|
+
|
|
162
|
+
## Public API Overview
|
|
163
|
+
|
|
164
|
+
### Modules
|
|
165
|
+
- `CacheModule.forRoot(options)`: Configures the cache store (memory/redis), default TTL, and key strategies.
|
|
166
|
+
This is the primary package entrypoint for application modules.
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
### Services
|
|
170
|
+
- `CacheService`: Main API for manual cache operations (`get`, `set`, `del`, `remember`, `reset`).
|
|
171
|
+
|
|
172
|
+
### Decorators
|
|
173
|
+
- `@CacheTTL(seconds)`: Sets the TTL for a specific handler.
|
|
174
|
+
- `@CacheKey(key)`: Sets a custom cache key for a specific handler.
|
|
175
|
+
- `@CacheEvict(key)`: Clears specific cache keys after a successful mutation (POST/PUT/DELETE).
|
|
176
|
+
|
|
177
|
+
### Interceptors
|
|
178
|
+
- `CacheInterceptor`: Handles automatic GET response caching and eviction logic.
|
|
179
|
+
|
|
180
|
+
## Related Packages
|
|
181
|
+
|
|
182
|
+
- `@fluojs/redis`: Required for Redis storage.
|
|
183
|
+
- `@fluojs/http`: Required for HTTP interceptors and decorators.
|
|
184
|
+
|
|
185
|
+
## Example Sources
|
|
186
|
+
|
|
187
|
+
- `packages/cache-manager/src/module.test.ts`: Module configuration and provider tests.
|
|
188
|
+
- `packages/cache-manager/src/interceptor.test.ts`: HTTP caching and eviction tests.
|
|
189
|
+
- `packages/cache-manager/src/service.ts`: Core `CacheService` implementation.
|
package/dist/clone.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clone.d.ts","sourceRoot":"","sources":["../src/clone.ts"],"names":[],"mappings":"AAAA,wBAAgB,eAAe,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAE9C"}
|
package/dist/clone.js
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { CacheEvictDecoratorValue, CacheKeyDecoratorValue } from './types.js';
|
|
2
|
+
/** Shared controller metadata key used to store per-route cache metadata records. */
|
|
3
|
+
export declare const cacheRouteMetadataKey: unique symbol;
|
|
4
|
+
type StandardMetadataBag = Record<PropertyKey, unknown>;
|
|
5
|
+
type StandardMethodDecoratorFn = (value: Function, context: ClassMethodDecoratorContext) => void;
|
|
6
|
+
/**
|
|
7
|
+
* Override the computed cache key for a GET handler.
|
|
8
|
+
*
|
|
9
|
+
* @param key Static cache key or resolver invoked with the interceptor context.
|
|
10
|
+
* @returns A method decorator that stores cache-key metadata for the handler.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```ts
|
|
14
|
+
* @CacheKey('/products:featured')
|
|
15
|
+
* @Get('/featured')
|
|
16
|
+
* listFeatured() {}
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export declare function CacheKey(key: CacheKeyDecoratorValue): StandardMethodDecoratorFn;
|
|
20
|
+
/**
|
|
21
|
+
* Override the cache TTL for a GET handler.
|
|
22
|
+
*
|
|
23
|
+
* @param ttlSeconds Cache lifetime in seconds. Use `0` to disable expiration.
|
|
24
|
+
* @returns A method decorator that stores per-handler TTL metadata.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* @CacheTTL(60)
|
|
29
|
+
* @Get('/')
|
|
30
|
+
* listProducts() {}
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function CacheTTL(ttlSeconds: number): StandardMethodDecoratorFn;
|
|
34
|
+
/**
|
|
35
|
+
* Evict one or more cache entries after a successful non-GET handler completes.
|
|
36
|
+
*
|
|
37
|
+
* @param value Static key list or resolver that derives eviction targets from the request/result.
|
|
38
|
+
* @returns A method decorator that stores eviction metadata for the handler.
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```ts
|
|
42
|
+
* @CacheEvict('/products')
|
|
43
|
+
* @Post('/refresh')
|
|
44
|
+
* refresh() {}
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export declare function CacheEvict(value: CacheEvictDecoratorValue): StandardMethodDecoratorFn;
|
|
48
|
+
/**
|
|
49
|
+
* Read `@CacheKey(...)` metadata from a method metadata bag.
|
|
50
|
+
*
|
|
51
|
+
* @param bag Route-level metadata bag captured from the controller.
|
|
52
|
+
* @returns The stored cache-key override, if present.
|
|
53
|
+
*/
|
|
54
|
+
export declare function getCacheKeyMetadata(bag: StandardMetadataBag): CacheKeyDecoratorValue | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Read `@CacheTTL(...)` metadata from a method metadata bag.
|
|
57
|
+
*
|
|
58
|
+
* @param bag Route-level metadata bag captured from the controller.
|
|
59
|
+
* @returns The stored TTL override in seconds, if present.
|
|
60
|
+
*/
|
|
61
|
+
export declare function getCacheTtlMetadata(bag: StandardMetadataBag): number | undefined;
|
|
62
|
+
/**
|
|
63
|
+
* Read `@CacheEvict(...)` metadata from a method metadata bag.
|
|
64
|
+
*
|
|
65
|
+
* @param bag Route-level metadata bag captured from the controller.
|
|
66
|
+
* @returns A defensive copy of the eviction metadata, if present.
|
|
67
|
+
*/
|
|
68
|
+
export declare function getCacheEvictMetadata(bag: StandardMetadataBag): CacheEvictDecoratorValue | undefined;
|
|
69
|
+
export {};
|
|
70
|
+
//# sourceMappingURL=decorators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEnF,qFAAqF;AACrF,eAAO,MAAM,qBAAqB,eAAoC,CAAC;AAKvE,KAAK,mBAAmB,GAAG,MAAM,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;AACxD,KAAK,yBAAyB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,2BAA2B,KAAK,IAAI,CAAC;AAiCjG;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,sBAAsB,GAAG,yBAAyB,CAI/E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,yBAAyB,CAItE;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,wBAAwB,GAAG,yBAAyB,CAIrF;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,GAAG,sBAAsB,GAAG,SAAS,CAEhG;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,GAAG,MAAM,GAAG,SAAS,CAQhF;AAED;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,mBAAmB,GAAG,wBAAwB,GAAG,SAAS,CAIpG"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/** Shared controller metadata key used to store per-route cache metadata records. */
|
|
2
|
+
export const cacheRouteMetadataKey = Symbol.for('fluo.standard.route');
|
|
3
|
+
const cacheKeyMetadataKey = Symbol.for('fluo.cache.key');
|
|
4
|
+
const cacheTtlMetadataKey = Symbol.for('fluo.cache.ttl');
|
|
5
|
+
const cacheEvictMetadataKey = Symbol.for('fluo.cache.evict');
|
|
6
|
+
function getMetadataBag(metadata) {
|
|
7
|
+
return metadata;
|
|
8
|
+
}
|
|
9
|
+
function getRouteRecord(metadata, name) {
|
|
10
|
+
const bag = getMetadataBag(metadata);
|
|
11
|
+
let routeMap = bag[cacheRouteMetadataKey];
|
|
12
|
+
if (!routeMap) {
|
|
13
|
+
routeMap = new Map();
|
|
14
|
+
bag[cacheRouteMetadataKey] = routeMap;
|
|
15
|
+
}
|
|
16
|
+
let record = routeMap.get(name);
|
|
17
|
+
if (!record) {
|
|
18
|
+
record = {};
|
|
19
|
+
routeMap.set(name, record);
|
|
20
|
+
}
|
|
21
|
+
return record;
|
|
22
|
+
}
|
|
23
|
+
function cloneEvictValue(value) {
|
|
24
|
+
if (Array.isArray(value)) {
|
|
25
|
+
return [...value];
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Override the computed cache key for a GET handler.
|
|
32
|
+
*
|
|
33
|
+
* @param key Static cache key or resolver invoked with the interceptor context.
|
|
34
|
+
* @returns A method decorator that stores cache-key metadata for the handler.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* @CacheKey('/products:featured')
|
|
39
|
+
* @Get('/featured')
|
|
40
|
+
* listFeatured() {}
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export function CacheKey(key) {
|
|
44
|
+
return (_value, context) => {
|
|
45
|
+
getRouteRecord(context.metadata, context.name)[cacheKeyMetadataKey] = key;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Override the cache TTL for a GET handler.
|
|
51
|
+
*
|
|
52
|
+
* @param ttlSeconds Cache lifetime in seconds. Use `0` to disable expiration.
|
|
53
|
+
* @returns A method decorator that stores per-handler TTL metadata.
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```ts
|
|
57
|
+
* @CacheTTL(60)
|
|
58
|
+
* @Get('/')
|
|
59
|
+
* listProducts() {}
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function CacheTTL(ttlSeconds) {
|
|
63
|
+
return (_value, context) => {
|
|
64
|
+
getRouteRecord(context.metadata, context.name)[cacheTtlMetadataKey] = ttlSeconds;
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Evict one or more cache entries after a successful non-GET handler completes.
|
|
70
|
+
*
|
|
71
|
+
* @param value Static key list or resolver that derives eviction targets from the request/result.
|
|
72
|
+
* @returns A method decorator that stores eviction metadata for the handler.
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```ts
|
|
76
|
+
* @CacheEvict('/products')
|
|
77
|
+
* @Post('/refresh')
|
|
78
|
+
* refresh() {}
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export function CacheEvict(value) {
|
|
82
|
+
return (_value, context) => {
|
|
83
|
+
getRouteRecord(context.metadata, context.name)[cacheEvictMetadataKey] = cloneEvictValue(value);
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read `@CacheKey(...)` metadata from a method metadata bag.
|
|
89
|
+
*
|
|
90
|
+
* @param bag Route-level metadata bag captured from the controller.
|
|
91
|
+
* @returns The stored cache-key override, if present.
|
|
92
|
+
*/
|
|
93
|
+
export function getCacheKeyMetadata(bag) {
|
|
94
|
+
return bag[cacheKeyMetadataKey];
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Read `@CacheTTL(...)` metadata from a method metadata bag.
|
|
99
|
+
*
|
|
100
|
+
* @param bag Route-level metadata bag captured from the controller.
|
|
101
|
+
* @returns The stored TTL override in seconds, if present.
|
|
102
|
+
*/
|
|
103
|
+
export function getCacheTtlMetadata(bag) {
|
|
104
|
+
const ttl = bag[cacheTtlMetadataKey];
|
|
105
|
+
if (typeof ttl !== 'number') {
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
return ttl;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read `@CacheEvict(...)` metadata from a method metadata bag.
|
|
113
|
+
*
|
|
114
|
+
* @param bag Route-level metadata bag captured from the controller.
|
|
115
|
+
* @returns A defensive copy of the eviction metadata, if present.
|
|
116
|
+
*/
|
|
117
|
+
export function getCacheEvictMetadata(bag) {
|
|
118
|
+
const value = bag[cacheEvictMetadataKey];
|
|
119
|
+
return value ? cloneEvictValue(value) : undefined;
|
|
120
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './decorators.js';
|
|
2
|
+
export * from './interceptor.js';
|
|
3
|
+
export * from './stores/memory-store.js';
|
|
4
|
+
export * from './module.js';
|
|
5
|
+
export * from './stores/redis-store.js';
|
|
6
|
+
export * from './service.js';
|
|
7
|
+
export * from './status.js';
|
|
8
|
+
export * from './tokens.js';
|
|
9
|
+
export * from './types.js';
|
|
10
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,kBAAkB,CAAC;AACjC,cAAc,0BAA0B,CAAC;AACzC,cAAc,aAAa,CAAC;AAC5B,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './decorators.js';
|
|
2
|
+
export * from './interceptor.js';
|
|
3
|
+
export * from './stores/memory-store.js';
|
|
4
|
+
export * from './module.js';
|
|
5
|
+
export * from './stores/redis-store.js';
|
|
6
|
+
export * from './service.js';
|
|
7
|
+
export * from './status.js';
|
|
8
|
+
export * from './tokens.js';
|
|
9
|
+
export * from './types.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type CallHandler, type Interceptor, type InterceptorContext } from '@fluojs/http';
|
|
2
|
+
import { CacheService } from './service.js';
|
|
3
|
+
import type { NormalizedCacheModuleOptions } from './types.js';
|
|
4
|
+
/**
|
|
5
|
+
* Caches GET responses and evicts related entries after successful write operations.
|
|
6
|
+
*/
|
|
7
|
+
export declare class CacheInterceptor implements Interceptor {
|
|
8
|
+
private readonly cache;
|
|
9
|
+
private readonly options;
|
|
10
|
+
constructor(cache: CacheService, options: NormalizedCacheModuleOptions);
|
|
11
|
+
intercept(context: InterceptorContext, next: CallHandler): Promise<unknown>;
|
|
12
|
+
private interceptGet;
|
|
13
|
+
private evictAfterWrite;
|
|
14
|
+
private resolveEvictKeys;
|
|
15
|
+
private shouldCacheValue;
|
|
16
|
+
private safeGet;
|
|
17
|
+
private safeSet;
|
|
18
|
+
private safeDel;
|
|
19
|
+
}
|
|
20
|
+
//# sourceMappingURL=interceptor.d.ts.map
|
|
@@ -0,0 +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;AA+K3J;;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;YA0BZ,eAAe;YA2Bf,gBAAgB;IAY9B,OAAO,CAAC,gBAAgB;YAYV,OAAO;YAQP,OAAO;YAOP,OAAO;CAMtB"}
|