@fluojs/redis 1.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ko.md +40 -14
- package/README.md +40 -14
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +37 -4
- package/dist/redis-service.d.ts +1 -1
- package/dist/redis-service.d.ts.map +1 -1
- package/dist/redis-service.js +9 -5
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +6 -2
- package/dist/types.d.ts +6 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -6
package/README.ko.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
<p><a href="./README.md"><kbd>English</kbd></a> <strong><kbd>한국어</kbd></strong></p>
|
|
4
4
|
|
|
5
|
+
Node.js 지원 범위는 `>=24.0.0 <27`입니다. 업그레이드 절차는 [Node.js 지원 및 마이그레이션](../../docs/reference/node-support.ko.md)을 참조하세요.
|
|
6
|
+
|
|
5
7
|
fluo를 위한 공유 Redis 연결 계층입니다. 기본 app-scoped `ioredis` client와 선택적인 named client를 제공하며, 모두 애플리케이션 lifecycle로 관리됩니다.
|
|
6
8
|
|
|
7
9
|
## 목차
|
|
@@ -32,7 +34,7 @@ npm install @fluojs/redis ioredis
|
|
|
32
34
|
|
|
33
35
|
`RedisModule.forRoot(options)`는 기본 Redis 클라이언트와 `RedisService` 파사드를 등록하는 지원되는 root entrypoint입니다.
|
|
34
36
|
|
|
35
|
-
`RedisModule.forRoot(...)`는 의도적으로 동기
|
|
37
|
+
`RedisModule.forRoot(...)`는 의도적으로 동기 방식이며 Redis constructor option으로 항상 새 `ioredis` client를 생성합니다. 외부에서 만든 client를 채택하지 않습니다. NestJS의 `forRootAsync(...)` 같은 async dynamic module에서 마이그레이션할 때는 secret, 환경별 host, TLS option을 애플리케이션 경계에서 먼저 해석한 뒤 최종 Redis option을 `forRoot(...)`에 전달하세요. 외부에서 만든 client는 이 module 밖에 두고 애플리케이션 lifecycle에서 닫아야 합니다. fluo는 Redis module wiring을 module graph 안의 숨겨진 async factory로 미루지 않습니다.
|
|
36
38
|
|
|
37
39
|
```typescript
|
|
38
40
|
import { Module } from '@fluojs/core';
|
|
@@ -53,7 +55,7 @@ export class AppModule {}
|
|
|
53
55
|
|
|
54
56
|
`RedisService`를 주입받아 고수준 작업을 수행하거나, `REDIS_CLIENT`를 통해 원시 `ioredis` 인스턴스를 직접 사용할 수 있습니다.
|
|
55
57
|
|
|
56
|
-
`RedisService.get()`은 JSON parse를 시도하고 실패하면 raw string을 반환합니다. 누락된 key는 `null`을 반환합니다. `RedisService.set()`은 값을 `JSON.stringify()`로
|
|
58
|
+
`RedisService.get()`은 JSON parse를 시도하고 실패하면 raw string을 반환합니다. 누락된 key는 `null`을 반환합니다. `RedisService.set()`은 값을 `JSON.stringify()`로 직렬화하며, 유한한 양의 정수 TTL에는 Redis `EX`를 사용하고 유한한 양의 소수 TTL에는 올림한 밀리초 값으로 `PX`를 사용합니다. TTL을 생략하거나 0 이하 또는 유한하지 않은 값을 전달하면 persistent key를 저장합니다.
|
|
57
59
|
|
|
58
60
|
```typescript
|
|
59
61
|
import { Inject } from '@fluojs/core';
|
|
@@ -77,20 +79,40 @@ export class CacheRepository {
|
|
|
77
79
|
|
|
78
80
|
### 수명 주기 소유권
|
|
79
81
|
|
|
80
|
-
`@fluojs/redis`는 `RedisModule.forRoot({ name, ... })`로 등록한 이름 있는 연결을
|
|
82
|
+
`RedisModule.forRoot(...)` 등록은 각각 새 client를 생성하며, `@fluojs/redis`는 `RedisModule.forRoot({ name, ... })`로 등록한 이름 있는 연결을 포함해 그 client의 lifecycle을 직접 관리합니다. 이 module은 기존 client instance를 채택하지 않습니다. 등록 identity는 애플리케이션 전체에서 유일해야 합니다. 이름 없는 기본 등록은 하나만 허용하고, trim한 각 name도 하나만 허용하며, bootstrap은 Redis client를 만들기 전에 중복 identity를 거부합니다.
|
|
81
83
|
|
|
82
84
|
- 호출자가 옵션을 강제로 캐스팅하더라도 Fluo는 항상 `lazyConnect: true`를 강제하므로, 소켓은 import 시점이 아니라 애플리케이션 bootstrap 중에 열립니다.
|
|
83
85
|
- bootstrap 단계에서는 클라이언트가 ioredis `wait` 상태일 때만 lifecycle service가 `connect()`를 호출합니다.
|
|
84
86
|
- lifecycle이 소유한 `connect()`와 `quit()` 호출은 package timeout(기본 `10_000` ms)으로 제한되어, Redis 명령이 멈춰도 bootstrap/shutdown이 무기한 대기하지 않습니다. `lifecycle.connectTimeoutMs`와 `lifecycle.quitTimeoutMs`로 재정의할 수 있으며, host process가 의도적으로 무제한 대기를 소유하는 경우에만 `0`을 전달하세요.
|
|
85
|
-
- shutdown 단계에서는 ready/connecting 계열 상태에 `quit()`를 우선 시도해 정상 종료를 노리고, wait/종료 전이 상태에서는 `disconnect()`를 직접 사용합니다.
|
|
87
|
+
- shutdown 단계에서는 ready/connecting 계열 상태에 `quit()`를 우선 시도해 정상 종료를 노리고, monitoring, wait/종료 전이 상태에서는 `disconnect()`를 직접 사용합니다.
|
|
86
88
|
- `quit()`가 실패하면 Fluo는 `disconnect()`로 fallback하고, 그 뒤에도 클라이언트가 닫히지 않은 경우에만 에러를 다시 던집니다.
|
|
87
89
|
|
|
90
|
+
### 옵션 정규화
|
|
91
|
+
|
|
92
|
+
`RedisModuleOptions`는 최종 `ioredis` 생성자 형태가 아니라 `RedisModule.forRoot(...)`가 받는 caller-facing 입력입니다. 일반 `ioredis` 옵션 중 `lazyConnect`와 `name`을 제외한 필드에 Fluo 전용 필드 네 개를 추가합니다.
|
|
93
|
+
|
|
94
|
+
- `name`은 Fluo 등록과 해당 DI 토큰을 식별합니다. 이 값은 `ioredis` 생성자 `name`이 되지 않습니다. trim한 각 name은 애플리케이션 전체에서 하나만 등록할 수 있는 ownership identity입니다.
|
|
95
|
+
- `global`은 module visibility를 제어합니다. 기본 등록은 `false`로 지정하지 않는 한 global이고, named registration은 항상 scoped이며 `global: true`를 거부합니다.
|
|
96
|
+
- `lifecycle`은 Fluo가 소유한 `connect()`와 `quit()` timeout guardrail을 설정합니다.
|
|
97
|
+
- `sentinelName`은 입력의 `name` 필드가 Fluo 등록 식별자에 예약되어 있으므로 ioredis Sentinel master name을 별도로 받습니다.
|
|
98
|
+
|
|
99
|
+
Client 생성 전에 다음 순서로 정규화합니다.
|
|
100
|
+
|
|
101
|
+
1. 일반 ioredis option에서 `name`, `global`, `lifecycle`, `sentinelName`을 분리합니다.
|
|
102
|
+
2. 등록 `name`을 trim한 뒤 lifecycle timeout을 정규화하고 검증합니다.
|
|
103
|
+
3. 빈 등록 `name`을 거부하고 named registration의 `global: true`를 거부합니다.
|
|
104
|
+
4. `sentinelName`이 있으면 ioredis 생성자 `name`으로 매핑해 `RedisClientOptions`를 만듭니다.
|
|
105
|
+
5. Provider가 `{ ...clientOptions, lazyConnect: true }`로 client를 생성합니다. 호출자가 type restriction을 우회해 `lazyConnect: false`를 강제로 cast하더라도 마지막 할당이 항상 우선합니다.
|
|
106
|
+
|
|
107
|
+
따라서 `RedisClientOptions`는 provider factory가 소비하는 정규화된 constructor-facing option을 나타냅니다. 일반 ioredis option과 선택적 Sentinel `name`을 포함하지만 Fluo 전용 field와 caller-controlled `lazyConnect`는 포함하지 않습니다. `forRoot(...)`가 받는 `RedisModuleOptions`와 같은 의미가 아닙니다.
|
|
108
|
+
|
|
88
109
|
### 이름 있는 클라이언트
|
|
89
110
|
|
|
90
111
|
하나의 애플리케이션에서 여러 Redis 연결이 필요하면 `RedisModule.forRoot({ name, ...options })`를 사용하세요. `name` 없는 `RedisModule.forRoot(options)`는 기본 `REDIS_CLIENT`와 `RedisService` 별칭을 제공하고, 이름 있는 등록은 `getRedisClientToken(name)`과 `getRedisServiceToken(name)`으로 해석합니다.
|
|
91
112
|
|
|
92
113
|
- `name`을 생략하면 기본 별칭인 `REDIS_CLIENT` / `RedisService`를 사용합니다.
|
|
93
114
|
- `name`을 지정하면 `getRedisClientToken(name)` / `getRedisServiceToken(name)`으로 이름 있는 바인딩을 가져옵니다.
|
|
115
|
+
- `name`은 Fluo 등록만 식별합니다. ioredis Sentinel master name은 `sentinelName`으로 전달하세요. Fluo는 등록 토큰을 바꾸지 않고 이를 ioredis 생성자 `name` 옵션으로 전달합니다.
|
|
94
116
|
- 이름 있는 클라이언트도 기본 클라이언트와 동일한 bootstrap/shutdown 계약을 따르며, `REDIS_CLIENT` / `RedisService` 별칭은 기본 등록에서만 export됩니다.
|
|
95
117
|
- 이름은 trim되며, blank 또는 whitespace-only name은 token/component helper에서 거부됩니다.
|
|
96
118
|
|
|
@@ -111,6 +133,7 @@ const ANALYTICS_REDIS_CLIENT = getRedisClientToken('analytics');
|
|
|
111
133
|
imports: [
|
|
112
134
|
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
113
135
|
RedisModule.forRoot({ name: 'analytics', host: 'localhost', port: 6380 }),
|
|
136
|
+
RedisModule.forRoot({ name: 'sentinel-cache', sentinelName: 'mymaster', sentinels: [{ host: 'localhost', port: 26379 }] }),
|
|
114
137
|
],
|
|
115
138
|
})
|
|
116
139
|
export class AppModule {}
|
|
@@ -159,14 +182,6 @@ import type Redis from 'ioredis';
|
|
|
159
182
|
const COMMAND_REDIS = getRedisClientToken();
|
|
160
183
|
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
|
|
161
184
|
|
|
162
|
-
@Module({
|
|
163
|
-
imports: [
|
|
164
|
-
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
165
|
-
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
|
|
166
|
-
],
|
|
167
|
-
})
|
|
168
|
-
export class RedisConnectionsModule {}
|
|
169
|
-
|
|
170
185
|
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
|
|
171
186
|
export class PubSubTransportFactory {
|
|
172
187
|
constructor(
|
|
@@ -181,8 +196,19 @@ export class PubSubTransportFactory {
|
|
|
181
196
|
});
|
|
182
197
|
}
|
|
183
198
|
}
|
|
199
|
+
|
|
200
|
+
@Module({
|
|
201
|
+
imports: [
|
|
202
|
+
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
203
|
+
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
|
|
204
|
+
],
|
|
205
|
+
providers: [PubSubTransportFactory],
|
|
206
|
+
})
|
|
207
|
+
export class RedisConnectionsModule {}
|
|
184
208
|
```
|
|
185
209
|
|
|
210
|
+
`@Inject(...)`는 클래스의 생성자 토큰만 선언하며 클래스를 등록하지 않습니다. fluo는 명시적 모듈 등록에서 프로바이더를 resolve하므로 `PubSubTransportFactory`는 Redis 등록을 import하는 모듈의 `providers` 배열에 반드시 나열해야 합니다. 어느 모듈에도 나열되지 않은 decorated 클래스는 resolve할 수 없습니다.
|
|
211
|
+
|
|
186
212
|
## 공개 API 개요
|
|
187
213
|
|
|
188
214
|
### 핵심 구성 요소
|
|
@@ -200,8 +226,8 @@ export class PubSubTransportFactory {
|
|
|
200
226
|
### 타입
|
|
201
227
|
- `DefaultRedisModuleOptions`: 이름 없는 기본 Redis 등록이 받는 옵션입니다. 선택적 global alias visibility와 lifecycle timeout control을 포함합니다.
|
|
202
228
|
- `NamedRedisModuleOptions`: 추가 이름 있는 Redis 등록이 받는 옵션입니다. 필수 `name`과 scoped lifecycle timeout control을 포함합니다.
|
|
203
|
-
- `RedisModuleOptions`:
|
|
204
|
-
- `RedisClientOptions`: Fluo가 module-only field를 제거하고
|
|
229
|
+
- `RedisModuleOptions`: `RedisModule.forRoot(...)`가 받는 caller-facing union입니다. `lazyConnect`와 `name`을 제외한 일반 ioredis option에 Fluo 전용 `name`, `global`, `lifecycle`, `sentinelName` field를 결합합니다.
|
|
230
|
+
- `RedisClientOptions`: Fluo가 module-only field를 제거하고 `sentinelName`을 ioredis Sentinel `name`으로 매핑한 뒤의 정규화된 constructor-facing option입니다. Provider는 그 다음 마지막 override로 `lazyConnect: true`를 추가합니다.
|
|
205
231
|
- `RedisLifecycleOptions`: Fluo가 소유한 `connect()`와 `quit()` lifecycle command의 timeout을 조정하는 선택적 옵션입니다.
|
|
206
232
|
- `PersistencePlatformStatusSnapshot`, `RedisStatusAdapterInput`: status snapshot input/output type입니다.
|
|
207
233
|
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
<p><strong><kbd>English</kbd></strong> <a href="./README.ko.md"><kbd>한국어</kbd></a></p>
|
|
4
4
|
|
|
5
|
+
Node.js support is `>=24.0.0 <27`. See [Node.js support and migration](../../docs/reference/node-support.md) before upgrading.
|
|
6
|
+
|
|
5
7
|
Shared Redis connection layer for fluo. It provides a default app-scoped `ioredis` client plus optional named clients, all managed by the application lifecycle.
|
|
6
8
|
|
|
7
9
|
## Table of Contents
|
|
@@ -32,7 +34,7 @@ npm install @fluojs/redis ioredis
|
|
|
32
34
|
|
|
33
35
|
Use `RedisModule.forRoot(options)` to register the default Redis client and `RedisService` facade.
|
|
34
36
|
|
|
35
|
-
`RedisModule.forRoot(...)` is intentionally synchronous. When migrating from NestJS async dynamic modules such as `forRootAsync(...)`, resolve secrets, environment-specific hosts,
|
|
37
|
+
`RedisModule.forRoot(...)` is intentionally synchronous and always creates a new `ioredis` client from Redis constructor options; it does not adopt an externally constructed client. When migrating from NestJS async dynamic modules such as `forRootAsync(...)`, resolve secrets, environment-specific hosts, and TLS options at the application boundary first, then pass the final Redis options into `forRoot(...)`. Keep externally constructed clients outside this module and close them from the application lifecycle. fluo does not defer Redis module wiring to an async factory hidden inside the module graph.
|
|
36
38
|
|
|
37
39
|
```typescript
|
|
38
40
|
import { Module } from '@fluojs/core';
|
|
@@ -53,7 +55,7 @@ export class AppModule {}
|
|
|
53
55
|
|
|
54
56
|
Inject `RedisService` for high-level operations or `REDIS_CLIENT` for the raw `ioredis` instance.
|
|
55
57
|
|
|
56
|
-
`RedisService.get()` attempts JSON parsing and falls back to the raw string; missing keys return `null`. `RedisService.set()` serializes values with `JSON.stringify()
|
|
58
|
+
`RedisService.get()` attempts JSON parsing and falls back to the raw string; missing keys return `null`. `RedisService.set()` serializes values with `JSON.stringify()`: finite positive integer TTLs use Redis `EX`, while finite positive fractional TTLs use `PX` with milliseconds rounded up. Omit the TTL or pass a non-positive or non-finite value for a persistent key.
|
|
57
59
|
|
|
58
60
|
```typescript
|
|
59
61
|
import { Inject } from '@fluojs/core';
|
|
@@ -77,20 +79,40 @@ export class CacheRepository {
|
|
|
77
79
|
|
|
78
80
|
### Lifecycle Ownership
|
|
79
81
|
|
|
80
|
-
|
|
82
|
+
Every `RedisModule.forRoot(...)` registration creates a new client that `@fluojs/redis` owns, including named clients registered through `RedisModule.forRoot({ name, ... })`. The module never adopts an existing client instance. Registration identity is application-wide: only one unnamed default registration and one registration for each trimmed name are allowed. Bootstrap rejects duplicate identities before creating a Redis client.
|
|
81
83
|
|
|
82
84
|
- Fluo always forces `lazyConnect: true`, even if callers cast options manually, so sockets open during application bootstrap instead of import time.
|
|
83
85
|
- During bootstrap, the lifecycle service only calls `connect()` while the client is still in ioredis `wait` state.
|
|
84
86
|
- Lifecycle-owned `connect()` and `quit()` calls are bounded by package timeouts (`10_000` ms by default) so bootstrap and shutdown do not wait forever on a stalled Redis command. Override them with `lifecycle.connectTimeoutMs` and `lifecycle.quitTimeoutMs`; pass `0` only when the host process intentionally owns an unbounded wait.
|
|
85
|
-
- During shutdown, ready/connecting clients attempt `quit()` first for graceful teardown, while wait
|
|
87
|
+
- During shutdown, ready/connecting clients attempt `quit()` first for graceful teardown, while monitoring, wait, and closed-transition states use `disconnect()` directly.
|
|
86
88
|
- If `quit()` fails, Fluo falls back to `disconnect()` and only rethrows when the client still remains open afterward.
|
|
87
89
|
|
|
90
|
+
### Option Normalization
|
|
91
|
+
|
|
92
|
+
`RedisModuleOptions` is the caller-facing input to `RedisModule.forRoot(...)`, not the final `ioredis` constructor shape. It accepts ordinary `ioredis` options except `lazyConnect` and `name`, then adds four Fluo-only fields:
|
|
93
|
+
|
|
94
|
+
- `name` identifies the Fluo registration and its DI tokens. It never becomes the `ioredis` constructor `name`. Each trimmed name is an application-wide ownership identity and can be registered only once.
|
|
95
|
+
- `global` controls module visibility. The default registration is global unless set to `false`; named registrations are always scoped and reject `global: true`.
|
|
96
|
+
- `lifecycle` configures Fluo-owned `connect()` and `quit()` timeout guardrails.
|
|
97
|
+
- `sentinelName` supplies the ioredis Sentinel master name because the input `name` field is reserved for Fluo registration identity.
|
|
98
|
+
|
|
99
|
+
Normalization happens before client construction in this order:
|
|
100
|
+
|
|
101
|
+
1. Fluo removes `name`, `global`, `lifecycle`, and `sentinelName` from the ordinary ioredis options.
|
|
102
|
+
2. It trims the registration `name`, then normalizes and validates the lifecycle timeouts.
|
|
103
|
+
3. It rejects a blank registration name and rejects `global: true` on a named registration.
|
|
104
|
+
4. It maps `sentinelName`, when present, to the ioredis constructor `name` and produces `RedisClientOptions`.
|
|
105
|
+
5. The provider creates the client with `{ ...clientOptions, lazyConnect: true }`. This final assignment always wins, including when a caller bypasses the type restriction and casts `lazyConnect: false` into the input.
|
|
106
|
+
|
|
107
|
+
`RedisClientOptions` therefore describes the normalized constructor-facing options consumed by the provider factory: ordinary ioredis options plus an optional Sentinel `name`, but no Fluo-only fields and no caller-controlled `lazyConnect`. It is not a synonym for the `RedisModuleOptions` accepted by `forRoot(...)`.
|
|
108
|
+
|
|
88
109
|
### Named Clients
|
|
89
110
|
|
|
90
111
|
Use `RedisModule.forRoot({ name, ...options })` when one application needs more than one Redis connection. `RedisModule.forRoot(options)` without `name` provides the default `REDIS_CLIENT` and `RedisService` aliases, and named registrations are resolved with `getRedisClientToken(name)` and `getRedisServiceToken(name)`.
|
|
91
112
|
|
|
92
113
|
- Omit `name` when you want the default aliases: `REDIS_CLIENT` / `RedisService`.
|
|
93
114
|
- Pass `name` when you want the named helpers: `getRedisClientToken(name)` / `getRedisServiceToken(name)`.
|
|
115
|
+
- `name` identifies the Fluo registration only. For an ioredis Sentinel master name, pass `sentinelName`; Fluo forwards it to ioredis as its constructor `name` option without changing the registration tokens.
|
|
94
116
|
- Named clients follow the same bootstrap/shutdown contract as the default client; only the default registration exports the `REDIS_CLIENT` / `RedisService` aliases.
|
|
95
117
|
- Names are trimmed, and blank or whitespace-only names are rejected by token/component helpers.
|
|
96
118
|
|
|
@@ -111,6 +133,7 @@ const ANALYTICS_REDIS_CLIENT = getRedisClientToken('analytics');
|
|
|
111
133
|
imports: [
|
|
112
134
|
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
113
135
|
RedisModule.forRoot({ name: 'analytics', host: 'localhost', port: 6380 }),
|
|
136
|
+
RedisModule.forRoot({ name: 'sentinel-cache', sentinelName: 'mymaster', sentinels: [{ host: 'localhost', port: 26379 }] }),
|
|
114
137
|
],
|
|
115
138
|
})
|
|
116
139
|
export class AppModule {}
|
|
@@ -159,14 +182,6 @@ import type Redis from 'ioredis';
|
|
|
159
182
|
const COMMAND_REDIS = getRedisClientToken();
|
|
160
183
|
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
|
|
161
184
|
|
|
162
|
-
@Module({
|
|
163
|
-
imports: [
|
|
164
|
-
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
165
|
-
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
|
|
166
|
-
],
|
|
167
|
-
})
|
|
168
|
-
export class RedisConnectionsModule {}
|
|
169
|
-
|
|
170
185
|
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
|
|
171
186
|
export class PubSubTransportFactory {
|
|
172
187
|
constructor(
|
|
@@ -181,8 +196,19 @@ export class PubSubTransportFactory {
|
|
|
181
196
|
});
|
|
182
197
|
}
|
|
183
198
|
}
|
|
199
|
+
|
|
200
|
+
@Module({
|
|
201
|
+
imports: [
|
|
202
|
+
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
203
|
+
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
|
|
204
|
+
],
|
|
205
|
+
providers: [PubSubTransportFactory],
|
|
206
|
+
})
|
|
207
|
+
export class RedisConnectionsModule {}
|
|
184
208
|
```
|
|
185
209
|
|
|
210
|
+
`@Inject(...)` only declares the constructor tokens of a class; it does not register that class. fluo resolves providers from explicit module registration, so `PubSubTransportFactory` must be listed in the `providers` array of the module that imports the Redis registrations it depends on. A decorated class that no module lists is not resolvable.
|
|
211
|
+
|
|
186
212
|
## Public API Overview
|
|
187
213
|
|
|
188
214
|
### Core
|
|
@@ -200,8 +226,8 @@ export class PubSubTransportFactory {
|
|
|
200
226
|
### Types
|
|
201
227
|
- `DefaultRedisModuleOptions`: Options accepted by the unnamed default Redis registration, including optional global alias visibility and lifecycle timeout controls.
|
|
202
228
|
- `NamedRedisModuleOptions`: Options accepted by additional named Redis registrations, including required `name` and scoped lifecycle timeout controls.
|
|
203
|
-
- `RedisModuleOptions`:
|
|
204
|
-
- `RedisClientOptions`:
|
|
229
|
+
- `RedisModuleOptions`: Caller-facing union accepted by `RedisModule.forRoot(...)`. It combines ordinary ioredis options except `lazyConnect` and `name` with the Fluo-only `name`, `global`, `lifecycle`, and `sentinelName` fields.
|
|
230
|
+
- `RedisClientOptions`: Normalized constructor-facing options after Fluo removes its module-only fields and maps `sentinelName` to the ioredis Sentinel `name`. The provider adds `lazyConnect: true` afterward as the final override.
|
|
205
231
|
- `RedisLifecycleOptions`: Optional timeout controls for Fluo-owned `connect()` and `quit()` lifecycle commands.
|
|
206
232
|
- `PersistencePlatformStatusSnapshot`, `RedisStatusAdapterInput`: Status snapshot input/output types.
|
|
207
233
|
|
package/dist/module.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMhE,OAAO,KAAK,EAA6C,kBAAkB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"module.d.ts","sourceRoot":"","sources":["../src/module.ts"],"names":[],"mappings":"AACA,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAMhE,OAAO,KAAK,EAA6C,kBAAkB,EAAE,MAAM,YAAY,CAAC;AA4KhG,yEAAyE;AACzE,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;CAYxD"}
|
package/dist/module.js
CHANGED
|
@@ -2,8 +2,9 @@ import { defineModule } from '@fluojs/runtime';
|
|
|
2
2
|
import Redis from 'ioredis';
|
|
3
3
|
import { getRedisServiceToken, RedisService } from './redis-service.js';
|
|
4
4
|
import { RedisLifecycleService } from './service.js';
|
|
5
|
-
import { getRedisClientToken, REDIS_CLIENT } from './tokens.js';
|
|
5
|
+
import { DEFAULT_REDIS_CLIENT_NAME, getRedisClientToken, REDIS_CLIENT } from './tokens.js';
|
|
6
6
|
const redisLifecycleTokens = new Map();
|
|
7
|
+
const REDIS_REGISTRATION_IDENTITIES = Symbol('fluo.redis.registration-identities');
|
|
7
8
|
function getRedisLifecycleToken(name) {
|
|
8
9
|
const existing = redisLifecycleTokens.get(name);
|
|
9
10
|
if (existing) {
|
|
@@ -26,6 +27,7 @@ function normalizeRedisModuleOptions(options) {
|
|
|
26
27
|
global,
|
|
27
28
|
lifecycle,
|
|
28
29
|
name,
|
|
30
|
+
sentinelName,
|
|
29
31
|
...clientOptions
|
|
30
32
|
} = options;
|
|
31
33
|
const normalizedName = name?.trim();
|
|
@@ -37,7 +39,12 @@ function normalizeRedisModuleOptions(options) {
|
|
|
37
39
|
throw new Error('Named Redis registrations are scoped and cannot be registered globally.');
|
|
38
40
|
}
|
|
39
41
|
return {
|
|
40
|
-
clientOptions
|
|
42
|
+
clientOptions: {
|
|
43
|
+
...clientOptions,
|
|
44
|
+
...(sentinelName === undefined ? {} : {
|
|
45
|
+
name: sentinelName
|
|
46
|
+
})
|
|
47
|
+
},
|
|
41
48
|
global: normalizedName === undefined ? global ?? true : false,
|
|
42
49
|
lifecycleOptions,
|
|
43
50
|
name: normalizedName
|
|
@@ -59,10 +66,35 @@ function assertValidLifecycleTimeoutMs(fieldName, value) {
|
|
|
59
66
|
throw new Error(`Redis lifecycle.${fieldName} must be a finite non-negative number.`);
|
|
60
67
|
}
|
|
61
68
|
}
|
|
69
|
+
function assertUniqueRedisRegistrationIdentities(identities) {
|
|
70
|
+
const seen = new Set();
|
|
71
|
+
for (const identity of identities) {
|
|
72
|
+
if (seen.has(identity)) {
|
|
73
|
+
throw new Error(`Duplicate @fluojs/redis registration identity "${identity}". Every RedisModule.forRoot(...) registration owns one lifecycle-managed client, so pass a distinct name to each additional registration.`);
|
|
74
|
+
}
|
|
75
|
+
seen.add(identity);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
62
78
|
function createRedisProviders(options, lifecycleOptions, name) {
|
|
63
79
|
const clientToken = getRedisClientToken(name);
|
|
80
|
+
const registrationIdentity = name ?? DEFAULT_REDIS_CLIENT_NAME;
|
|
81
|
+
const registrationGuardToken = Symbol(`fluo.redis.registration-guard:${registrationIdentity}`);
|
|
82
|
+
const registrationProviders = [{
|
|
83
|
+
multi: true,
|
|
84
|
+
provide: REDIS_REGISTRATION_IDENTITIES,
|
|
85
|
+
useValue: registrationIdentity
|
|
86
|
+
}, {
|
|
87
|
+
inject: [REDIS_REGISTRATION_IDENTITIES],
|
|
88
|
+
provide: registrationGuardToken,
|
|
89
|
+
scope: 'singleton',
|
|
90
|
+
useFactory: (...deps) => {
|
|
91
|
+
const [identities] = deps;
|
|
92
|
+
assertUniqueRedisRegistrationIdentities(identities);
|
|
93
|
+
}
|
|
94
|
+
}];
|
|
64
95
|
if (clientToken === REDIS_CLIENT) {
|
|
65
|
-
return [{
|
|
96
|
+
return [...registrationProviders, {
|
|
97
|
+
inject: [registrationGuardToken],
|
|
66
98
|
scope: 'singleton',
|
|
67
99
|
provide: REDIS_CLIENT,
|
|
68
100
|
useFactory: () => new Redis({
|
|
@@ -83,7 +115,8 @@ function createRedisProviders(options, lifecycleOptions, name) {
|
|
|
83
115
|
throw new Error('Redis client name must be defined for named provider creation.');
|
|
84
116
|
}
|
|
85
117
|
const serviceToken = getRedisServiceToken(name);
|
|
86
|
-
return [{
|
|
118
|
+
return [...registrationProviders, {
|
|
119
|
+
inject: [registrationGuardToken],
|
|
87
120
|
scope: 'singleton',
|
|
88
121
|
provide: clientToken,
|
|
89
122
|
useFactory: () => new Redis({
|
package/dist/redis-service.d.ts
CHANGED
|
@@ -38,7 +38,7 @@ export declare class RedisService {
|
|
|
38
38
|
*
|
|
39
39
|
* @param key Redis key to write.
|
|
40
40
|
* @param value Serializable value stored as JSON.
|
|
41
|
-
* @param ttlSeconds Optional TTL in seconds. Omit or pass a non-positive value for a persistent key.
|
|
41
|
+
* @param ttlSeconds Optional TTL in seconds. Finite positive integers use `EX`; finite positive fractions use `PX` rounded up to milliseconds. Omit or pass a non-positive or non-finite value for a persistent key.
|
|
42
42
|
* @returns A promise that resolves after Redis acknowledges the write.
|
|
43
43
|
*/
|
|
44
44
|
set<T>(key: string, value: T, ttlSeconds?: number): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"redis-service.d.ts","sourceRoot":"","sources":["../src/redis-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AA0BjC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,KAAK;IAE1C;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IASrD;;;;;;;OAOG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"redis-service.d.ts","sourceRoot":"","sources":["../src/redis-service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAU,KAAK,KAAK,EAAE,MAAM,cAAc,CAAC;AAClD,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AA0BjC;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,qBACa,YAAY;IACX,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,KAAK;IAE1C;;;;;OAKG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,MAAM,GAAG,IAAI,CAAC;IASrD;;;;;;;OAOG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAevE;;;;;OAKG;IACG,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrC;;;;OAIG;IACH,YAAY,IAAI,KAAK;CAGtB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,KAAK,CAAC,YAAY,CAAC,CAMvE"}
|
package/dist/redis-service.js
CHANGED
|
@@ -75,16 +75,20 @@ class RedisService {
|
|
|
75
75
|
*
|
|
76
76
|
* @param key Redis key to write.
|
|
77
77
|
* @param value Serializable value stored as JSON.
|
|
78
|
-
* @param ttlSeconds Optional TTL in seconds. Omit or pass a non-positive value for a persistent key.
|
|
78
|
+
* @param ttlSeconds Optional TTL in seconds. Finite positive integers use `EX`; finite positive fractions use `PX` rounded up to milliseconds. Omit or pass a non-positive or non-finite value for a persistent key.
|
|
79
79
|
* @returns A promise that resolves after Redis acknowledges the write.
|
|
80
80
|
*/
|
|
81
81
|
async set(key, value, ttlSeconds) {
|
|
82
82
|
const serialized = JSON.stringify(value);
|
|
83
|
-
if (ttlSeconds !== undefined && ttlSeconds > 0) {
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
if (ttlSeconds !== undefined && Number.isFinite(ttlSeconds) && ttlSeconds > 0) {
|
|
84
|
+
if (Number.isInteger(ttlSeconds)) {
|
|
85
|
+
await this.client.set(key, serialized, 'EX', ttlSeconds);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
await this.client.set(key, serialized, 'PX', Math.ceil(ttlSeconds * 1000));
|
|
89
|
+
return;
|
|
87
90
|
}
|
|
91
|
+
await this.client.set(key, serialized);
|
|
88
92
|
}
|
|
89
93
|
|
|
90
94
|
/**
|
package/dist/service.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqDxD;;GAEG;AACH,qBACa,qBAAsB,YAAW,YAAY,EAAE,qBAAqB;IAE7E,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBAFhB,MAAM,EAAE,KAAK,EACb,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,gBAAgB,GAAE,qBAA0B;IAGzD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5C,4BAA4B;IAO5B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,oBAAoB;YAMd,0BAA0B;YAiB1B,6BAA6B;
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../src/service.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AACjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAI3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,YAAY,CAAC;AAqDxD;;GAEG;AACH,qBACa,qBAAsB,YAAW,YAAY,EAAE,qBAAqB;IAE7E,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAC;IAC5B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBAFhB,MAAM,EAAE,KAAK,EACb,UAAU,CAAC,EAAE,MAAM,YAAA,EACnB,gBAAgB,GAAE,qBAA0B;IAGzD,YAAY,IAAI,OAAO,CAAC,IAAI,CAAC;IAQ7B,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB5C,4BAA4B;IAO5B,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,oBAAoB;YAMd,0BAA0B;YAiB1B,6BAA6B;IAsB3C,OAAO,CAAC,cAAc;CAGvB"}
|
package/dist/service.js
CHANGED
|
@@ -8,7 +8,7 @@ import { Inject } from '@fluojs/core';
|
|
|
8
8
|
import { createRedisPlatformStatusSnapshot } from './status.js';
|
|
9
9
|
import { getRedisComponentId, REDIS_CLIENT } from './tokens.js';
|
|
10
10
|
const QUITTABLE_STATUSES = new Set(['connect', 'connecting', 'ready', 'reconnecting']);
|
|
11
|
-
const DISCONNECTABLE_STATUSES = new Set(['close', 'connect', 'connecting', 'ready', 'reconnecting', 'wait']);
|
|
11
|
+
const DISCONNECTABLE_STATUSES = new Set(['close', 'connect', 'connecting', 'monitoring', 'ready', 'reconnecting', 'wait']);
|
|
12
12
|
const DEFAULT_REDIS_LIFECYCLE_TIMEOUT_MS = 10_000;
|
|
13
13
|
function isClosed(status) {
|
|
14
14
|
return status === 'end';
|
|
@@ -102,10 +102,14 @@ class RedisLifecycleService {
|
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
104
|
async connectWithDisconnectFallback() {
|
|
105
|
+
const connectPromise = this.client.connect();
|
|
105
106
|
try {
|
|
106
|
-
await withLifecycleTimeout(
|
|
107
|
+
await withLifecycleTimeout(connectPromise, normalizeTimeoutMs(this.lifecycleOptions.connectTimeoutMs), `Redis client ${this.describeClient()} connect timed out after ${String(normalizeTimeoutMs(this.lifecycleOptions.connectTimeoutMs))}ms.`);
|
|
107
108
|
} catch (error) {
|
|
108
109
|
this.disconnectIfPossible(this.client.status);
|
|
110
|
+
void connectPromise.then(() => {
|
|
111
|
+
this.disconnectIfPossible(this.client.status);
|
|
112
|
+
}, () => undefined);
|
|
109
113
|
throw error;
|
|
110
114
|
}
|
|
111
115
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { RedisOptions } from 'ioredis';
|
|
2
|
-
type RedisConnectionOptions = Omit<RedisOptions, 'lazyConnect'>;
|
|
2
|
+
type RedisConnectionOptions = Omit<RedisOptions, 'lazyConnect' | 'name'>;
|
|
3
3
|
/** Lifecycle timeout controls for Redis connections owned by Fluo. */
|
|
4
4
|
export interface RedisLifecycleOptions {
|
|
5
5
|
/** Maximum finite non-negative time to wait for bootstrap `connect()` before failing startup. Defaults to `10_000`; `0` disables the timeout. */
|
|
@@ -14,6 +14,8 @@ export type DefaultRedisModuleOptions = RedisConnectionOptions & {
|
|
|
14
14
|
/** Timeout controls for lifecycle-owned `connect()` and `quit()` calls. */
|
|
15
15
|
lifecycle?: RedisLifecycleOptions;
|
|
16
16
|
name?: undefined;
|
|
17
|
+
/** ioredis Sentinel master name forwarded as the Redis constructor `name` option. */
|
|
18
|
+
sentinelName?: string;
|
|
17
19
|
};
|
|
18
20
|
/** Options accepted by an additional named Redis registration. */
|
|
19
21
|
export type NamedRedisModuleOptions = RedisConnectionOptions & {
|
|
@@ -21,6 +23,8 @@ export type NamedRedisModuleOptions = RedisConnectionOptions & {
|
|
|
21
23
|
lifecycle?: RedisLifecycleOptions;
|
|
22
24
|
/** Registration name used to derive named raw-client and facade tokens. */
|
|
23
25
|
name: string;
|
|
26
|
+
/** ioredis Sentinel master name forwarded as the Redis constructor `name` option. */
|
|
27
|
+
sentinelName?: string;
|
|
24
28
|
/** Named Redis registrations remain scoped to their importing module. */
|
|
25
29
|
global?: false;
|
|
26
30
|
};
|
|
@@ -32,6 +36,6 @@ export type NamedRedisModuleOptions = RedisConnectionOptions & {
|
|
|
32
36
|
*/
|
|
33
37
|
export type RedisModuleOptions = DefaultRedisModuleOptions | NamedRedisModuleOptions;
|
|
34
38
|
/** Redis constructor options after Fluo module-only fields are removed. */
|
|
35
|
-
export type RedisClientOptions = RedisConnectionOptions
|
|
39
|
+
export type RedisClientOptions = RedisConnectionOptions & Pick<RedisOptions, 'name'>;
|
|
36
40
|
export {};
|
|
37
41
|
//# sourceMappingURL=types.d.ts.map
|
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,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,KAAK,sBAAsB,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAE5C,KAAK,sBAAsB,GAAG,IAAI,CAAC,YAAY,EAAE,aAAa,GAAG,MAAM,CAAC,CAAC;AAEzE,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,iJAAiJ;IACjJ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6JAA6J;IAC7J,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,kEAAkE;AAClE,MAAM,MAAM,yBAAyB,GAAG,sBAAsB,GAAG;IAC/D,wFAAwF;IACxF,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,kEAAkE;AAClE,MAAM,MAAM,uBAAuB,GAAG,sBAAsB,GAAG;IAC7D,2EAA2E;IAC3E,SAAS,CAAC,EAAE,qBAAqB,CAAC;IAClC,2EAA2E;IAC3E,IAAI,EAAE,MAAM,CAAC;IACb,qFAAqF;IACrF,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,MAAM,CAAC,EAAE,KAAK,CAAC;CAChB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,kBAAkB,GAAG,yBAAyB,GAAG,uBAAuB,CAAC;AAErF,2EAA2E;AAC3E,MAAM,MAAM,kBAAkB,GAAG,sBAAsB,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"connection",
|
|
10
10
|
"lifecycle"
|
|
11
11
|
],
|
|
12
|
-
"version": "
|
|
12
|
+
"version": "2.0.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"directory": "packages/redis"
|
|
19
19
|
},
|
|
20
20
|
"engines": {
|
|
21
|
-
"node": ">=
|
|
21
|
+
"node": ">=24.0.0 <27"
|
|
22
22
|
},
|
|
23
23
|
"publishConfig": {
|
|
24
24
|
"access": "public"
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/core": "^
|
|
40
|
-
"@fluojs/di": "^
|
|
41
|
-
"@fluojs/runtime": "^
|
|
39
|
+
"@fluojs/core": "^2.0.0",
|
|
40
|
+
"@fluojs/di": "^3.0.0",
|
|
41
|
+
"@fluojs/runtime": "^3.0.0"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"ioredis": "^5.10.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"ioredis": "^5.10.0",
|
|
48
|
-
"vitest": "^
|
|
48
|
+
"vitest": "^4.1.11"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"prebuild": "node ../../tooling/scripts/clean-dist.mjs",
|