@mrxsys/mrx-core 2.7.0 → 2.8.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/CHANGELOG.md +67 -48
- package/dist/chunk-7g8k2epn.js +104 -0
- package/dist/chunk-kv9hms2z.js +28 -0
- package/dist/{chunk-qb6x364m.js → chunk-m18th1g5.js} +1 -1
- package/dist/chunk-ncc0m208.js +8 -0
- package/dist/chunk-r1kcf1q6.js +62 -0
- package/dist/chunk-sepwfqdh.js +6 -0
- package/dist/{chunk-z6q192p8.js → chunk-syhskygx.js} +4 -4
- package/dist/{chunk-tm71j126.js → chunk-y78xrx17.js} +2 -2
- package/dist/chunk-z1skzn1j.js +8 -0
- package/dist/modules/data/data.d.ts +1 -1
- package/dist/modules/database/index.js +2 -2
- package/dist/modules/elysia/cache/cache.d.ts +306 -0
- package/dist/modules/elysia/cache/index.d.ts +1 -0
- package/dist/modules/elysia/cache/index.js +68 -0
- package/dist/modules/elysia/cache/types/cacheItem.d.ts +7 -0
- package/dist/modules/elysia/cache/types/cacheOptions.d.ts +21 -0
- package/dist/modules/elysia/cache/types/index.d.ts +1 -0
- package/dist/modules/elysia/cache/utils/generateCacheKey.d.ts +5 -0
- package/dist/modules/elysia/cache/utils/index.d.ts +1 -0
- package/dist/modules/elysia/cache/utils/index.js +7 -0
- package/dist/modules/elysia/crud/crud.d.ts +1 -1
- package/dist/modules/elysia/crud/index.js +13 -10
- package/dist/modules/elysia/crud/types/crudOptions.d.ts +2 -0
- package/dist/modules/elysia/dbResolver/dbResolver.d.ts +1 -1
- package/dist/modules/elysia/dbResolver/index.js +3 -3
- package/dist/modules/elysia/error/index.js +11 -2
- package/dist/modules/elysia/rateLimit/index.js +15 -95
- package/dist/modules/elysia/rateLimit/rateLimit.d.ts +28 -11
- package/dist/modules/elysia/rateLimit/types/rateLimitOptions.d.ts +3 -15
- package/dist/modules/jwt/enums/index.d.ts +2 -0
- package/dist/modules/jwt/enums/index.js +11 -0
- package/dist/modules/jwt/enums/jwtErrorKeys.d.ts +5 -0
- package/dist/modules/jwt/enums/parseHumanTimeToSecondsErrorKeys.d.ts +3 -0
- package/dist/modules/jwt/index.d.ts +1 -0
- package/dist/modules/jwt/index.js +58 -0
- package/dist/modules/jwt/jwt.d.ts +3 -0
- package/dist/modules/jwt/utils/index.d.ts +1 -0
- package/dist/modules/jwt/utils/index.js +9 -0
- package/dist/modules/jwt/utils/parseHumanTimeToSeconds.d.ts +17 -0
- package/dist/modules/kvStore/enums/index.d.ts +1 -0
- package/dist/modules/kvStore/enums/index.js +7 -0
- package/dist/modules/kvStore/enums/kvStoreErrorKeys.d.ts +5 -0
- package/dist/modules/kvStore/ioredis/index.d.ts +1 -0
- package/dist/modules/kvStore/ioredis/index.js +102 -0
- package/dist/modules/kvStore/ioredis/ioredisStore.d.ts +107 -0
- package/dist/modules/kvStore/memory/index.d.ts +1 -0
- package/dist/modules/kvStore/memory/index.js +9 -0
- package/dist/modules/kvStore/memory/memoryStore.d.ts +119 -0
- package/dist/modules/{elysia/rateLimit → kvStore/memory}/types/memoryStoreEntry.d.ts +2 -2
- package/dist/modules/kvStore/types/index.d.ts +1 -0
- package/dist/modules/kvStore/types/index.js +1 -0
- package/dist/modules/kvStore/types/kvStore.d.ts +81 -0
- package/dist/modules/repository/index.js +1 -1
- package/dist/modules/repository/types/queryOptions.d.ts +5 -1
- package/dist/modules/totp/hotp.d.ts +11 -0
- package/dist/modules/totp/index.d.ts +3 -1
- package/dist/modules/totp/index.js +27 -30
- package/dist/modules/totp/otpAuthUri.d.ts +21 -0
- package/dist/modules/totp/totp.d.ts +0 -40
- package/dist/modules/totp/utils/base32.d.ts +1 -1
- package/dist/modules/totp/utils/index.d.ts +3 -2
- package/dist/modules/totp/utils/index.js +6 -0
- package/dist/modules/totp/utils/timeRemaining.d.ts +9 -0
- package/package.json +79 -72
- package/dist/chunk-cqw9xq4y.js +0 -7
- package/dist/modules/elysia/jwt/enums/index.d.ts +0 -1
- package/dist/modules/elysia/jwt/enums/index.js +0 -7
- package/dist/modules/elysia/jwt/enums/jwtErrorKeys.d.ts +0 -4
- package/dist/modules/elysia/jwt/index.d.ts +0 -1
- package/dist/modules/elysia/jwt/index.js +0 -77
- package/dist/modules/elysia/jwt/jwt.d.ts +0 -119
- package/dist/modules/elysia/jwt/types/index.d.ts +0 -1
- package/dist/modules/elysia/jwt/types/jwtOptions.d.ts +0 -98
- package/dist/modules/elysia/rateLimit/stores/memoryStore.d.ts +0 -47
- package/dist/modules/elysia/rateLimit/types/rateLimitStore.d.ts +0 -21
- /package/dist/modules/elysia/{jwt → cache}/types/index.js +0 -0
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
import {
|
|
3
3
|
RATE_LIMIT_ERROR_KEYS
|
|
4
4
|
} from "../../../chunk-yd82hdxv.js";
|
|
5
|
+
import {
|
|
6
|
+
MemoryStore
|
|
7
|
+
} from "../../../chunk-7g8k2epn.js";
|
|
8
|
+
import"../../../chunk-z1skzn1j.js";
|
|
5
9
|
import {
|
|
6
10
|
HttpError
|
|
7
11
|
} from "../../../chunk-683sda6e.js";
|
|
@@ -10,90 +14,6 @@ import"../../../chunk-vknq69e0.js";
|
|
|
10
14
|
|
|
11
15
|
// source/modules/elysia/rateLimit/rateLimit.ts
|
|
12
16
|
import { Elysia } from "elysia";
|
|
13
|
-
|
|
14
|
-
// source/modules/elysia/rateLimit/stores/memoryStore.ts
|
|
15
|
-
class MemoryStore {
|
|
16
|
-
_store = new Map;
|
|
17
|
-
_cleanupInterval;
|
|
18
|
-
_cleanupTimer = null;
|
|
19
|
-
constructor(cleanupIntervalMs) {
|
|
20
|
-
this._cleanupInterval = cleanupIntervalMs ?? 300000;
|
|
21
|
-
this._startCleanup();
|
|
22
|
-
}
|
|
23
|
-
get(key) {
|
|
24
|
-
const entry = this._store.get(key);
|
|
25
|
-
if (!entry)
|
|
26
|
-
return null;
|
|
27
|
-
const now = Date.now();
|
|
28
|
-
if (now > entry.expiresAt && entry.expiresAt !== -1) {
|
|
29
|
-
this._store.delete(key);
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
return entry.value;
|
|
33
|
-
}
|
|
34
|
-
setex(key, seconds, value) {
|
|
35
|
-
if (seconds <= 0)
|
|
36
|
-
return;
|
|
37
|
-
const expiresAt = Date.now() + seconds * 1000;
|
|
38
|
-
this._store.set(key, {
|
|
39
|
-
value,
|
|
40
|
-
expiresAt
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
incr(key) {
|
|
44
|
-
const now = Date.now();
|
|
45
|
-
const entry = this._store.get(key);
|
|
46
|
-
if (!entry || now > entry.expiresAt && entry.expiresAt !== -1) {
|
|
47
|
-
if (entry)
|
|
48
|
-
this._store.delete(key);
|
|
49
|
-
this._store.set(key, {
|
|
50
|
-
value: "1",
|
|
51
|
-
expiresAt: -1
|
|
52
|
-
});
|
|
53
|
-
return 1;
|
|
54
|
-
}
|
|
55
|
-
const currentValue = parseInt(entry.value) || 0;
|
|
56
|
-
const newValue = currentValue + 1;
|
|
57
|
-
this._store.set(key, {
|
|
58
|
-
value: newValue.toString(),
|
|
59
|
-
expiresAt: entry.expiresAt
|
|
60
|
-
});
|
|
61
|
-
return newValue;
|
|
62
|
-
}
|
|
63
|
-
ttl(key) {
|
|
64
|
-
const entry = this._store.get(key);
|
|
65
|
-
if (!entry)
|
|
66
|
-
return -1;
|
|
67
|
-
if (entry.expiresAt === -1)
|
|
68
|
-
return -1;
|
|
69
|
-
const now = Date.now();
|
|
70
|
-
if (now > entry.expiresAt) {
|
|
71
|
-
this._store.delete(key);
|
|
72
|
-
return -1;
|
|
73
|
-
}
|
|
74
|
-
return Math.ceil((entry.expiresAt - now) / 1000);
|
|
75
|
-
}
|
|
76
|
-
_startCleanup() {
|
|
77
|
-
this._cleanupTimer = setInterval(() => {
|
|
78
|
-
this._cleanup();
|
|
79
|
-
}, this._cleanupInterval);
|
|
80
|
-
}
|
|
81
|
-
_cleanup() {
|
|
82
|
-
const now = Date.now();
|
|
83
|
-
for (const [key, entry] of this._store.entries())
|
|
84
|
-
if (now > entry.expiresAt && entry.expiresAt !== -1)
|
|
85
|
-
this._store.delete(key);
|
|
86
|
-
}
|
|
87
|
-
destroy() {
|
|
88
|
-
if (this._cleanupTimer) {
|
|
89
|
-
clearInterval(this._cleanupTimer);
|
|
90
|
-
this._cleanupTimer = null;
|
|
91
|
-
}
|
|
92
|
-
this._store.clear();
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// source/modules/elysia/rateLimit/rateLimit.ts
|
|
97
17
|
var rateLimit = ({ store, limit, window }) => {
|
|
98
18
|
const storeInstance = store === ":memory:" || !store ? new MemoryStore : store;
|
|
99
19
|
return new Elysia({
|
|
@@ -105,16 +25,16 @@ var rateLimit = ({ store, limit, window }) => {
|
|
|
105
25
|
}
|
|
106
26
|
}).onRequest(async ({ set, request, server }) => {
|
|
107
27
|
const ip = request.headers.get("x-forwarded-for") || request.headers.get("x-real-ip") || server?.requestIP(request)?.address || "127.0.0.1";
|
|
108
|
-
const key = `
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
if (count ===
|
|
112
|
-
await storeInstance.
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
28
|
+
const key = `ratelimit:${ip}`;
|
|
29
|
+
const count = await storeInstance.get(key);
|
|
30
|
+
let newCount;
|
|
31
|
+
if (count === null) {
|
|
32
|
+
await storeInstance.set(key, 1, window);
|
|
33
|
+
newCount = 1;
|
|
34
|
+
} else {
|
|
35
|
+
newCount = await storeInstance.increment(key);
|
|
36
|
+
}
|
|
37
|
+
if (newCount > limit) {
|
|
118
38
|
set.status = 429;
|
|
119
39
|
throw new HttpError({
|
|
120
40
|
message: RATE_LIMIT_ERROR_KEYS.RATE_LIMIT_EXCEEDED,
|
|
@@ -129,7 +49,7 @@ var rateLimit = ({ store, limit, window }) => {
|
|
|
129
49
|
}
|
|
130
50
|
set.headers = {
|
|
131
51
|
"X-RateLimit-Limit": limit.toString(),
|
|
132
|
-
"X-RateLimit-Remaining": Math.max(0, limit -
|
|
52
|
+
"X-RateLimit-Remaining": Math.max(0, limit - newCount).toString(),
|
|
133
53
|
"X-RateLimit-Reset": (await storeInstance.ttl(key)).toString()
|
|
134
54
|
};
|
|
135
55
|
}).as("global");
|
|
@@ -16,26 +16,43 @@ import type { RateLimitOptions } from './types/rateLimitOptions';
|
|
|
16
16
|
* @returns An {@link Elysia} plugin that adds rate limiting functionality
|
|
17
17
|
*
|
|
18
18
|
* @example
|
|
19
|
+
* Basic usage with default in-memory store
|
|
19
20
|
* ```ts
|
|
20
|
-
*
|
|
21
|
-
*
|
|
21
|
+
* import { rateLimit } from '@nowarajs/elysia-ratelimit';
|
|
22
|
+
* import { Elysia } from 'elysia';
|
|
23
|
+
*
|
|
24
|
+
* const app = new Elysia()
|
|
25
|
+
* .use(rateLimit({
|
|
26
|
+
* store: ':memory:', // Use in-memory store
|
|
27
|
+
* limit: 100, // 100 requests
|
|
28
|
+
* window: 60, // per minute
|
|
29
|
+
* }))
|
|
30
|
+
* .get('/api/endpoint', () => ({ message: 'Hello World' }));
|
|
31
|
+
*
|
|
32
|
+
* app.listen(3000);
|
|
33
|
+
* ```
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* Using Redis store for distributed rate limiting
|
|
37
|
+
* ```ts
|
|
38
|
+
* import { IoRedisStore } from '@nowarajs/kv-store';
|
|
39
|
+
* import { rateLimit } from '@nowarajs/elysia-ratelimit';
|
|
40
|
+
* import { Elysia } from 'elysia';
|
|
41
|
+
*
|
|
42
|
+
* const redisStore = new IoRedisStore({
|
|
22
43
|
* host: 'localhost',
|
|
23
44
|
* port: 6379
|
|
24
45
|
* });
|
|
25
|
-
* await
|
|
46
|
+
* await redisStore.connect();
|
|
26
47
|
*
|
|
27
|
-
* // Create and configure the application with rate limiting
|
|
28
48
|
* const app = new Elysia()
|
|
29
49
|
* .use(rateLimit({
|
|
30
|
-
* store:
|
|
31
|
-
* limit:
|
|
32
|
-
* window:
|
|
50
|
+
* store: redisStore,
|
|
51
|
+
* limit: 1000, // 1000 requests
|
|
52
|
+
* window: 3600, // per hour
|
|
33
53
|
* }))
|
|
34
|
-
* .get('/
|
|
35
|
-
* return { success: true, message: 'This endpoint is rate limited' };
|
|
36
|
-
* });
|
|
54
|
+
* .get('/api/endpoint', () => ({ message: 'Hello World' }));
|
|
37
55
|
*
|
|
38
|
-
* // Start the server
|
|
39
56
|
* app.listen(3000);
|
|
40
57
|
* ```
|
|
41
58
|
*/
|
|
@@ -1,25 +1,13 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
/**
|
|
3
|
-
* Options to configure the rate limit plugin.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```ts
|
|
7
|
-
* const options: RateLimitOptions = {
|
|
8
|
-
* store: redisInstance, // Your Redis instance
|
|
9
|
-
* limit: 100, // Allow 100 requests
|
|
10
|
-
* window: 60, // Per 60 seconds
|
|
11
|
-
* };
|
|
12
|
-
* ```
|
|
13
|
-
*/
|
|
1
|
+
import type { KvStore } from '../../../../modules/kvStore/types/kvStore';
|
|
14
2
|
export interface RateLimitOptions {
|
|
15
3
|
/**
|
|
16
4
|
* Storage backend for rate limit data.
|
|
17
5
|
*
|
|
18
6
|
* - If not specified, defaults to in-memory storage
|
|
19
7
|
* - Use ':memory:' to explicitly specify in-memory storage
|
|
20
|
-
* - Provide a
|
|
8
|
+
* - Provide a KvStore instance for persistent distributed storage
|
|
21
9
|
*/
|
|
22
|
-
readonly store?: ':memory:' |
|
|
10
|
+
readonly store?: ':memory:' | KvStore;
|
|
23
11
|
/**
|
|
24
12
|
* Maximum number of requests allowed in the time window.
|
|
25
13
|
*
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { signJWT, verifyJWT } from './jwt';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
JWT_ERROR_KEYS
|
|
4
|
+
} from "../../chunk-ncc0m208.js";
|
|
5
|
+
import {
|
|
6
|
+
parseHumanTimeToSeconds
|
|
7
|
+
} from "../../chunk-r1kcf1q6.js";
|
|
8
|
+
import"../../chunk-sepwfqdh.js";
|
|
9
|
+
import {
|
|
10
|
+
HttpError
|
|
11
|
+
} from "../../chunk-683sda6e.js";
|
|
12
|
+
import"../../chunk-9nw6qekv.js";
|
|
13
|
+
import"../../chunk-vknq69e0.js";
|
|
14
|
+
|
|
15
|
+
// source/modules/jwt/jwt.ts
|
|
16
|
+
import {
|
|
17
|
+
SignJWT,
|
|
18
|
+
jwtVerify
|
|
19
|
+
} from "jose";
|
|
20
|
+
var signJWT = (secret, payload, expiration = Math.floor(Date.now() / 1000) + 60 * 15) => {
|
|
21
|
+
const exp = expiration instanceof Date ? Math.floor(expiration.getTime() / 1000) : typeof expiration === "number" ? expiration : parseHumanTimeToSeconds(expiration);
|
|
22
|
+
if (exp <= Math.floor(Date.now() / 1000))
|
|
23
|
+
throw new HttpError({
|
|
24
|
+
message: JWT_ERROR_KEYS.JWT_EXPIRATION_PASSED,
|
|
25
|
+
httpStatusCode: "BAD_REQUEST"
|
|
26
|
+
});
|
|
27
|
+
const finalPayload = {
|
|
28
|
+
iss: "Core-Issuer",
|
|
29
|
+
sub: "",
|
|
30
|
+
aud: ["Core-Audience"],
|
|
31
|
+
jti: Bun.randomUUIDv7(),
|
|
32
|
+
nbf: Math.floor(Date.now() / 1000),
|
|
33
|
+
iat: Math.floor(Date.now() / 1000),
|
|
34
|
+
exp,
|
|
35
|
+
...payload
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
const jwt = new SignJWT(finalPayload).setProtectedHeader({ alg: "HS256", typ: "JWT" }).setIssuer(finalPayload.iss).setSubject(finalPayload.sub).setAudience(finalPayload.aud).setJti(finalPayload.jti).setNotBefore(finalPayload.nbf).setIssuedAt(finalPayload.iat).setExpirationTime(expiration).sign(new TextEncoder().encode(secret));
|
|
39
|
+
return jwt;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new HttpError({
|
|
42
|
+
message: JWT_ERROR_KEYS.JWT_SIGN_ERROR,
|
|
43
|
+
httpStatusCode: "INTERNAL_SERVER_ERROR",
|
|
44
|
+
cause: error
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
var verifyJWT = async (token, secret) => {
|
|
49
|
+
try {
|
|
50
|
+
return await jwtVerify(token, new TextEncoder().encode(secret));
|
|
51
|
+
} catch {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
export {
|
|
56
|
+
verifyJWT,
|
|
57
|
+
signJWT
|
|
58
|
+
};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type JWTPayload, type JWTVerifyResult } from 'jose';
|
|
2
|
+
export declare const signJWT: (secret: string, payload: JWTPayload, expiration?: number | string | Date) => Promise<string>;
|
|
3
|
+
export declare const verifyJWT: (token: string, secret: string) => Promise<JWTVerifyResult | false>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseHumanTimeToSeconds } from './parseHumanTimeToSeconds';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Converts a human-readable time expression to seconds
|
|
3
|
+
*
|
|
4
|
+
* @param timeExpression - A string representing a time period (e.g., "2 hours", "30 minutes ago", "+1 day")
|
|
5
|
+
*
|
|
6
|
+
* @throws ({@link BaseError}) - If the time expression is invalid or contains an unknown unit
|
|
7
|
+
*
|
|
8
|
+
* @returns The time period in seconds (negative for past times)
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* parseHumanTimeToSeconds("2 hours") // Returns 7200
|
|
13
|
+
* parseHumanTimeToSeconds("30 mins ago") // Returns -1800
|
|
14
|
+
* parseHumanTimeToSeconds("+1 day") // Returns 86400
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare const parseHumanTimeToSeconds: (timeExpression: string) => number;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { KV_STORE_ERROR_KEYS } from './kvStoreErrorKeys';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { IoRedisStore } from './ioredisStore';
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
KV_STORE_ERROR_KEYS
|
|
4
|
+
} from "../../../chunk-z1skzn1j.js";
|
|
5
|
+
import {
|
|
6
|
+
BaseError
|
|
7
|
+
} from "../../../chunk-vknq69e0.js";
|
|
8
|
+
|
|
9
|
+
// source/modules/kvStore/ioredis/ioredisStore.ts
|
|
10
|
+
import { Redis } from "ioredis";
|
|
11
|
+
class IoRedisStore {
|
|
12
|
+
_client;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this._client = new Redis({
|
|
15
|
+
...options,
|
|
16
|
+
lazyConnect: true
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
async connect() {
|
|
20
|
+
try {
|
|
21
|
+
await this._client.connect();
|
|
22
|
+
} catch (e) {
|
|
23
|
+
throw new BaseError({
|
|
24
|
+
message: KV_STORE_ERROR_KEYS.CONNECTION_FAILED,
|
|
25
|
+
cause: e
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
async close() {
|
|
30
|
+
try {
|
|
31
|
+
await this._client.quit();
|
|
32
|
+
} catch (e) {
|
|
33
|
+
throw new BaseError({
|
|
34
|
+
message: KV_STORE_ERROR_KEYS.CLOSING_CONNECTION_FAILED,
|
|
35
|
+
cause: e
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
async get(key) {
|
|
40
|
+
const value = await this._client.get(key);
|
|
41
|
+
if (value === null)
|
|
42
|
+
return null;
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(value);
|
|
45
|
+
} catch {
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async set(key, value, ttlSec) {
|
|
50
|
+
const serialized = typeof value === "string" ? value : JSON.stringify(value);
|
|
51
|
+
if (ttlSec)
|
|
52
|
+
await this._client.setex(key, ttlSec, serialized);
|
|
53
|
+
else
|
|
54
|
+
await this._client.set(key, serialized);
|
|
55
|
+
}
|
|
56
|
+
async increment(key, amount = 1) {
|
|
57
|
+
const current = await this._client.get(key);
|
|
58
|
+
if (current !== null) {
|
|
59
|
+
const parsed = Number(current);
|
|
60
|
+
if (Number.isNaN(parsed))
|
|
61
|
+
throw new BaseError({
|
|
62
|
+
message: KV_STORE_ERROR_KEYS.NOT_INTEGER
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
if (amount === 1)
|
|
66
|
+
return this._client.incr(key);
|
|
67
|
+
return this._client.incrby(key, amount);
|
|
68
|
+
}
|
|
69
|
+
async decrement(key, amount = 1) {
|
|
70
|
+
const current = await this._client.get(key);
|
|
71
|
+
if (current !== null) {
|
|
72
|
+
const parsed = Number(current);
|
|
73
|
+
if (Number.isNaN(parsed))
|
|
74
|
+
throw new BaseError({
|
|
75
|
+
message: KV_STORE_ERROR_KEYS.NOT_INTEGER
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
if (amount === 1)
|
|
79
|
+
return this._client.decr(key);
|
|
80
|
+
return this._client.decrby(key, amount);
|
|
81
|
+
}
|
|
82
|
+
async del(key) {
|
|
83
|
+
const result = await this._client.del(key);
|
|
84
|
+
return result === 1;
|
|
85
|
+
}
|
|
86
|
+
async expire(key, ttlSec) {
|
|
87
|
+
const result = await this._client.expire(key, ttlSec);
|
|
88
|
+
return result === 1;
|
|
89
|
+
}
|
|
90
|
+
ttl(key) {
|
|
91
|
+
return this._client.ttl(key);
|
|
92
|
+
}
|
|
93
|
+
async clean() {
|
|
94
|
+
const keys = await this._client.keys("*");
|
|
95
|
+
if (keys.length === 0)
|
|
96
|
+
return 0;
|
|
97
|
+
return this._client.del(...keys);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
export {
|
|
101
|
+
IoRedisStore
|
|
102
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { type RedisOptions } from 'ioredis';
|
|
2
|
+
import type { KvStore } from '../../../modules/kvStore/types/kvStore';
|
|
3
|
+
/**
|
|
4
|
+
* Redis-based key-value store implementation using ioredis client.
|
|
5
|
+
*
|
|
6
|
+
* Provides a Redis-backed implementation of the KvStore interface with
|
|
7
|
+
* automatic JSON serialization/deserialization and proper error handling.
|
|
8
|
+
*/
|
|
9
|
+
export declare class IoRedisStore implements KvStore {
|
|
10
|
+
/**
|
|
11
|
+
* Redis client instance.
|
|
12
|
+
*/
|
|
13
|
+
private readonly _client;
|
|
14
|
+
/**
|
|
15
|
+
* Creates an IoRedis store instance.
|
|
16
|
+
*
|
|
17
|
+
* @param options - Redis connection options
|
|
18
|
+
*/
|
|
19
|
+
constructor(options: RedisOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Establishes connection to Redis server.
|
|
22
|
+
*
|
|
23
|
+
* @throws ({@link BaseError}) - When connection fails
|
|
24
|
+
*/
|
|
25
|
+
connect(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Closes the Redis connection gracefully.
|
|
28
|
+
*
|
|
29
|
+
* @throws ({@link BaseError}) - When closing connection fails
|
|
30
|
+
*/
|
|
31
|
+
close(): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Retrieves a value from Redis by key.
|
|
34
|
+
*
|
|
35
|
+
* @template T - The expected type of the stored value
|
|
36
|
+
*
|
|
37
|
+
* @param key - The key to retrieve
|
|
38
|
+
*
|
|
39
|
+
* @returns The value associated with the key, or null if not found or expired
|
|
40
|
+
*/
|
|
41
|
+
get<T = unknown>(key: string): Promise<T | null>;
|
|
42
|
+
/**
|
|
43
|
+
* Stores a value in Redis with optional TTL.
|
|
44
|
+
*
|
|
45
|
+
* @template T - The type of the value being stored
|
|
46
|
+
*
|
|
47
|
+
* @param key - The key to store the value under
|
|
48
|
+
* @param value - The value to store
|
|
49
|
+
* @param ttlSec - Time to live in seconds (optional)
|
|
50
|
+
*/
|
|
51
|
+
set<T = unknown>(key: string, value: T, ttlSec?: number): Promise<void>;
|
|
52
|
+
/**
|
|
53
|
+
* Increments a numeric value stored at key by the specified amount.
|
|
54
|
+
* If the key does not exist, it is set to 0 before performing the operation.
|
|
55
|
+
*
|
|
56
|
+
* @param key - The key containing the numeric value
|
|
57
|
+
* @param amount - The amount to increment by (default: 1)
|
|
58
|
+
*
|
|
59
|
+
* @throws ({@link BaseError}) - When the value is not a valid integer
|
|
60
|
+
*
|
|
61
|
+
* @returns The value after incrementing
|
|
62
|
+
*/
|
|
63
|
+
increment(key: string, amount?: number): Promise<number>;
|
|
64
|
+
/**
|
|
65
|
+
* Decrements a numeric value stored at key by the specified amount.
|
|
66
|
+
* If the key does not exist, it is set to 0 before performing the operation.
|
|
67
|
+
*
|
|
68
|
+
* @param key - The key containing the numeric value
|
|
69
|
+
* @param amount - The amount to decrement by (default: 1)
|
|
70
|
+
*
|
|
71
|
+
* @throws ({@link BaseError}) - When the value is not a valid integer
|
|
72
|
+
*
|
|
73
|
+
* @returns The value after decrementing
|
|
74
|
+
*/
|
|
75
|
+
decrement(key: string, amount?: number): Promise<number>;
|
|
76
|
+
/**
|
|
77
|
+
* Deletes a key from Redis.
|
|
78
|
+
*
|
|
79
|
+
* @param key - The key to delete
|
|
80
|
+
*
|
|
81
|
+
* @returns True if the key was deleted, false if it did not exist
|
|
82
|
+
*/
|
|
83
|
+
del(key: string): Promise<boolean>;
|
|
84
|
+
/**
|
|
85
|
+
* Sets an expiration time for a key.
|
|
86
|
+
*
|
|
87
|
+
* @param key - The key to set expiration for
|
|
88
|
+
* @param ttlSec - Time to live in seconds
|
|
89
|
+
*
|
|
90
|
+
* @returns True if the expiration was set, false if the key does not exist
|
|
91
|
+
*/
|
|
92
|
+
expire(key: string, ttlSec: number): Promise<boolean>;
|
|
93
|
+
/**
|
|
94
|
+
* Gets the remaining time to live for a key.
|
|
95
|
+
*
|
|
96
|
+
* @param key - The key to check
|
|
97
|
+
*
|
|
98
|
+
* @returns Time to live in seconds, -1 if key has no expiration, -2 if key does not exist
|
|
99
|
+
*/
|
|
100
|
+
ttl(key: string): Promise<number>;
|
|
101
|
+
/**
|
|
102
|
+
* Removes all keys from the Redis database.
|
|
103
|
+
*
|
|
104
|
+
* @returns The number of keys that were deleted
|
|
105
|
+
*/
|
|
106
|
+
clean(): Promise<number>;
|
|
107
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { MemoryStore } from './memoryStore';
|