ioredis-toolkit 0.0.9 → 0.0.11

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.
Files changed (267) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +1 -1
  3. package/README.md +68 -1058
  4. package/dist/cache/cache.d.ts +30 -0
  5. package/dist/cache/cache.d.ts.map +1 -0
  6. package/dist/cache/cache.js +59 -0
  7. package/dist/cache/cache.js.map +1 -0
  8. package/dist/cache/config.d.ts +12 -0
  9. package/dist/cache/config.d.ts.map +1 -0
  10. package/dist/cache/config.js +13 -0
  11. package/dist/cache/config.js.map +1 -0
  12. package/dist/cache/types.d.ts +32 -0
  13. package/dist/cache/types.d.ts.map +1 -0
  14. package/dist/cache/types.js +5 -0
  15. package/dist/cache/types.js.map +1 -0
  16. package/dist/index.d.ts +43 -51
  17. package/dist/index.d.ts.map +1 -0
  18. package/dist/index.js +32 -44
  19. package/dist/index.js.map +1 -0
  20. package/dist/lock/config.d.ts +12 -0
  21. package/dist/lock/config.d.ts.map +1 -0
  22. package/dist/lock/config.js +8 -0
  23. package/dist/lock/config.js.map +1 -0
  24. package/dist/lock/lock.d.ts +20 -0
  25. package/dist/lock/lock.d.ts.map +1 -0
  26. package/dist/lock/lock.js +44 -0
  27. package/dist/lock/lock.js.map +1 -0
  28. package/dist/lock/types.d.ts +19 -0
  29. package/dist/lock/types.d.ts.map +1 -0
  30. package/dist/lock/types.js +2 -0
  31. package/dist/lock/types.js.map +1 -0
  32. package/dist/modules-config.d.ts +3 -0
  33. package/dist/modules-config.d.ts.map +1 -0
  34. package/dist/modules-config.js +2 -0
  35. package/dist/modules-config.js.map +1 -0
  36. package/dist/pubsub/config.d.ts +11 -0
  37. package/dist/pubsub/config.d.ts.map +1 -0
  38. package/dist/pubsub/config.js +6 -0
  39. package/dist/pubsub/config.js.map +1 -0
  40. package/dist/pubsub/pubsub.d.ts +20 -0
  41. package/dist/pubsub/pubsub.d.ts.map +1 -0
  42. package/dist/pubsub/pubsub.js +54 -0
  43. package/dist/pubsub/pubsub.js.map +1 -0
  44. package/dist/pubsub/types.d.ts +24 -0
  45. package/dist/pubsub/types.d.ts.map +1 -0
  46. package/dist/pubsub/types.js +2 -0
  47. package/dist/pubsub/types.js.map +1 -0
  48. package/dist/rate-limit/config.d.ts +12 -0
  49. package/dist/rate-limit/config.d.ts.map +1 -0
  50. package/dist/rate-limit/config.js +6 -0
  51. package/dist/rate-limit/config.js.map +1 -0
  52. package/dist/rate-limit/rate-limiter.d.ts +18 -0
  53. package/dist/rate-limit/rate-limiter.d.ts.map +1 -0
  54. package/dist/rate-limit/rate-limiter.js +37 -0
  55. package/dist/rate-limit/rate-limiter.js.map +1 -0
  56. package/dist/rate-limit/types.d.ts +27 -0
  57. package/dist/rate-limit/types.d.ts.map +1 -0
  58. package/dist/rate-limit/types.js +2 -0
  59. package/dist/rate-limit/types.js.map +1 -0
  60. package/dist/redis/client-facade.d.ts +77 -0
  61. package/dist/redis/client-facade.d.ts.map +1 -0
  62. package/dist/redis/client-facade.js +102 -0
  63. package/dist/redis/client-facade.js.map +1 -0
  64. package/dist/redis/client.d.ts +10 -0
  65. package/dist/redis/client.d.ts.map +1 -0
  66. package/dist/redis/client.js +29 -0
  67. package/dist/redis/client.js.map +1 -0
  68. package/dist/redis/cluster.d.ts +7 -0
  69. package/dist/redis/cluster.d.ts.map +1 -0
  70. package/dist/redis/cluster.js +47 -0
  71. package/dist/redis/cluster.js.map +1 -0
  72. package/dist/redis/config.d.ts +39 -0
  73. package/dist/redis/config.d.ts.map +1 -0
  74. package/dist/redis/config.js +52 -0
  75. package/dist/redis/config.js.map +1 -0
  76. package/dist/redis/errors.d.ts +5 -0
  77. package/dist/redis/errors.d.ts.map +1 -0
  78. package/dist/redis/errors.js +5 -0
  79. package/dist/redis/errors.js.map +1 -0
  80. package/dist/redis/types.d.ts +135 -0
  81. package/dist/redis/types.d.ts.map +1 -0
  82. package/dist/redis/types.js +2 -0
  83. package/dist/redis/types.js.map +1 -0
  84. package/dist/redis/wrapper.d.ts +88 -0
  85. package/dist/redis/wrapper.d.ts.map +1 -0
  86. package/dist/redis/wrapper.js +206 -0
  87. package/dist/redis/wrapper.js.map +1 -0
  88. package/dist/session/config.d.ts +47 -0
  89. package/dist/session/config.d.ts.map +1 -0
  90. package/dist/session/config.js +101 -0
  91. package/dist/session/config.js.map +1 -0
  92. package/dist/session/cookie.d.ts +16 -0
  93. package/dist/session/cookie.d.ts.map +1 -0
  94. package/dist/session/cookie.js +28 -0
  95. package/dist/session/cookie.js.map +1 -0
  96. package/dist/session/errors.d.ts +56 -0
  97. package/dist/session/errors.d.ts.map +1 -0
  98. package/dist/session/errors.js +58 -0
  99. package/dist/session/errors.js.map +1 -0
  100. package/dist/session/factory.d.ts +21 -0
  101. package/dist/session/factory.d.ts.map +1 -0
  102. package/dist/session/factory.js +30 -0
  103. package/dist/session/factory.js.map +1 -0
  104. package/dist/session/health.d.ts +12 -0
  105. package/dist/session/health.d.ts.map +1 -0
  106. package/dist/session/health.js +23 -0
  107. package/dist/session/health.js.map +1 -0
  108. package/dist/session/keys.d.ts +23 -0
  109. package/dist/session/keys.d.ts.map +1 -0
  110. package/dist/session/keys.js +27 -0
  111. package/dist/session/keys.js.map +1 -0
  112. package/dist/session/manager.d.ts +34 -0
  113. package/dist/session/manager.d.ts.map +1 -0
  114. package/dist/session/manager.js +31 -0
  115. package/dist/session/manager.js.map +1 -0
  116. package/dist/session/metrics.d.ts +11 -0
  117. package/dist/session/metrics.d.ts.map +1 -0
  118. package/dist/session/metrics.js +10 -0
  119. package/dist/session/metrics.js.map +1 -0
  120. package/dist/session/repository.d.ts +49 -0
  121. package/dist/session/repository.d.ts.map +1 -0
  122. package/dist/session/repository.js +203 -0
  123. package/dist/session/repository.js.map +1 -0
  124. package/dist/session/revocation.d.ts +22 -0
  125. package/dist/session/revocation.d.ts.map +1 -0
  126. package/dist/session/revocation.js +41 -0
  127. package/dist/session/revocation.js.map +1 -0
  128. package/dist/session/script-sources.d.ts +11 -0
  129. package/dist/session/script-sources.d.ts.map +1 -0
  130. package/dist/session/script-sources.js +140 -0
  131. package/dist/session/script-sources.js.map +1 -0
  132. package/dist/session/scripts.d.ts +15 -0
  133. package/dist/session/scripts.d.ts.map +1 -0
  134. package/dist/session/scripts.js +41 -0
  135. package/dist/session/scripts.js.map +1 -0
  136. package/dist/session/serializer.d.ts +12 -0
  137. package/dist/session/serializer.d.ts.map +1 -0
  138. package/dist/session/serializer.js +77 -0
  139. package/dist/session/serializer.js.map +1 -0
  140. package/dist/session/service.d.ts +48 -0
  141. package/dist/session/service.d.ts.map +1 -0
  142. package/dist/session/service.js +235 -0
  143. package/dist/session/service.js.map +1 -0
  144. package/dist/session/token.d.ts +16 -0
  145. package/dist/session/token.d.ts.map +1 -0
  146. package/dist/session/token.js +32 -0
  147. package/dist/session/token.js.map +1 -0
  148. package/dist/session/types.d.ts +134 -0
  149. package/dist/session/types.d.ts.map +1 -0
  150. package/dist/session/types.js +2 -0
  151. package/dist/session/types.js.map +1 -0
  152. package/dist/streams/config.d.ts +12 -0
  153. package/dist/streams/config.d.ts.map +1 -0
  154. package/dist/streams/config.js +6 -0
  155. package/dist/streams/config.js.map +1 -0
  156. package/dist/streams/streams.d.ts +24 -0
  157. package/dist/streams/streams.d.ts.map +1 -0
  158. package/dist/streams/streams.js +55 -0
  159. package/dist/streams/streams.js.map +1 -0
  160. package/dist/streams/types.d.ts +32 -0
  161. package/dist/streams/types.d.ts.map +1 -0
  162. package/dist/streams/types.js +2 -0
  163. package/dist/streams/types.js.map +1 -0
  164. package/docs/ACCEPTANCE-REPORT.md +70 -0
  165. package/docs/ARCHITECTURE.md +61 -0
  166. package/docs/CAPACITY.md +33 -0
  167. package/docs/DEPLOYMENT.md +22 -0
  168. package/docs/README-API.md +15 -0
  169. package/docs/STATE-MACHINE.md +38 -0
  170. package/docs/TESTING.md +37 -0
  171. package/docs/THREAT-MODEL.md +23 -0
  172. package/docs/TYPE-SAFETY.md +34 -0
  173. package/docs/modules/cache/README.md +7 -0
  174. package/docs/modules/cache/usage.md +156 -0
  175. package/docs/modules/lock/README.md +7 -0
  176. package/docs/modules/lock/usage.md +105 -0
  177. package/docs/modules/pubsub/README.md +7 -0
  178. package/docs/modules/pubsub/usage.md +106 -0
  179. package/docs/modules/rate-limit/README.md +7 -0
  180. package/docs/modules/rate-limit/usage.md +100 -0
  181. package/docs/modules/sessions/README.md +7 -0
  182. package/docs/modules/sessions/usage.md +262 -0
  183. package/docs/modules/streams/README.md +7 -0
  184. package/docs/modules/streams/usage.md +141 -0
  185. package/package.json +50 -60
  186. package/src/scripts/cleanup-index.lua +4 -0
  187. package/src/scripts/conditional-update.lua +21 -0
  188. package/src/scripts/consume-session.lua +21 -0
  189. package/src/scripts/create-session.lua +28 -0
  190. package/src/scripts/delete.lua +2 -0
  191. package/src/scripts/destroy-user.lua +13 -0
  192. package/src/scripts/enforce-limit.lua +17 -0
  193. package/src/scripts/revoke-session.lua +13 -0
  194. package/src/scripts/rotate.lua +24 -0
  195. package/src/scripts/touch-session.lua +28 -0
  196. package/src/scripts/update-session.lua +18 -0
  197. package/dist/cache.d.ts +0 -796
  198. package/dist/cache.js +0 -1120
  199. package/dist/client.d.ts +0 -284
  200. package/dist/client.js +0 -1114
  201. package/dist/cluster-slot.d.ts +0 -4
  202. package/dist/cluster-slot.js +0 -31
  203. package/dist/cluster.d.ts +0 -79
  204. package/dist/cluster.js +0 -156
  205. package/dist/errors.d.ts +0 -30
  206. package/dist/errors.js +0 -63
  207. package/dist/health.d.ts +0 -180
  208. package/dist/health.js +0 -239
  209. package/dist/lock.d.ts +0 -248
  210. package/dist/lock.js +0 -397
  211. package/dist/logger.d.ts +0 -12
  212. package/dist/logger.js +0 -40
  213. package/dist/pubsub.d.ts +0 -423
  214. package/dist/pubsub.js +0 -537
  215. package/dist/ratelimiter.d.ts +0 -441
  216. package/dist/ratelimiter.js +0 -539
  217. package/dist/session/index.d.ts +0 -23
  218. package/dist/session/index.js +0 -16
  219. package/dist/session/revocation-store.d.ts +0 -176
  220. package/dist/session/revocation-store.js +0 -318
  221. package/dist/session/scripts/cleanup-index.lua +0 -21
  222. package/dist/session/scripts/conditional-update-encrypted.lua +0 -60
  223. package/dist/session/scripts/conditional-update.lua +0 -63
  224. package/dist/session/scripts/create.lua +0 -83
  225. package/dist/session/scripts/delete-by-user.lua +0 -29
  226. package/dist/session/scripts/delete.lua +0 -15
  227. package/dist/session/scripts/enforce-limit.lua +0 -38
  228. package/dist/session/scripts/revoke.lua +0 -61
  229. package/dist/session/scripts/rotate-encrypted.lua +0 -110
  230. package/dist/session/scripts/rotate.lua +0 -122
  231. package/dist/session/scripts/touch-encrypted.lua +0 -89
  232. package/dist/session/scripts/touch.lua +0 -72
  233. package/dist/session/scripts/validate.lua +0 -90
  234. package/dist/session/session-circuit-breaker.d.ts +0 -42
  235. package/dist/session/session-circuit-breaker.js +0 -129
  236. package/dist/session/session-config.d.ts +0 -335
  237. package/dist/session/session-config.js +0 -162
  238. package/dist/session/session-cookie.d.ts +0 -72
  239. package/dist/session/session-cookie.js +0 -101
  240. package/dist/session/session-encryption.d.ts +0 -87
  241. package/dist/session/session-encryption.js +0 -139
  242. package/dist/session/session-errors.d.ts +0 -85
  243. package/dist/session/session-errors.js +0 -145
  244. package/dist/session/session-health.d.ts +0 -38
  245. package/dist/session/session-health.js +0 -60
  246. package/dist/session/session-keys.d.ts +0 -51
  247. package/dist/session/session-keys.js +0 -113
  248. package/dist/session/session-manager.d.ts +0 -73
  249. package/dist/session/session-manager.js +0 -94
  250. package/dist/session/session-metrics.d.ts +0 -33
  251. package/dist/session/session-metrics.js +0 -112
  252. package/dist/session/session-repository.d.ts +0 -161
  253. package/dist/session/session-repository.js +0 -683
  254. package/dist/session/session-scripts.d.ts +0 -36
  255. package/dist/session/session-scripts.js +0 -130
  256. package/dist/session/session-serializer.d.ts +0 -42
  257. package/dist/session/session-serializer.js +0 -248
  258. package/dist/session/session-service.d.ts +0 -104
  259. package/dist/session/session-service.js +0 -611
  260. package/dist/session/session-token.d.ts +0 -38
  261. package/dist/session/session-token.js +0 -86
  262. package/dist/session/session-types.d.ts +0 -253
  263. package/dist/session/session-types.js +0 -16
  264. package/dist/types.d.ts +0 -924
  265. package/dist/types.js +0 -151
  266. package/dist/utils/deepmerge.d.ts +0 -9
  267. package/dist/utils/deepmerge.js +0 -61
