@commandkit/redis 0.1.1 → 1.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -0
- package/dist/index.d.ts +13 -3
- package/dist/index.js +42 -8
- package/dist/mutex-storage.d.ts +35 -0
- package/dist/mutex-storage.js +91 -0
- package/dist/ratelimit-storage.d.ts +9 -0
- package/dist/ratelimit-storage.js +19 -0
- package/dist/semaphore-storage.d.ts +53 -0
- package/dist/semaphore-storage.js +154 -0
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -31,4 +31,143 @@ async function getCachedData() {
|
|
|
31
31
|
|
|
32
32
|
return data;
|
|
33
33
|
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Manual configuration
|
|
37
|
+
|
|
38
|
+
If you want to configure the Redis client manually, you can do so by not registering this plugin and instead updating the cache provider at runtime:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import { setCacheProvider } from '@commandkit/cache';
|
|
42
|
+
import { RedisCacheProvider } from '@commandkit/redis';
|
|
43
|
+
import { Redis } from 'ioredis';
|
|
44
|
+
|
|
45
|
+
// configure the redis client as needed
|
|
46
|
+
const redis = new Redis();
|
|
47
|
+
const redisProvider = new RedisCacheProvider(redis);
|
|
48
|
+
|
|
49
|
+
setCacheProvider(redisProvider)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Redis Mutex Storage
|
|
53
|
+
|
|
54
|
+
This package also provides a Redis-based mutex storage implementation for distributed locking:
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createMutex } from 'commandkit/mutex';
|
|
58
|
+
import { RedisMutexStorage } from '@commandkit/redis';
|
|
59
|
+
import { Redis } from 'ioredis';
|
|
60
|
+
|
|
61
|
+
// Create Redis client
|
|
62
|
+
const redis = new Redis();
|
|
63
|
+
|
|
64
|
+
// Create Redis-based mutex storage
|
|
65
|
+
const redisMutexStorage = new RedisMutexStorage(redis);
|
|
66
|
+
|
|
67
|
+
// Create mutex with Redis storage
|
|
68
|
+
const mutex = createMutex({
|
|
69
|
+
timeout: 30000,
|
|
70
|
+
storage: redisMutexStorage,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
// Use the mutex for distributed locking
|
|
74
|
+
const result = await mutex.withLock('shared-resource', async () => {
|
|
75
|
+
// This code runs with exclusive access across all instances
|
|
76
|
+
return await updateSharedResource();
|
|
77
|
+
});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Redis Mutex Features
|
|
81
|
+
|
|
82
|
+
- **Distributed Locking**: Works across multiple application instances
|
|
83
|
+
- **Automatic Expiration**: Locks automatically expire to prevent deadlocks
|
|
84
|
+
- **Abort Signal Support**: Can be cancelled using AbortSignal
|
|
85
|
+
- **Atomic Operations**: Uses Lua scripts for atomic lock operations
|
|
86
|
+
- **Lock Extension**: Extend lock timeouts if needed
|
|
87
|
+
- **Force Release**: Emergency release of locks (use with caution)
|
|
88
|
+
|
|
89
|
+
### Advanced Mutex Usage
|
|
90
|
+
|
|
91
|
+
```ts
|
|
92
|
+
import { RedisMutexStorage } from '@commandkit/redis';
|
|
93
|
+
|
|
94
|
+
const redisStorage = new RedisMutexStorage(redis);
|
|
95
|
+
|
|
96
|
+
// Get detailed lock information
|
|
97
|
+
const lockInfo = await redisStorage.getLockInfo('my-resource');
|
|
98
|
+
console.log(`Locked: ${lockInfo.locked}, TTL: ${lockInfo.ttl}ms`);
|
|
99
|
+
|
|
100
|
+
// Extend a lock
|
|
101
|
+
const extended = await redisStorage.extendLock('my-resource', 60000);
|
|
102
|
+
if (extended) {
|
|
103
|
+
console.log('Lock extended by 60 seconds');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Force release a lock (emergency use only)
|
|
107
|
+
await redisStorage.forceRelease('my-resource');
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Redis Semaphore Storage
|
|
111
|
+
|
|
112
|
+
This package also provides a Redis-based semaphore storage implementation for distributed concurrency control:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { createSemaphore } from 'commandkit/semaphore';
|
|
116
|
+
import { RedisSemaphoreStorage } from '@commandkit/redis';
|
|
117
|
+
import { Redis } from 'ioredis';
|
|
118
|
+
|
|
119
|
+
// Create Redis client
|
|
120
|
+
const redis = new Redis();
|
|
121
|
+
|
|
122
|
+
// Create Redis-based semaphore storage
|
|
123
|
+
const redisSemaphoreStorage = new RedisSemaphoreStorage(redis);
|
|
124
|
+
|
|
125
|
+
// Create semaphore with Redis storage
|
|
126
|
+
const semaphore = createSemaphore({
|
|
127
|
+
permits: 5,
|
|
128
|
+
timeout: 30000,
|
|
129
|
+
storage: redisSemaphoreStorage,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Use the semaphore for distributed concurrency control
|
|
133
|
+
const result = await semaphore.withPermit('database-connection', async () => {
|
|
134
|
+
// This code runs with limited concurrency across all instances
|
|
135
|
+
return await executeDatabaseQuery();
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Redis Semaphore Features
|
|
140
|
+
|
|
141
|
+
- **Distributed Concurrency Control**: Works across multiple application instances
|
|
142
|
+
- **Automatic Initialization**: Semaphores are automatically initialized when first used
|
|
143
|
+
- **Abort Signal Support**: Can be cancelled using AbortSignal
|
|
144
|
+
- **Atomic Operations**: Uses Lua scripts for atomic permit operations
|
|
145
|
+
- **Dynamic Permit Management**: Increase or decrease permits at runtime
|
|
146
|
+
- **Semaphore Information**: Get detailed information about permit usage
|
|
147
|
+
|
|
148
|
+
### Advanced Semaphore Usage
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import { RedisSemaphoreStorage } from '@commandkit/redis';
|
|
152
|
+
|
|
153
|
+
const redisStorage = new RedisSemaphoreStorage(redis);
|
|
154
|
+
|
|
155
|
+
// Get detailed semaphore information
|
|
156
|
+
const info = await redisStorage.getSemaphoreInfo('database');
|
|
157
|
+
console.log(`Total: ${info.total}, Available: ${info.available}, Acquired: ${info.acquired}`);
|
|
158
|
+
|
|
159
|
+
// Initialize a semaphore with specific permits
|
|
160
|
+
await redisStorage.initialize('api-endpoint', 10);
|
|
161
|
+
|
|
162
|
+
// Increase permits dynamically
|
|
163
|
+
await redisStorage.increasePermits('database', 5);
|
|
164
|
+
|
|
165
|
+
// Decrease permits dynamically
|
|
166
|
+
await redisStorage.decreasePermits('api-endpoint', 2);
|
|
167
|
+
|
|
168
|
+
// Reset semaphore to initial state
|
|
169
|
+
await redisStorage.reset('database');
|
|
170
|
+
|
|
171
|
+
// Clear semaphore completely
|
|
172
|
+
await redisStorage.clear('old-semaphore');
|
|
34
173
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Redis, type RedisOptions } from 'ioredis';
|
|
2
|
-
import {
|
|
2
|
+
import { CommandKitPluginRuntime, RuntimePlugin } from 'commandkit';
|
|
3
|
+
import { CacheProvider, CacheEntry } from '@commandkit/cache';
|
|
3
4
|
export type Awaitable<T> = T | Promise<T>;
|
|
4
5
|
export type SerializeFunction = (value: any) => Awaitable<string>;
|
|
5
6
|
export type DeserializeFunction = (value: string) => Awaitable<any>;
|
|
@@ -12,7 +13,7 @@ export type DeserializeFunction = (value: string) => Awaitable<any>;
|
|
|
12
13
|
* });
|
|
13
14
|
*/
|
|
14
15
|
export declare class RedisCache extends CacheProvider {
|
|
15
|
-
|
|
16
|
+
redis: Redis;
|
|
16
17
|
/**
|
|
17
18
|
* Serialize function used to serialize values before storing them in the cache.
|
|
18
19
|
* By default, it uses `JSON.stringify`.
|
|
@@ -42,7 +43,7 @@ export declare class RedisCache extends CacheProvider {
|
|
|
42
43
|
* @param key The key to retrieve the value for.
|
|
43
44
|
* @returns The value stored in the cache, or `undefined` if it does not exist.
|
|
44
45
|
*/
|
|
45
|
-
get<T>(key: string): Promise<T | undefined>;
|
|
46
|
+
get<T>(key: string): Promise<CacheEntry<T> | undefined>;
|
|
46
47
|
/**
|
|
47
48
|
* Store a value in the cache.
|
|
48
49
|
* @param key The key to store the value under.
|
|
@@ -76,4 +77,13 @@ export declare class RedisPlugin extends RuntimePlugin<RedisOptions> {
|
|
|
76
77
|
readonly name = "RedisPlugin";
|
|
77
78
|
activate(ctx: CommandKitPluginRuntime): Promise<void>;
|
|
78
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Create a new Redis plugin instance.
|
|
82
|
+
* @param options The options to configure the Redis client.
|
|
83
|
+
* @returns The created Redis plugin instance.
|
|
84
|
+
*/
|
|
79
85
|
export declare function redis(options?: RedisOptions): RedisPlugin;
|
|
86
|
+
export * from './ratelimit-storage';
|
|
87
|
+
export * from './mutex-storage';
|
|
88
|
+
export * from './semaphore-storage';
|
|
89
|
+
export { RedisCache as RedisCacheProvider };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
2
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.RedisPlugin = exports.RedisCache = void 0;
|
|
17
|
+
exports.RedisCacheProvider = exports.RedisPlugin = exports.RedisCache = void 0;
|
|
4
18
|
exports.redis = redis;
|
|
5
19
|
const ioredis_1 = require("ioredis");
|
|
6
20
|
const commandkit_1 = require("commandkit");
|
|
21
|
+
const cache_1 = require("@commandkit/cache");
|
|
7
22
|
/**
|
|
8
23
|
* A cache provider that uses Redis as the cache store.
|
|
9
24
|
* @example const redisCache = new RedisCache();
|
|
@@ -12,7 +27,7 @@ const commandkit_1 = require("commandkit");
|
|
|
12
27
|
* cacheProvider: redisCache,
|
|
13
28
|
* });
|
|
14
29
|
*/
|
|
15
|
-
class RedisCache extends
|
|
30
|
+
class RedisCache extends cache_1.CacheProvider {
|
|
16
31
|
constructor(redis) {
|
|
17
32
|
super();
|
|
18
33
|
if (redis instanceof ioredis_1.Redis) {
|
|
@@ -34,7 +49,12 @@ class RedisCache extends commandkit_1.CacheProvider {
|
|
|
34
49
|
if (value === null) {
|
|
35
50
|
return undefined;
|
|
36
51
|
}
|
|
37
|
-
|
|
52
|
+
const entry = this.deserialize(value);
|
|
53
|
+
if (entry.ttl && Date.now() > entry.ttl) {
|
|
54
|
+
await this.delete(key);
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
return entry;
|
|
38
58
|
}
|
|
39
59
|
/**
|
|
40
60
|
* Store a value in the cache.
|
|
@@ -43,12 +63,17 @@ class RedisCache extends commandkit_1.CacheProvider {
|
|
|
43
63
|
* @param ttl The time-to-live for the cache entry in milliseconds.
|
|
44
64
|
*/
|
|
45
65
|
async set(key, value, ttl) {
|
|
46
|
-
const
|
|
66
|
+
const entry = {
|
|
67
|
+
value,
|
|
68
|
+
ttl: ttl != null ? Date.now() + ttl : undefined,
|
|
69
|
+
};
|
|
70
|
+
const serialized = this.serialize(entry);
|
|
71
|
+
const finalValue = serialized instanceof Promise ? await serialized : serialized;
|
|
47
72
|
if (typeof ttl === 'number') {
|
|
48
|
-
await this.redis.set(key,
|
|
73
|
+
await this.redis.set(key, finalValue, 'PX', ttl);
|
|
49
74
|
}
|
|
50
75
|
else {
|
|
51
|
-
await this.redis.set(key,
|
|
76
|
+
await this.redis.set(key, finalValue);
|
|
52
77
|
}
|
|
53
78
|
}
|
|
54
79
|
/**
|
|
@@ -82,17 +107,26 @@ class RedisCache extends commandkit_1.CacheProvider {
|
|
|
82
107
|
}
|
|
83
108
|
}
|
|
84
109
|
exports.RedisCache = RedisCache;
|
|
110
|
+
exports.RedisCacheProvider = RedisCache;
|
|
85
111
|
class RedisPlugin extends commandkit_1.RuntimePlugin {
|
|
86
112
|
constructor() {
|
|
87
113
|
super(...arguments);
|
|
88
114
|
this.name = 'RedisPlugin';
|
|
89
115
|
}
|
|
90
116
|
async activate(ctx) {
|
|
91
|
-
|
|
117
|
+
(0, cache_1.setCacheProvider)(new RedisCache(this.options));
|
|
92
118
|
}
|
|
93
119
|
}
|
|
94
120
|
exports.RedisPlugin = RedisPlugin;
|
|
121
|
+
/**
|
|
122
|
+
* Create a new Redis plugin instance.
|
|
123
|
+
* @param options The options to configure the Redis client.
|
|
124
|
+
* @returns The created Redis plugin instance.
|
|
125
|
+
*/
|
|
95
126
|
function redis(options) {
|
|
96
127
|
return new RedisPlugin(options ?? {});
|
|
97
128
|
}
|
|
98
|
-
|
|
129
|
+
__exportStar(require("./ratelimit-storage"), exports);
|
|
130
|
+
__exportStar(require("./mutex-storage"), exports);
|
|
131
|
+
__exportStar(require("./semaphore-storage"), exports);
|
|
132
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7Ozs7QUFxSkEsc0JBRUM7QUF2SkQscUNBQW1EO0FBQ25ELDJDQUFvRTtBQUNwRSw2Q0FBZ0Y7QUFNaEY7Ozs7Ozs7R0FPRztBQUNILE1BQWEsVUFBVyxTQUFRLHFCQUFhO0lBNkIzQyxZQUFtQixLQUE0QjtRQUM3QyxLQUFLLEVBQUUsQ0FBQztRQUVSLElBQUksS0FBSyxZQUFZLGVBQUssRUFBRSxDQUFDO1lBQzNCLElBQUksQ0FBQyxLQUFLLEdBQUcsS0FBSyxDQUFDO1FBQ3JCLENBQUM7YUFBTSxDQUFDO1lBQ04sSUFBSSxDQUFDLEtBQUssR0FBRyxJQUFJLGVBQUssQ0FBQyxLQUFLLElBQUksRUFBRSxDQUFDLENBQUM7UUFDdEMsQ0FBQztRQUVELElBQUksQ0FBQyxTQUFTLEdBQUcsSUFBSSxDQUFDLFNBQVMsQ0FBQztRQUNoQyxJQUFJLENBQUMsV0FBVyxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUM7SUFDaEMsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsR0FBRyxDQUFJLEdBQVc7UUFDN0IsTUFBTSxLQUFLLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUV4QyxJQUFJLEtBQUssS0FBSyxJQUFJLEVBQUUsQ0FBQztZQUNuQixPQUFPLFNBQVMsQ0FBQztRQUNuQixDQUFDO1FBRUQsTUFBTSxLQUFLLEdBQUcsSUFBSSxDQUFDLFdBQVcsQ0FBQyxLQUFLLENBQWtCLENBQUM7UUFDdkQsSUFBSSxLQUFLLENBQUMsR0FBRyxJQUFJLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxLQUFLLENBQUMsR0FBRyxFQUFFLENBQUM7WUFDeEMsTUFBTSxJQUFJLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDO1lBQ3ZCLE9BQU8sU0FBUyxDQUFDO1FBQ25CLENBQUM7UUFFRCxPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNJLEtBQUssQ0FBQyxHQUFHLENBQUksR0FBVyxFQUFFLEtBQVEsRUFBRSxHQUFZO1FBQ3JELE1BQU0sS0FBSyxHQUFrQjtZQUMzQixLQUFLO1lBQ0wsR0FBRyxFQUFFLEdBQUcsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxHQUFHLENBQUMsQ0FBQyxDQUFDLFNBQVM7U0FDaEQsQ0FBQztRQUVGLE1BQU0sVUFBVSxHQUFHLElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUM7UUFDekMsTUFBTSxVQUFVLEdBQ2QsVUFBVSxZQUFZLE9BQU8sQ0FBQyxDQUFDLENBQUMsTUFBTSxVQUFVLENBQUMsQ0FBQyxDQUFDLFVBQVUsQ0FBQztRQUVoRSxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsRUFBRSxDQUFDO1lBQzVCLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLFVBQVUsRUFBRSxJQUFJLEVBQUUsR0FBRyxDQUFDLENBQUM7UUFDbkQsQ0FBQzthQUFNLENBQUM7WUFDTixNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsRUFBRSxVQUFVLENBQUMsQ0FBQztRQUN4QyxDQUFDO0lBQ0gsQ0FBQztJQUVEOztPQUVHO0lBQ0ksS0FBSyxDQUFDLEtBQUs7UUFDaEIsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLFFBQVEsRUFBRSxDQUFDO0lBQzlCLENBQUM7SUFFRDs7O09BR0c7SUFDSSxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQVc7UUFDN0IsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM1QixDQUFDO0lBRUQ7Ozs7T0FJRztJQUNJLEtBQUssQ0FBQyxNQUFNLENBQUMsR0FBVztRQUM3QixPQUFPLE9BQU8sQ0FBQyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDL0MsQ0FBQztJQUVEOzs7O09BSUc7SUFDSSxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQVcsRUFBRSxHQUFXO1FBQzFDLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsR0FBRyxFQUFFLEdBQUcsQ0FBQyxDQUFDO0lBQ3JDLENBQUM7Q0FDRjtBQXRIRCxnQ0FzSEM7QUFzQnNCLHdDQUFrQjtBQXBCekMsTUFBYSxXQUFZLFNBQVEsMEJBQTJCO0lBQTVEOztRQUNrQixTQUFJLEdBQUcsYUFBYSxDQUFDO0lBS3ZDLENBQUM7SUFIUSxLQUFLLENBQUMsUUFBUSxDQUFDLEdBQTRCO1FBQ2hELElBQUEsd0JBQWdCLEVBQUMsSUFBSSxVQUFVLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDakQsQ0FBQztDQUNGO0FBTkQsa0NBTUM7QUFFRDs7OztHQUlHO0FBQ0gsU0FBZ0IsS0FBSyxDQUFDLE9BQXNCO0lBQzFDLE9BQU8sSUFBSSxXQUFXLENBQUMsT0FBTyxJQUFJLEVBQUUsQ0FBQyxDQUFDO0FBQ3hDLENBQUM7QUFFRCxzREFBb0M7QUFDcEMsa0RBQWdDO0FBQ2hDLHNEQUFvQyJ9
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { MutexStorage } from 'commandkit/mutex';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
export declare class RedisMutexStorage implements MutexStorage {
|
|
4
|
+
private readonly redis;
|
|
5
|
+
private readonly lockPrefix;
|
|
6
|
+
private readonly defaultTimeout;
|
|
7
|
+
constructor(redis: Redis);
|
|
8
|
+
acquire(key: string, timeout?: number, signal?: AbortSignal): Promise<boolean>;
|
|
9
|
+
release(key: string): Promise<void>;
|
|
10
|
+
isLocked(key: string): Promise<boolean>;
|
|
11
|
+
/**
|
|
12
|
+
* Gets information about a lock including its TTL
|
|
13
|
+
* @param key - The lock key
|
|
14
|
+
* @returns Object containing lock information
|
|
15
|
+
*/
|
|
16
|
+
getLockInfo(key: string): Promise<{
|
|
17
|
+
locked: boolean;
|
|
18
|
+
ttl: number;
|
|
19
|
+
value?: string;
|
|
20
|
+
}>;
|
|
21
|
+
/**
|
|
22
|
+
* Force releases a lock (use with caution)
|
|
23
|
+
* @param key - The lock key
|
|
24
|
+
*/
|
|
25
|
+
forceRelease(key: string): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Extends the lock timeout
|
|
28
|
+
* @param key - The lock key
|
|
29
|
+
* @param additionalTime - Additional time in milliseconds
|
|
30
|
+
* @returns True if lock was extended, false if lock doesn't exist
|
|
31
|
+
*/
|
|
32
|
+
extendLock(key: string, additionalTime: number): Promise<boolean>;
|
|
33
|
+
private generateLockValue;
|
|
34
|
+
private delay;
|
|
35
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisMutexStorage = void 0;
|
|
4
|
+
class RedisMutexStorage {
|
|
5
|
+
constructor(redis) {
|
|
6
|
+
this.redis = redis;
|
|
7
|
+
this.lockPrefix = 'mutex:';
|
|
8
|
+
this.defaultTimeout = 30000; // 30 seconds
|
|
9
|
+
}
|
|
10
|
+
async acquire(key, timeout = this.defaultTimeout, signal) {
|
|
11
|
+
const lockKey = this.lockPrefix + key;
|
|
12
|
+
const lockValue = this.generateLockValue();
|
|
13
|
+
const startTime = Date.now();
|
|
14
|
+
while (Date.now() - startTime < timeout) {
|
|
15
|
+
// Check if aborted
|
|
16
|
+
if (signal?.aborted) {
|
|
17
|
+
throw new Error('Lock acquisition was aborted');
|
|
18
|
+
}
|
|
19
|
+
// Try to acquire the lock using SET with NX (only if not exists) and EX (expiration)
|
|
20
|
+
const result = await this.redis.set(lockKey, lockValue, 'EX', Math.ceil(timeout / 1000), // Convert to seconds
|
|
21
|
+
'NX');
|
|
22
|
+
if (result === 'OK') {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
// Wait a bit before trying again
|
|
26
|
+
await this.delay(10);
|
|
27
|
+
}
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
async release(key) {
|
|
31
|
+
const lockKey = this.lockPrefix + key;
|
|
32
|
+
// Simple delete - in a real scenario, you might want to check ownership
|
|
33
|
+
// but for simplicity, we'll just delete the key
|
|
34
|
+
await this.redis.del(lockKey);
|
|
35
|
+
}
|
|
36
|
+
async isLocked(key) {
|
|
37
|
+
const lockKey = this.lockPrefix + key;
|
|
38
|
+
const exists = await this.redis.exists(lockKey);
|
|
39
|
+
return exists === 1;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Gets information about a lock including its TTL
|
|
43
|
+
* @param key - The lock key
|
|
44
|
+
* @returns Object containing lock information
|
|
45
|
+
*/
|
|
46
|
+
async getLockInfo(key) {
|
|
47
|
+
const lockKey = this.lockPrefix + key;
|
|
48
|
+
const exists = await this.redis.exists(lockKey);
|
|
49
|
+
if (exists === 0) {
|
|
50
|
+
return { locked: false, ttl: 0 };
|
|
51
|
+
}
|
|
52
|
+
const ttl = await this.redis.ttl(lockKey);
|
|
53
|
+
const value = await this.redis.get(lockKey);
|
|
54
|
+
return {
|
|
55
|
+
locked: true,
|
|
56
|
+
ttl: ttl > 0 ? ttl * 1000 : 0, // Convert to milliseconds
|
|
57
|
+
value: value || undefined,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Force releases a lock (use with caution)
|
|
62
|
+
* @param key - The lock key
|
|
63
|
+
*/
|
|
64
|
+
async forceRelease(key) {
|
|
65
|
+
const lockKey = this.lockPrefix + key;
|
|
66
|
+
await this.redis.del(lockKey);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Extends the lock timeout
|
|
70
|
+
* @param key - The lock key
|
|
71
|
+
* @param additionalTime - Additional time in milliseconds
|
|
72
|
+
* @returns True if lock was extended, false if lock doesn't exist
|
|
73
|
+
*/
|
|
74
|
+
async extendLock(key, additionalTime) {
|
|
75
|
+
const lockKey = this.lockPrefix + key;
|
|
76
|
+
const exists = await this.redis.exists(lockKey);
|
|
77
|
+
if (exists === 0) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const result = await this.redis.expire(lockKey, Math.ceil(additionalTime / 1000));
|
|
81
|
+
return result === 1;
|
|
82
|
+
}
|
|
83
|
+
generateLockValue() {
|
|
84
|
+
return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
85
|
+
}
|
|
86
|
+
delay(ms) {
|
|
87
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
exports.RedisMutexStorage = RedisMutexStorage;
|
|
91
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibXV0ZXgtc3RvcmFnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9tdXRleC1zdG9yYWdlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUdBLE1BQWEsaUJBQWlCO0lBSTVCLFlBQW9DLEtBQVk7UUFBWixVQUFLLEdBQUwsS0FBSyxDQUFPO1FBSC9CLGVBQVUsR0FBRyxRQUFRLENBQUM7UUFDdEIsbUJBQWMsR0FBRyxLQUFLLENBQUMsQ0FBQyxhQUFhO0lBRUgsQ0FBQztJQUU3QyxLQUFLLENBQUMsT0FBTyxDQUNsQixHQUFXLEVBQ1gsVUFBa0IsSUFBSSxDQUFDLGNBQWMsRUFDckMsTUFBb0I7UUFFcEIsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLFVBQVUsR0FBRyxHQUFHLENBQUM7UUFDdEMsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLGlCQUFpQixFQUFFLENBQUM7UUFDM0MsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRTdCLE9BQU8sSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFNBQVMsR0FBRyxPQUFPLEVBQUUsQ0FBQztZQUN4QyxtQkFBbUI7WUFDbkIsSUFBSSxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7Z0JBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsOEJBQThCLENBQUMsQ0FBQztZQUNsRCxDQUFDO1lBRUQscUZBQXFGO1lBQ3JGLE1BQU0sTUFBTSxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQ2pDLE9BQU8sRUFDUCxTQUFTLEVBQ1QsSUFBSSxFQUNKLElBQUksQ0FBQyxJQUFJLENBQUMsT0FBTyxHQUFHLElBQUksQ0FBQyxFQUFFLHFCQUFxQjtZQUNoRCxJQUFJLENBQ0wsQ0FBQztZQUVGLElBQUksTUFBTSxLQUFLLElBQUksRUFBRSxDQUFDO2dCQUNwQixPQUFPLElBQUksQ0FBQztZQUNkLENBQUM7WUFFRCxpQ0FBaUM7WUFDakMsTUFBTSxJQUFJLENB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
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { RateLimitStorage } from 'commandkit/ratelimit';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
export declare class RedisRateLimitStorage implements RateLimitStorage {
|
|
4
|
+
private readonly redis;
|
|
5
|
+
constructor(redis: Redis);
|
|
6
|
+
get(key: string): Promise<number>;
|
|
7
|
+
set(key: string, value: number): Promise<void>;
|
|
8
|
+
delete(key: string): Promise<void>;
|
|
9
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisRateLimitStorage = void 0;
|
|
4
|
+
class RedisRateLimitStorage {
|
|
5
|
+
constructor(redis) {
|
|
6
|
+
this.redis = redis;
|
|
7
|
+
}
|
|
8
|
+
async get(key) {
|
|
9
|
+
return Number(await this.redis.get(key)) || 0;
|
|
10
|
+
}
|
|
11
|
+
async set(key, value) {
|
|
12
|
+
await this.redis.set(key, value);
|
|
13
|
+
}
|
|
14
|
+
async delete(key) {
|
|
15
|
+
await this.redis.del(key);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.RedisRateLimitStorage = RedisRateLimitStorage;
|
|
19
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmF0ZWxpbWl0LXN0b3JhZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvcmF0ZWxpbWl0LXN0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBR0EsTUFBYSxxQkFBcUI7SUFDaEMsWUFBb0MsS0FBWTtRQUFaLFVBQUssR0FBTCxLQUFLLENBQU87SUFBRyxDQUFDO0lBRTdDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBVztRQUMxQixPQUFPLE1BQU0sQ0FBQyxNQUFNLElBQUksQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQyxDQUFDO0lBQ2hELENBQUM7SUFFTSxLQUFLLENBQUMsR0FBRyxDQUFDLEdBQVcsRUFBRSxLQUFhO1FBQ3pDLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsR0FBRyxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ25DLENBQUM7SUFFTSxLQUFLLENBQUMsTUFBTSxDQUFDLEdBQVc7UUFDN0IsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxHQUFHLENBQUMsQ0FBQztJQUM1QixDQUFDO0NBQ0Y7QUFkRCxzREFjQyJ9
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { SemaphoreStorage } from 'commandkit/semaphore';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
export declare class RedisSemaphoreStorage implements SemaphoreStorage {
|
|
4
|
+
private readonly redis;
|
|
5
|
+
private readonly semaphorePrefix;
|
|
6
|
+
private readonly defaultTimeout;
|
|
7
|
+
constructor(redis: Redis);
|
|
8
|
+
acquire(key: string, timeout?: number, signal?: AbortSignal): Promise<boolean>;
|
|
9
|
+
release(key: string): Promise<void>;
|
|
10
|
+
getAvailablePermits(key: string): Promise<number>;
|
|
11
|
+
getTotalPermits(key: string): Promise<number>;
|
|
12
|
+
/**
|
|
13
|
+
* Initializes a semaphore with the specified number of permits
|
|
14
|
+
* @param key - The semaphore key
|
|
15
|
+
* @param permits - The total number of permits to allocate
|
|
16
|
+
*/
|
|
17
|
+
initialize(key: string, permits: number): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Gets detailed information about a semaphore
|
|
20
|
+
* @param key - The semaphore key
|
|
21
|
+
* @returns Object containing semaphore information
|
|
22
|
+
*/
|
|
23
|
+
getSemaphoreInfo(key: string): Promise<{
|
|
24
|
+
total: number;
|
|
25
|
+
available: number;
|
|
26
|
+
acquired: number;
|
|
27
|
+
initialized: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
/**
|
|
30
|
+
* Resets a semaphore to its initial state
|
|
31
|
+
* @param key - The semaphore key
|
|
32
|
+
* @param permits - The number of permits to reset to (optional, uses current total if not provided)
|
|
33
|
+
*/
|
|
34
|
+
reset(key: string, permits?: number): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Increases the total number of permits for a semaphore
|
|
37
|
+
* @param key - The semaphore key
|
|
38
|
+
* @param additionalPermits - The number of additional permits to add
|
|
39
|
+
*/
|
|
40
|
+
increasePermits(key: string, additionalPermits: number): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* Decreases the total number of permits for a semaphore
|
|
43
|
+
* @param key - The semaphore key
|
|
44
|
+
* @param permitsToRemove - The number of permits to remove
|
|
45
|
+
*/
|
|
46
|
+
decreasePermits(key: string, permitsToRemove: number): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Clears a semaphore completely
|
|
49
|
+
* @param key - The semaphore key
|
|
50
|
+
*/
|
|
51
|
+
clear(key: string): Promise<void>;
|
|
52
|
+
private delay;
|
|
53
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RedisSemaphoreStorage = void 0;
|
|
4
|
+
class RedisSemaphoreStorage {
|
|
5
|
+
constructor(redis) {
|
|
6
|
+
this.redis = redis;
|
|
7
|
+
this.semaphorePrefix = 'semaphore:';
|
|
8
|
+
this.defaultTimeout = 30000; // 30 seconds
|
|
9
|
+
}
|
|
10
|
+
async acquire(key, timeout = this.defaultTimeout, signal) {
|
|
11
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
12
|
+
const startTime = Date.now();
|
|
13
|
+
while (Date.now() - startTime < timeout) {
|
|
14
|
+
// Check if aborted
|
|
15
|
+
if (signal?.aborted) {
|
|
16
|
+
throw new Error('Permit acquisition was aborted');
|
|
17
|
+
}
|
|
18
|
+
// Check if semaphore exists, if not initialize it
|
|
19
|
+
const total = await this.redis.get(semaphoreKey + ':total');
|
|
20
|
+
if (!total) {
|
|
21
|
+
await this.initialize(key, 10); // Default 10 permits
|
|
22
|
+
}
|
|
23
|
+
// Try to decrement available permits
|
|
24
|
+
const available = await this.redis.get(semaphoreKey + ':available');
|
|
25
|
+
const availableCount = Number(available) || 0;
|
|
26
|
+
if (availableCount > 0) {
|
|
27
|
+
// Use DECR to atomically decrease the count
|
|
28
|
+
const newCount = await this.redis.decr(semaphoreKey + ':available');
|
|
29
|
+
if (newCount >= 0) {
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
// If we went negative, increment back
|
|
34
|
+
await this.redis.incr(semaphoreKey + ':available');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Wait a bit before trying again
|
|
38
|
+
await this.delay(10);
|
|
39
|
+
}
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
async release(key) {
|
|
43
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
44
|
+
// Get current values
|
|
45
|
+
const total = await this.redis.get(semaphoreKey + ':total');
|
|
46
|
+
const available = await this.redis.get(semaphoreKey + ':available');
|
|
47
|
+
const totalPermits = Number(total) || 0;
|
|
48
|
+
const availablePermits = Number(available) || 0;
|
|
49
|
+
// Only increment if we haven't reached the total
|
|
50
|
+
if (availablePermits < totalPermits) {
|
|
51
|
+
await this.redis.incr(semaphoreKey + ':available');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
async getAvailablePermits(key) {
|
|
55
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
56
|
+
const available = await this.redis.get(semaphoreKey + ':available');
|
|
57
|
+
return Number(available) || 0;
|
|
58
|
+
}
|
|
59
|
+
async getTotalPermits(key) {
|
|
60
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
61
|
+
const total = await this.redis.get(semaphoreKey + ':total');
|
|
62
|
+
return Number(total) || 0;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Initializes a semaphore with the specified number of permits
|
|
66
|
+
* @param key - The semaphore key
|
|
67
|
+
* @param permits - The total number of permits to allocate
|
|
68
|
+
*/
|
|
69
|
+
async initialize(key, permits) {
|
|
70
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
71
|
+
await this.redis.set(semaphoreKey + ':total', permits);
|
|
72
|
+
await this.redis.set(semaphoreKey + ':available', permits);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Gets detailed information about a semaphore
|
|
76
|
+
* @param key - The semaphore key
|
|
77
|
+
* @returns Object containing semaphore information
|
|
78
|
+
*/
|
|
79
|
+
async getSemaphoreInfo(key) {
|
|
80
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
81
|
+
const total = await this.redis.get(semaphoreKey + ':total');
|
|
82
|
+
const available = await this.redis.get(semaphoreKey + ':available');
|
|
83
|
+
const totalPermits = Number(total) || 0;
|
|
84
|
+
const availablePermits = Number(available) || 0;
|
|
85
|
+
return {
|
|
86
|
+
total: totalPermits,
|
|
87
|
+
available: availablePermits,
|
|
88
|
+
acquired: totalPermits - availablePermits,
|
|
89
|
+
initialized: totalPermits > 0,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Resets a semaphore to its initial state
|
|
94
|
+
* @param key - The semaphore key
|
|
95
|
+
* @param permits - The number of permits to reset to (optional, uses current total if not provided)
|
|
96
|
+
*/
|
|
97
|
+
async reset(key, permits) {
|
|
98
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
99
|
+
if (permits !== undefined) {
|
|
100
|
+
await this.initialize(key, permits);
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
const total = await this.getTotalPermits(key);
|
|
104
|
+
if (total > 0) {
|
|
105
|
+
await this.redis.set(semaphoreKey + ':available', total);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Increases the total number of permits for a semaphore
|
|
111
|
+
* @param key - The semaphore key
|
|
112
|
+
* @param additionalPermits - The number of additional permits to add
|
|
113
|
+
*/
|
|
114
|
+
async increasePermits(key, additionalPermits) {
|
|
115
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
116
|
+
const total = await this.redis.get(semaphoreKey + ':total');
|
|
117
|
+
const available = await this.redis.get(semaphoreKey + ':available');
|
|
118
|
+
const totalPermits = Number(total) || 0;
|
|
119
|
+
const availablePermits = Number(available) || 0;
|
|
120
|
+
const newTotal = totalPermits + additionalPermits;
|
|
121
|
+
const newAvailable = availablePermits + additionalPermits;
|
|
122
|
+
await this.redis.set(semaphoreKey + ':total', newTotal);
|
|
123
|
+
await this.redis.set(semaphoreKey + ':available', newAvailable);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Decreases the total number of permits for a semaphore
|
|
127
|
+
* @param key - The semaphore key
|
|
128
|
+
* @param permitsToRemove - The number of permits to remove
|
|
129
|
+
*/
|
|
130
|
+
async decreasePermits(key, permitsToRemove) {
|
|
131
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
132
|
+
const total = await this.redis.get(semaphoreKey + ':total');
|
|
133
|
+
const available = await this.redis.get(semaphoreKey + ':available');
|
|
134
|
+
const totalPermits = Number(total) || 0;
|
|
135
|
+
const availablePermits = Number(available) || 0;
|
|
136
|
+
const newTotal = Math.max(0, totalPermits - permitsToRemove);
|
|
137
|
+
const newAvailable = Math.max(0, availablePermits - permitsToRemove);
|
|
138
|
+
await this.redis.set(semaphoreKey + ':total', newTotal);
|
|
139
|
+
await this.redis.set(semaphoreKey + ':available', newAvailable);
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Clears a semaphore completely
|
|
143
|
+
* @param key - The semaphore key
|
|
144
|
+
*/
|
|
145
|
+
async clear(key) {
|
|
146
|
+
const semaphoreKey = this.semaphorePrefix + key;
|
|
147
|
+
await this.redis.del(semaphoreKey + ':total', semaphoreKey + ':available');
|
|
148
|
+
}
|
|
149
|
+
delay(ms) {
|
|
150
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
exports.RedisSemaphoreStorage = RedisSemaphoreStorage;
|
|
154
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic2VtYXBob3JlLXN0b3JhZ2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvc2VtYXBob3JlLXN0b3JhZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBR0EsTUFBYSxxQkFBcUI7SUFJaEMsWUFBb0MsS0FBWTtRQUFaLFVBQUssR0FBTCxLQUFLLENBQU87UUFIL0Isb0JBQWUsR0FBRyxZQUFZLENBQUM7UUFDL0IsbUJBQWMsR0FBRyxLQUFLLENBQUMsQ0FBQyxhQUFhO0lBRUgsQ0FBQztJQUU3QyxLQUFLLENBQUMsT0FBTyxDQUNsQixHQUFXLEVBQ1gsVUFBa0IsSUFBSSxDQUFDLGNBQWMsRUFDckMsTUFBb0I7UUFFcEIsTUFBTSxZQUFZLEdBQUcsSUFBSSxDQUFDLGVBQWUsR0FBRyxHQUFHLENBQUM7UUFDaEQsTUFBTSxTQUFTLEdBQUcsSUFBSSxDQUFDLEdBQUcsRUFBRSxDQUFDO1FBRTdCLE9BQU8sSUFBSSxDQUFDLEdBQUcsRUFBRSxHQUFHLFNBQVMsR0FBRyxPQUFPLEVBQUUsQ0FBQztZQUN4QyxtQkFBbUI7WUFDbkIsSUFBSSxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7Z0JBQ3BCLE1BQU0sSUFBSSxLQUFLLENBQUMsZ0NBQWdDLENBQUMsQ0FBQztZQUNwRCxDQUFDO1lBRUQsa0RBQWtEO1lBQ2xELE1BQU0sS0FBSyxHQUFHLE1BQU0sSUFBSSxDQUFDLEtBQUssQ0FBQyxHQUFHLENBQUMsWUFBWSxHQUFHLFFBQVEsQ0FBQyxDQUFDO1lBQzVELElBQUksQ0FBQyxLQUFLLEVBQUUsQ0FBQztnQkFDWCxNQUFNLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMscUJBQXFCO1lBQ3ZELENBQUM7WUFFRCxxQ0FBcUM7WUFDckMsTUFBTSxTQUFTLEdBQUcsTUFBTSxJQUFJLENBQUMsS0FBSyxDQUFDLEdBQUcsQ0FBQyxZQUFZLEdBQUcsWUFBWSxDQUFDLENBQUM7WUFDcEUsTUFBTSxj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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@commandkit/redis",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "1.2.0-rc.1",
|
|
4
4
|
"description": "Redis cache provider for CommandKit",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -16,7 +16,10 @@
|
|
|
16
16
|
"redis",
|
|
17
17
|
"cache"
|
|
18
18
|
],
|
|
19
|
-
"
|
|
19
|
+
"contributors": [
|
|
20
|
+
"Twilight <hello@twlite.dev>",
|
|
21
|
+
"Avraj <avraj@underctrl.io>"
|
|
22
|
+
],
|
|
20
23
|
"license": "MIT",
|
|
21
24
|
"bugs": {
|
|
22
25
|
"url": "https://github.com/underctrl-io/commandkit/issues"
|
|
@@ -27,11 +30,12 @@
|
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"typescript": "^5.7.3",
|
|
30
|
-
"
|
|
31
|
-
"commandkit": "0.1
|
|
33
|
+
"@commandkit/cache": "1.2.0-rc.1",
|
|
34
|
+
"commandkit": "1.2.0-rc.1",
|
|
35
|
+
"tsconfig": "0.0.0-rc.1"
|
|
32
36
|
},
|
|
33
37
|
"scripts": {
|
|
34
|
-
"
|
|
38
|
+
"check-types": "tsc --noEmit",
|
|
35
39
|
"build": "tsc"
|
|
36
40
|
}
|
|
37
41
|
}
|