@fluojs/throttler 1.0.0-beta.3 → 1.0.0-beta.5

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 CHANGED
@@ -69,13 +69,14 @@ class AuthController {
69
69
 
70
70
  ### Redis 저장소 사용
71
71
 
72
- 다중 인스턴스 배포 환경에서는 `RedisThrottlerStore`를 사용하여 모든 인스턴스 간에 속도 제한 상태를 공유하세요.
72
+ 다중 인스턴스 배포 환경에서는 `RedisThrottlerStore`를 사용하여 모든 인스턴스 간에 속도 제한 상태를 공유하세요. Redis 기반 윈도우는 Redis 서버 시간을 기준으로 고정되므로, 애플리케이션 노드 간 시계 오차가 있어도 하나의 공통 reset 경계를 강제합니다.
73
73
 
74
74
  ```typescript
75
75
  import { ThrottlerModule, RedisThrottlerStore } from '@fluojs/throttler';
76
76
  import { REDIS_CLIENT } from '@fluojs/redis';
77
77
 
78
78
  // 프로바이더 또는 모듈 팩토리 내부에서
79
+ const redisClient = await container.resolve(REDIS_CLIENT);
79
80
  const redisStore = new RedisThrottlerStore(redisClient);
80
81
 
81
82
  ThrottlerModule.forRoot({
@@ -85,10 +86,14 @@ ThrottlerModule.forRoot({
85
86
  });
86
87
  ```
87
88
 
89
+ `ThrottlerStore` 계약을 구현한 객체도 `store` 옵션으로 직접 전달할 수 있습니다.
90
+
88
91
  ### 커스텀 키 생성
89
92
 
90
93
  기본적으로 throttler는 raw socket `remoteAddress`만으로 클라이언트 식별자를 해석합니다. 배포가 `Forwarded`, `X-Forwarded-For`, `X-Real-IP`를 덮어쓰는 신뢰 가능한 리버스 프록시 뒤에 있다면 `trustProxyHeaders: true`로 명시적으로 opt-in 하세요. 신뢰 가능한 소켓 식별자나 프록시 식별자가 없으면 서로 다른 호출자를 같은 버킷으로 합치지 않도록 예외를 던집니다. API 키나 사용자 ID 등 다른 식별자를 사용하도록 커스터마이징할 수도 있습니다.
91
94
 
95
+ 카운터는 route identity와 client identity로 구분됩니다. route 부분에는 method, path, version, handler identity가 포함되므로 서로 다른 핸들러가 실수로 같은 버킷을 공유하지 않습니다. 요청이 거부되면 `ThrottlerGuard`는 `429`를 반환하고 `Retry-After`를 설정합니다.
96
+
92
97
  ```typescript
93
98
  ThrottlerModule.forRoot({
94
99
  ttl: 60,
@@ -120,6 +125,8 @@ ThrottlerModule.forRoot({
120
125
  - `ThrottlerModule.forRoot(options)`: throttler 옵션, 저장소, `ThrottlerGuard`를 모듈 그래프에 제공합니다.
121
126
  - 패키지 수준 등록은 `ThrottlerModule.forRoot(options)`를 통해 지원합니다. 내부 프로바이더 조합 헬퍼는 공개 계약에 포함되지 않습니다.
122
127
 
128
+ `ttl`과 `limit`은 양의 finite integer여야 합니다. `store`, `trustProxyHeaders`, `keyGenerator`로 persistence와 client identity를 조정할 수 있습니다.
129
+
123
130
  ### 데코레이터
124
131
  - `@Throttle({ ttl, limit })`: 클래스나 메서드에 특정 속도 제한을 설정합니다.
125
132
  - `@SkipThrottle()`: 클래스나 메서드에 대해 속도 제한을 비활성화합니다.
@@ -130,6 +137,13 @@ ThrottlerModule.forRoot({
130
137
  ### 저장소(Store)
131
138
  - `createMemoryThrottlerStore()`: 간단한 메모리 내 저장소를 생성합니다 (기본값).
132
139
  - `RedisThrottlerStore`: Redis용 저장소 어댑터입니다.
140
+ - `ThrottlerStore`: custom store를 위한 공개 계약입니다.
141
+
142
+ ### status와 diagnostics
143
+ - `createThrottlerPlatformStatusSnapshot(...)`: 플랫폼 status snapshot을 생성합니다.
144
+ - `createThrottlerPlatformDiagnosticIssues(...)`: 잘못된 throttler 상태에 대한 diagnostic issue를 생성합니다.
145
+
146
+ 메서드 수준 `@Throttle(...)`은 클래스 수준 설정보다 우선하고, 클래스 수준 설정은 모듈 기본값보다 우선합니다. `@SkipThrottle()`은 클래스나 메서드 수준 모두에서 throttling을 우회합니다.
133
147
 
134
148
  ## 관련 패키지
135
149
 
@@ -140,3 +154,5 @@ ThrottlerModule.forRoot({
140
154
 
141
155
  - `packages/throttler/src/module.test.ts`: 모듈 설정 및 데코레이터 오버라이드 테스트.
142
156
  - `packages/throttler/src/guard.ts`: 요청 제한 및 헤더 관리 코어 로직.
157
+ - `packages/throttler/src/redis-store.test.ts`: Redis store 계약과 server-time 동작.
158
+ - `packages/throttler/src/status.test.ts`: status 및 diagnostic helper 동작.
package/README.md CHANGED
@@ -69,13 +69,14 @@ class AuthController {
69
69
 
70
70
  ### Redis Storage
71
71
 
72
- For multi-instance deployments, use `RedisThrottlerStore` to share the rate limit state across all instances.
72
+ For multi-instance deployments, use `RedisThrottlerStore` to share the rate limit state across all instances. Redis-backed windows are anchored to Redis server time, so distributed app nodes with clock skew still enforce one shared reset boundary.
73
73
 
74
74
  ```typescript
75
75
  import { ThrottlerModule, RedisThrottlerStore } from '@fluojs/throttler';
76
76
  import { REDIS_CLIENT } from '@fluojs/redis';
77
77
 
78
78
  // Inside a provider or module factory
79
+ const redisClient = await container.resolve(REDIS_CLIENT);
79
80
  const redisStore = new RedisThrottlerStore(redisClient);
80
81
 
81
82
  ThrottlerModule.forRoot({
@@ -85,10 +86,14 @@ ThrottlerModule.forRoot({
85
86
  });
86
87
  ```
87
88
 
89
+ You can also pass any object that implements the `ThrottlerStore` contract through the `store` option.
90
+
88
91
  ### Custom Key Generation
89
92
 
90
93
  By default, the throttler resolves client identity from the raw socket `remoteAddress` only. If your deployment sits behind a trusted reverse proxy that rewrites `Forwarded`, `X-Forwarded-For`, or `X-Real-IP`, opt in with `trustProxyHeaders: true`. If no trusted socket or proxy identity is available, it throws instead of collapsing unrelated callers into a shared bucket. You can also customize this to use API keys, user IDs, or other identifiers.
91
94
 
95
+ Counters are scoped by route identity and client identity. The route portion includes method, path, version, and handler identity so different handlers do not share buckets accidentally. When a request is rejected, `ThrottlerGuard` returns `429` and sets `Retry-After`.
96
+
92
97
  ```typescript
93
98
  ThrottlerModule.forRoot({
94
99
  ttl: 60,
@@ -120,6 +125,8 @@ ThrottlerModule.forRoot({
120
125
  - `ThrottlerModule.forRoot(options)`: Provides throttler options, storage, and `ThrottlerGuard` to the module graph.
121
126
  - Package-level registration is supported through `ThrottlerModule.forRoot(options)`. Internal provider-composition helpers are not part of the public contract.
122
127
 
128
+ `ttl` and `limit` must be positive finite integers. `store`, `trustProxyHeaders`, and `keyGenerator` customize persistence and client identity.
129
+
123
130
  ### Decorators
124
131
  - `@Throttle({ ttl, limit })`: Sets a specific rate limit for a class or method.
125
132
  - `@SkipThrottle()`: Disables throttling for a class or method.
@@ -130,6 +137,13 @@ ThrottlerModule.forRoot({
130
137
  ### Stores
131
138
  - `createMemoryThrottlerStore()`: Creates a simple in-memory store (default).
132
139
  - `RedisThrottlerStore`: Store adapter for Redis.
140
+ - `ThrottlerStore`: Public contract for custom stores.
141
+
142
+ ### Status and diagnostics
143
+ - `createThrottlerPlatformStatusSnapshot(...)`: Creates a platform status snapshot.
144
+ - `createThrottlerPlatformDiagnosticIssues(...)`: Creates diagnostic issues for invalid throttler state.
145
+
146
+ Method-level `@Throttle(...)` overrides class-level settings, class-level settings override module defaults, and `@SkipThrottle()` bypasses throttling at either class or method level.
133
147
 
134
148
  ## Related Packages
135
149
 
@@ -140,3 +154,5 @@ ThrottlerModule.forRoot({
140
154
 
141
155
  - `packages/throttler/src/module.test.ts`: Tests for module configuration and decorator overrides.
142
156
  - `packages/throttler/src/guard.ts`: The core logic for request throttling and header management.
157
+ - `packages/throttler/src/redis-store.test.ts`: Redis store contract and server-time behavior.
158
+ - `packages/throttler/src/status.test.ts`: Status and diagnostic helper behavior.
@@ -1 +1 @@
1
- {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAG1D,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,eAAoC,CAAC;AAM1E,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;AACjG,KAAK,wBAAwB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAC1F,KAAK,0BAA0B,GAAG,wBAAwB,GAAG,yBAAyB,CAAC;AAgCvF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,uBAAuB,GAAG,0BAA0B,CAUrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,0BAA0B,CAUzD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,GAAG,uBAAuB,GAAG,SAAS,CAGjG;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,mBAAmB,GAAG,uBAAuB,GAAG,SAAS,CAGtG;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAE9E"}
1
+ {"version":3,"file":"decorators.d.ts","sourceRoot":"","sources":["../src/decorators.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,YAAY,CAAC;AAG1D,0FAA0F;AAC1F,eAAO,MAAM,wBAAwB,eAAoC,CAAC;AAM1E,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;AACjG,KAAK,wBAAwB,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAC1F,KAAK,0BAA0B,GAAG,wBAAwB,GAAG,yBAAyB,CAAC;AAkCvF;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,uBAAuB,GAAG,0BAA0B,CAUrF;AAED;;;;GAIG;AACH,wBAAgB,YAAY,IAAI,0BAA0B,CAUzD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,mBAAmB,GAAG,uBAAuB,GAAG,SAAS,CAGjG;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAEzE;AAED;;;;;GAKG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,mBAAmB,GAAG,uBAAuB,GAAG,SAAS,CAGtG;AAED;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,GAAG,EAAE,mBAAmB,GAAG,OAAO,CAE9E"}
@@ -1,3 +1,4 @@
1
+ import { ensureMetadataSymbol } from '@fluojs/core/internal';
1
2
  import { validateThrottleOptions } from './validation.js';
2
3
 
3
4
  /** Shared controller metadata key used to store per-route throttling metadata records. */
@@ -6,6 +7,7 @@ const throttleKey = Symbol.for('fluo.throttler.throttle');
6
7
  const skipThrottleKey = Symbol.for('fluo.throttler.skip');
7
8
  const classThrottleKey = Symbol.for('fluo.throttler.class-throttle');
8
9
  const classSkipThrottleKey = Symbol.for('fluo.throttler.class-skip');
10
+ ensureMetadataSymbol();
9
11
  function getMetadataBag(metadata) {
10
12
  return metadata;
11
13
  }
@@ -1 +1 @@
1
- {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,KAAK,KAAK,EAAE,KAAK,YAAY,EAA0B,MAAM,cAAc,CAAC;AAY/G,OAAO,KAAK,EAAE,sBAAsB,EAAkB,MAAM,YAAY,CAAC;AA2CzE;;GAEG;AACH,qBACa,cAAe,YAAW,KAAK;IAG9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;gBAEV,OAAO,EAAE,sBAAsB;IAK5D;;;;;;OAMG;IACG,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;CAiD3D"}
1
+ {"version":3,"file":"guard.d.ts","sourceRoot":"","sources":["../src/guard.ts"],"names":[],"mappings":"AAEA,OAAO,EAA4B,KAAK,KAAK,EAAE,KAAK,YAAY,EAA0B,MAAM,cAAc,CAAC;AAa/G,OAAO,KAAK,EAAE,sBAAsB,EAAuC,MAAM,YAAY,CAAC;AAuD9F;;GAEG;AACH,qBACa,cAAe,YAAW,KAAK;IAG9B,OAAO,CAAC,QAAQ,CAAC,OAAO;IAFpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAiB;gBAEV,OAAO,EAAE,sBAAsB;IAK5D;;;;;;OAMG;IACG,WAAW,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;CAkD3D"}
package/dist/guard.js CHANGED
@@ -9,6 +9,7 @@ import { getStandardMetadataBag } from '@fluojs/core/internal';
9
9
  import { TooManyRequestsException } from '@fluojs/http';
10
10
  import { resolveClientIdentity } from '@fluojs/http/internal';
11
11
  import { getClassSkipThrottleMetadata, getClassThrottleMetadata, getSkipThrottleMetadata, getThrottleMetadata, throttleRouteMetadataKey } from './decorators.js';
12
+ import { throttlerRetryAfterMsSymbol } from './store-internals.js';
12
13
  import { createMemoryThrottlerStore } from './store.js';
13
14
  import { THROTTLER_OPTIONS } from './tokens.js';
14
15
  import { validateThrottleOptions, validateThrottlerModuleOptions, validateThrottlerStoreEntry } from './validation.js';
@@ -37,6 +38,13 @@ function buildHandlerKey(handler) {
37
38
  const version = handler.route.version ?? handler.metadata.effectiveVersion ?? 'unversioned';
38
39
  return [`method:${handler.route.method}`, `path:${encodeURIComponent(handler.route.path)}`, `version:${encodeURIComponent(version)}`, `handler:${encodeURIComponent(handler.methodName)}`].join('|');
39
40
  }
41
+ function resolveRetryAfterSeconds(entry, now) {
42
+ const retryAfterMs = entry[throttlerRetryAfterMsSymbol];
43
+ if (typeof retryAfterMs === 'number' && Number.isFinite(retryAfterMs)) {
44
+ return Math.max(1, Math.ceil(retryAfterMs / 1000));
45
+ }
46
+ return Math.max(1, Math.ceil((entry.resetAt - now) / 1000));
47
+ }
40
48
 
41
49
  /**
42
50
  * Guard that enforces module-, class-, and method-level throttling policies.
@@ -89,12 +97,13 @@ class ThrottlerGuard {
89
97
  const handlerKey = buildHandlerKey(handler);
90
98
  const storeKey = buildStoreKey(handlerKey, clientKey);
91
99
  const now = Date.now();
92
- const entry = validateThrottlerStoreEntry(await this.store.consume(storeKey, {
100
+ const rawEntry = await this.store.consume(storeKey, {
93
101
  now,
94
102
  ttlSeconds
95
- }));
103
+ });
104
+ const entry = validateThrottlerStoreEntry(rawEntry);
96
105
  if (entry.count > limit) {
97
- const retryAfter = Math.ceil((entry.resetAt - now) / 1000);
106
+ const retryAfter = resolveRetryAfterSeconds(rawEntry, now);
98
107
  requestContext.response.setHeader('Retry-After', String(retryAfter));
99
108
  throw new TooManyRequestsException('Too Many Requests', {
100
109
  meta: {
@@ -1 +1 @@
1
- {"version":3,"file":"redis-store.d.ts","sourceRoot":"","sources":["../src/redis-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAEjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA4C7F;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,KAAK;IAE1C;;;;;;OAMG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAWvF"}
1
+ {"version":3,"file":"redis-store.d.ts","sourceRoot":"","sources":["../src/redis-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AA8D7F;;;;;;GAMG;AACH,qBAAa,mBAAoB,YAAW,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,MAAM;gBAAN,MAAM,EAAE,KAAK;IAE1C;;;;;;OAMG;IACG,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,qBAAqB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAUvF"}
@@ -1,18 +1,29 @@
1
+ import { throttlerRetryAfterMsSymbol } from './store-internals.js';
1
2
  import { validateThrottlerStoreEntry } from './validation.js';
2
- const CONSUME_LUA = ["local key = KEYS[1]", "local now = tonumber(ARGV[1])", "local ttlMs = tonumber(ARGV[2])", "local raw = redis.call('GET', key)", "local count", "local resetAt", 'if not raw then', ' count = 1', ' resetAt = now + ttlMs', 'else', ' local decoded = cjson.decode(raw)', " count = tonumber(decoded['count']) or 0", " resetAt = tonumber(decoded['resetAt']) or (now + ttlMs)", ' if now >= resetAt then', ' count = 1', ' resetAt = now + ttlMs', ' else', ' count = count + 1', ' end', 'end', 'local ttlMsLeft = math.max(resetAt - now, 1)', "redis.call('SET', key, cjson.encode({ count = count, resetAt = resetAt }), 'PX', ttlMsLeft)", 'return {count, resetAt}'].join('\n');
3
+ const CONSUME_LUA = ["local key = KEYS[1]", "local ttlMs = tonumber(ARGV[1])", "local time = redis.call('TIME')", "local nowSeconds = tonumber(time[1])", "local nowMicros = tonumber(time[2])", 'local now = math.floor((nowSeconds * 1000) + (nowMicros / 1000))', "local raw = redis.call('GET', key)", "local count", "local resetAt", 'if not raw then', ' count = 1', ' resetAt = now + ttlMs', 'else', ' local decoded = cjson.decode(raw)', " count = tonumber(decoded['count']) or 0", " resetAt = tonumber(decoded['resetAt']) or (now + ttlMs)", ' if now >= resetAt then', ' count = 1', ' resetAt = now + ttlMs', ' else', ' count = count + 1', ' end', 'end', 'local ttlMsLeft = math.max(resetAt - now, 1)', "redis.call('SET', key, cjson.encode({ count = count, resetAt = resetAt }), 'PX', ttlMsLeft)", 'return {count, resetAt, ttlMsLeft}'].join('\n');
3
4
  function parseConsumeResult(result) {
4
5
  if (!Array.isArray(result) || result.length < 2) {
5
6
  throw new Error('Redis throttler consume script returned an invalid response.');
6
7
  }
7
8
  const count = Number(result[0]);
8
9
  const resetAt = Number(result[1]);
10
+ const retryAfterMs = result.length >= 3 ? Number(result[2]) : Number.NaN;
9
11
  if (!Number.isFinite(count) || !Number.isFinite(resetAt)) {
10
12
  throw new Error('Redis throttler consume script returned non-numeric counters.');
11
13
  }
12
- return validateThrottlerStoreEntry({
14
+ const entry = validateThrottlerStoreEntry({
13
15
  count,
14
16
  resetAt
15
17
  });
18
+ if (Number.isFinite(retryAfterMs)) {
19
+ Object.defineProperty(entry, throttlerRetryAfterMsSymbol, {
20
+ configurable: false,
21
+ enumerable: false,
22
+ value: retryAfterMs,
23
+ writable: false
24
+ });
25
+ }
26
+ return entry;
16
27
  }
17
28
 
18
29
  /**
@@ -35,7 +46,7 @@ export class RedisThrottlerStore {
35
46
  * @returns The updated counter value and reset timestamp for the current window.
36
47
  */
37
48
  async consume(key, input) {
38
- const result = await this.client.eval(CONSUME_LUA, 1, key, String(input.now), String(input.ttlSeconds * 1000));
49
+ const result = await this.client.eval(CONSUME_LUA, 1, key, String(input.ttlSeconds * 1000));
39
50
  return parseConsumeResult(result);
40
51
  }
41
52
  }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Internal store-entry symbol used to carry Redis-derived retry-after milliseconds.
3
+ */
4
+ export declare const throttlerRetryAfterMsSymbol: unique symbol;
5
+ //# sourceMappingURL=store-internals.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store-internals.d.ts","sourceRoot":"","sources":["../src/store-internals.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,eAAO,MAAM,2BAA2B,eAAmC,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Internal store-entry symbol used to carry Redis-derived retry-after milliseconds.
3
+ */
4
+ export const throttlerRetryAfterMsSymbol = Symbol('throttler.retryAfterMs');
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "redis",
10
10
  "decorator"
11
11
  ],
12
- "version": "1.0.0-beta.3",
12
+ "version": "1.0.0-beta.5",
13
13
  "private": false,
14
14
  "license": "MIT",
15
15
  "repository": {
@@ -36,10 +36,10 @@
36
36
  "dist"
37
37
  ],
38
38
  "dependencies": {
39
- "@fluojs/core": "^1.0.0-beta.2",
40
- "@fluojs/di": "^1.0.0-beta.3",
41
- "@fluojs/http": "^1.0.0-beta.2",
42
- "@fluojs/runtime": "^1.0.0-beta.3"
39
+ "@fluojs/core": "^1.0.0-beta.4",
40
+ "@fluojs/di": "^1.0.0-beta.6",
41
+ "@fluojs/http": "^1.0.0-beta.10",
42
+ "@fluojs/runtime": "^1.0.0-beta.11"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "ioredis": "^5.0.0",