@prismakit/redis 3.0.2 → 3.2.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 +99 -58
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +37 -15
- package/dist/index.d.ts +37 -15
- package/dist/index.js +97 -58
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/redis-cache-adapter.integration.spec.ts +46 -0
- package/src/__tests__/redis-cache-adapter.spec.ts +14 -0
- package/src/index.ts +4 -0
- package/src/redis-cache-adapter.ts +42 -50
- package/src/redis-json.ts +115 -17
package/dist/index.cjs
CHANGED
|
@@ -32,6 +32,8 @@ var index_exports = {};
|
|
|
32
32
|
__export(index_exports, {
|
|
33
33
|
RedisCacheAdapter: () => RedisCacheAdapter,
|
|
34
34
|
cacheDebugStorage: () => import_core.cacheDebugStorage,
|
|
35
|
+
cloneWithCodec: () => cloneWithCodec,
|
|
36
|
+
createRedisJsonReviver: () => createRedisJsonReviver,
|
|
35
37
|
isCacheDebugEnabled: () => import_core.isCacheDebugEnabled,
|
|
36
38
|
recordCacheDebug: () => import_core.recordCacheDebug,
|
|
37
39
|
redisJsonParse: () => redisJsonParse,
|
|
@@ -47,26 +49,83 @@ var import_zlib = require("zlib");
|
|
|
47
49
|
|
|
48
50
|
// src/redis-json.ts
|
|
49
51
|
var BIGINT_TAG = "__bigint";
|
|
52
|
+
var DATE_TAG = "__date";
|
|
53
|
+
var BYTES_TAG = "__bytes";
|
|
54
|
+
var DECIMAL_TAG = "__decimal";
|
|
55
|
+
function isPlainObject(value) {
|
|
56
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) && !(value instanceof Date) && !Buffer.isBuffer(value);
|
|
57
|
+
}
|
|
58
|
+
function isDecimalLike(value) {
|
|
59
|
+
return isPlainObject(value) && typeof value.constructor?.name === "string" && (value.constructor.name === "Decimal" || typeof value.toFixed === "function" && typeof value.d === "object");
|
|
60
|
+
}
|
|
50
61
|
function redisJsonReplacer(_key, value) {
|
|
51
62
|
if (typeof value === "bigint") {
|
|
52
63
|
return { [BIGINT_TAG]: value.toString() };
|
|
53
64
|
}
|
|
65
|
+
if (value instanceof Date) {
|
|
66
|
+
return { [DATE_TAG]: value.toISOString() };
|
|
67
|
+
}
|
|
68
|
+
if (Buffer.isBuffer(value)) {
|
|
69
|
+
return { [BYTES_TAG]: value.toString("base64") };
|
|
70
|
+
}
|
|
71
|
+
if (value && typeof value === "object" && value.type === "Buffer" && Array.isArray(value.data)) {
|
|
72
|
+
return {
|
|
73
|
+
[BYTES_TAG]: Buffer.from(
|
|
74
|
+
value.data
|
|
75
|
+
).toString("base64")
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
if (isDecimalLike(value)) {
|
|
79
|
+
return { [DECIMAL_TAG]: value.toFixed() };
|
|
80
|
+
}
|
|
54
81
|
return value;
|
|
55
82
|
}
|
|
56
|
-
function
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
if (
|
|
60
|
-
|
|
83
|
+
function createRedisJsonReviver(options = {}) {
|
|
84
|
+
const decimalFactory = options.decimalFactory ?? ((s) => s);
|
|
85
|
+
return (_key, value) => {
|
|
86
|
+
if (!isPlainObject(value)) return value;
|
|
87
|
+
const keys = Object.keys(value);
|
|
88
|
+
if (keys.length !== 1) return value;
|
|
89
|
+
if (BIGINT_TAG in value && typeof value[BIGINT_TAG] === "string") {
|
|
90
|
+
return BigInt(value[BIGINT_TAG]);
|
|
61
91
|
}
|
|
62
|
-
|
|
63
|
-
|
|
92
|
+
if (DATE_TAG in value && typeof value[DATE_TAG] === "string") {
|
|
93
|
+
return new Date(value[DATE_TAG]);
|
|
94
|
+
}
|
|
95
|
+
if (BYTES_TAG in value && typeof value[BYTES_TAG] === "string") {
|
|
96
|
+
return Buffer.from(value[BYTES_TAG], "base64");
|
|
97
|
+
}
|
|
98
|
+
if (DECIMAL_TAG in value && typeof value[DECIMAL_TAG] === "string") {
|
|
99
|
+
return decimalFactory(value[DECIMAL_TAG]);
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
function redisJsonReviver(_key, value) {
|
|
105
|
+
return createRedisJsonReviver()(_key, value);
|
|
64
106
|
}
|
|
65
107
|
function redisJsonStringify(value) {
|
|
66
|
-
return JSON.stringify(value,
|
|
108
|
+
return JSON.stringify(value, function(key, val) {
|
|
109
|
+
const holder = this;
|
|
110
|
+
const raw = key === "" ? value : holder[key];
|
|
111
|
+
if (raw instanceof Date) {
|
|
112
|
+
return redisJsonReplacer(key, raw);
|
|
113
|
+
}
|
|
114
|
+
return redisJsonReplacer(key, val);
|
|
115
|
+
});
|
|
67
116
|
}
|
|
68
|
-
function redisJsonParse(raw) {
|
|
69
|
-
return JSON.parse(raw,
|
|
117
|
+
function redisJsonParse(raw, options) {
|
|
118
|
+
return JSON.parse(raw, createRedisJsonReviver(options));
|
|
119
|
+
}
|
|
120
|
+
function cloneWithCodec(value, options) {
|
|
121
|
+
if (value === null || value === void 0) return value;
|
|
122
|
+
if (typeof structuredClone === "function") {
|
|
123
|
+
try {
|
|
124
|
+
return structuredClone(value);
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return redisJsonParse(redisJsonStringify(value), options);
|
|
70
129
|
}
|
|
71
130
|
|
|
72
131
|
// src/redis-cache-adapter.ts
|
|
@@ -86,6 +145,8 @@ var RedisCacheAdapter = class {
|
|
|
86
145
|
ready = false;
|
|
87
146
|
compression;
|
|
88
147
|
compressionThreshold;
|
|
148
|
+
jsonOptions;
|
|
149
|
+
onError;
|
|
89
150
|
constructor(options = {}) {
|
|
90
151
|
const {
|
|
91
152
|
url,
|
|
@@ -93,11 +154,15 @@ var RedisCacheAdapter = class {
|
|
|
93
154
|
port = 6379,
|
|
94
155
|
prefix = "prismakit",
|
|
95
156
|
compression = "none",
|
|
96
|
-
compressionThresholdBytes = 1024
|
|
157
|
+
compressionThresholdBytes = 1024,
|
|
158
|
+
decimalFactory,
|
|
159
|
+
onError
|
|
97
160
|
} = options;
|
|
98
161
|
this.prefix = prefix;
|
|
99
162
|
this.compression = compression;
|
|
100
163
|
this.compressionThreshold = compressionThresholdBytes;
|
|
164
|
+
this.jsonOptions = { decimalFactory };
|
|
165
|
+
this.onError = onError;
|
|
101
166
|
this.client = url ? new import_ioredis.default(url, { lazyConnect: true }) : new import_ioredis.default({ host, port, lazyConnect: true });
|
|
102
167
|
this.client.on("ready", () => {
|
|
103
168
|
this.ready = true;
|
|
@@ -111,6 +176,16 @@ var RedisCacheAdapter = class {
|
|
|
111
176
|
});
|
|
112
177
|
void this.connect();
|
|
113
178
|
}
|
|
179
|
+
report(op, err) {
|
|
180
|
+
console.warn(
|
|
181
|
+
`[RedisCacheAdapter] ${op} failed`,
|
|
182
|
+
err?.message ?? err
|
|
183
|
+
);
|
|
184
|
+
try {
|
|
185
|
+
this.onError?.(err, op);
|
|
186
|
+
} catch {
|
|
187
|
+
}
|
|
188
|
+
}
|
|
114
189
|
async connect() {
|
|
115
190
|
if (this.client.status === "ready" || this.client.status === "connecting") {
|
|
116
191
|
return;
|
|
@@ -118,10 +193,7 @@ var RedisCacheAdapter = class {
|
|
|
118
193
|
try {
|
|
119
194
|
await this.client.connect();
|
|
120
195
|
} catch (err) {
|
|
121
|
-
|
|
122
|
-
"[RedisCacheAdapter] failed to connect",
|
|
123
|
-
err.message
|
|
124
|
-
);
|
|
196
|
+
this.report("connect", err);
|
|
125
197
|
}
|
|
126
198
|
}
|
|
127
199
|
async disconnect() {
|
|
@@ -146,11 +218,10 @@ var RedisCacheAdapter = class {
|
|
|
146
218
|
if (raw.startsWith(COMPRESSED_PREFIX)) {
|
|
147
219
|
const buf = Buffer.from(raw.slice(COMPRESSED_PREFIX.length), "base64");
|
|
148
220
|
const json = (0, import_zlib.gunzipSync)(buf).toString("utf8");
|
|
149
|
-
return redisJsonParse(json);
|
|
221
|
+
return redisJsonParse(json, this.jsonOptions);
|
|
150
222
|
}
|
|
151
|
-
return redisJsonParse(raw);
|
|
223
|
+
return redisJsonParse(raw, this.jsonOptions);
|
|
152
224
|
}
|
|
153
|
-
// --- low-level ops (may throw) ---
|
|
154
225
|
async get(key) {
|
|
155
226
|
const raw = await this.client.get(key);
|
|
156
227
|
if (raw === null) return null;
|
|
@@ -174,10 +245,6 @@ var RedisCacheAdapter = class {
|
|
|
174
245
|
const result = await this.client.set(key, "1", "EX", ttlSeconds, "NX");
|
|
175
246
|
return result === "OK";
|
|
176
247
|
}
|
|
177
|
-
/**
|
|
178
|
-
* Atomically SET a cache key + SADD into an index SET + EXPIRE the index
|
|
179
|
-
* via a single Redis pipeline.
|
|
180
|
-
*/
|
|
181
248
|
async setWithIndex(key, value, ttlSeconds, indexKey) {
|
|
182
249
|
const pipeline = this.client.pipeline();
|
|
183
250
|
pipeline.set(key, this.encode(value), "EX", ttlSeconds);
|
|
@@ -185,9 +252,6 @@ var RedisCacheAdapter = class {
|
|
|
185
252
|
pipeline.expire(indexKey, ttlSeconds + INDEX_TTL_BUFFER);
|
|
186
253
|
await pipeline.exec();
|
|
187
254
|
}
|
|
188
|
-
/**
|
|
189
|
-
* Atomic invalidation via Lua (SMEMBERS + DEL members + DEL index).
|
|
190
|
-
*/
|
|
191
255
|
async invalidateByIndex(indexKey) {
|
|
192
256
|
await this.client.eval(INVALIDATE_BY_INDEX_LUA, 1, indexKey);
|
|
193
257
|
}
|
|
@@ -198,15 +262,11 @@ var RedisCacheAdapter = class {
|
|
|
198
262
|
pipeline.expire(key, ttlSeconds + INDEX_TTL_BUFFER);
|
|
199
263
|
await pipeline.exec();
|
|
200
264
|
}
|
|
201
|
-
// --- safe wrappers (never throw — warn + return fallback) ---
|
|
202
265
|
async safeGet(key) {
|
|
203
266
|
try {
|
|
204
267
|
return await this.get(key);
|
|
205
268
|
} catch (err) {
|
|
206
|
-
|
|
207
|
-
`[RedisCacheAdapter] safeGet failed for key=${key}`,
|
|
208
|
-
err.message
|
|
209
|
-
);
|
|
269
|
+
this.report(`safeGet key=${key}`, err);
|
|
210
270
|
return null;
|
|
211
271
|
}
|
|
212
272
|
}
|
|
@@ -214,30 +274,21 @@ var RedisCacheAdapter = class {
|
|
|
214
274
|
try {
|
|
215
275
|
await this.set(key, value, ttlSeconds);
|
|
216
276
|
} catch (err) {
|
|
217
|
-
|
|
218
|
-
`[RedisCacheAdapter] safeSet failed for key=${key}`,
|
|
219
|
-
err.message
|
|
220
|
-
);
|
|
277
|
+
this.report(`safeSet key=${key}`, err);
|
|
221
278
|
}
|
|
222
279
|
}
|
|
223
280
|
async safeDel(...keys) {
|
|
224
281
|
try {
|
|
225
282
|
await this.del(...keys);
|
|
226
283
|
} catch (err) {
|
|
227
|
-
|
|
228
|
-
"[RedisCacheAdapter] safeDel failed",
|
|
229
|
-
err.message
|
|
230
|
-
);
|
|
284
|
+
this.report("safeDel", err);
|
|
231
285
|
}
|
|
232
286
|
}
|
|
233
287
|
async safeSetNx(key, ttlSeconds) {
|
|
234
288
|
try {
|
|
235
289
|
return await this.setNx(key, ttlSeconds);
|
|
236
290
|
} catch (err) {
|
|
237
|
-
|
|
238
|
-
`[RedisCacheAdapter] safeSetNx failed for key=${key}`,
|
|
239
|
-
err.message
|
|
240
|
-
);
|
|
291
|
+
this.report(`safeSetNx key=${key}`, err);
|
|
241
292
|
return false;
|
|
242
293
|
}
|
|
243
294
|
}
|
|
@@ -245,40 +296,28 @@ var RedisCacheAdapter = class {
|
|
|
245
296
|
try {
|
|
246
297
|
await this.setWithIndex(key, value, ttlSeconds, indexKey);
|
|
247
298
|
} catch (err) {
|
|
248
|
-
|
|
249
|
-
`[RedisCacheAdapter] safeSetWithIndex failed for key=${key}`,
|
|
250
|
-
err.message
|
|
251
|
-
);
|
|
299
|
+
this.report(`safeSetWithIndex key=${key}`, err);
|
|
252
300
|
}
|
|
253
301
|
}
|
|
254
302
|
async safeInvalidateByIndex(indexKey) {
|
|
255
303
|
try {
|
|
256
304
|
await this.invalidateByIndex(indexKey);
|
|
257
305
|
} catch (err) {
|
|
258
|
-
|
|
259
|
-
`[RedisCacheAdapter] safeInvalidateByIndex failed for idx=${indexKey}`,
|
|
260
|
-
err.message
|
|
261
|
-
);
|
|
306
|
+
this.report(`safeInvalidateByIndex idx=${indexKey}`, err);
|
|
262
307
|
}
|
|
263
308
|
}
|
|
264
309
|
async safeSaddAndExpire(key, members, ttlSeconds) {
|
|
265
310
|
try {
|
|
266
311
|
await this.saddAndExpire(key, members, ttlSeconds);
|
|
267
312
|
} catch (err) {
|
|
268
|
-
|
|
269
|
-
`[RedisCacheAdapter] safeSaddAndExpire failed for key=${key}`,
|
|
270
|
-
err.message
|
|
271
|
-
);
|
|
313
|
+
this.report(`safeSaddAndExpire key=${key}`, err);
|
|
272
314
|
}
|
|
273
315
|
}
|
|
274
316
|
async safeSmembers(key) {
|
|
275
317
|
try {
|
|
276
318
|
return await this.smembers(key);
|
|
277
319
|
} catch (err) {
|
|
278
|
-
|
|
279
|
-
`[RedisCacheAdapter] safeSmembers failed for key=${key}`,
|
|
280
|
-
err.message
|
|
281
|
-
);
|
|
320
|
+
this.report(`safeSmembers key=${key}`, err);
|
|
282
321
|
return [];
|
|
283
322
|
}
|
|
284
323
|
}
|
|
@@ -290,6 +329,8 @@ var import_core = require("@prismakit/core");
|
|
|
290
329
|
0 && (module.exports = {
|
|
291
330
|
RedisCacheAdapter,
|
|
292
331
|
cacheDebugStorage,
|
|
332
|
+
cloneWithCodec,
|
|
333
|
+
createRedisJsonReviver,
|
|
293
334
|
isCacheDebugEnabled,
|
|
294
335
|
recordCacheDebug,
|
|
295
336
|
redisJsonParse,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/redis-cache-adapter.ts","../src/redis-json.ts"],"sourcesContent":["export {\n RedisCacheAdapter,\n type RedisCacheAdapterOptions,\n type RedisCompression,\n} from './redis-cache-adapter';\n\nexport {\n redisJsonParse,\n redisJsonStringify,\n redisJsonReplacer,\n redisJsonReviver,\n} from './redis-json';\n\n/** Re-export cache debug helpers from core — do not duplicate. */\nexport {\n type CacheDebugStatus,\n cacheDebugStorage,\n isCacheDebugEnabled,\n recordCacheDebug,\n} from '@prismakit/core';\n\nexport type { CacheAdapter } from '@prismakit/core';\n","import Redis from 'ioredis';\nimport { gzipSync, gunzipSync } from 'zlib';\nimport type { CacheAdapter } from '@prismakit/core';\nimport { redisJsonParse, redisJsonStringify } from './redis-json';\n\nconst INDEX_TTL_BUFFER = 60;\n\n/** Lua: atomically SMEMBERS index + DEL members + index. */\nconst INVALIDATE_BY_INDEX_LUA = `\nlocal members = redis.call('SMEMBERS', KEYS[1])\nfor i, member in ipairs(members) do\n redis.call('DEL', member)\nend\nredis.call('DEL', KEYS[1])\nreturn #members\n`;\n\nexport type RedisCompression = 'none' | 'gzip';\n\nexport type RedisCacheAdapterOptions = {\n url?: string;\n host?: string;\n port?: number;\n prefix?: string;\n /**\n * Compress payloads larger than `compressionThresholdBytes` (default 1024).\n * Uses gzip (widely available; zstd/lz4 can be added later).\n */\n compression?: RedisCompression;\n /** Minimum payload size (bytes) before compression (default 1024). */\n compressionThresholdBytes?: number;\n};\n\nconst COMPRESSED_PREFIX = 'gz:';\n\n/**\n * Redis-backed {@link CacheAdapter}. Framework-agnostic — no NestJS / ConfigService.\n */\nexport class RedisCacheAdapter implements CacheAdapter {\n private readonly client: Redis;\n private readonly prefix: string;\n private ready = false;\n private readonly compression: RedisCompression;\n private readonly compressionThreshold: number;\n\n constructor(options: RedisCacheAdapterOptions = {}) {\n const {\n url,\n host = 'localhost',\n port = 6379,\n prefix = 'prismakit',\n compression = 'none',\n compressionThresholdBytes = 1024,\n } = options;\n\n this.prefix = prefix;\n this.compression = compression;\n this.compressionThreshold = compressionThresholdBytes;\n this.client = url\n ? new Redis(url, { lazyConnect: true })\n : new Redis({ host, port, lazyConnect: true });\n\n this.client.on('ready', () => {\n this.ready = true;\n });\n this.client.on('error', (err) => {\n this.ready = false;\n console.warn('[RedisCacheAdapter] connection error', err.message);\n });\n this.client.on('close', () => {\n this.ready = false;\n });\n\n void this.connect();\n }\n\n async connect(): Promise<void> {\n if (this.client.status === 'ready' || this.client.status === 'connecting') {\n return;\n }\n try {\n await this.client.connect();\n } catch (err) {\n console.warn(\n '[RedisCacheAdapter] failed to connect',\n (err as Error).message,\n );\n }\n }\n\n async disconnect(): Promise<void> {\n await this.client?.quit();\n this.ready = false;\n }\n\n isReady(): boolean {\n return this.ready;\n }\n\n getPrefix(): string {\n return this.prefix;\n }\n\n private encode(value: unknown): string {\n const json = redisJsonStringify(value);\n if (\n this.compression === 'gzip' &&\n Buffer.byteLength(json, 'utf8') >= this.compressionThreshold\n ) {\n const compressed = gzipSync(Buffer.from(json, 'utf8')).toString('base64');\n return COMPRESSED_PREFIX + compressed;\n }\n return json;\n }\n\n private decode<T>(raw: string): T {\n if (raw.startsWith(COMPRESSED_PREFIX)) {\n const buf = Buffer.from(raw.slice(COMPRESSED_PREFIX.length), 'base64');\n const json = gunzipSync(buf).toString('utf8');\n return redisJsonParse<T>(json);\n }\n return redisJsonParse<T>(raw);\n }\n\n // --- low-level ops (may throw) ---\n\n async get<T>(key: string): Promise<T | null> {\n const raw = await this.client.get(key);\n if (raw === null) return null;\n return this.decode<T>(raw);\n }\n\n async set(key: string, value: unknown, ttlSeconds: number): Promise<void> {\n await this.client.set(key, this.encode(value), 'EX', ttlSeconds);\n }\n\n async del(...keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n await this.client.del(...keys);\n }\n\n async sadd(key: string, ...members: string[]): Promise<void> {\n if (members.length === 0) return;\n await this.client.sadd(key, ...members);\n }\n\n async smembers(key: string): Promise<string[]> {\n return this.client.smembers(key);\n }\n\n async setNx(key: string, ttlSeconds: number): Promise<boolean> {\n const result = await this.client.set(key, '1', 'EX', ttlSeconds, 'NX');\n return result === 'OK';\n }\n\n /**\n * Atomically SET a cache key + SADD into an index SET + EXPIRE the index\n * via a single Redis pipeline.\n */\n async setWithIndex(\n key: string,\n value: unknown,\n ttlSeconds: number,\n indexKey: string,\n ): Promise<void> {\n const pipeline = this.client.pipeline();\n pipeline.set(key, this.encode(value), 'EX', ttlSeconds);\n pipeline.sadd(indexKey, key);\n pipeline.expire(indexKey, ttlSeconds + INDEX_TTL_BUFFER);\n await pipeline.exec();\n }\n\n /**\n * Atomic invalidation via Lua (SMEMBERS + DEL members + DEL index).\n */\n async invalidateByIndex(indexKey: string): Promise<void> {\n await this.client.eval(INVALIDATE_BY_INDEX_LUA, 1, indexKey);\n }\n\n async saddAndExpire(\n key: string,\n members: string[],\n ttlSeconds: number,\n ): Promise<void> {\n if (members.length === 0) return;\n const pipeline = this.client.pipeline();\n pipeline.sadd(key, ...members);\n pipeline.expire(key, ttlSeconds + INDEX_TTL_BUFFER);\n await pipeline.exec();\n }\n\n // --- safe wrappers (never throw — warn + return fallback) ---\n\n async safeGet<T>(key: string): Promise<T | null> {\n try {\n return await this.get<T>(key);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeGet failed for key=${key}`,\n (err as Error).message,\n );\n return null;\n }\n }\n\n async safeSet(\n key: string,\n value: unknown,\n ttlSeconds: number,\n ): Promise<void> {\n try {\n await this.set(key, value, ttlSeconds);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeSet failed for key=${key}`,\n (err as Error).message,\n );\n }\n }\n\n async safeDel(...keys: string[]): Promise<void> {\n try {\n await this.del(...keys);\n } catch (err) {\n console.warn(\n '[RedisCacheAdapter] safeDel failed',\n (err as Error).message,\n );\n }\n }\n\n async safeSetNx(key: string, ttlSeconds: number): Promise<boolean> {\n try {\n return await this.setNx(key, ttlSeconds);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeSetNx failed for key=${key}`,\n (err as Error).message,\n );\n return false;\n }\n }\n\n async safeSetWithIndex(\n key: string,\n value: unknown,\n ttlSeconds: number,\n indexKey: string,\n ): Promise<void> {\n try {\n await this.setWithIndex(key, value, ttlSeconds, indexKey);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeSetWithIndex failed for key=${key}`,\n (err as Error).message,\n );\n }\n }\n\n async safeInvalidateByIndex(indexKey: string): Promise<void> {\n try {\n await this.invalidateByIndex(indexKey);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeInvalidateByIndex failed for idx=${indexKey}`,\n (err as Error).message,\n );\n }\n }\n\n async safeSaddAndExpire(\n key: string,\n members: string[],\n ttlSeconds: number,\n ): Promise<void> {\n try {\n await this.saddAndExpire(key, members, ttlSeconds);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeSaddAndExpire failed for key=${key}`,\n (err as Error).message,\n );\n }\n }\n\n async safeSmembers(key: string): Promise<string[]> {\n try {\n return await this.smembers(key);\n } catch (err) {\n console.warn(\n `[RedisCacheAdapter] safeSmembers failed for key=${key}`,\n (err as Error).message,\n );\n return [];\n }\n }\n}\n","const BIGINT_TAG = '__bigint';\n\n/** JSON.stringify replacer — Prisma `BigInt` fields are not JSON-serializable by default. */\nexport function redisJsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return { [BIGINT_TAG]: value.toString() };\n }\n return value;\n}\n\n/** JSON.parse reviver — restore tagged BigInt payloads from {@link redisJsonReplacer}. */\nexport function redisJsonReviver(_key: string, value: unknown): unknown {\n if (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n Object.keys(value).length === 1 &&\n BIGINT_TAG in value\n ) {\n const raw = (value as Record<string, unknown>)[BIGINT_TAG];\n if (typeof raw === 'string') {\n return BigInt(raw);\n }\n }\n return value;\n}\n\nexport function redisJsonStringify(value: unknown): string {\n return JSON.stringify(value, redisJsonReplacer);\n}\n\nexport function redisJsonParse<T>(raw: string): T {\n return JSON.parse(raw, redisJsonReviver) as T;\n}\n"],"mappings":"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","names":["Redis"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/redis-cache-adapter.ts","../src/redis-json.ts"],"sourcesContent":["export {\n RedisCacheAdapter,\n type RedisCacheAdapterOptions,\n type RedisCompression,\n} from './redis-cache-adapter';\n\nexport {\n redisJsonParse,\n redisJsonStringify,\n redisJsonReplacer,\n redisJsonReviver,\n createRedisJsonReviver,\n cloneWithCodec,\n type DecimalFactory,\n type RedisJsonOptions,\n} from './redis-json';\n\n/** Re-export cache debug helpers from core — do not duplicate. */\nexport {\n type CacheDebugStatus,\n cacheDebugStorage,\n isCacheDebugEnabled,\n recordCacheDebug,\n} from '@prismakit/core';\n\nexport type { CacheAdapter } from '@prismakit/core';\n","import Redis from 'ioredis';\nimport { gzipSync, gunzipSync } from 'zlib';\nimport type { CacheAdapter } from '@prismakit/core';\nimport {\n redisJsonParse,\n redisJsonStringify,\n type DecimalFactory,\n type RedisJsonOptions,\n} from './redis-json';\n\nconst INDEX_TTL_BUFFER = 60;\n\n/** Lua: atomically SMEMBERS index + DEL members + index. */\nconst INVALIDATE_BY_INDEX_LUA = `\nlocal members = redis.call('SMEMBERS', KEYS[1])\nfor i, member in ipairs(members) do\n redis.call('DEL', member)\nend\nredis.call('DEL', KEYS[1])\nreturn #members\n`;\n\nexport type RedisCompression = 'none' | 'gzip';\n\nexport type RedisCacheAdapterOptions = {\n url?: string;\n host?: string;\n port?: number;\n prefix?: string;\n /**\n * Compress payloads larger than `compressionThresholdBytes` (default 1024).\n * Uses gzip (widely available; zstd/lz4 can be added later).\n */\n compression?: RedisCompression;\n /** Minimum payload size (bytes) before compression (default 1024). */\n compressionThresholdBytes?: number;\n /**\n * Reconstruct Prisma Decimal from tagged cache payloads.\n * @example decimalFactory: (s) => new Prisma.Decimal(s)\n */\n decimalFactory?: DecimalFactory;\n /** Optional error hook for safe* wrappers (also used by telemetry). */\n onError?: (err: unknown, op?: string) => void;\n};\n\nconst COMPRESSED_PREFIX = 'gz:';\n\n/**\n * Redis-backed {@link CacheAdapter}. Framework-agnostic — no NestJS / ConfigService.\n */\nexport class RedisCacheAdapter implements CacheAdapter {\n private readonly client: Redis;\n private readonly prefix: string;\n private ready = false;\n private readonly compression: RedisCompression;\n private readonly compressionThreshold: number;\n private readonly jsonOptions: RedisJsonOptions;\n onError?: (err: unknown, op?: string) => void;\n\n constructor(options: RedisCacheAdapterOptions = {}) {\n const {\n url,\n host = 'localhost',\n port = 6379,\n prefix = 'prismakit',\n compression = 'none',\n compressionThresholdBytes = 1024,\n decimalFactory,\n onError,\n } = options;\n\n this.prefix = prefix;\n this.compression = compression;\n this.compressionThreshold = compressionThresholdBytes;\n this.jsonOptions = { decimalFactory };\n this.onError = onError;\n this.client = url\n ? new Redis(url, { lazyConnect: true })\n : new Redis({ host, port, lazyConnect: true });\n\n this.client.on('ready', () => {\n this.ready = true;\n });\n this.client.on('error', (err) => {\n this.ready = false;\n console.warn('[RedisCacheAdapter] connection error', err.message);\n });\n this.client.on('close', () => {\n this.ready = false;\n });\n\n void this.connect();\n }\n\n private report(op: string, err: unknown): void {\n console.warn(\n `[RedisCacheAdapter] ${op} failed`,\n (err as Error)?.message ?? err,\n );\n try {\n this.onError?.(err, op);\n } catch {\n /* ignore hook errors */\n }\n }\n\n async connect(): Promise<void> {\n if (this.client.status === 'ready' || this.client.status === 'connecting') {\n return;\n }\n try {\n await this.client.connect();\n } catch (err) {\n this.report('connect', err);\n }\n }\n\n async disconnect(): Promise<void> {\n await this.client?.quit();\n this.ready = false;\n }\n\n isReady(): boolean {\n return this.ready;\n }\n\n getPrefix(): string {\n return this.prefix;\n }\n\n private encode(value: unknown): string {\n const json = redisJsonStringify(value);\n if (\n this.compression === 'gzip' &&\n Buffer.byteLength(json, 'utf8') >= this.compressionThreshold\n ) {\n const compressed = gzipSync(Buffer.from(json, 'utf8')).toString('base64');\n return COMPRESSED_PREFIX + compressed;\n }\n return json;\n }\n\n private decode<T>(raw: string): T {\n if (raw.startsWith(COMPRESSED_PREFIX)) {\n const buf = Buffer.from(raw.slice(COMPRESSED_PREFIX.length), 'base64');\n const json = gunzipSync(buf).toString('utf8');\n return redisJsonParse<T>(json, this.jsonOptions);\n }\n return redisJsonParse<T>(raw, this.jsonOptions);\n }\n\n async get<T>(key: string): Promise<T | null> {\n const raw = await this.client.get(key);\n if (raw === null) return null;\n return this.decode<T>(raw);\n }\n\n async set(key: string, value: unknown, ttlSeconds: number): Promise<void> {\n await this.client.set(key, this.encode(value), 'EX', ttlSeconds);\n }\n\n async del(...keys: string[]): Promise<void> {\n if (keys.length === 0) return;\n await this.client.del(...keys);\n }\n\n async sadd(key: string, ...members: string[]): Promise<void> {\n if (members.length === 0) return;\n await this.client.sadd(key, ...members);\n }\n\n async smembers(key: string): Promise<string[]> {\n return this.client.smembers(key);\n }\n\n async setNx(key: string, ttlSeconds: number): Promise<boolean> {\n const result = await this.client.set(key, '1', 'EX', ttlSeconds, 'NX');\n return result === 'OK';\n }\n\n async setWithIndex(\n key: string,\n value: unknown,\n ttlSeconds: number,\n indexKey: string,\n ): Promise<void> {\n const pipeline = this.client.pipeline();\n pipeline.set(key, this.encode(value), 'EX', ttlSeconds);\n pipeline.sadd(indexKey, key);\n pipeline.expire(indexKey, ttlSeconds + INDEX_TTL_BUFFER);\n await pipeline.exec();\n }\n\n async invalidateByIndex(indexKey: string): Promise<void> {\n await this.client.eval(INVALIDATE_BY_INDEX_LUA, 1, indexKey);\n }\n\n async saddAndExpire(\n key: string,\n members: string[],\n ttlSeconds: number,\n ): Promise<void> {\n if (members.length === 0) return;\n const pipeline = this.client.pipeline();\n pipeline.sadd(key, ...members);\n pipeline.expire(key, ttlSeconds + INDEX_TTL_BUFFER);\n await pipeline.exec();\n }\n\n async safeGet<T>(key: string): Promise<T | null> {\n try {\n return await this.get<T>(key);\n } catch (err) {\n this.report(`safeGet key=${key}`, err);\n return null;\n }\n }\n\n async safeSet(\n key: string,\n value: unknown,\n ttlSeconds: number,\n ): Promise<void> {\n try {\n await this.set(key, value, ttlSeconds);\n } catch (err) {\n this.report(`safeSet key=${key}`, err);\n }\n }\n\n async safeDel(...keys: string[]): Promise<void> {\n try {\n await this.del(...keys);\n } catch (err) {\n this.report('safeDel', err);\n }\n }\n\n async safeSetNx(key: string, ttlSeconds: number): Promise<boolean> {\n try {\n return await this.setNx(key, ttlSeconds);\n } catch (err) {\n this.report(`safeSetNx key=${key}`, err);\n return false;\n }\n }\n\n async safeSetWithIndex(\n key: string,\n value: unknown,\n ttlSeconds: number,\n indexKey: string,\n ): Promise<void> {\n try {\n await this.setWithIndex(key, value, ttlSeconds, indexKey);\n } catch (err) {\n this.report(`safeSetWithIndex key=${key}`, err);\n }\n }\n\n async safeInvalidateByIndex(indexKey: string): Promise<void> {\n try {\n await this.invalidateByIndex(indexKey);\n } catch (err) {\n this.report(`safeInvalidateByIndex idx=${indexKey}`, err);\n }\n }\n\n async safeSaddAndExpire(\n key: string,\n members: string[],\n ttlSeconds: number,\n ): Promise<void> {\n try {\n await this.saddAndExpire(key, members, ttlSeconds);\n } catch (err) {\n this.report(`safeSaddAndExpire key=${key}`, err);\n }\n }\n\n async safeSmembers(key: string): Promise<string[]> {\n try {\n return await this.smembers(key);\n } catch (err) {\n this.report(`safeSmembers key=${key}`, err);\n return [];\n }\n }\n}\n","const BIGINT_TAG = '__bigint';\nconst DATE_TAG = '__date';\nconst BYTES_TAG = '__bytes';\nconst DECIMAL_TAG = '__decimal';\n\nexport type DecimalFactory = (value: string) => unknown;\n\nexport type RedisJsonOptions = {\n /**\n * Reconstruct Prisma `Decimal` (or equivalent) from the tagged string.\n * Default keeps the precision-preserving string so values are not silently\n * coerced to numbers.\n *\n * @example\n * decimalFactory: (s) => new Prisma.Decimal(s)\n */\n decimalFactory?: DecimalFactory;\n};\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n value !== null &&\n typeof value === 'object' &&\n !Array.isArray(value) &&\n !(value instanceof Date) &&\n !Buffer.isBuffer(value)\n );\n}\n\nfunction isDecimalLike(value: unknown): value is { toFixed: () => string } {\n return (\n isPlainObject(value) &&\n typeof (value as { constructor?: { name?: string } }).constructor?.name ===\n 'string' &&\n ((value as { constructor: { name: string } }).constructor.name ===\n 'Decimal' ||\n (typeof (value as { toFixed?: unknown }).toFixed === 'function' &&\n typeof (value as { d?: unknown }).d === 'object'))\n );\n}\n\n/** JSON.stringify replacer — Prisma BigInt / Date / Bytes / Decimal. */\nexport function redisJsonReplacer(_key: string, value: unknown): unknown {\n if (typeof value === 'bigint') {\n return { [BIGINT_TAG]: value.toString() };\n }\n if (value instanceof Date) {\n return { [DATE_TAG]: value.toISOString() };\n }\n if (Buffer.isBuffer(value)) {\n return { [BYTES_TAG]: value.toString('base64') };\n }\n if (\n value &&\n typeof value === 'object' &&\n (value as { type?: string }).type === 'Buffer' &&\n Array.isArray((value as { data?: unknown }).data)\n ) {\n return {\n [BYTES_TAG]: Buffer.from(\n (value as { data: number[] }).data,\n ).toString('base64'),\n };\n }\n if (isDecimalLike(value)) {\n return { [DECIMAL_TAG]: value.toFixed() };\n }\n return value;\n}\n\n/** JSON.parse reviver — restore tagged payloads from {@link redisJsonReplacer}. */\nexport function createRedisJsonReviver(options: RedisJsonOptions = {}) {\n const decimalFactory = options.decimalFactory ?? ((s: string) => s);\n return (_key: string, value: unknown): unknown => {\n if (!isPlainObject(value)) return value;\n const keys = Object.keys(value);\n if (keys.length !== 1) return value;\n\n if (BIGINT_TAG in value && typeof value[BIGINT_TAG] === 'string') {\n return BigInt(value[BIGINT_TAG] as string);\n }\n if (DATE_TAG in value && typeof value[DATE_TAG] === 'string') {\n return new Date(value[DATE_TAG] as string);\n }\n if (BYTES_TAG in value && typeof value[BYTES_TAG] === 'string') {\n return Buffer.from(value[BYTES_TAG] as string, 'base64');\n }\n if (DECIMAL_TAG in value && typeof value[DECIMAL_TAG] === 'string') {\n return decimalFactory(value[DECIMAL_TAG] as string);\n }\n return value;\n };\n}\n\n/** @deprecated Prefer {@link createRedisJsonReviver} when a decimalFactory is needed. */\nexport function redisJsonReviver(_key: string, value: unknown): unknown {\n return createRedisJsonReviver()(_key, value);\n}\n\nexport function redisJsonStringify(value: unknown): string {\n return JSON.stringify(value, function (key, val) {\n const holder = this as Record<string, unknown>;\n const raw = key === '' ? value : holder[key];\n if (raw instanceof Date) {\n return redisJsonReplacer(key, raw);\n }\n return redisJsonReplacer(key, val);\n });\n}\n\nexport function redisJsonParse<T>(\n raw: string,\n options?: RedisJsonOptions,\n): T {\n return JSON.parse(raw, createRedisJsonReviver(options)) as T;\n}\n\n/**\n * Deep clone via structuredClone, falling back to the tagged JSON codec so\n * Date / BigInt / Buffer / Decimal survive (unlike plain JSON.stringify).\n */\nexport function cloneWithCodec<T>(value: T, options?: RedisJsonOptions): T {\n if (value === null || value === undefined) return value;\n if (typeof structuredClone === 'function') {\n try {\n return structuredClone(value);\n } catch {\n // Fall through for non-cloneable values (e.g. Decimal)\n }\n }\n return redisJsonParse(redisJsonStringify(value), options);\n}\n"],"mappings":"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","names":["Redis"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { CacheAdapter } from '@prismakit/core';
|
|
2
2
|
export { CacheAdapter, CacheDebugStatus, cacheDebugStorage, isCacheDebugEnabled, recordCacheDebug } from '@prismakit/core';
|
|
3
3
|
|
|
4
|
+
type DecimalFactory = (value: string) => unknown;
|
|
5
|
+
type RedisJsonOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Reconstruct Prisma `Decimal` (or equivalent) from the tagged string.
|
|
8
|
+
* Default keeps the precision-preserving string so values are not silently
|
|
9
|
+
* coerced to numbers.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* decimalFactory: (s) => new Prisma.Decimal(s)
|
|
13
|
+
*/
|
|
14
|
+
decimalFactory?: DecimalFactory;
|
|
15
|
+
};
|
|
16
|
+
/** JSON.stringify replacer — Prisma BigInt / Date / Bytes / Decimal. */
|
|
17
|
+
declare function redisJsonReplacer(_key: string, value: unknown): unknown;
|
|
18
|
+
/** JSON.parse reviver — restore tagged payloads from {@link redisJsonReplacer}. */
|
|
19
|
+
declare function createRedisJsonReviver(options?: RedisJsonOptions): (_key: string, value: unknown) => unknown;
|
|
20
|
+
/** @deprecated Prefer {@link createRedisJsonReviver} when a decimalFactory is needed. */
|
|
21
|
+
declare function redisJsonReviver(_key: string, value: unknown): unknown;
|
|
22
|
+
declare function redisJsonStringify(value: unknown): string;
|
|
23
|
+
declare function redisJsonParse<T>(raw: string, options?: RedisJsonOptions): T;
|
|
24
|
+
/**
|
|
25
|
+
* Deep clone via structuredClone, falling back to the tagged JSON codec so
|
|
26
|
+
* Date / BigInt / Buffer / Decimal survive (unlike plain JSON.stringify).
|
|
27
|
+
*/
|
|
28
|
+
declare function cloneWithCodec<T>(value: T, options?: RedisJsonOptions): T;
|
|
29
|
+
|
|
4
30
|
type RedisCompression = 'none' | 'gzip';
|
|
5
31
|
type RedisCacheAdapterOptions = {
|
|
6
32
|
url?: string;
|
|
@@ -14,6 +40,13 @@ type RedisCacheAdapterOptions = {
|
|
|
14
40
|
compression?: RedisCompression;
|
|
15
41
|
/** Minimum payload size (bytes) before compression (default 1024). */
|
|
16
42
|
compressionThresholdBytes?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Reconstruct Prisma Decimal from tagged cache payloads.
|
|
45
|
+
* @example decimalFactory: (s) => new Prisma.Decimal(s)
|
|
46
|
+
*/
|
|
47
|
+
decimalFactory?: DecimalFactory;
|
|
48
|
+
/** Optional error hook for safe* wrappers (also used by telemetry). */
|
|
49
|
+
onError?: (err: unknown, op?: string) => void;
|
|
17
50
|
};
|
|
18
51
|
/**
|
|
19
52
|
* Redis-backed {@link CacheAdapter}. Framework-agnostic — no NestJS / ConfigService.
|
|
@@ -24,7 +57,10 @@ declare class RedisCacheAdapter implements CacheAdapter {
|
|
|
24
57
|
private ready;
|
|
25
58
|
private readonly compression;
|
|
26
59
|
private readonly compressionThreshold;
|
|
60
|
+
private readonly jsonOptions;
|
|
61
|
+
onError?: (err: unknown, op?: string) => void;
|
|
27
62
|
constructor(options?: RedisCacheAdapterOptions);
|
|
63
|
+
private report;
|
|
28
64
|
connect(): Promise<void>;
|
|
29
65
|
disconnect(): Promise<void>;
|
|
30
66
|
isReady(): boolean;
|
|
@@ -37,14 +73,7 @@ declare class RedisCacheAdapter implements CacheAdapter {
|
|
|
37
73
|
sadd(key: string, ...members: string[]): Promise<void>;
|
|
38
74
|
smembers(key: string): Promise<string[]>;
|
|
39
75
|
setNx(key: string, ttlSeconds: number): Promise<boolean>;
|
|
40
|
-
/**
|
|
41
|
-
* Atomically SET a cache key + SADD into an index SET + EXPIRE the index
|
|
42
|
-
* via a single Redis pipeline.
|
|
43
|
-
*/
|
|
44
76
|
setWithIndex(key: string, value: unknown, ttlSeconds: number, indexKey: string): Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* Atomic invalidation via Lua (SMEMBERS + DEL members + DEL index).
|
|
47
|
-
*/
|
|
48
77
|
invalidateByIndex(indexKey: string): Promise<void>;
|
|
49
78
|
saddAndExpire(key: string, members: string[], ttlSeconds: number): Promise<void>;
|
|
50
79
|
safeGet<T>(key: string): Promise<T | null>;
|
|
@@ -57,11 +86,4 @@ declare class RedisCacheAdapter implements CacheAdapter {
|
|
|
57
86
|
safeSmembers(key: string): Promise<string[]>;
|
|
58
87
|
}
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
declare function redisJsonReplacer(_key: string, value: unknown): unknown;
|
|
62
|
-
/** JSON.parse reviver — restore tagged BigInt payloads from {@link redisJsonReplacer}. */
|
|
63
|
-
declare function redisJsonReviver(_key: string, value: unknown): unknown;
|
|
64
|
-
declare function redisJsonStringify(value: unknown): string;
|
|
65
|
-
declare function redisJsonParse<T>(raw: string): T;
|
|
66
|
-
|
|
67
|
-
export { RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, redisJsonParse, redisJsonReplacer, redisJsonReviver, redisJsonStringify };
|
|
89
|
+
export { type DecimalFactory, RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, type RedisJsonOptions, cloneWithCodec, createRedisJsonReviver, redisJsonParse, redisJsonReplacer, redisJsonReviver, redisJsonStringify };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { CacheAdapter } from '@prismakit/core';
|
|
2
2
|
export { CacheAdapter, CacheDebugStatus, cacheDebugStorage, isCacheDebugEnabled, recordCacheDebug } from '@prismakit/core';
|
|
3
3
|
|
|
4
|
+
type DecimalFactory = (value: string) => unknown;
|
|
5
|
+
type RedisJsonOptions = {
|
|
6
|
+
/**
|
|
7
|
+
* Reconstruct Prisma `Decimal` (or equivalent) from the tagged string.
|
|
8
|
+
* Default keeps the precision-preserving string so values are not silently
|
|
9
|
+
* coerced to numbers.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* decimalFactory: (s) => new Prisma.Decimal(s)
|
|
13
|
+
*/
|
|
14
|
+
decimalFactory?: DecimalFactory;
|
|
15
|
+
};
|
|
16
|
+
/** JSON.stringify replacer — Prisma BigInt / Date / Bytes / Decimal. */
|
|
17
|
+
declare function redisJsonReplacer(_key: string, value: unknown): unknown;
|
|
18
|
+
/** JSON.parse reviver — restore tagged payloads from {@link redisJsonReplacer}. */
|
|
19
|
+
declare function createRedisJsonReviver(options?: RedisJsonOptions): (_key: string, value: unknown) => unknown;
|
|
20
|
+
/** @deprecated Prefer {@link createRedisJsonReviver} when a decimalFactory is needed. */
|
|
21
|
+
declare function redisJsonReviver(_key: string, value: unknown): unknown;
|
|
22
|
+
declare function redisJsonStringify(value: unknown): string;
|
|
23
|
+
declare function redisJsonParse<T>(raw: string, options?: RedisJsonOptions): T;
|
|
24
|
+
/**
|
|
25
|
+
* Deep clone via structuredClone, falling back to the tagged JSON codec so
|
|
26
|
+
* Date / BigInt / Buffer / Decimal survive (unlike plain JSON.stringify).
|
|
27
|
+
*/
|
|
28
|
+
declare function cloneWithCodec<T>(value: T, options?: RedisJsonOptions): T;
|
|
29
|
+
|
|
4
30
|
type RedisCompression = 'none' | 'gzip';
|
|
5
31
|
type RedisCacheAdapterOptions = {
|
|
6
32
|
url?: string;
|
|
@@ -14,6 +40,13 @@ type RedisCacheAdapterOptions = {
|
|
|
14
40
|
compression?: RedisCompression;
|
|
15
41
|
/** Minimum payload size (bytes) before compression (default 1024). */
|
|
16
42
|
compressionThresholdBytes?: number;
|
|
43
|
+
/**
|
|
44
|
+
* Reconstruct Prisma Decimal from tagged cache payloads.
|
|
45
|
+
* @example decimalFactory: (s) => new Prisma.Decimal(s)
|
|
46
|
+
*/
|
|
47
|
+
decimalFactory?: DecimalFactory;
|
|
48
|
+
/** Optional error hook for safe* wrappers (also used by telemetry). */
|
|
49
|
+
onError?: (err: unknown, op?: string) => void;
|
|
17
50
|
};
|
|
18
51
|
/**
|
|
19
52
|
* Redis-backed {@link CacheAdapter}. Framework-agnostic — no NestJS / ConfigService.
|
|
@@ -24,7 +57,10 @@ declare class RedisCacheAdapter implements CacheAdapter {
|
|
|
24
57
|
private ready;
|
|
25
58
|
private readonly compression;
|
|
26
59
|
private readonly compressionThreshold;
|
|
60
|
+
private readonly jsonOptions;
|
|
61
|
+
onError?: (err: unknown, op?: string) => void;
|
|
27
62
|
constructor(options?: RedisCacheAdapterOptions);
|
|
63
|
+
private report;
|
|
28
64
|
connect(): Promise<void>;
|
|
29
65
|
disconnect(): Promise<void>;
|
|
30
66
|
isReady(): boolean;
|
|
@@ -37,14 +73,7 @@ declare class RedisCacheAdapter implements CacheAdapter {
|
|
|
37
73
|
sadd(key: string, ...members: string[]): Promise<void>;
|
|
38
74
|
smembers(key: string): Promise<string[]>;
|
|
39
75
|
setNx(key: string, ttlSeconds: number): Promise<boolean>;
|
|
40
|
-
/**
|
|
41
|
-
* Atomically SET a cache key + SADD into an index SET + EXPIRE the index
|
|
42
|
-
* via a single Redis pipeline.
|
|
43
|
-
*/
|
|
44
76
|
setWithIndex(key: string, value: unknown, ttlSeconds: number, indexKey: string): Promise<void>;
|
|
45
|
-
/**
|
|
46
|
-
* Atomic invalidation via Lua (SMEMBERS + DEL members + DEL index).
|
|
47
|
-
*/
|
|
48
77
|
invalidateByIndex(indexKey: string): Promise<void>;
|
|
49
78
|
saddAndExpire(key: string, members: string[], ttlSeconds: number): Promise<void>;
|
|
50
79
|
safeGet<T>(key: string): Promise<T | null>;
|
|
@@ -57,11 +86,4 @@ declare class RedisCacheAdapter implements CacheAdapter {
|
|
|
57
86
|
safeSmembers(key: string): Promise<string[]>;
|
|
58
87
|
}
|
|
59
88
|
|
|
60
|
-
|
|
61
|
-
declare function redisJsonReplacer(_key: string, value: unknown): unknown;
|
|
62
|
-
/** JSON.parse reviver — restore tagged BigInt payloads from {@link redisJsonReplacer}. */
|
|
63
|
-
declare function redisJsonReviver(_key: string, value: unknown): unknown;
|
|
64
|
-
declare function redisJsonStringify(value: unknown): string;
|
|
65
|
-
declare function redisJsonParse<T>(raw: string): T;
|
|
66
|
-
|
|
67
|
-
export { RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, redisJsonParse, redisJsonReplacer, redisJsonReviver, redisJsonStringify };
|
|
89
|
+
export { type DecimalFactory, RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, type RedisJsonOptions, cloneWithCodec, createRedisJsonReviver, redisJsonParse, redisJsonReplacer, redisJsonReviver, redisJsonStringify };
|