@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 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;
@@ -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 };
@@ -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
+ }