@prismakit/redis 3.2.0 → 4.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,6 +4,8 @@ Redis `CacheAdapter` for `@prismakit/core` repositories (ioredis).
4
4
 
5
5
  Fail-open: cache errors do not break reads/writes.
6
6
 
7
+ **Status:** pre-stable (4.0).
8
+
7
9
  [Documentation](https://github.com/fikiap23/prismakit/blob/master/docs/README.md) · [Cache guide](https://github.com/fikiap23/prismakit/blob/master/docs/guide/cache.md) · [GitHub](https://github.com/fikiap23/prismakit)
8
10
 
9
11
  ## Install
@@ -49,6 +51,18 @@ PrismaKitModule.forRoot({
49
51
  });
50
52
  ```
51
53
 
54
+ ### JSON codec
55
+
56
+ Tagged JSON for `Date`, `BigInt`, `Bytes`, and Prisma `Decimal`. Custom revive:
57
+
58
+ ```typescript
59
+ import { createRedisJsonReviver } from '@prismakit/redis';
60
+
61
+ const reviver = createRedisJsonReviver({ /* DecimalFactory? */ });
62
+ ```
63
+
64
+ Cache debug (`CACHE_DEBUG=true`, `cacheDebugStorage`, …) lives on `@prismakit/core`.
65
+
52
66
  ## Cache behavior (reminder)
53
67
 
54
68
  - Reads cache only when `setCache: true`
package/dist/index.cjs CHANGED
@@ -31,14 +31,10 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
33
  RedisCacheAdapter: () => RedisCacheAdapter,
34
- cacheDebugStorage: () => import_core.cacheDebugStorage,
35
34
  cloneWithCodec: () => cloneWithCodec,
36
35
  createRedisJsonReviver: () => createRedisJsonReviver,
37
- isCacheDebugEnabled: () => import_core.isCacheDebugEnabled,
38
- recordCacheDebug: () => import_core.recordCacheDebug,
39
36
  redisJsonParse: () => redisJsonParse,
40
37
  redisJsonReplacer: () => redisJsonReplacer,
41
- redisJsonReviver: () => redisJsonReviver,
42
38
  redisJsonStringify: () => redisJsonStringify
43
39
  });
44
40
  module.exports = __toCommonJS(index_exports);
@@ -101,9 +97,6 @@ function createRedisJsonReviver(options = {}) {
101
97
  return value;
102
98
  };
103
99
  }
104
- function redisJsonReviver(_key, value) {
105
- return createRedisJsonReviver()(_key, value);
106
- }
107
100
  function redisJsonStringify(value) {
108
101
  return JSON.stringify(value, function(key, val) {
109
102
  const holder = this;
@@ -322,20 +315,13 @@ var RedisCacheAdapter = class {
322
315
  }
323
316
  }
324
317
  };
325
-
326
- // src/index.ts
327
- var import_core = require("@prismakit/core");
328
318
  // Annotate the CommonJS export names for ESM import in node:
329
319
  0 && (module.exports = {
330
320
  RedisCacheAdapter,
331
- cacheDebugStorage,
332
321
  cloneWithCodec,
333
322
  createRedisJsonReviver,
334
- isCacheDebugEnabled,
335
- recordCacheDebug,
336
323
  redisJsonParse,
337
324
  redisJsonReplacer,
338
- redisJsonReviver,
339
325
  redisJsonStringify
340
326
  });
341
327
  //# sourceMappingURL=index.cjs.map
@@ -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 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"]}
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 createRedisJsonReviver,\n cloneWithCodec,\n type DecimalFactory,\n type RedisJsonOptions,\n} from './redis-json';\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\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,5 +1,5 @@
1
1
  import { CacheAdapter } from '@prismakit/core';
2
- export { CacheAdapter, CacheDebugStatus, cacheDebugStorage, isCacheDebugEnabled, recordCacheDebug } from '@prismakit/core';
2
+ export { CacheAdapter } from '@prismakit/core';
3
3
 
4
4
  type DecimalFactory = (value: string) => unknown;
5
5
  type RedisJsonOptions = {
@@ -17,8 +17,6 @@ type RedisJsonOptions = {
17
17
  declare function redisJsonReplacer(_key: string, value: unknown): unknown;
18
18
  /** JSON.parse reviver — restore tagged payloads from {@link redisJsonReplacer}. */
19
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
20
  declare function redisJsonStringify(value: unknown): string;
23
21
  declare function redisJsonParse<T>(raw: string, options?: RedisJsonOptions): T;
24
22
  /**
@@ -86,4 +84,4 @@ declare class RedisCacheAdapter implements CacheAdapter {
86
84
  safeSmembers(key: string): Promise<string[]>;
87
85
  }
88
86
 
89
- export { type DecimalFactory, RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, type RedisJsonOptions, cloneWithCodec, createRedisJsonReviver, redisJsonParse, redisJsonReplacer, redisJsonReviver, redisJsonStringify };
87
+ export { type DecimalFactory, RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, type RedisJsonOptions, cloneWithCodec, createRedisJsonReviver, redisJsonParse, redisJsonReplacer, redisJsonStringify };
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { CacheAdapter } from '@prismakit/core';
2
- export { CacheAdapter, CacheDebugStatus, cacheDebugStorage, isCacheDebugEnabled, recordCacheDebug } from '@prismakit/core';
2
+ export { CacheAdapter } from '@prismakit/core';
3
3
 
4
4
  type DecimalFactory = (value: string) => unknown;
5
5
  type RedisJsonOptions = {
@@ -17,8 +17,6 @@ type RedisJsonOptions = {
17
17
  declare function redisJsonReplacer(_key: string, value: unknown): unknown;
18
18
  /** JSON.parse reviver — restore tagged payloads from {@link redisJsonReplacer}. */
19
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
20
  declare function redisJsonStringify(value: unknown): string;
23
21
  declare function redisJsonParse<T>(raw: string, options?: RedisJsonOptions): T;
24
22
  /**
@@ -86,4 +84,4 @@ declare class RedisCacheAdapter implements CacheAdapter {
86
84
  safeSmembers(key: string): Promise<string[]>;
87
85
  }
88
86
 
89
- export { type DecimalFactory, RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, type RedisJsonOptions, cloneWithCodec, createRedisJsonReviver, redisJsonParse, redisJsonReplacer, redisJsonReviver, redisJsonStringify };
87
+ export { type DecimalFactory, RedisCacheAdapter, type RedisCacheAdapterOptions, type RedisCompression, type RedisJsonOptions, cloneWithCodec, createRedisJsonReviver, redisJsonParse, redisJsonReplacer, redisJsonStringify };
package/dist/index.js CHANGED
@@ -56,9 +56,6 @@ function createRedisJsonReviver(options = {}) {
56
56
  return value;
57
57
  };
58
58
  }
59
- function redisJsonReviver(_key, value) {
60
- return createRedisJsonReviver()(_key, value);
61
- }
62
59
  function redisJsonStringify(value) {
63
60
  return JSON.stringify(value, function(key, val) {
64
61
  const holder = this;
@@ -277,23 +274,12 @@ var RedisCacheAdapter = class {
277
274
  }
278
275
  }
279
276
  };
280
-
281
- // src/index.ts
282
- import {
283
- cacheDebugStorage,
284
- isCacheDebugEnabled,
285
- recordCacheDebug
286
- } from "@prismakit/core";
287
277
  export {
288
278
  RedisCacheAdapter,
289
- cacheDebugStorage,
290
279
  cloneWithCodec,
291
280
  createRedisJsonReviver,
292
- isCacheDebugEnabled,
293
- recordCacheDebug,
294
281
  redisJsonParse,
295
282
  redisJsonReplacer,
296
- redisJsonReviver,
297
283
  redisJsonStringify
298
284
  };
299
285
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/redis-cache-adapter.ts","../src/redis-json.ts","../src/index.ts"],"sourcesContent":["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","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"],"mappings":";AAAA,OAAO,WAAW;AAClB,SAAS,UAAU,kBAAkB;;;ACDrC,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,cAAc;AAgBpB,SAAS,cAAc,OAAkD;AACvE,SACE,UAAU,QACV,OAAO,UAAU,YACjB,CAAC,MAAM,QAAQ,KAAK,KACpB,EAAE,iBAAiB,SACnB,CAAC,OAAO,SAAS,KAAK;AAE1B;AAEA,SAAS,cAAc,OAAoD;AACzE,SACE,cAAc,KAAK,KACnB,OAAQ,MAA8C,aAAa,SACjE,aACA,MAA4C,YAAY,SACxD,aACC,OAAQ,MAAgC,YAAY,cACnD,OAAQ,MAA0B,MAAM;AAEhD;AAGO,SAAS,kBAAkB,MAAc,OAAyB;AACvE,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,CAAC,UAAU,GAAG,MAAM,SAAS,EAAE;AAAA,EAC1C;AACA,MAAI,iBAAiB,MAAM;AACzB,WAAO,EAAE,CAAC,QAAQ,GAAG,MAAM,YAAY,EAAE;AAAA,EAC3C;AACA,MAAI,OAAO,SAAS,KAAK,GAAG;AAC1B,WAAO,EAAE,CAAC,SAAS,GAAG,MAAM,SAAS,QAAQ,EAAE;AAAA,EACjD;AACA,MACE,SACA,OAAO,UAAU,YAChB,MAA4B,SAAS,YACtC,MAAM,QAAS,MAA6B,IAAI,GAChD;AACA,WAAO;AAAA,MACL,CAAC,SAAS,GAAG,OAAO;AAAA,QACjB,MAA6B;AAAA,MAChC,EAAE,SAAS,QAAQ;AAAA,IACrB;AAAA,EACF;AACA,MAAI,cAAc,KAAK,GAAG;AACxB,WAAO,EAAE,CAAC,WAAW,GAAG,MAAM,QAAQ,EAAE;AAAA,EAC1C;AACA,SAAO;AACT;AAGO,SAAS,uBAAuB,UAA4B,CAAC,GAAG;AACrE,QAAM,iBAAiB,QAAQ,mBAAmB,CAAC,MAAc;AACjE,SAAO,CAAC,MAAc,UAA4B;AAChD,QAAI,CAAC,cAAc,KAAK,EAAG,QAAO;AAClC,UAAM,OAAO,OAAO,KAAK,KAAK;AAC9B,QAAI,KAAK,WAAW,EAAG,QAAO;AAE9B,QAAI,cAAc,SAAS,OAAO,MAAM,UAAU,MAAM,UAAU;AAChE,aAAO,OAAO,MAAM,UAAU,CAAW;AAAA,IAC3C;AACA,QAAI,YAAY,SAAS,OAAO,MAAM,QAAQ,MAAM,UAAU;AAC5D,aAAO,IAAI,KAAK,MAAM,QAAQ,CAAW;AAAA,IAC3C;AACA,QAAI,aAAa,SAAS,OAAO,MAAM,SAAS,MAAM,UAAU;AAC9D,aAAO,OAAO,KAAK,MAAM,SAAS,GAAa,QAAQ;AAAA,IACzD;AACA,QAAI,eAAe,SAAS,OAAO,MAAM,WAAW,MAAM,UAAU;AAClE,aAAO,eAAe,MAAM,WAAW,CAAW;AAAA,IACpD;AACA,WAAO;AAAA,EACT;AACF;AAGO,SAAS,iBAAiB,MAAc,OAAyB;AACtE,SAAO,uBAAuB,EAAE,MAAM,KAAK;AAC7C;AAEO,SAAS,mBAAmB,OAAwB;AACzD,SAAO,KAAK,UAAU,OAAO,SAAU,KAAK,KAAK;AAC/C,UAAM,SAAS;AACf,UAAM,MAAM,QAAQ,KAAK,QAAQ,OAAO,GAAG;AAC3C,QAAI,eAAe,MAAM;AACvB,aAAO,kBAAkB,KAAK,GAAG;AAAA,IACnC;AACA,WAAO,kBAAkB,KAAK,GAAG;AAAA,EACnC,CAAC;AACH;AAEO,SAAS,eACd,KACA,SACG;AACH,SAAO,KAAK,MAAM,KAAK,uBAAuB,OAAO,CAAC;AACxD;AAMO,SAAS,eAAkB,OAAU,SAA+B;AACzE,MAAI,UAAU,QAAQ,UAAU,OAAW,QAAO;AAClD,MAAI,OAAO,oBAAoB,YAAY;AACzC,QAAI;AACF,aAAO,gBAAgB,KAAK;AAAA,IAC9B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,eAAe,mBAAmB,KAAK,GAAG,OAAO;AAC1D;;;ADzHA,IAAM,mBAAmB;AAGzB,IAAM,0BAA0B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgChC,IAAM,oBAAoB;AAKnB,IAAM,oBAAN,MAAgD;AAAA,EACpC;AAAA,EACA;AAAA,EACT,QAAQ;AAAA,EACC;AAAA,EACA;AAAA,EACA;AAAA,EACjB;AAAA,EAEA,YAAY,UAAoC,CAAC,GAAG;AAClD,UAAM;AAAA,MACJ;AAAA,MACA,OAAO;AAAA,MACP,OAAO;AAAA,MACP,SAAS;AAAA,MACT,cAAc;AAAA,MACd,4BAA4B;AAAA,MAC5B;AAAA,MACA;AAAA,IACF,IAAI;AAEJ,SAAK,SAAS;AACd,SAAK,cAAc;AACnB,SAAK,uBAAuB;AAC5B,SAAK,cAAc,EAAE,eAAe;AACpC,SAAK,UAAU;AACf,SAAK,SAAS,MACV,IAAI,MAAM,KAAK,EAAE,aAAa,KAAK,CAAC,IACpC,IAAI,MAAM,EAAE,MAAM,MAAM,aAAa,KAAK,CAAC;AAE/C,SAAK,OAAO,GAAG,SAAS,MAAM;AAC5B,WAAK,QAAQ;AAAA,IACf,CAAC;AACD,SAAK,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC/B,WAAK,QAAQ;AACb,cAAQ,KAAK,wCAAwC,IAAI,OAAO;AAAA,IAClE,CAAC;AACD,SAAK,OAAO,GAAG,SAAS,MAAM;AAC5B,WAAK,QAAQ;AAAA,IACf,CAAC;AAED,SAAK,KAAK,QAAQ;AAAA,EACpB;AAAA,EAEQ,OAAO,IAAY,KAAoB;AAC7C,YAAQ;AAAA,MACN,uBAAuB,EAAE;AAAA,MACxB,KAAe,WAAW;AAAA,IAC7B;AACA,QAAI;AACF,WAAK,UAAU,KAAK,EAAE;AAAA,IACxB,QAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAM,UAAyB;AAC7B,QAAI,KAAK,OAAO,WAAW,WAAW,KAAK,OAAO,WAAW,cAAc;AACzE;AAAA,IACF;AACA,QAAI;AACF,YAAM,KAAK,OAAO,QAAQ;AAAA,IAC5B,SAAS,KAAK;AACZ,WAAK,OAAO,WAAW,GAAG;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,aAA4B;AAChC,UAAM,KAAK,QAAQ,KAAK;AACxB,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,UAAmB;AACjB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,YAAoB;AAClB,WAAO,KAAK;AAAA,EACd;AAAA,EAEQ,OAAO,OAAwB;AACrC,UAAM,OAAO,mBAAmB,KAAK;AACrC,QACE,KAAK,gBAAgB,UACrB,OAAO,WAAW,MAAM,MAAM,KAAK,KAAK,sBACxC;AACA,YAAM,aAAa,SAAS,OAAO,KAAK,MAAM,MAAM,CAAC,EAAE,SAAS,QAAQ;AACxE,aAAO,oBAAoB;AAAA,IAC7B;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,OAAU,KAAgB;AAChC,QAAI,IAAI,WAAW,iBAAiB,GAAG;AACrC,YAAM,MAAM,OAAO,KAAK,IAAI,MAAM,kBAAkB,MAAM,GAAG,QAAQ;AACrE,YAAM,OAAO,WAAW,GAAG,EAAE,SAAS,MAAM;AAC5C,aAAO,eAAkB,MAAM,KAAK,WAAW;AAAA,IACjD;AACA,WAAO,eAAkB,KAAK,KAAK,WAAW;AAAA,EAChD;AAAA,EAEA,MAAM,IAAO,KAAgC;AAC3C,UAAM,MAAM,MAAM,KAAK,OAAO,IAAI,GAAG;AACrC,QAAI,QAAQ,KAAM,QAAO;AACzB,WAAO,KAAK,OAAU,GAAG;AAAA,EAC3B;AAAA,EAEA,MAAM,IAAI,KAAa,OAAgB,YAAmC;AACxE,UAAM,KAAK,OAAO,IAAI,KAAK,KAAK,OAAO,KAAK,GAAG,MAAM,UAAU;AAAA,EACjE;AAAA,EAEA,MAAM,OAAO,MAA+B;AAC1C,QAAI,KAAK,WAAW,EAAG;AACvB,UAAM,KAAK,OAAO,IAAI,GAAG,IAAI;AAAA,EAC/B;AAAA,EAEA,MAAM,KAAK,QAAgB,SAAkC;AAC3D,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,KAAK,OAAO,KAAK,KAAK,GAAG,OAAO;AAAA,EACxC;AAAA,EAEA,MAAM,SAAS,KAAgC;AAC7C,WAAO,KAAK,OAAO,SAAS,GAAG;AAAA,EACjC;AAAA,EAEA,MAAM,MAAM,KAAa,YAAsC;AAC7D,UAAM,SAAS,MAAM,KAAK,OAAO,IAAI,KAAK,KAAK,MAAM,YAAY,IAAI;AACrE,WAAO,WAAW;AAAA,EACpB;AAAA,EAEA,MAAM,aACJ,KACA,OACA,YACA,UACe;AACf,UAAM,WAAW,KAAK,OAAO,SAAS;AACtC,aAAS,IAAI,KAAK,KAAK,OAAO,KAAK,GAAG,MAAM,UAAU;AACtD,aAAS,KAAK,UAAU,GAAG;AAC3B,aAAS,OAAO,UAAU,aAAa,gBAAgB;AACvD,UAAM,SAAS,KAAK;AAAA,EACtB;AAAA,EAEA,MAAM,kBAAkB,UAAiC;AACvD,UAAM,KAAK,OAAO,KAAK,yBAAyB,GAAG,QAAQ;AAAA,EAC7D;AAAA,EAEA,MAAM,cACJ,KACA,SACA,YACe;AACf,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,WAAW,KAAK,OAAO,SAAS;AACtC,aAAS,KAAK,KAAK,GAAG,OAAO;AAC7B,aAAS,OAAO,KAAK,aAAa,gBAAgB;AAClD,UAAM,SAAS,KAAK;AAAA,EACtB;AAAA,EAEA,MAAM,QAAW,KAAgC;AAC/C,QAAI;AACF,aAAO,MAAM,KAAK,IAAO,GAAG;AAAA,IAC9B,SAAS,KAAK;AACZ,WAAK,OAAO,eAAe,GAAG,IAAI,GAAG;AACrC,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,QACJ,KACA,OACA,YACe;AACf,QAAI;AACF,YAAM,KAAK,IAAI,KAAK,OAAO,UAAU;AAAA,IACvC,SAAS,KAAK;AACZ,WAAK,OAAO,eAAe,GAAG,IAAI,GAAG;AAAA,IACvC;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,MAA+B;AAC9C,QAAI;AACF,YAAM,KAAK,IAAI,GAAG,IAAI;AAAA,IACxB,SAAS,KAAK;AACZ,WAAK,OAAO,WAAW,GAAG;AAAA,IAC5B;AAAA,EACF;AAAA,EAEA,MAAM,UAAU,KAAa,YAAsC;AACjE,QAAI;AACF,aAAO,MAAM,KAAK,MAAM,KAAK,UAAU;AAAA,IACzC,SAAS,KAAK;AACZ,WAAK,OAAO,iBAAiB,GAAG,IAAI,GAAG;AACvC,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,iBACJ,KACA,OACA,YACA,UACe;AACf,QAAI;AACF,YAAM,KAAK,aAAa,KAAK,OAAO,YAAY,QAAQ;AAAA,IAC1D,SAAS,KAAK;AACZ,WAAK,OAAO,wBAAwB,GAAG,IAAI,GAAG;AAAA,IAChD;AAAA,EACF;AAAA,EAEA,MAAM,sBAAsB,UAAiC;AAC3D,QAAI;AACF,YAAM,KAAK,kBAAkB,QAAQ;AAAA,IACvC,SAAS,KAAK;AACZ,WAAK,OAAO,6BAA6B,QAAQ,IAAI,GAAG;AAAA,IAC1D;AAAA,EACF;AAAA,EAEA,MAAM,kBACJ,KACA,SACA,YACe;AACf,QAAI;AACF,YAAM,KAAK,cAAc,KAAK,SAAS,UAAU;AAAA,IACnD,SAAS,KAAK;AACZ,WAAK,OAAO,yBAAyB,GAAG,IAAI,GAAG;AAAA,IACjD;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,KAAgC;AACjD,QAAI;AACF,aAAO,MAAM,KAAK,SAAS,GAAG;AAAA,IAChC,SAAS,KAAK;AACZ,WAAK,OAAO,oBAAoB,GAAG,IAAI,GAAG;AAC1C,aAAO,CAAC;AAAA,IACV;AAAA,EACF;AACF;;;AE9QA;AAAA,EAEE;AAAA,EACA;AAAA,EACA;AAAA,OACK;","names":[]}
1
+ {"version":3,"sources":["../src/redis-cache-adapter.ts","../src/redis-json.ts"],"sourcesContent":["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\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":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prismakit/redis",
3
- "version": "3.2.0",
3
+ "version": "4.0.0",
4
4
  "description": "Redis CacheAdapter for @prismakit/core",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -24,10 +24,10 @@
24
24
  "README.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@prismakit/core": "3.2.0"
27
+ "@prismakit/core": "4.0.0"
28
28
  },
29
29
  "peerDependencies": {
30
- "@prismakit/core": ">=3.2.0 <4",
30
+ "@prismakit/core": ">=4.0.0 <5",
31
31
  "ioredis": ">=5.0.0"
32
32
  },
33
33
  "devDependencies": {
package/src/index.ts CHANGED
@@ -8,19 +8,10 @@ export {
8
8
  redisJsonParse,
9
9
  redisJsonStringify,
10
10
  redisJsonReplacer,
11
- redisJsonReviver,
12
11
  createRedisJsonReviver,
13
12
  cloneWithCodec,
14
13
  type DecimalFactory,
15
14
  type RedisJsonOptions,
16
15
  } from './redis-json';
17
16
 
18
- /** Re-export cache debug helpers from core — do not duplicate. */
19
- export {
20
- type CacheDebugStatus,
21
- cacheDebugStorage,
22
- isCacheDebugEnabled,
23
- recordCacheDebug,
24
- } from '@prismakit/core';
25
-
26
17
  export type { CacheAdapter } from '@prismakit/core';
package/src/redis-json.ts CHANGED
@@ -92,11 +92,6 @@ export function createRedisJsonReviver(options: RedisJsonOptions = {}) {
92
92
  };
93
93
  }
94
94
 
95
- /** @deprecated Prefer {@link createRedisJsonReviver} when a decimalFactory is needed. */
96
- export function redisJsonReviver(_key: string, value: unknown): unknown {
97
- return createRedisJsonReviver()(_key, value);
98
- }
99
-
100
95
  export function redisJsonStringify(value: unknown): string {
101
96
  return JSON.stringify(value, function (key, val) {
102
97
  const holder = this as Record<string, unknown>;