package/dist/cache.js DELETED
@@ -1,1120 +0,0 @@
1
- import zlib from 'node:zlib';
2
- import { promisify } from 'node:util';
3
- import { defaultLogger } from './logger.js';
4
- const gzip = promisify(zlib.gzip);
5
- const gunzip = promisify(zlib.gunzip);
6
- /**
7
- * Cache layer on top of {@link RedisClientWrapper} with JSON serialization,
8
- * optional gzip compression and namespace support.
9
- *
10
- * Works in all three modes (standalone, sentinel, cluster): multi-key operations
11
- * are slot-aware and pattern scans cover every cluster node.
12
- *
13
- * @example
14
- * ```ts
15
- * const cache = new Cache(client, { defaultTTL: 3600, compressionThreshold: 1024 });
16
- * await cache.set('user:1', { name: 'alice' });
17
- * const user = await cache.get('user:1');
18
- * ```
19
- */
20
- export class Cache {
21
- client;
22
- logger;
23
- // private config: RedisConfig;
24
- defaultTTL;
25
- compressionThreshold;
26
- namespace;
27
- /**
28
- * Creates a cache bound to a Redis client.
29
- *
30
- * **Parameters:**
31
- * - `client` - The underlying {@link RedisClientWrapper}. All cache operations
32
- * delegate to this client.
33
- * - `config` - Configuration object with the following fields:
34
- * - `defaultTTL` (number, optional, default: `3600`) - Default TTL in seconds
35
- * applied when no per-call TTL is specified.
36
- * - `compressionThreshold` (number, optional, default: `1024`) - Byte threshold
37
- * above which values are gzip-compressed transparently.
38
- * - `namespace` (string, optional, default: `cache`) - Namespace prefix for all keys.
39
- * - `logger` - Optional pino-compatible logger. Supports `trace/debug/info/warn/error/fatal`
40
- * levels and `child()` for namespace logging. Defaults to `console`.
41
- *
42
- * **Type Parameters:**
43
- * - `T` - The type of values stored/retrieved from the cache.
44
- *
45
- * **Example:**
46
- * ```ts
47
- * const cache = new Cache(client, { defaultTTL: 600, compressionThreshold: 2048, namespace: "myapp" });
48
- * ```
49
- */
50
- constructor(client, config, logger = defaultLogger) {
51
- this.client = client;
52
- this.logger = logger.child({ component: 'Cache' });
53
- // this.config = config;
54
- this.defaultTTL = config.defaultTTL || 3600;
55
- this.compressionThreshold = config.compressionThreshold || 1024;
56
- this.namespace = config.namespace?.trim() || "cache";
57
- }
58
- async serialize(value) {
59
- // Convert to Buffer
60
- let data;
61
- if (Buffer.isBuffer(value)) {
62
- data = value;
63
- }
64
- else if (typeof value === 'string') {
65
- data = Buffer.from(value);
66
- }
67
- else if (typeof value === 'number' || typeof value === 'boolean') {
68
- data = Buffer.from(String(value));
69
- }
70
- else {
71
- // JSON for objects
72
- data = Buffer.from(JSON.stringify(value));
73
- }
74
- // Compress if large enough
75
- if (data.length > this.compressionThreshold) {
76
- try {
77
- const compressed = await gzip(data);
78
- return { data: compressed, compressed: true };
79
- }
80
- catch (error) {
81
- this.logger.warn('Compression failed, storing uncompressed');
82
- return { data, compressed: false };
83
- }
84
- }
85
- return { data, compressed: false };
86
- }
87
- async deserialize(data, compressed) {
88
- let buffer = data;
89
- if (compressed) {
90
- try {
91
- buffer = await gunzip(data);
92
- }
93
- catch (error) {
94
- this.logger.warn('Decompression failed, trying raw data');
95
- // Attempt to use raw data if decompression fails
96
- }
97
- }
98
- // Try to parse as JSON if it looks like JSON
99
- const str = buffer.toString();
100
- try {
101
- if (str.startsWith('{') || str.startsWith('[')) {
102
- return JSON.parse(str);
103
- }
104
- }
105
- catch {
106
- // Not JSON, return as string
107
- }
108
- return str;
109
- }
110
- getKey(key, namespace) {
111
- if (namespace?.trim()) {
112
- return `${this.namespace}:${namespace.trim()}:${key}`;
113
- }
114
- return `${this.namespace}:${key}`;
115
- }
116
- /**
117
- * Reads a cached value.
118
- *
119
- * Objects are parsed from JSON and compressed values are transparently
120
- * decompressed. Strings that are not JSON are returned as-is.
121
- *
122
- * @param key - Cache key.
123
- * @param namespace - Optional namespace prefix (`namespace:key`).
124
- * @returns The stored value, or `null` when missing.
125
- *
126
- * @example
127
- * ```ts
128
- * const user = await cache.get<User>('user:1');
129
- * const token = await cache.get('token', 'auth');
130
- * ```
131
- */
132
- /**
133
- * Reads a cached value.
134
- *
135
- * **Behavior:**
136
- * - Objects are parsed from JSON when stored as JSON.
137
- * - Values larger than `compressionThreshold` bytes are transparently
138
- * decompressed (gzip) when reading.
139
- * - Strings that are not JSON are returned as-is.
140
- * - If the key does not exist, returns `null`.
141
- *
142
- * **Type Parameters:**
143
- * - `T` - The expected return type. When the stored value is a JSON object/array,
144
- * it will be parsed and returned as `T`. When it's a primitive (string/number/bool),
145
- * it is returned as-is and typed as `T`.
146
- *
147
- * **Returns:**
148
- * - The stored value, parsed as `T` when possible, or `null` when the key is missing.
149
- *
150
- * **Example:**
151
- * ```ts
152
- * // Store an object
153
- * await cache.set('user:1', { name: 'alice', age: 30 });
154
- *
155
- * // Read it back with type coercion
156
- * const user: { name: string; age: number } | null = await cache.get<UserProfile>('user:1');
157
- * // user === { name: 'alice', age: 30 }
158
- *
159
- * // Read a string value
160
- * const token = await cache.get('token'); // 'abc' | null
161
- * ```
162
- *
163
- * **Parameters:**
164
- * - `key` - Cache key.
165
- * - `namespace` - Optional namespace prefix (`namespace:key`). When provided,
166
- * the key is internally transformed to `${namespace}:${key}`.
167
- *
168
- * @returns The stored value, or `null` when missing.
169
- */
170
- async get(key, namespace) {
171
- const fullKey = this.getKey(key, namespace);
172
- const raw = await this.client.get(fullKey);
173
- if (!raw)
174
- return null;
175
- try {
176
- // Check if stored with metadata
177
- const parsed = JSON.parse(raw);
178
- if (parsed._compressed && parsed._data) {
179
- const data = Buffer.from(parsed._data, 'base64');
180
- return this.deserialize(data, parsed._compressed);
181
- }
182
- // Legacy format - try to parse as JSON
183
- return JSON.parse(raw);
184
- }
185
- catch {
186
- // Raw string value
187
- return raw;
188
- }
189
- }
190
- /**
191
- * Stores a value in the cache.
192
- *
193
- * @param key - Cache key.
194
- * @param value - Any serializable value (string, number, boolean, Buffer, object).
195
- * @param options - `ttl` in seconds (defaults to `defaultTTL`), `namespace`,
196
- * and `compress` (default `true`). Values larger than `compressionThreshold`
197
- * bytes are gzip-compressed.
198
- * @returns `true` when stored successfully.
199
- *
200
- * @example
201
- * ```ts
202
- * await cache.set('user:1', user, { ttl: 300 });
203
- * await cache.set('token', 'abc', { namespace: 'auth', compress: false });
204
- * ```
205
- */
206
- /**
207
- * Stores a value in the cache.
208
- *
209
- * **Behavior:**
210
- * - Values are JSON-serialized when they are objects, arrays, or booleans/strings/numbers
211
- * are stored as-is.
212
- * - Values larger than `compressionThreshold` bytes are gzip-compressed transparently.
213
- * The compressed form is stored with metadata (`_compressed: true`, `_data: base64`) so
214
- * it is transparently decompressed on read.
215
- * - Set `compress: false` to disable compression for a single write, regardless of size.
216
- * - Per-call TTL overrides the cache's `defaultTTL`.
217
- * - Per-call `namespace` overrides the cache's configured namespace for that operation.
218
- *
219
- * **Type Parameters:**
220
- * - `T` - The type of the value being stored. Can be any serializable JavaScript value.
221
- *
222
- * **Returns:**
223
- * - `true` when the value was stored successfully (`result === 'OK'`).
224
- *
225
- * **Example:**
226
- * ```ts
227
- * // Store an object with a custom TTL
228
- * await cache.set('user:1', { name: 'alice' }, { ttl: 300 });
229
- *
230
- * // Store with compression disabled
231
- * await cache.set('token', 'abc123', { compress: false });
232
- *
233
- * // Store with a namespace
234
- * await cache.set('token', 'abc', { namespace: 'auth' });
235
- * ```
236
- *
237
- * **Parameters:**
238
- * - `key` - Cache key.
239
- * - `value` - Any serializable value (string, number, boolean, Buffer, or object).
240
- * - `options` - Optional configuration:
241
- * - `ttl` (number, optional) - TTL in seconds. Falls back to `defaultTTL`.
242
- * - `namespace` (string, optional) - Namespace prefix. Falls back to cache config.
243
- * - `compress` (boolean, optional) - Force compression or disable it. Defaults to `true`.
244
- *
245
- * @returns `true` when stored successfully.
246
- */
247
- async set(key, value, options = {}) {
248
- const fullKey = this.getKey(key, options.namespace);
249
- const ttl = options.ttl || this.defaultTTL;
250
- const shouldCompress = options.compress !== undefined ? options.compress : true;
251
- try {
252
- let rawValue;
253
- if (shouldCompress) {
254
- const { data, compressed } = await this.serialize(value);
255
- if (compressed) {
256
- // Store with metadata
257
- rawValue = JSON.stringify({
258
- _compressed: true,
259
- _data: data.toString('base64'),
260
- });
261
- }
262
- else {
263
- rawValue = data;
264
- }
265
- }
266
- else {
267
- if (typeof value === 'string') {
268
- rawValue = value;
269
- }
270
- else if (Buffer.isBuffer(value)) {
271
- rawValue = value;
272
- }
273
- else {
274
- rawValue = JSON.stringify(value);
275
- }
276
- }
277
- const result = await this.client.set(fullKey, rawValue, ttl);
278
- this.logger.debug('Cache set', { key: fullKey, ttl, compressed: shouldCompress });
279
- return result === 'OK';
280
- }
281
- catch (error) {
282
- this.logger.error('Cache set failed:', error);
283
- return false;
284
- }
285
- }
286
- /**
287
- * Stores a value only if the key does not exist yet (`SETNX`).
288
- *
289
- * @param key - Cache key.
290
- * @param value - The value to store.
291
- * @param options - `ttl` in seconds and `namespace`.
292
- * @returns `true` only when the value was actually stored.
293
- *
294
- * @example
295
- * ```ts
296
- * const claimed = await cache.setNX('job:1', 'worker-1', { ttl: 60 });
297
- * ```
298
- */
299
- /**
300
- * Stores a value only if the key does not exist yet (`SETNX`).
301
- *
302
- * **Behavior:**
303
- * - The value is stored atomically using Redis `SET key value EX ttl NX`.
304
- * - Returns `true` only when the key did not exist and the value was set.
305
- * - Per-call TTL overrides the cache's `defaultTTL`.
306
- * - Per-call `namespace` is applied to the key.
307
- *
308
- * **Type Parameters:**
309
- * - `T` - The type of the value being stored. Will be JSON-stringified if not a string.
310
- *
311
- * **Returns:**
312
- * - `true` only when the value was actually stored (Redis SETNX returned `1`).
313
- *
314
- * **Example:**
315
- * ```ts
316
- * const claimed = await cache.setNX('job:1', 'worker-1', { ttl: 60 });
317
- * // claimed === true (job was claimed by this worker)
318
- * ```
319
- *
320
- * **Parameters:**
321
- * - `key` - Cache key.
322
- * - `value` - The value to store. String stored as-is; objects are JSON-stringified.
323
- * - `options` - Optional configuration:
324
- * - `ttl` (number, optional) - TTL in seconds. Falls back to `defaultTTL`.
325
- * - `namespace` (string, optional) - Namespace prefix.
326
- *
327
- * @returns `true` only when the value was actually stored.
328
- */
329
- async setNX(key, value, options = {}) {
330
- const fullKey = this.getKey(key, options.namespace);
331
- const ttl = options.ttl || this.defaultTTL;
332
- try {
333
- const rawValue = typeof value === 'string' ? value : JSON.stringify(value);
334
- const result = await this.client.setnx(fullKey, rawValue, ttl);
335
- return result === 1;
336
- }
337
- catch (error) {
338
- this.logger.error('Cache setNX failed:', error);
339
- return false;
340
- }
341
- }
342
- /**
343
- * Stores a value only if the key does not exist yet, atomically with the TTL
344
- * (`SET ... EX NX`).
345
- *
346
- * @param key - Cache key.
347
- * @param value - The value to store.
348
- * @param options - `ttl` in seconds and `namespace`.
349
- * @returns `true` only when the value was actually stored.
350
- *
351
- * @example
352
- * ```ts
353
- * const locked = await cache.setEXNX('lock:order:42', 'txn-id', { ttl: 30 });
354
- * ```
355
- */
356
- /**
357
- * Stores a value only if the key does not exist yet, atomically with the TTL
358
- * (`SET ... EX NX`).
359
- *
360
- * **Behavior:**
361
- * - The value is stored atomically using Redis `SET key value EX ttl NX`.
362
- * - This is the atomic equivalent of calling `SET key value EX ttl` followed by
363
- * `SET key value NX` - but done in a single Redis call.
364
- * - Returns `true` only when the key did not exist and the value was set with TTL.
365
- * - Per-call TTL overrides the cache's `defaultTTL`.
366
- * - Per-call `namespace` is applied to the key.
367
- *
368
- * **Type Parameters:**
369
- * - `T` - The type of the value being stored. Will be JSON-stringified if not a string.
370
- *
371
- * **Returns:**
372
- * - `true` only when the value was actually stored (Redis SET returned `OK`).
373
- *
374
- * **Example:**
375
- * ```ts
376
- * const locked = await cache.setEXNX('lock:order:42', 'txn-id', { ttl: 30 });
377
- * // locked === true (lock was acquired with 30s TTL)
378
- * ```
379
- *
380
- * **Parameters:**
381
- * - `key` - Cache key.
382
- * - `value` - The value to store. String stored as-is; objects are JSON-stringified.
383
- * - `options` - Optional configuration:
384
- * - `ttl` (number, optional) - TTL in seconds. Falls back to `defaultTTL`.
385
- * - `namespace` (string, optional) - Namespace prefix.
386
- *
387
- * @returns `true` only when the value was actually stored.
388
- */
389
- async setEXNX(key, value, options = {}) {
390
- const fullKey = this.getKey(key, options.namespace);
391
- const ttl = options.ttl || this.defaultTTL;
392
- try {
393
- const rawValue = typeof value === 'string' ? value : JSON.stringify(value);
394
- const result = await this.client.setexnx(fullKey, rawValue, ttl);
395
- return result === 'OK';
396
- }
397
- catch (error) {
398
- this.logger.error('Cache setEXNX failed:', error);
399
- return false;
400
- }
401
- }
402
- // Old mget - CROSSSLOT error in cluster mode when keys span different slots
403
- // async mget<T = any>(keys: string[], namespace?: string): Promise<(T | null)[]> {
404
- // const fullKeys = keys.map(k => this.getKey(k, namespace));
405
- // const raw = await this.client.mget(...fullKeys);
406
- //
407
- // return Promise.all(
408
- // raw.map(async (item) => {
409
- // if (!item) return null;
410
- // try {
411
- // const parsed = JSON.parse(item);
412
- // if (parsed._compressed && parsed._data) {
413
- // const data = Buffer.from(parsed._data, 'base64');
414
- // return this.deserialize<T>(data, parsed._compressed);
415
- // }
416
- // return parsed;
417
- // } catch {
418
- // return item as T;
419
- // }
420
- // })
421
- // );
422
- // }
423
- // Cluster-safe: groups keys by slot via mgetClusterAware
424
- /**
425
- * Reads multiple cache keys in one call.
426
- *
427
- * Cluster-safe: keys are grouped by hash slot under the hood.
428
- *
429
- * @param keys - Cache keys to read.
430
- * @param namespace - Optional namespace prefix applied to every key.
431
- * @returns Values in input order; `null` for missing keys.
432
- *
433
- * @example
434
- * ```ts
435
- * const [a, b] = await cache.mget(['user:1', 'user:2']);
436
- * ```
437
- */
438
- /**
439
- * Reads multiple cache keys in one call.
440
- *
441
- * **Behavior:**
442
- * - Cluster-safe: keys are grouped by hash slot under the hood, avoiding CROSS-SLOT errors.
443
- * - Values are deserialized from JSON when stored as JSON. Strings/numbers/buffers
444
- * are returned as-is.
445
- * - Missing keys return `null` in the corresponding position.
446
- *
447
- * **Type Parameters:**
448
- * - `T` - The expected type of each returned value. When the stored value is JSON,
449
- * it will be parsed and coerced to `T`.
450
- *
451
- * **Returns:**
452
- * - An array of values in the same order as the input `keys`. Each element is `T | null`.
453
- * `null` indicates the key did not exist.
454
- *
455
- * **Example:**
456
- * ```ts
457
- * const [a, b] = await cache.mget(['user:1', 'user:2']);
458
- * // a === { name: 'alice' }, b === { name: 'bob' }
459
- * ```
460
- *
461
- * **Parameters:**
462
- * - `keys` - Cache keys to read. Will have the namespace prefix applied automatically
463
- * if a namespace is configured.
464
- * - `namespace` - Optional namespace prefix applied to every key. When provided,
465
- * each key is internally transformed to `${namespace}:${key}`.
466
- *
467
- * @returns Values in input order; `null` for missing keys.
468
- */
469
- async mget(keys, namespace) {
470
- const fullKeys = keys.map(k => this.getKey(k, namespace));
471
- const raw = await this.client.mgetClusterAware(fullKeys);
472
- return Promise.all(raw.map(async (item) => {
473
- if (!item)
474
- return null;
475
- try {
476
- const parsed = JSON.parse(item);
477
- if (parsed._compressed && parsed._data) {
478
- const data = Buffer.from(parsed._data, 'base64');
479
- return this.deserialize(data, parsed._compressed);
480
- }
481
- return parsed;
482
- }
483
- catch {
484
- return item;
485
- }
486
- }));
487
- }
488
- // Old mset - a pipeline whose keys span different slots is rejected in cluster mode
489
- // async mset<T>(
490
- // entries: Record<string, T>,
491
- // options: CacheOptions = {}
492
- // ): Promise<boolean> {
493
- // const ttl = options.ttl || this.defaultTTL;
494
- // const namespace = options.namespace;
495
- //
496
- // try {
497
- // const pipeline = this.client.pipeline();
498
- //
499
- // for (const [key, value] of Object.entries(entries)) {
500
- // const fullKey = this.getKey(key, namespace);
501
- // const rawValue = typeof value === 'string' ? value : JSON.stringify(value);
502
- // pipeline.set(fullKey, rawValue, 'EX', ttl);
503
- // }
504
- //
505
- // const results = await pipeline.exec();
506
- // return !!results?.every((result: any) => result[1] === 'OK');
507
- // } catch (error) {
508
- // this.logger.error('Cache mset failed:', error as Record<string, any>);
509
- // return false;
510
- // }
511
- // }
512
- // Cluster-safe: one pipeline per hash slot
513
- /**
514
- * Stores multiple key/value entries in one call.
515
- *
516
- * Cluster-safe: entries are grouped by hash slot, one pipeline per slot.
517
- *
518
- * @param entries - Object mapping cache keys to values.
519
- * @param options - `ttl` in seconds (defaults to `defaultTTL`) and `namespace`.
520
- * @returns `true` when every entry was stored.
521
- *
522
- * @example
523
- * ```ts
524
- * await cache.mset({ 'user:1': alice, 'user:2': bob }, { ttl: 300 });
525
- * ```
526
- */
527
- /**
528
- * Stores multiple key/value entries in one call.
529
- *
530
- * **Behavior:**
531
- * - Cluster-safe: entries are grouped by hash slot, one pipeline per slot.
532
- * This avoids CROSS-SLOT errors that would occur if keys spanned multiple slots in a
533
- * single pipeline.
534
- * - Values are JSON-serialized when they are objects; strings/buffers are stored as-is.
535
- * - Per-call TTL overrides the cache's `defaultTTL`.
536
- * - Per-call `namespace` is applied to all keys.
537
- *
538
- * **Type Parameters:**
539
- * - `T` - The type of values being stored. Objects are JSON-stringified; strings/buffers
540
- * are stored as-is.
541
- *
542
- * **Returns:**
543
- * - `true` when every entry was stored successfully.
544
- * - `false` if any entry failed to store.
545
- *
546
- * **Example:**
547
- * ```ts
548
- * await cache.mset({ 'user:1': alice, 'user:2': bob }, { ttl: 300 });
549
- * // Both entries stored with a 5-minute TTL
550
- * ```
551
- *
552
- * **Parameters:**
553
- * - `entries` - Object mapping cache keys to values.
554
- * - `options` - Optional configuration:
555
- * - `ttl` (number, optional) - TTL in seconds. Falls back to `defaultTTL`.
556
- * - `namespace` (string, optional) - Namespace prefix. Applied to all keys.
557
- *
558
- * @returns `true` when every entry was stored.
559
- */
560
- async mset(entries, options = {}) {
561
- const ttl = options.ttl || this.defaultTTL;
562
- const namespace = options.namespace;
563
- try {
564
- const groups = new Map();
565
- for (const [key, value] of Object.entries(entries)) {
566
- const fullKey = this.getKey(key, namespace);
567
- const rawValue = typeof value === 'string' ? value : JSON.stringify(value);
568
- const slot = this.client.calculateSlot(fullKey);
569
- if (!groups.has(slot)) {
570
- groups.set(slot, []);
571
- }
572
- groups.get(slot).push([fullKey, rawValue]);
573
- }
574
- for (const group of groups.values()) {
575
- const pipeline = this.client.pipeline();
576
- for (const [fullKey, rawValue] of group) {
577
- pipeline.set(fullKey, rawValue, 'EX', ttl);
578
- }
579
- const results = await pipeline.exec();
580
- if (!results?.every((result) => result[1] === 'OK')) {
581
- return false;
582
- }
583
- }
584
- return true;
585
- }
586
- catch (error) {
587
- this.logger.error('Cache mset failed:', error);
588
- return false;
589
- }
590
- }
591
- /**
592
- * Deletes a cache key.
593
- *
594
- * @param key - Cache key.
595
- * @param namespace - Optional namespace prefix.
596
- * @returns `true` if the key existed and was deleted.
597
- *
598
- * @example
599
- * ```ts
600
- * const removed = await cache.delete('user:1');
601
- * ```
602
- */
603
- /**
604
- * Deletes a cache key.
605
- *
606
- * **Behavior:**
607
- * - Deletes the full key (including any namespace prefix).
608
- * - Returns `true` only when the key existed and was deleted (Redis DEL returned `1`).
609
- *
610
- * **Returns:**
611
- * - `true` if the key existed and was deleted.
612
- *
613
- * **Example:**
614
- * ```ts
615
- * const removed = await cache.delete('user:1');
616
- * // removed === true
617
- * ```
618
- *
619
- * **Parameters:**
620
- * - `key` - Cache key.
621
- * - `namespace` - Optional namespace prefix.
622
- *
623
- * @returns `true` if the key existed and was deleted.
624
- */
625
- async delete(key, namespace) {
626
- const fullKey = this.getKey(key, namespace);
627
- const result = await this.client.del(fullKey);
628
- return result > 0;
629
- }
630
- /**
631
- * Checks whether a cache key exists.
632
- *
633
- * @param key - Cache key.
634
- * @param namespace - Optional namespace prefix.
635
- * @returns `true` if the key exists.
636
- *
637
- * @example
638
- * ```ts
639
- * const cached = await cache.exists('user:1');
640
- * ```
641
- */
642
- /**
643
- * Checks whether a cache key exists.
644
- *
645
- * **Returns:**
646
- * - `true` if the key exists in Redis.
647
- * - `false` if the key does not exist.
648
- *
649
- * **Example:**
650
- * ```ts
651
- * const cached = await cache.exists('user:1');
652
- * // cached === true when 'user:1' has been set
653
- * ```
654
- *
655
- * **Parameters:**
656
- * - `key` - Cache key.
657
- * - `namespace` - Optional namespace prefix.
658
- *
659
- * @returns `true` if the key exists.
660
- */
661
- async exists(key, namespace) {
662
- const fullKey = this.getKey(key, namespace);
663
- const result = await this.client.exists(fullKey);
664
- return result === 1;
665
- }
666
- /**
667
- * Sets the TTL of an existing cache key.
668
- *
669
- * @param key - Cache key.
670
- * @param ttl - TTL in seconds.
671
- * @param namespace - Optional namespace prefix.
672
- * @returns `true` if the TTL was applied.
673
- *
674
- * @example
675
- * ```ts
676
- * const extended = await cache.expire('session:42', 3600);
677
- * ```
678
- */
679
- /**
680
- * Sets the TTL of an existing cache key.
681
- *
682
- * **Returns:**
683
- * - `true` if the TTL was applied (Redis EXPIRE returned `1`).
684
- * - `false` if the key did not exist.
685
- *
686
- * **Example:**
687
- * ```ts
688
- * const extended = await cache.expire('session:42', 3600);
689
- * // extended === true (TTL was set to 1 hour)
690
- * ```
691
- *
692
- * **Parameters:**
693
- * - `key` - Cache key.
694
- * - `ttl` - TTL in seconds.
695
- * - `namespace` - Optional namespace prefix.
696
- *
697
- * @returns `true` if the TTL was applied.
698
- */
699
- async expire(key, ttl, namespace) {
700
- const fullKey = this.getKey(key, namespace);
701
- const result = await this.client.expire(fullKey, ttl);
702
- return result === 1;
703
- }
704
- /**
705
- * Returns the remaining TTL of a cache key in seconds.
706
- *
707
- * @param key - Cache key.
708
- * @param namespace - Optional namespace prefix.
709
- * @returns Remaining TTL in seconds (`-2` if missing, `-1` if no TTL).
710
- *
711
- * @example
712
- * ```ts
713
- * const secondsLeft = await cache.ttl('session:42');
714
- * ```
715
- */
716
- /**
717
- * Returns the remaining TTL of a cache key in seconds.
718
- *
719
- * **Returns:**
720
- * - The remaining TTL in seconds.
721
- * - `-2` if the key does not exist.
722
- * - `-1` if the key exists but has no TTL set.
723
- *
724
- * **Example:**
725
- * ```ts
726
- * const secondsLeft = await cache.ttl('session:42');
727
- * // secondsLeft === 2500 (approximately 42 minutes remaining)
728
- * ```
729
- *
730
- * **Parameters:**
731
- * - `key` - Cache key.
732
- * - `namespace` - Optional namespace prefix.
733
- *
734
- * @returns Remaining TTL in seconds (`-2` if missing, `-1` if no TTL).
735
- */
736
- async ttl(key, namespace) {
737
- const fullKey = this.getKey(key, namespace);
738
- return this.client.ttl(fullKey);
739
- }
740
- /**
741
- * Atomically increments a cache counter.
742
- *
743
- * @param key - Counter key.
744
- * @param by - Amount to increment by (default `1`; ignored by Redis, kept for API parity).
745
- * @param namespace - Optional namespace prefix.
746
- * @returns The new counter value.
747
- *
748
- * @example
749
- * ```ts
750
- * const visits = await cache.increment('stats:visits');
751
- * ```
752
- */
753
- /**
754
- * Atomically increments a cache counter.
755
- *
756
- * **Behavior:**
757
- * - Uses Redis `INCR` command on the full key (including namespace if set).
758
- * - The counter starts at `0` if the key does not exist, then increments to `1`.
759
- * - The `by` parameter is passed to Redis but note: Redis `INCR` always increments
760
- * by `1`. The `by` parameter is kept for API parity with other cache implementations
761
- * but has no effect on the actual Redis command result.
762
- *
763
- * **Returns:**
764
- * - The new counter value (the value after incrementing).
765
- *
766
- * **Example:**
767
- * ```ts
768
- * const visits = await cache.increment('stats:visits');
769
- * // visits === 1 (first increment)
770
- * const more = await cache.increment('stats:visits', 5); // by parameter ignored
771
- * // more === 2
772
- * ```
773
- *
774
- * **Parameters:**
775
- * - `key` - Counter key.
776
- * - `by` - Amount to increment by (default `1`). Note: Redis `INCR` always increments
777
- * by `1`; this parameter is kept for API parity.
778
- * - `namespace` - Optional namespace prefix.
779
- *
780
- * @returns The new counter value.
781
- */
782
- async increment(key, by = 1, namespace) {
783
- const fullKey = this.getKey(key, namespace);
784
- return this.client.incr(fullKey);
785
- }
786
- /**
787
- * Atomically decrements a cache counter.
788
- *
789
- * @param key - Counter key.
790
- * @param by - Amount to decrement by (default `1`; ignored by Redis, kept for API parity).
791
- * @param namespace - Optional namespace prefix.
792
- * @returns The new counter value.
793
- *
794
- * @example
795
- * ```ts
796
- * const stock = await cache.decrement('inventory:sku-1');
797
- * ```
798
- */
799
- /**
800
- * Atomically decrements a cache counter.
801
- *
802
- * **Behavior:**
803
- * - Uses Redis `DECR` command on the full key (including namespace if set).
804
- * - The `by` parameter is passed to Redis but note: Redis `DECR` always decrements
805
- * by `1`. The `by` parameter is kept for API parity with other cache implementations
806
- * but has no effect on the actual Redis command result.
807
- *
808
- * **Returns:**
809
- * - The new counter value (the value after decrementing).
810
- *
811
- * **Example:**
812
- * ```ts
813
- * const stock = await cache.decrement('inventory:sku-1');
814
- * // stock === 99 (started at 100, decremented by 1)
815
- * ```
816
- *
817
- * **Parameters:**
818
- * - `key` - Counter key.
819
- * - `by` - Amount to decrement by (default `1`). Note: Redis `DECR` always decrements
820
- * by `1`; this parameter is kept for API parity.
821
- * - `namespace` - Optional namespace prefix.
822
- *
823
- * @returns The new counter value.
824
- */
825
- async decrement(key, by = 1, namespace) {
826
- const fullKey = this.getKey(key, namespace);
827
- return this.client.decr(fullKey);
828
- }
829
- // Hash helpers
830
- /**
831
- * Reads a field from a hash-style cache key.
832
- *
833
- * @param key - Cache key.
834
- * @param field - Hash field.
835
- * @param namespace - Optional namespace prefix.
836
- * @returns The field value (JSON-parsed when possible), or `null`.
837
- *
838
- * @example
839
- * ```ts
840
- * const name = await cache.hget('user:1', 'name');
841
- * ```
842
- */
843
- /**
844
- * Reads a field from a hash-style cache key.
845
- *
846
- * **Behavior:**
847
- * - The field value is retrieved from the Redis hash.
848
- * - When the stored value is a JSON string, it is parsed and returned as the typed result.
849
- * - When the stored value is not JSON, it is returned as a raw string, typed as `T`.
850
- *
851
- * **Returns:**
852
- * - The field value, parsed as `T` when possible, or `null` when the key or field does not exist.
853
- *
854
- * **Example:**
855
- * ```ts
856
- * const name = await cache.hget('user:1', 'name');
857
- * // name === 'alice' | null
858
- * ```
859
- *
860
- * **Parameters:**
861
- * - `key` - Cache key (hash key in Redis).
862
- * - `field` - Hash field to read.
863
- * - `namespace` - Optional namespace prefix. Applied to the key.
864
- *
865
- * @returns The field value, or `null`.
866
- */
867
- async hget(key, field, namespace) {
868
- const fullKey = this.getKey(key, namespace);
869
- const result = await this.client.hget(fullKey, field);
870
- if (!result)
871
- return null;
872
- try {
873
- return JSON.parse(result);
874
- }
875
- catch {
876
- return result;
877
- }
878
- }
879
- /**
880
- * Writes a field into a hash-style cache key.
881
- *
882
- * @param key - Cache key.
883
- * @param field - Hash field.
884
- * @param value - Any serializable value (JSON-stringified unless it is a string).
885
- * @param namespace - Optional namespace prefix.
886
- * @returns `true` if a new field was created.
887
- *
888
- * @example
889
- * ```ts
890
- * await cache.hset('user:1', 'age', 30);
891
- * ```
892
- */
893
- /**
894
- * Writes a field into a hash-style cache key.
895
- *
896
- * **Behavior:**
897
- * - The value is JSON-stringified when it is not a string (objects, arrays, etc.).
898
- * Strings are stored as-is.
899
- * - Returns `true` only when a new field was created (Redis HSET returned `1`).
900
- * If the field already exists, its value is overwritten and `true` is still returned.
901
- *
902
- * **Returns:**
903
- * - `true` if a new field was created.
904
- *
905
- * **Example:**
906
- * ```ts
907
- * await cache.hset('user:1', 'age', 30);
908
- * // Field 'age' set to '30' (stringified) in hash 'user:1'
909
- * ```
910
- *
911
- * **Parameters:**
912
- * - `key` - Cache key (hash key in Redis).
913
- * - `field` - Hash field to write.
914
- * - `value` - Any serializable value. Strings stored as-is; objects are JSON-stringified.
915
- * - `namespace` - Optional namespace prefix. Applied to the key.
916
- *
917
- * @returns `true` if a new field was created.
918
- */
919
- async hset(key, field, value, namespace) {
920
- const fullKey = this.getKey(key, namespace);
921
- const rawValue = typeof value === 'string' ? value : JSON.stringify(value);
922
- const result = await this.client.hset(fullKey, field, rawValue);
923
- return result === 1;
924
- }
925
- /**
926
- * Returns every field of a hash-style cache key.
927
- *
928
- * @param key - Cache key.
929
- * @param namespace - Optional namespace prefix.
930
- * @returns Object mapping fields to values (JSON-parsed when possible).
931
- *
932
- * @example
933
- * ```ts
934
- * const profile = await cache.hgetall('user:1');
935
- * ```
936
- */
937
- /**
938
- * Returns every field of a hash-style cache key.
939
- *
940
- * **Behavior:**
941
- * - Retrieves all fields and values from the Redis hash.
942
- * - Each value is parsed from JSON when possible. Non-JSON values are returned as raw strings.
943
- * - Returns an empty object `{}` when the key does not exist or has no fields.
944
- *
945
- * **Returns:**
946
- * - An object mapping field names to their values, with values parsed as `T` when possible.
947
- *
948
- * **Example:**
949
- * ```ts
950
- * const profile = await cache.hgetall('user:1');
951
- * // profile === { age: 30, name: 'alice' }
952
- * ```
953
- *
954
- * **Parameters:**
955
- * - `key` - Cache key (hash key in Redis).
956
- * - `namespace` - Optional namespace prefix. Applied to the key.
957
- *
958
- * @returns Object mapping fields to values (JSON-parsed when possible).
959
- */
960
- async hgetall(key, namespace) {
961
- const fullKey = this.getKey(key, namespace);
962
- const result = await this.client.hgetall(fullKey);
963
- const parsed = {};
964
- for (const [field, value] of Object.entries(result)) {
965
- try {
966
- parsed[field] = JSON.parse(value);
967
- }
968
- catch {
969
- parsed[field] = value;
970
- }
971
- }
972
- return parsed;
973
- }
974
- // Delete by pattern
975
- /**
976
- * Deletes every cache key matching a glob pattern.
977
- *
978
- * Cluster-safe: scans every node before deleting.
979
- *
980
- * @param pattern - Glob pattern, e.g. `'user:*'`.
981
- * @param namespace - Optional namespace prefix (`namespace:pattern`).
982
- * @returns The number of deleted keys.
983
- *
984
- * @example
985
- * ```ts
986
- * const removed = await cache.deletePattern('temp:*');
987
- * ```
988
- */
989
- /**
990
- * Deletes every cache key matching a glob pattern.
991
- *
992
- * **Behavior:**
993
- * - Cluster-safe: scans every node before deleting.
994
- * - Uses `SCAN` iteratively to avoid blocking the Redis server on large datasets.
995
- * - Each matching key is individually deleted via `DEL`.
996
- * - The `batchSize` and `scanCount` options control the scan batching.
997
- *
998
- * **Returns:**
999
- * - The number of deleted keys.
1000
- *
1001
- * **Example:**
1002
- * ```ts
1003
- * const removed = await cache.deletePattern('temp:*');
1004
- * // removed === number of keys matching 'temp:*' that were deleted
1005
- * ```
1006
- *
1007
- * **Parameters:**
1008
- * - `pattern` - Glob pattern, e.g. `'temp:*'`.
1009
- * - `namespace` - Optional namespace prefix. The pattern is transformed to
1010
- * `${namespace}:${pattern}` before scanning.
1011
- *
1012
- * @returns The number of deleted keys.
1013
- */
1014
- async deletePattern(pattern, namespace) {
1015
- let fullPattern;
1016
- if (namespace?.trim()) {
1017
- fullPattern = `${this.namespace}:${namespace.trim()}:${pattern}`;
1018
- }
1019
- else {
1020
- fullPattern = `${this.namespace}:${pattern}`;
1021
- }
1022
- let deleted = 0;
1023
- for await (const key of this.client.scanIterator(fullPattern)) {
1024
- const result = await this.client.del(key);
1025
- deleted += result;
1026
- }
1027
- return deleted;
1028
- }
1029
- // Get all keys matching pattern
1030
- /**
1031
- * Lists every cache key matching a glob pattern.
1032
- *
1033
- * Cluster-safe: scans every node.
1034
- *
1035
- * @param pattern - Glob pattern, e.g. `'session:*'`.
1036
- * @param namespace - Optional namespace prefix (`namespace:pattern`).
1037
- * @returns Matching keys.
1038
- *
1039
- * @example
1040
- * ```ts
1041
- * const sessions = await cache.keys('session:*');
1042
- * ```
1043
- */
1044
- /**
1045
- * Lists every cache key matching a glob pattern.
1046
- *
1047
- * **Behavior:**
1048
- * - Cluster-safe: scans every node.
1049
- * - Uses `SCAN` iteratively to avoid blocking the Redis server on large datasets.
1050
- * - Returns all keys matching the glob pattern across all nodes (in cluster mode).
1051
- *
1052
- * **Returns:**
1053
- * - An array of matching keys (relative format, without namespace prefix unless
1054
- * one was provided in the options).
1055
- *
1056
- * **Example:**
1057
- * ```ts
1058
- * const sessions = await cache.keys('session:*');
1059
- * // sessions === ['session:1', 'session:2', ...]
1060
- * ```
1061
- *
1062
- * **Parameters:**
1063
- * - `pattern` - Glob pattern, e.g. `'session:*'`.
1064
- * - `namespace` - Optional namespace prefix. The pattern is transformed to
1065
- * `${namespace}:${pattern}` before scanning.
1066
- *
1067
- * @returns Matching keys.
1068
- */
1069
- async keys(pattern, namespace) {
1070
- let fullPattern;
1071
- if (namespace?.trim()) {
1072
- fullPattern = `${this.namespace}:${namespace.trim()}:${pattern}`;
1073
- }
1074
- else {
1075
- fullPattern = `${this.namespace}:${pattern}`;
1076
- }
1077
- const keys = [];
1078
- for await (const key of this.client.scanIterator(fullPattern)) {
1079
- keys.push(key);
1080
- }
1081
- return keys;
1082
- }
1083
- // Clear entire namespace
1084
- /**
1085
- * Deletes every key inside a namespace.
1086
- *
1087
- * @param namespace - Namespace to wipe (`namespace:*`).
1088
- * @returns The number of deleted keys.
1089
- *
1090
- * @example
1091
- * ```ts
1092
- * const cleared = await cache.clearNamespace('sessions');
1093
- * ```
1094
- */
1095
- /**
1096
- * Deletes every key inside a namespace.
1097
- *
1098
- * **Behavior:**
1099
- * - Deletes all keys matching the pattern `*` within the specified namespace.
1100
- * - Internally calls {@link deletePattern} with the pattern `*` and the given namespace.
1101
- *
1102
- * **Returns:**
1103
- * - The number of deleted keys.
1104
- *
1105
- * **Example:**
1106
- * ```ts
1107
- * const cleared = await cache.clearNamespace('sessions');
1108
- * // cleared === number of keys deleted in the 'sessions' namespace
1109
- * ```
1110
- *
1111
- * **Parameters:**
1112
- * - `namespace` - Namespace to wipe (e.g. `'sessions'`). Every key of the form
1113
- * `namespace:*` will be deleted.
1114
- *
1115
- * @returns The number of deleted keys.
1116
- */
1117
- async clearNamespace(namespace) {
1118
- return this.deletePattern('*', namespace);
1119
- }
1120
- }