@magnet-cms/adapter-cache-redis 1.0.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/dist/index.cjs +138 -0
- package/dist/index.d.cts +64 -0
- package/dist/index.d.ts +64 -0
- package/dist/index.js +131 -0
- package/package.json +38 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var common = require('@magnet-cms/common');
|
|
4
|
+
var Redis = require('ioredis');
|
|
5
|
+
|
|
6
|
+
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
7
|
+
|
|
8
|
+
var Redis__default = /*#__PURE__*/_interopDefault(Redis);
|
|
9
|
+
|
|
10
|
+
var __defProp = Object.defineProperty;
|
|
11
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
12
|
+
var RedisCacheAdapter = class _RedisCacheAdapter extends common.CacheAdapter {
|
|
13
|
+
static {
|
|
14
|
+
__name(this, "RedisCacheAdapter");
|
|
15
|
+
}
|
|
16
|
+
name = "redis";
|
|
17
|
+
redis;
|
|
18
|
+
keyPrefix;
|
|
19
|
+
/** Environment variables used by this adapter */
|
|
20
|
+
static envVars = [
|
|
21
|
+
{
|
|
22
|
+
name: "CACHE_REDIS_URL",
|
|
23
|
+
required: false,
|
|
24
|
+
description: "Redis connection URL for cache (falls back to REDIS_URL)"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: "REDIS_URL",
|
|
28
|
+
required: false,
|
|
29
|
+
description: "Generic Redis connection URL (fallback for CACHE_REDIS_URL)"
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
constructor(config = {}) {
|
|
33
|
+
super();
|
|
34
|
+
this.keyPrefix = config.keyPrefix ?? "";
|
|
35
|
+
const url = config.url ?? process.env.CACHE_REDIS_URL ?? process.env.REDIS_URL;
|
|
36
|
+
if (url) {
|
|
37
|
+
this.redis = new Redis__default.default(url, {
|
|
38
|
+
lazyConnect: true
|
|
39
|
+
});
|
|
40
|
+
} else {
|
|
41
|
+
this.redis = new Redis__default.default({
|
|
42
|
+
host: config.host ?? "localhost",
|
|
43
|
+
port: config.port ?? 6379,
|
|
44
|
+
password: config.password,
|
|
45
|
+
db: config.db ?? 0,
|
|
46
|
+
lazyConnect: true
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
this.redis.on("error", () => {
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Create a configured cache provider for MagnetModule.forRoot().
|
|
54
|
+
* Auto-resolves connection from CACHE_REDIS_URL or REDIS_URL env vars.
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* ```typescript
|
|
58
|
+
* MagnetModule.forRoot([
|
|
59
|
+
* RedisCacheAdapter.forRoot(),
|
|
60
|
+
* // or with explicit config:
|
|
61
|
+
* RedisCacheAdapter.forRoot({ url: 'redis://localhost:6379', keyPrefix: 'myapp:' }),
|
|
62
|
+
* ])
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
static forRoot(config = {}) {
|
|
66
|
+
return {
|
|
67
|
+
type: "cache",
|
|
68
|
+
adapter: new _RedisCacheAdapter(config),
|
|
69
|
+
envVars: _RedisCacheAdapter.envVars
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
async get(key) {
|
|
73
|
+
const raw = await this.redis.get(this.prefixKey(key));
|
|
74
|
+
if (raw === null) return null;
|
|
75
|
+
try {
|
|
76
|
+
return JSON.parse(raw);
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
async set(key, value, ttl) {
|
|
82
|
+
const serialized = JSON.stringify(value);
|
|
83
|
+
const prefixed = this.prefixKey(key);
|
|
84
|
+
if (ttl !== void 0) {
|
|
85
|
+
await this.redis.set(prefixed, serialized, "EX", ttl);
|
|
86
|
+
} else {
|
|
87
|
+
await this.redis.set(prefixed, serialized);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
async delete(key) {
|
|
91
|
+
await this.redis.del(this.prefixKey(key));
|
|
92
|
+
}
|
|
93
|
+
async deleteByPattern(pattern) {
|
|
94
|
+
await this.scanAndDelete(this.prefixKey(pattern));
|
|
95
|
+
}
|
|
96
|
+
async has(key) {
|
|
97
|
+
const count = await this.redis.exists(this.prefixKey(key));
|
|
98
|
+
return count > 0;
|
|
99
|
+
}
|
|
100
|
+
async clear() {
|
|
101
|
+
const pattern = this.keyPrefix ? `${this.keyPrefix}*` : "*";
|
|
102
|
+
await this.scanAndDelete(pattern);
|
|
103
|
+
}
|
|
104
|
+
async healthCheck() {
|
|
105
|
+
try {
|
|
106
|
+
await this.redis.ping();
|
|
107
|
+
return {
|
|
108
|
+
healthy: true
|
|
109
|
+
};
|
|
110
|
+
} catch (err) {
|
|
111
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
112
|
+
return {
|
|
113
|
+
healthy: false,
|
|
114
|
+
message
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async dispose() {
|
|
119
|
+
await this.redis.quit();
|
|
120
|
+
}
|
|
121
|
+
prefixKey(key) {
|
|
122
|
+
return this.keyPrefix ? `${this.keyPrefix}${key}` : key;
|
|
123
|
+
}
|
|
124
|
+
/** SCAN-based deletion — safe for production (never uses KEYS) */
|
|
125
|
+
async scanAndDelete(pattern) {
|
|
126
|
+
let cursor = "0";
|
|
127
|
+
do {
|
|
128
|
+
const [nextCursor, keys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
129
|
+
cursor = nextCursor;
|
|
130
|
+
if (keys.length > 0) {
|
|
131
|
+
await this.redis.del(...keys);
|
|
132
|
+
}
|
|
133
|
+
} while (cursor !== "0");
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
exports.CacheAdapter = RedisCacheAdapter;
|
|
138
|
+
exports.RedisCacheAdapter = RedisCacheAdapter;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CacheAdapter, EnvVarRequirement, CacheMagnetProvider, CacheHealthResult } from '@magnet-cms/common';
|
|
2
|
+
|
|
3
|
+
interface RedisCacheAdapterConfig {
|
|
4
|
+
/** Redis connection URL (e.g., redis://localhost:6379). Takes precedence over host/port. */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Redis host (default: localhost) */
|
|
7
|
+
host?: string;
|
|
8
|
+
/** Redis port (default: 6379) */
|
|
9
|
+
port?: number;
|
|
10
|
+
/** Redis password */
|
|
11
|
+
password?: string;
|
|
12
|
+
/** Redis database index (default: 0) */
|
|
13
|
+
db?: number;
|
|
14
|
+
/** Key prefix for all cache entries (default: '') */
|
|
15
|
+
keyPrefix?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Redis-based cache adapter using ioredis.
|
|
19
|
+
*
|
|
20
|
+
* Compatible with Redis 6+ and Dragonfly (Redis wire-compatible).
|
|
21
|
+
* Uses SCAN for safe pattern-based deletion (never KEYS in production).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* MagnetModule.forRoot([
|
|
26
|
+
* RedisCacheAdapter.forRoot({ url: process.env.REDIS_URL }),
|
|
27
|
+
* ])
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare class RedisCacheAdapter extends CacheAdapter {
|
|
31
|
+
readonly name = "redis";
|
|
32
|
+
private readonly redis;
|
|
33
|
+
private readonly keyPrefix;
|
|
34
|
+
/** Environment variables used by this adapter */
|
|
35
|
+
static readonly envVars: EnvVarRequirement[];
|
|
36
|
+
constructor(config?: RedisCacheAdapterConfig);
|
|
37
|
+
/**
|
|
38
|
+
* Create a configured cache provider for MagnetModule.forRoot().
|
|
39
|
+
* Auto-resolves connection from CACHE_REDIS_URL or REDIS_URL env vars.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* MagnetModule.forRoot([
|
|
44
|
+
* RedisCacheAdapter.forRoot(),
|
|
45
|
+
* // or with explicit config:
|
|
46
|
+
* RedisCacheAdapter.forRoot({ url: 'redis://localhost:6379', keyPrefix: 'myapp:' }),
|
|
47
|
+
* ])
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
static forRoot(config?: RedisCacheAdapterConfig): CacheMagnetProvider;
|
|
51
|
+
get<T>(key: string): Promise<T | null>;
|
|
52
|
+
set<T>(key: string, value: T, ttl?: number): Promise<void>;
|
|
53
|
+
delete(key: string): Promise<void>;
|
|
54
|
+
deleteByPattern(pattern: string): Promise<void>;
|
|
55
|
+
has(key: string): Promise<boolean>;
|
|
56
|
+
clear(): Promise<void>;
|
|
57
|
+
healthCheck(): Promise<CacheHealthResult>;
|
|
58
|
+
dispose(): Promise<void>;
|
|
59
|
+
private prefixKey;
|
|
60
|
+
/** SCAN-based deletion — safe for production (never uses KEYS) */
|
|
61
|
+
private scanAndDelete;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { RedisCacheAdapter as CacheAdapter, RedisCacheAdapter, type RedisCacheAdapterConfig };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { CacheAdapter, EnvVarRequirement, CacheMagnetProvider, CacheHealthResult } from '@magnet-cms/common';
|
|
2
|
+
|
|
3
|
+
interface RedisCacheAdapterConfig {
|
|
4
|
+
/** Redis connection URL (e.g., redis://localhost:6379). Takes precedence over host/port. */
|
|
5
|
+
url?: string;
|
|
6
|
+
/** Redis host (default: localhost) */
|
|
7
|
+
host?: string;
|
|
8
|
+
/** Redis port (default: 6379) */
|
|
9
|
+
port?: number;
|
|
10
|
+
/** Redis password */
|
|
11
|
+
password?: string;
|
|
12
|
+
/** Redis database index (default: 0) */
|
|
13
|
+
db?: number;
|
|
14
|
+
/** Key prefix for all cache entries (default: '') */
|
|
15
|
+
keyPrefix?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Redis-based cache adapter using ioredis.
|
|
19
|
+
*
|
|
20
|
+
* Compatible with Redis 6+ and Dragonfly (Redis wire-compatible).
|
|
21
|
+
* Uses SCAN for safe pattern-based deletion (never KEYS in production).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* MagnetModule.forRoot([
|
|
26
|
+
* RedisCacheAdapter.forRoot({ url: process.env.REDIS_URL }),
|
|
27
|
+
* ])
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
declare class RedisCacheAdapter extends CacheAdapter {
|
|
31
|
+
readonly name = "redis";
|
|
32
|
+
private readonly redis;
|
|
33
|
+
private readonly keyPrefix;
|
|
34
|
+
/** Environment variables used by this adapter */
|
|
35
|
+
static readonly envVars: EnvVarRequirement[];
|
|
36
|
+
constructor(config?: RedisCacheAdapterConfig);
|
|
37
|
+
/**
|
|
38
|
+
* Create a configured cache provider for MagnetModule.forRoot().
|
|
39
|
+
* Auto-resolves connection from CACHE_REDIS_URL or REDIS_URL env vars.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```typescript
|
|
43
|
+
* MagnetModule.forRoot([
|
|
44
|
+
* RedisCacheAdapter.forRoot(),
|
|
45
|
+
* // or with explicit config:
|
|
46
|
+
* RedisCacheAdapter.forRoot({ url: 'redis://localhost:6379', keyPrefix: 'myapp:' }),
|
|
47
|
+
* ])
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
static forRoot(config?: RedisCacheAdapterConfig): CacheMagnetProvider;
|
|
51
|
+
get<T>(key: string): Promise<T | null>;
|
|
52
|
+
set<T>(key: string, value: T, ttl?: number): Promise<void>;
|
|
53
|
+
delete(key: string): Promise<void>;
|
|
54
|
+
deleteByPattern(pattern: string): Promise<void>;
|
|
55
|
+
has(key: string): Promise<boolean>;
|
|
56
|
+
clear(): Promise<void>;
|
|
57
|
+
healthCheck(): Promise<CacheHealthResult>;
|
|
58
|
+
dispose(): Promise<void>;
|
|
59
|
+
private prefixKey;
|
|
60
|
+
/** SCAN-based deletion — safe for production (never uses KEYS) */
|
|
61
|
+
private scanAndDelete;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export { RedisCacheAdapter as CacheAdapter, RedisCacheAdapter, type RedisCacheAdapterConfig };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { CacheAdapter } from '@magnet-cms/common';
|
|
2
|
+
import Redis from 'ioredis';
|
|
3
|
+
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var RedisCacheAdapter = class _RedisCacheAdapter extends CacheAdapter {
|
|
7
|
+
static {
|
|
8
|
+
__name(this, "RedisCacheAdapter");
|
|
9
|
+
}
|
|
10
|
+
name = "redis";
|
|
11
|
+
redis;
|
|
12
|
+
keyPrefix;
|
|
13
|
+
/** Environment variables used by this adapter */
|
|
14
|
+
static envVars = [
|
|
15
|
+
{
|
|
16
|
+
name: "CACHE_REDIS_URL",
|
|
17
|
+
required: false,
|
|
18
|
+
description: "Redis connection URL for cache (falls back to REDIS_URL)"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "REDIS_URL",
|
|
22
|
+
required: false,
|
|
23
|
+
description: "Generic Redis connection URL (fallback for CACHE_REDIS_URL)"
|
|
24
|
+
}
|
|
25
|
+
];
|
|
26
|
+
constructor(config = {}) {
|
|
27
|
+
super();
|
|
28
|
+
this.keyPrefix = config.keyPrefix ?? "";
|
|
29
|
+
const url = config.url ?? process.env.CACHE_REDIS_URL ?? process.env.REDIS_URL;
|
|
30
|
+
if (url) {
|
|
31
|
+
this.redis = new Redis(url, {
|
|
32
|
+
lazyConnect: true
|
|
33
|
+
});
|
|
34
|
+
} else {
|
|
35
|
+
this.redis = new Redis({
|
|
36
|
+
host: config.host ?? "localhost",
|
|
37
|
+
port: config.port ?? 6379,
|
|
38
|
+
password: config.password,
|
|
39
|
+
db: config.db ?? 0,
|
|
40
|
+
lazyConnect: true
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
this.redis.on("error", () => {
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Create a configured cache provider for MagnetModule.forRoot().
|
|
48
|
+
* Auto-resolves connection from CACHE_REDIS_URL or REDIS_URL env vars.
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* MagnetModule.forRoot([
|
|
53
|
+
* RedisCacheAdapter.forRoot(),
|
|
54
|
+
* // or with explicit config:
|
|
55
|
+
* RedisCacheAdapter.forRoot({ url: 'redis://localhost:6379', keyPrefix: 'myapp:' }),
|
|
56
|
+
* ])
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
static forRoot(config = {}) {
|
|
60
|
+
return {
|
|
61
|
+
type: "cache",
|
|
62
|
+
adapter: new _RedisCacheAdapter(config),
|
|
63
|
+
envVars: _RedisCacheAdapter.envVars
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
async get(key) {
|
|
67
|
+
const raw = await this.redis.get(this.prefixKey(key));
|
|
68
|
+
if (raw === null) return null;
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(raw);
|
|
71
|
+
} catch {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async set(key, value, ttl) {
|
|
76
|
+
const serialized = JSON.stringify(value);
|
|
77
|
+
const prefixed = this.prefixKey(key);
|
|
78
|
+
if (ttl !== void 0) {
|
|
79
|
+
await this.redis.set(prefixed, serialized, "EX", ttl);
|
|
80
|
+
} else {
|
|
81
|
+
await this.redis.set(prefixed, serialized);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async delete(key) {
|
|
85
|
+
await this.redis.del(this.prefixKey(key));
|
|
86
|
+
}
|
|
87
|
+
async deleteByPattern(pattern) {
|
|
88
|
+
await this.scanAndDelete(this.prefixKey(pattern));
|
|
89
|
+
}
|
|
90
|
+
async has(key) {
|
|
91
|
+
const count = await this.redis.exists(this.prefixKey(key));
|
|
92
|
+
return count > 0;
|
|
93
|
+
}
|
|
94
|
+
async clear() {
|
|
95
|
+
const pattern = this.keyPrefix ? `${this.keyPrefix}*` : "*";
|
|
96
|
+
await this.scanAndDelete(pattern);
|
|
97
|
+
}
|
|
98
|
+
async healthCheck() {
|
|
99
|
+
try {
|
|
100
|
+
await this.redis.ping();
|
|
101
|
+
return {
|
|
102
|
+
healthy: true
|
|
103
|
+
};
|
|
104
|
+
} catch (err) {
|
|
105
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
106
|
+
return {
|
|
107
|
+
healthy: false,
|
|
108
|
+
message
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
async dispose() {
|
|
113
|
+
await this.redis.quit();
|
|
114
|
+
}
|
|
115
|
+
prefixKey(key) {
|
|
116
|
+
return this.keyPrefix ? `${this.keyPrefix}${key}` : key;
|
|
117
|
+
}
|
|
118
|
+
/** SCAN-based deletion — safe for production (never uses KEYS) */
|
|
119
|
+
async scanAndDelete(pattern) {
|
|
120
|
+
let cursor = "0";
|
|
121
|
+
do {
|
|
122
|
+
const [nextCursor, keys] = await this.redis.scan(cursor, "MATCH", pattern, "COUNT", 100);
|
|
123
|
+
cursor = nextCursor;
|
|
124
|
+
if (keys.length > 0) {
|
|
125
|
+
await this.redis.del(...keys);
|
|
126
|
+
}
|
|
127
|
+
} while (cursor !== "0");
|
|
128
|
+
}
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
export { RedisCacheAdapter as CacheAdapter, RedisCacheAdapter };
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@magnet-cms/adapter-cache-redis",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Redis cache adapter for Magnet CMS",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.cjs",
|
|
7
|
+
"module": "./dist/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.cts",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build:dev": "tsup --watch",
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"check-types": "tsc --noEmit"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@magnet-cms/common": "workspace:*",
|
|
29
|
+
"@repo/biome": "workspace:*",
|
|
30
|
+
"@repo/tsup": "workspace:*",
|
|
31
|
+
"@repo/typescript-config": "workspace:*",
|
|
32
|
+
"ioredis": "^5.4.2"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"@magnet-cms/common": "^0.2.0",
|
|
36
|
+
"ioredis": "^5.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|