@fluojs/redis 1.0.1 → 1.1.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 +43 -4
- package/README.md +43 -4
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +25 -2
- 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 +10 -3
- package/dist/types.d.ts +8 -4
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -4
package/README.ko.md
CHANGED
|
@@ -32,6 +32,8 @@ npm install @fluojs/redis ioredis
|
|
|
32
32
|
|
|
33
33
|
`RedisModule.forRoot(options)`는 기본 Redis 클라이언트와 `RedisService` 파사드를 등록하는 지원되는 root entrypoint입니다.
|
|
34
34
|
|
|
35
|
+
`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
|
+
|
|
35
37
|
```typescript
|
|
36
38
|
import { Module } from '@fluojs/core';
|
|
37
39
|
import { RedisModule } from '@fluojs/redis';
|
|
@@ -51,7 +53,7 @@ export class AppModule {}
|
|
|
51
53
|
|
|
52
54
|
`RedisService`를 주입받아 고수준 작업을 수행하거나, `REDIS_CLIENT`를 통해 원시 `ioredis` 인스턴스를 직접 사용할 수 있습니다.
|
|
53
55
|
|
|
54
|
-
`RedisService.get()`은 JSON parse를 시도하고 실패하면 raw string을 반환합니다. 누락된 key는 `null`을 반환합니다. `RedisService.set()`은 값을 `JSON.stringify()`로
|
|
56
|
+
`RedisService.get()`은 JSON parse를 시도하고 실패하면 raw string을 반환합니다. 누락된 key는 `null`을 반환합니다. `RedisService.set()`은 값을 `JSON.stringify()`로 직렬화하며, 유한한 양의 정수 TTL에는 Redis `EX`를 사용하고 유한한 양의 소수 TTL에는 올림한 밀리초 값으로 `PX`를 사용합니다. TTL을 생략하거나 0 이하 또는 유한하지 않은 값을 전달하면 persistent key를 저장합니다.
|
|
55
57
|
|
|
56
58
|
```typescript
|
|
57
59
|
import { Inject } from '@fluojs/core';
|
|
@@ -75,12 +77,12 @@ export class CacheRepository {
|
|
|
75
77
|
|
|
76
78
|
### 수명 주기 소유권
|
|
77
79
|
|
|
78
|
-
`@fluojs/redis`는 `RedisModule.forRoot({ name, ... })`로 등록한 이름 있는 연결을
|
|
80
|
+
`RedisModule.forRoot(...)` 등록은 각각 새 client를 생성하며, `@fluojs/redis`는 `RedisModule.forRoot({ name, ... })`로 등록한 이름 있는 연결을 포함해 그 client의 lifecycle을 직접 관리합니다. 이 module은 기존 client instance를 채택하지 않습니다.
|
|
79
81
|
|
|
80
82
|
- 호출자가 옵션을 강제로 캐스팅하더라도 Fluo는 항상 `lazyConnect: true`를 강제하므로, 소켓은 import 시점이 아니라 애플리케이션 bootstrap 중에 열립니다.
|
|
81
83
|
- bootstrap 단계에서는 클라이언트가 ioredis `wait` 상태일 때만 lifecycle service가 `connect()`를 호출합니다.
|
|
82
84
|
- lifecycle이 소유한 `connect()`와 `quit()` 호출은 package timeout(기본 `10_000` ms)으로 제한되어, Redis 명령이 멈춰도 bootstrap/shutdown이 무기한 대기하지 않습니다. `lifecycle.connectTimeoutMs`와 `lifecycle.quitTimeoutMs`로 재정의할 수 있으며, host process가 의도적으로 무제한 대기를 소유하는 경우에만 `0`을 전달하세요.
|
|
83
|
-
- shutdown 단계에서는 ready/connecting 계열 상태에 `quit()`를 우선 시도해 정상 종료를 노리고, wait/종료 전이 상태에서는 `disconnect()`를 직접 사용합니다.
|
|
85
|
+
- shutdown 단계에서는 ready/connecting 계열 상태에 `quit()`를 우선 시도해 정상 종료를 노리고, monitoring, wait/종료 전이 상태에서는 `disconnect()`를 직접 사용합니다.
|
|
84
86
|
- `quit()`가 실패하면 Fluo는 `disconnect()`로 fallback하고, 그 뒤에도 클라이언트가 닫히지 않은 경우에만 에러를 다시 던집니다.
|
|
85
87
|
|
|
86
88
|
### 이름 있는 클라이언트
|
|
@@ -89,6 +91,7 @@ export class CacheRepository {
|
|
|
89
91
|
|
|
90
92
|
- `name`을 생략하면 기본 별칭인 `REDIS_CLIENT` / `RedisService`를 사용합니다.
|
|
91
93
|
- `name`을 지정하면 `getRedisClientToken(name)` / `getRedisServiceToken(name)`으로 이름 있는 바인딩을 가져옵니다.
|
|
94
|
+
- `name`은 Fluo 등록만 식별합니다. ioredis Sentinel master name은 `sentinelName`으로 전달하세요. Fluo는 등록 토큰을 바꾸지 않고 이를 ioredis 생성자 `name` 옵션으로 전달합니다.
|
|
92
95
|
- 이름 있는 클라이언트도 기본 클라이언트와 동일한 bootstrap/shutdown 계약을 따르며, `REDIS_CLIENT` / `RedisService` 별칭은 기본 등록에서만 export됩니다.
|
|
93
96
|
- 이름은 trim되며, blank 또는 whitespace-only name은 token/component helper에서 거부됩니다.
|
|
94
97
|
|
|
@@ -109,6 +112,7 @@ const ANALYTICS_REDIS_CLIENT = getRedisClientToken('analytics');
|
|
|
109
112
|
imports: [
|
|
110
113
|
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
111
114
|
RedisModule.forRoot({ name: 'analytics', host: 'localhost', port: 6380 }),
|
|
115
|
+
RedisModule.forRoot({ name: 'sentinel-cache', sentinelName: 'mymaster', sentinels: [{ host: 'localhost', port: 26379 }] }),
|
|
112
116
|
],
|
|
113
117
|
})
|
|
114
118
|
export class AppModule {}
|
|
@@ -131,6 +135,8 @@ export class AnalyticsStore {
|
|
|
131
135
|
|
|
132
136
|
Redis Pub/Sub은 일반적인 shared-client 재사용의 예외입니다. Redis는 구독한 연결을 subscribe mode로 전환하므로, lifecycle-managed `REDIS_CLIENT`나 `RedisService.getRawClient()` 결과를 publisher와 subscriber로 동시에 사용하지 마세요. `client.duplicate()`로 전용 subscriber 연결을 만들거나 명시적인 `RedisModule.forRoot({ name: 'subscriber', ... })` 등록을 사용하고, 그 연결도 별도 lifecycle owner를 갖게 하세요.
|
|
133
137
|
|
|
138
|
+
`client.duplicate()`를 사용한다면 그 duplicate는 애플리케이션이 소유합니다. 직접 연결하고, subscribe에 사용하며, 자체 shutdown 경로에서 닫아야 합니다. Subscriber 시작/종료 timeout을 fluo가 소유하게 하려면 named registration을 선호하고 `getRedisClientToken(name)`으로 주입하세요.
|
|
139
|
+
|
|
134
140
|
```typescript
|
|
135
141
|
import { Inject } from '@fluojs/core';
|
|
136
142
|
import { REDIS_CLIENT } from '@fluojs/redis';
|
|
@@ -146,6 +152,39 @@ export class AdvancedService {
|
|
|
146
152
|
}
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
```typescript
|
|
156
|
+
import { Inject, Module } from '@fluojs/core';
|
|
157
|
+
import { getRedisClientToken, RedisModule } from '@fluojs/redis';
|
|
158
|
+
import { RedisPubSubMicroserviceTransport } from '@fluojs/microservices';
|
|
159
|
+
import type Redis from 'ioredis';
|
|
160
|
+
|
|
161
|
+
const COMMAND_REDIS = getRedisClientToken();
|
|
162
|
+
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
|
|
163
|
+
|
|
164
|
+
@Module({
|
|
165
|
+
imports: [
|
|
166
|
+
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
167
|
+
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
|
|
168
|
+
],
|
|
169
|
+
})
|
|
170
|
+
export class RedisConnectionsModule {}
|
|
171
|
+
|
|
172
|
+
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
|
|
173
|
+
export class PubSubTransportFactory {
|
|
174
|
+
constructor(
|
|
175
|
+
private readonly commandClient: Redis,
|
|
176
|
+
private readonly subscriberClient: Redis,
|
|
177
|
+
) {}
|
|
178
|
+
|
|
179
|
+
createTransport() {
|
|
180
|
+
return new RedisPubSubMicroserviceTransport({
|
|
181
|
+
publishClient: this.commandClient,
|
|
182
|
+
subscribeClient: this.subscriberClient,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
149
188
|
## 공개 API 개요
|
|
150
189
|
|
|
151
190
|
### 핵심 구성 요소
|
|
@@ -163,7 +202,7 @@ export class AdvancedService {
|
|
|
163
202
|
### 타입
|
|
164
203
|
- `DefaultRedisModuleOptions`: 이름 없는 기본 Redis 등록이 받는 옵션입니다. 선택적 global alias visibility와 lifecycle timeout control을 포함합니다.
|
|
165
204
|
- `NamedRedisModuleOptions`: 추가 이름 있는 Redis 등록이 받는 옵션입니다. 필수 `name`과 scoped lifecycle timeout control을 포함합니다.
|
|
166
|
-
- `RedisModuleOptions`: Fluo가 module-only `name`, `global`, `lifecycle` 필드를 제거한 뒤 `ioredis` 생성자에 전달하는 설정 옵션입니다.
|
|
205
|
+
- `RedisModuleOptions`: Fluo가 module-only `name`, `global`, `lifecycle`, `sentinelName` 필드를 제거한 뒤 `ioredis` 생성자에 전달하는 설정 옵션입니다. `sentinelName`은 ioredis Sentinel master `name`으로 전달되고, `name`은 Fluo 등록 식별자로 유지됩니다.
|
|
167
206
|
- `RedisClientOptions`: Fluo가 module-only field를 제거하고 내부에서 `lazyConnect: true`를 강제하기 전의 Redis constructor option입니다.
|
|
168
207
|
- `RedisLifecycleOptions`: Fluo가 소유한 `connect()`와 `quit()` lifecycle command의 timeout을 조정하는 선택적 옵션입니다.
|
|
169
208
|
- `PersistencePlatformStatusSnapshot`, `RedisStatusAdapterInput`: status snapshot input/output type입니다.
|
package/README.md
CHANGED
|
@@ -32,6 +32,8 @@ npm install @fluojs/redis ioredis
|
|
|
32
32
|
|
|
33
33
|
Use `RedisModule.forRoot(options)` to register the default Redis client and `RedisService` facade.
|
|
34
34
|
|
|
35
|
+
`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
|
+
|
|
35
37
|
```typescript
|
|
36
38
|
import { Module } from '@fluojs/core';
|
|
37
39
|
import { RedisModule } from '@fluojs/redis';
|
|
@@ -51,7 +53,7 @@ export class AppModule {}
|
|
|
51
53
|
|
|
52
54
|
Inject `RedisService` for high-level operations or `REDIS_CLIENT` for the raw `ioredis` instance.
|
|
53
55
|
|
|
54
|
-
`RedisService.get()` attempts JSON parsing and falls back to the raw string; missing keys return `null`. `RedisService.set()` serializes values with `JSON.stringify()
|
|
56
|
+
`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.
|
|
55
57
|
|
|
56
58
|
```typescript
|
|
57
59
|
import { Inject } from '@fluojs/core';
|
|
@@ -75,12 +77,12 @@ export class CacheRepository {
|
|
|
75
77
|
|
|
76
78
|
### Lifecycle Ownership
|
|
77
79
|
|
|
78
|
-
|
|
80
|
+
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.
|
|
79
81
|
|
|
80
82
|
- Fluo always forces `lazyConnect: true`, even if callers cast options manually, so sockets open during application bootstrap instead of import time.
|
|
81
83
|
- During bootstrap, the lifecycle service only calls `connect()` while the client is still in ioredis `wait` state.
|
|
82
84
|
- 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.
|
|
83
|
-
- During shutdown, ready/connecting clients attempt `quit()` first for graceful teardown, while wait
|
|
85
|
+
- During shutdown, ready/connecting clients attempt `quit()` first for graceful teardown, while monitoring, wait, and closed-transition states use `disconnect()` directly.
|
|
84
86
|
- If `quit()` fails, Fluo falls back to `disconnect()` and only rethrows when the client still remains open afterward.
|
|
85
87
|
|
|
86
88
|
### Named Clients
|
|
@@ -89,6 +91,7 @@ Use `RedisModule.forRoot({ name, ...options })` when one application needs more
|
|
|
89
91
|
|
|
90
92
|
- Omit `name` when you want the default aliases: `REDIS_CLIENT` / `RedisService`.
|
|
91
93
|
- Pass `name` when you want the named helpers: `getRedisClientToken(name)` / `getRedisServiceToken(name)`.
|
|
94
|
+
- `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.
|
|
92
95
|
- Named clients follow the same bootstrap/shutdown contract as the default client; only the default registration exports the `REDIS_CLIENT` / `RedisService` aliases.
|
|
93
96
|
- Names are trimmed, and blank or whitespace-only names are rejected by token/component helpers.
|
|
94
97
|
|
|
@@ -109,6 +112,7 @@ const ANALYTICS_REDIS_CLIENT = getRedisClientToken('analytics');
|
|
|
109
112
|
imports: [
|
|
110
113
|
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
111
114
|
RedisModule.forRoot({ name: 'analytics', host: 'localhost', port: 6380 }),
|
|
115
|
+
RedisModule.forRoot({ name: 'sentinel-cache', sentinelName: 'mymaster', sentinels: [{ host: 'localhost', port: 26379 }] }),
|
|
112
116
|
],
|
|
113
117
|
})
|
|
114
118
|
export class AppModule {}
|
|
@@ -131,6 +135,8 @@ If you already injected `RedisService`, call `redis.getRawClient()` to access th
|
|
|
131
135
|
|
|
132
136
|
Redis Pub/Sub is the exception to ordinary shared-client reuse: Redis switches subscribed connections into subscribe mode, so do not use the lifecycle-managed `REDIS_CLIENT` or a `RedisService.getRawClient()` result as both publisher and subscriber. Create a dedicated subscriber connection with `client.duplicate()` (or an explicitly named `RedisModule.forRoot({ name: 'subscriber', ... })` registration) and let that connection have its own lifecycle owner.
|
|
133
137
|
|
|
138
|
+
If you use `client.duplicate()`, the duplicate is application-owned: connect it, subscribe with it, and close it from your own shutdown path. If you want fluo to own subscriber startup/shutdown timeouts, prefer a named registration and inject it with `getRedisClientToken(name)`.
|
|
139
|
+
|
|
134
140
|
```typescript
|
|
135
141
|
import { Inject } from '@fluojs/core';
|
|
136
142
|
import { REDIS_CLIENT } from '@fluojs/redis';
|
|
@@ -146,6 +152,39 @@ export class AdvancedService {
|
|
|
146
152
|
}
|
|
147
153
|
```
|
|
148
154
|
|
|
155
|
+
```typescript
|
|
156
|
+
import { Inject, Module } from '@fluojs/core';
|
|
157
|
+
import { getRedisClientToken, RedisModule } from '@fluojs/redis';
|
|
158
|
+
import { RedisPubSubMicroserviceTransport } from '@fluojs/microservices';
|
|
159
|
+
import type Redis from 'ioredis';
|
|
160
|
+
|
|
161
|
+
const COMMAND_REDIS = getRedisClientToken();
|
|
162
|
+
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
|
|
163
|
+
|
|
164
|
+
@Module({
|
|
165
|
+
imports: [
|
|
166
|
+
RedisModule.forRoot({ host: 'localhost', port: 6379 }),
|
|
167
|
+
RedisModule.forRoot({ name: 'subscriber', host: 'localhost', port: 6379 }),
|
|
168
|
+
],
|
|
169
|
+
})
|
|
170
|
+
export class RedisConnectionsModule {}
|
|
171
|
+
|
|
172
|
+
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
|
|
173
|
+
export class PubSubTransportFactory {
|
|
174
|
+
constructor(
|
|
175
|
+
private readonly commandClient: Redis,
|
|
176
|
+
private readonly subscriberClient: Redis,
|
|
177
|
+
) {}
|
|
178
|
+
|
|
179
|
+
createTransport() {
|
|
180
|
+
return new RedisPubSubMicroserviceTransport({
|
|
181
|
+
publishClient: this.commandClient,
|
|
182
|
+
subscribeClient: this.subscriberClient,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
149
188
|
## Public API Overview
|
|
150
189
|
|
|
151
190
|
### Core
|
|
@@ -163,7 +202,7 @@ export class AdvancedService {
|
|
|
163
202
|
### Types
|
|
164
203
|
- `DefaultRedisModuleOptions`: Options accepted by the unnamed default Redis registration, including optional global alias visibility and lifecycle timeout controls.
|
|
165
204
|
- `NamedRedisModuleOptions`: Options accepted by additional named Redis registrations, including required `name` and scoped lifecycle timeout controls.
|
|
166
|
-
- `RedisModuleOptions`: Configuration options passed to the `ioredis` constructor after Fluo removes module-only `name`, `global`, and `
|
|
205
|
+
- `RedisModuleOptions`: Configuration options passed to the `ioredis` constructor after Fluo removes module-only `name`, `global`, `lifecycle`, and `sentinelName` fields. `sentinelName` is forwarded as the ioredis Sentinel master `name`, while `name` remains the Fluo registration identifier.
|
|
167
206
|
- `RedisClientOptions`: Redis constructor options after Fluo removes module-only fields and before it forces `lazyConnect: true` internally.
|
|
168
207
|
- `RedisLifecycleOptions`: Optional timeout controls for Fluo-owned `connect()` and `quit()` lifecycle commands.
|
|
169
208
|
- `PersistencePlatformStatusSnapshot`, `RedisStatusAdapterInput`: Status snapshot input/output types.
|
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;AAuIhG,yEAAyE;AACzE,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;CAYxD"}
|
package/dist/module.js
CHANGED
|
@@ -26,9 +26,11 @@ function normalizeRedisModuleOptions(options) {
|
|
|
26
26
|
global,
|
|
27
27
|
lifecycle,
|
|
28
28
|
name,
|
|
29
|
+
sentinelName,
|
|
29
30
|
...clientOptions
|
|
30
31
|
} = options;
|
|
31
32
|
const normalizedName = name?.trim();
|
|
33
|
+
const lifecycleOptions = normalizeRedisLifecycleOptions(lifecycle);
|
|
32
34
|
if (normalizedName !== undefined && normalizedName.length === 0) {
|
|
33
35
|
throw new Error('Redis client name must be a non-empty string when provided.');
|
|
34
36
|
}
|
|
@@ -36,12 +38,33 @@ function normalizeRedisModuleOptions(options) {
|
|
|
36
38
|
throw new Error('Named Redis registrations are scoped and cannot be registered globally.');
|
|
37
39
|
}
|
|
38
40
|
return {
|
|
39
|
-
clientOptions
|
|
41
|
+
clientOptions: {
|
|
42
|
+
...clientOptions,
|
|
43
|
+
...(sentinelName === undefined ? {} : {
|
|
44
|
+
name: sentinelName
|
|
45
|
+
})
|
|
46
|
+
},
|
|
40
47
|
global: normalizedName === undefined ? global ?? true : false,
|
|
41
|
-
lifecycleOptions
|
|
48
|
+
lifecycleOptions,
|
|
42
49
|
name: normalizedName
|
|
43
50
|
};
|
|
44
51
|
}
|
|
52
|
+
function normalizeRedisLifecycleOptions(lifecycle) {
|
|
53
|
+
if (lifecycle === undefined) {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
assertValidLifecycleTimeoutMs('connectTimeoutMs', lifecycle.connectTimeoutMs);
|
|
57
|
+
assertValidLifecycleTimeoutMs('quitTimeoutMs', lifecycle.quitTimeoutMs);
|
|
58
|
+
return lifecycle;
|
|
59
|
+
}
|
|
60
|
+
function assertValidLifecycleTimeoutMs(fieldName, value) {
|
|
61
|
+
if (value === undefined) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
65
|
+
throw new Error(`Redis lifecycle.${fieldName} must be a finite non-negative number.`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
45
68
|
function createRedisProviders(options, lifecycleOptions, name) {
|
|
46
69
|
const clientToken = getRedisClientToken(name);
|
|
47
70
|
if (clientToken === REDIS_CLIENT) {
|
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;
|
|
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,11 +8,14 @@ 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';
|
|
15
15
|
}
|
|
16
|
+
function isOpen(status) {
|
|
17
|
+
return QUITTABLE_STATUSES.has(status);
|
|
18
|
+
}
|
|
16
19
|
function isConnectable(status) {
|
|
17
20
|
return status === 'wait';
|
|
18
21
|
}
|
|
@@ -93,16 +96,20 @@ class RedisLifecycleService {
|
|
|
93
96
|
return;
|
|
94
97
|
} catch (error) {
|
|
95
98
|
this.disconnectIfPossible(this.client.status);
|
|
96
|
-
if (
|
|
99
|
+
if (isOpen(this.client.status)) {
|
|
97
100
|
throw error;
|
|
98
101
|
}
|
|
99
102
|
}
|
|
100
103
|
}
|
|
101
104
|
async connectWithDisconnectFallback() {
|
|
105
|
+
const connectPromise = this.client.connect();
|
|
102
106
|
try {
|
|
103
|
-
await withLifecycleTimeout(
|
|
107
|
+
await withLifecycleTimeout(connectPromise, normalizeTimeoutMs(this.lifecycleOptions.connectTimeoutMs), `Redis client ${this.describeClient()} connect timed out after ${String(normalizeTimeoutMs(this.lifecycleOptions.connectTimeoutMs))}ms.`);
|
|
104
108
|
} catch (error) {
|
|
105
109
|
this.disconnectIfPossible(this.client.status);
|
|
110
|
+
void connectPromise.then(() => {
|
|
111
|
+
this.disconnectIfPossible(this.client.status);
|
|
112
|
+
}, () => undefined);
|
|
106
113
|
throw error;
|
|
107
114
|
}
|
|
108
115
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
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
|
-
/** Maximum time to wait for bootstrap `connect()` before failing startup. Defaults to `10_000
|
|
5
|
+
/** Maximum finite non-negative time to wait for bootstrap `connect()` before failing startup. Defaults to `10_000`; `0` disables the timeout. */
|
|
6
6
|
connectTimeoutMs?: number;
|
|
7
|
-
/** Maximum time to wait for graceful shutdown `quit()` before forcing `disconnect()`. Defaults to `10_000
|
|
7
|
+
/** Maximum finite non-negative time to wait for graceful shutdown `quit()` before forcing `disconnect()`. Defaults to `10_000`; `0` disables the timeout. */
|
|
8
8
|
quitTimeoutMs?: number;
|
|
9
9
|
}
|
|
10
10
|
/** Options accepted by the default unnamed Redis registration. */
|
|
@@ -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": "1.0
|
|
12
|
+
"version": "1.1.0",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -36,9 +36,9 @@
|
|
|
36
36
|
"dist"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@fluojs/
|
|
40
|
-
"@fluojs/
|
|
41
|
-
"@fluojs/runtime": "^
|
|
39
|
+
"@fluojs/di": "^2.0.0",
|
|
40
|
+
"@fluojs/core": "^1.1.0",
|
|
41
|
+
"@fluojs/runtime": "^2.0.1"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"ioredis": "^5.10.0"
|