@fluojs/redis 1.0.1 → 1.0.2
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 +37 -0
- package/README.md +37 -0
- package/dist/module.d.ts.map +1 -1
- package/dist/module.js +18 -1
- package/dist/service.d.ts.map +1 -1
- package/dist/service.js +4 -1
- package/dist/types.d.ts +2 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +3 -3
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(...)`는 의도적으로 동기 방식입니다. NestJS의 `forRootAsync(...)` 같은 async dynamic module에서 마이그레이션할 때는 secret, 환경별 host, 외부에서 만든 client를 애플리케이션 경계에서 먼저 해석한 뒤 최종 Redis 옵션을 `forRoot(...)`에 전달하세요. 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';
|
|
@@ -131,6 +133,8 @@ export class AnalyticsStore {
|
|
|
131
133
|
|
|
132
134
|
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
135
|
|
|
136
|
+
`client.duplicate()`를 사용한다면 그 duplicate는 애플리케이션이 소유합니다. 직접 연결하고, subscribe에 사용하며, 자체 shutdown 경로에서 닫아야 합니다. Subscriber 시작/종료 timeout을 fluo가 소유하게 하려면 named registration을 선호하고 `getRedisClientToken(name)`으로 주입하세요.
|
|
137
|
+
|
|
134
138
|
```typescript
|
|
135
139
|
import { Inject } from '@fluojs/core';
|
|
136
140
|
import { REDIS_CLIENT } from '@fluojs/redis';
|
|
@@ -146,6 +150,39 @@ export class AdvancedService {
|
|
|
146
150
|
}
|
|
147
151
|
```
|
|
148
152
|
|
|
153
|
+
```typescript
|
|
154
|
+
import { Inject, Module } from '@fluojs/core';
|
|
155
|
+
import { getRedisClientToken, RedisModule } from '@fluojs/redis';
|
|
156
|
+
import { RedisPubSubMicroserviceTransport } from '@fluojs/microservices';
|
|
157
|
+
import type Redis from 'ioredis';
|
|
158
|
+
|
|
159
|
+
const COMMAND_REDIS = getRedisClientToken();
|
|
160
|
+
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
|
|
161
|
+
|
|
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
|
+
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
|
|
171
|
+
export class PubSubTransportFactory {
|
|
172
|
+
constructor(
|
|
173
|
+
private readonly commandClient: Redis,
|
|
174
|
+
private readonly subscriberClient: Redis,
|
|
175
|
+
) {}
|
|
176
|
+
|
|
177
|
+
createTransport() {
|
|
178
|
+
return new RedisPubSubMicroserviceTransport({
|
|
179
|
+
publishClient: this.commandClient,
|
|
180
|
+
subscribeClient: this.subscriberClient,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
149
186
|
## 공개 API 개요
|
|
150
187
|
|
|
151
188
|
### 핵심 구성 요소
|
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. When migrating from NestJS async dynamic modules such as `forRootAsync(...)`, resolve secrets, environment-specific hosts, or externally constructed clients at the application boundary first, then pass the final Redis options into `forRoot(...)`. 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';
|
|
@@ -131,6 +133,8 @@ If you already injected `RedisService`, call `redis.getRawClient()` to access th
|
|
|
131
133
|
|
|
132
134
|
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
135
|
|
|
136
|
+
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)`.
|
|
137
|
+
|
|
134
138
|
```typescript
|
|
135
139
|
import { Inject } from '@fluojs/core';
|
|
136
140
|
import { REDIS_CLIENT } from '@fluojs/redis';
|
|
@@ -146,6 +150,39 @@ export class AdvancedService {
|
|
|
146
150
|
}
|
|
147
151
|
```
|
|
148
152
|
|
|
153
|
+
```typescript
|
|
154
|
+
import { Inject, Module } from '@fluojs/core';
|
|
155
|
+
import { getRedisClientToken, RedisModule } from '@fluojs/redis';
|
|
156
|
+
import { RedisPubSubMicroserviceTransport } from '@fluojs/microservices';
|
|
157
|
+
import type Redis from 'ioredis';
|
|
158
|
+
|
|
159
|
+
const COMMAND_REDIS = getRedisClientToken();
|
|
160
|
+
const SUBSCRIBER_REDIS = getRedisClientToken('subscriber');
|
|
161
|
+
|
|
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
|
+
@Inject(COMMAND_REDIS, SUBSCRIBER_REDIS)
|
|
171
|
+
export class PubSubTransportFactory {
|
|
172
|
+
constructor(
|
|
173
|
+
private readonly commandClient: Redis,
|
|
174
|
+
private readonly subscriberClient: Redis,
|
|
175
|
+
) {}
|
|
176
|
+
|
|
177
|
+
createTransport() {
|
|
178
|
+
return new RedisPubSubMicroserviceTransport({
|
|
179
|
+
publishClient: this.commandClient,
|
|
180
|
+
subscribeClient: this.subscriberClient,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
```
|
|
185
|
+
|
|
149
186
|
## Public API Overview
|
|
150
187
|
|
|
151
188
|
### Core
|
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;AAoIhG,yEAAyE;AACzE,qBAAa,WAAW;IACtB;;;;;;;;;;;;;;;;;;;;;;OAsBG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,UAAU;CAYxD"}
|
package/dist/module.js
CHANGED
|
@@ -29,6 +29,7 @@ function normalizeRedisModuleOptions(options) {
|
|
|
29
29
|
...clientOptions
|
|
30
30
|
} = options;
|
|
31
31
|
const normalizedName = name?.trim();
|
|
32
|
+
const lifecycleOptions = normalizeRedisLifecycleOptions(lifecycle);
|
|
32
33
|
if (normalizedName !== undefined && normalizedName.length === 0) {
|
|
33
34
|
throw new Error('Redis client name must be a non-empty string when provided.');
|
|
34
35
|
}
|
|
@@ -38,10 +39,26 @@ function normalizeRedisModuleOptions(options) {
|
|
|
38
39
|
return {
|
|
39
40
|
clientOptions,
|
|
40
41
|
global: normalizedName === undefined ? global ?? true : false,
|
|
41
|
-
lifecycleOptions
|
|
42
|
+
lifecycleOptions,
|
|
42
43
|
name: normalizedName
|
|
43
44
|
};
|
|
44
45
|
}
|
|
46
|
+
function normalizeRedisLifecycleOptions(lifecycle) {
|
|
47
|
+
if (lifecycle === undefined) {
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
assertValidLifecycleTimeoutMs('connectTimeoutMs', lifecycle.connectTimeoutMs);
|
|
51
|
+
assertValidLifecycleTimeoutMs('quitTimeoutMs', lifecycle.quitTimeoutMs);
|
|
52
|
+
return lifecycle;
|
|
53
|
+
}
|
|
54
|
+
function assertValidLifecycleTimeoutMs(fieldName, value) {
|
|
55
|
+
if (value === undefined) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
59
|
+
throw new Error(`Redis lifecycle.${fieldName} must be a finite non-negative number.`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
45
62
|
function createRedisProviders(options, lifecycleOptions, name) {
|
|
46
63
|
const clientToken = getRedisClientToken(name);
|
|
47
64
|
if (clientToken === REDIS_CLIENT) {
|
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;IAc3C,OAAO,CAAC,cAAc;CAGvB"}
|
package/dist/service.js
CHANGED
|
@@ -13,6 +13,9 @@ 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,7 +96,7 @@ 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
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { RedisOptions } from 'ioredis';
|
|
|
2
2
|
type RedisConnectionOptions = Omit<RedisOptions, 'lazyConnect'>;
|
|
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. */
|
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;AAEhE,sEAAsE;AACtE,MAAM,WAAW,qBAAqB;IACpC,
|
|
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;AAEhE,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;CAClB,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,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,CAAC"}
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"connection",
|
|
10
10
|
"lifecycle"
|
|
11
11
|
],
|
|
12
|
-
"version": "1.0.
|
|
12
|
+
"version": "1.0.2",
|
|
13
13
|
"private": false,
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"repository": {
|
|
@@ -37,8 +37,8 @@
|
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@fluojs/core": "^1.0.3",
|
|
40
|
-
"@fluojs/di": "^1.0
|
|
41
|
-
"@fluojs/runtime": "^1.1.
|
|
40
|
+
"@fluojs/di": "^1.1.0",
|
|
41
|
+
"@fluojs/runtime": "^1.1.8"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"ioredis": "^5.10.0"
|