clhq-cache-module 1.1.0-alpha.100

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 ADDED
@@ -0,0 +1,47 @@
1
+ # Clippy Cache Module
2
+
3
+ Reusable NestJS module that wires Redis backed caching (via `cache-manager`) for all Clippy API services.
4
+
5
+ ## Features
6
+
7
+ - Async Redis store bootstrap with automatic fallback to in-memory cache for local development.
8
+ - Helper service with `get`, `set`, `del`, `delMany`, and `wrap` helpers.
9
+ - Configurable through environment variables or module options.
10
+
11
+ ## Environment Variables
12
+
13
+ | Variable | Description | Default |
14
+ | --- | --- | --- |
15
+ | `REDIS_HOST` | Redis endpoint hostname | `undefined` |
16
+ | `REDIS_PORT` | Redis endpoint port | `6379` |
17
+ | `REDIS_URL` | Full connection URL. Takes precedence over host/port | `undefined` |
18
+ | `REDIS_USERNAME` | ACL user | `undefined` |
19
+ | `REDIS_PASSWORD` | Auth token | `undefined` |
20
+ | `REDIS_DB` | Database index | `0` |
21
+ | `REDIS_TLS_ENABLED` | Enable TLS | `true` |
22
+ | `REDIS_CACHE_TTL_SECONDS` | Default TTL for cache entries | `300` |
23
+ | `REDIS_CACHE_MAX_ITEMS` | Max number of cache entries | `1000` |
24
+
25
+ ## Usage
26
+
27
+ ```ts
28
+ import { RedisCacheModule, RedisCacheService } from 'clhq-cache-module';
29
+
30
+ @Module({
31
+ imports: [RedisCacheModule.register()],
32
+ providers: [SampleService],
33
+ })
34
+ export class SampleModule {}
35
+
36
+ @Injectable()
37
+ export class SampleService {
38
+ constructor(private readonly cache: RedisCacheService) {}
39
+
40
+ async getValue(id: string) {
41
+ return this.cache.wrap(`sample:${id}`, async () => {
42
+ return this.repository.findById(id);
43
+ }, 600);
44
+ }
45
+ }
46
+ ```
47
+
@@ -0,0 +1,4 @@
1
+ export * from './interfaces/redis-cache-options.interface';
2
+ export * from './redis-cache.module';
3
+ export * from './redis-cache.service';
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":"AAAA,cAAc,4CAA4C,CAAC;AAC3D,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./interfaces/redis-cache-options.interface"), exports);
18
+ __exportStar(require("./redis-cache.module"), exports);
19
+ __exportStar(require("./redis-cache.service"), exports);
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"/","sources":["index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,6EAA2D;AAC3D,uDAAqC;AACrC,wDAAsC","sourcesContent":["export * from './interfaces/redis-cache-options.interface';\nexport * from './redis-cache.module';\nexport * from './redis-cache.service';\n// ValkeyProxyClient removed - using in-memory cache only\n// To re-enable: uncomment and recreate valkey-proxy-client.ts\n// export * from './valkey-proxy-client';\n"]}
@@ -0,0 +1,33 @@
1
+ export interface RedisCacheModuleOptions {
2
+ host?: string;
3
+ port?: number;
4
+ url?: string;
5
+ username?: string;
6
+ password?: string;
7
+ db?: number;
8
+ ttlMs?: number;
9
+ max?: number;
10
+ clusterNodes?: Array<{
11
+ host: string;
12
+ port?: number;
13
+ }>;
14
+ tlsEnabled?: boolean;
15
+ isGlobal?: boolean;
16
+ }
17
+ export interface InternalRedisCacheOptions extends RedisCacheModuleOptions {
18
+ resolvedHost?: string;
19
+ resolvedPort?: number;
20
+ resolvedUrl?: string;
21
+ resolvedUsername?: string;
22
+ resolvedPassword?: string;
23
+ resolvedDb?: number;
24
+ resolvedTtlMs?: number;
25
+ resolvedMax?: number;
26
+ resolvedTlsEnabled?: boolean;
27
+ resolvedIsGlobal?: boolean;
28
+ resolvedClusterNodes?: Array<{
29
+ host: string;
30
+ port?: number;
31
+ }>;
32
+ }
33
+ //# sourceMappingURL=redis-cache-options.interface.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache-options.interface.d.ts","sourceRoot":"/","sources":["interfaces/redis-cache-options.interface.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,uBAAuB;IAKtC,IAAI,CAAC,EAAE,MAAM,CAAC;IAKd,IAAI,CAAC,EAAE,MAAM,CAAC;IAId,GAAG,CAAC,EAAE,MAAM,CAAC;IAIb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAIlB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAIlB,EAAE,CAAC,EAAE,MAAM,CAAC;IAIZ,KAAK,CAAC,EAAE,MAAM,CAAC;IAIf,GAAG,CAAC,EAAE,MAAM,CAAC;IAKb,YAAY,CAAC,EAAE,KAAK,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;IAIH,UAAU,CAAC,EAAE,OAAO,CAAC;IAIrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,yBAA0B,SAAQ,uBAAuB;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,oBAAoB,CAAC,EAAE,KAAK,CAAC;QAC3B,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;KACf,CAAC,CAAC;CACJ"}
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=redis-cache-options.interface.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache-options.interface.js","sourceRoot":"/","sources":["interfaces/redis-cache-options.interface.ts"],"names":[],"mappings":"","sourcesContent":["export interface RedisCacheModuleOptions {\n /**\n * Hostname for the Redis endpoint.\n * When omitted the module falls back to REDIS_HOST env variable.\n */\n host?: string;\n /**\n * Port for the Redis endpoint.\n * Defaults to REDIS_PORT env variable or 6379.\n */\n port?: number;\n /**\n * Full connection url. Takes precedence over host/port when provided.\n */\n url?: string;\n /**\n * Optional username for Redis ACL based auth.\n */\n username?: string;\n /**\n * Optional password/token for Redis auth.\n */\n password?: string;\n /**\n * Database index to use. Defaults to 0.\n */\n db?: number;\n /**\n * TTL for cached entries in milliseconds. Defaults to 300_000 (5 minutes).\n */\n ttlMs?: number;\n /**\n * Max number of items to keep in local memory cache.\n */\n max?: number;\n /**\n * Optional Valkey/Redis cluster nodes in host[:port] format.\n * Takes precedence over single host/port when provided.\n */\n clusterNodes?: Array<{\n host: string;\n port?: number;\n }>;\n /**\n * Enable TLS when connecting to Redis.\n */\n tlsEnabled?: boolean;\n /**\n * Whether to register the CacheModule globally.\n */\n isGlobal?: boolean;\n}\n\nexport interface InternalRedisCacheOptions extends RedisCacheModuleOptions {\n resolvedHost?: string;\n resolvedPort?: number;\n resolvedUrl?: string;\n resolvedUsername?: string;\n resolvedPassword?: string;\n resolvedDb?: number;\n resolvedTtlMs?: number;\n resolvedMax?: number;\n resolvedTlsEnabled?: boolean;\n resolvedIsGlobal?: boolean;\n resolvedClusterNodes?: Array<{\n host: string;\n port?: number;\n }>;\n}\n"]}
@@ -0,0 +1,2 @@
1
+ export declare const REDIS_CACHE_OPTIONS: unique symbol;
2
+ //# sourceMappingURL=redis-cache.constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache.constants.d.ts","sourceRoot":"/","sources":["redis-cache.constants.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,mBAAmB,eAAgC,CAAC"}
@@ -0,0 +1,5 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.REDIS_CACHE_OPTIONS = void 0;
4
+ exports.REDIS_CACHE_OPTIONS = Symbol('REDIS_CACHE_OPTIONS');
5
+ //# sourceMappingURL=redis-cache.constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache.constants.js","sourceRoot":"/","sources":["redis-cache.constants.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG,MAAM,CAAC,qBAAqB,CAAC,CAAC","sourcesContent":["export const REDIS_CACHE_OPTIONS = Symbol('REDIS_CACHE_OPTIONS');\n\n"]}
@@ -0,0 +1,6 @@
1
+ import { DynamicModule } from '@nestjs/common';
2
+ import { RedisCacheModuleOptions } from './interfaces/redis-cache-options.interface';
3
+ export declare class RedisCacheModule {
4
+ static register(options?: RedisCacheModuleOptions): DynamicModule;
5
+ }
6
+ //# sourceMappingURL=redis-cache.module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache.module.d.ts","sourceRoot":"/","sources":["redis-cache.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAkB,MAAM,gBAAgB,CAAC;AAE/D,OAAO,EAEL,uBAAuB,EACxB,MAAM,4CAA4C,CAAC;AAGpD,qBAEa,gBAAgB;IAC3B,MAAM,CAAC,QAAQ,CAAC,OAAO,GAAE,uBAA4B,GAAG,aAAa;CAatE"}
@@ -0,0 +1,79 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var RedisCacheModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.RedisCacheModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const redis_cache_service_1 = require("./redis-cache.service");
13
+ const redis_cache_constants_1 = require("./redis-cache.constants");
14
+ let RedisCacheModule = RedisCacheModule_1 = class RedisCacheModule {
15
+ static register(options = {}) {
16
+ const resolvedOptions = resolveOptions(options);
17
+ const cacheOptionsProvider = {
18
+ provide: redis_cache_constants_1.REDIS_CACHE_OPTIONS,
19
+ useValue: resolvedOptions,
20
+ };
21
+ return {
22
+ module: RedisCacheModule_1,
23
+ providers: [cacheOptionsProvider, redis_cache_service_1.RedisCacheService],
24
+ exports: [redis_cache_service_1.RedisCacheService],
25
+ };
26
+ }
27
+ };
28
+ exports.RedisCacheModule = RedisCacheModule;
29
+ exports.RedisCacheModule = RedisCacheModule = RedisCacheModule_1 = __decorate([
30
+ (0, common_1.Global)(),
31
+ (0, common_1.Module)({})
32
+ ], RedisCacheModule);
33
+ function resolveOptions(options) {
34
+ return {
35
+ ...options,
36
+ resolvedClusterNodes: options.clusterNodes ??
37
+ parseClusterNodes(process.env.VALKEY_CLUSTER_ENDPOINTS ??
38
+ process.env.REDIS_CLUSTER_ENDPOINTS ??
39
+ ''),
40
+ resolvedHost: options.host,
41
+ resolvedPort: options.port ?? 6379,
42
+ resolvedUrl: options.url,
43
+ resolvedUsername: options.username,
44
+ resolvedPassword: options.password,
45
+ resolvedDb: options.db ?? 0,
46
+ resolvedTtlMs: Number(options.ttlMs ?? process.env.REDIS_CACHE_TTL_MS ?? 300000),
47
+ resolvedMax: Number(options.max ?? process.env.REDIS_CACHE_MAX_ITEMS ?? 1000),
48
+ resolvedTlsEnabled: parseBoolean(options.tlsEnabled ?? process.env.REDIS_TLS_ENABLED ?? 'true'),
49
+ resolvedIsGlobal: options.isGlobal ?? true,
50
+ };
51
+ }
52
+ function parseBoolean(value) {
53
+ if (typeof value === 'boolean') {
54
+ return value;
55
+ }
56
+ if (typeof value === 'string') {
57
+ return ['true', '1', 'yes'].includes(value.toLowerCase());
58
+ }
59
+ return Boolean(value);
60
+ }
61
+ function parseClusterNodes(value) {
62
+ if (!value) {
63
+ return undefined;
64
+ }
65
+ const nodes = value
66
+ .split(',')
67
+ .map((token) => token.trim())
68
+ .filter(Boolean)
69
+ .map((token) => {
70
+ const [host, port] = token.split(':');
71
+ return {
72
+ host,
73
+ port: port ? Number(port) : undefined,
74
+ };
75
+ })
76
+ .filter((node) => node.host);
77
+ return nodes.length ? nodes : undefined;
78
+ }
79
+ //# sourceMappingURL=redis-cache.module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache.module.js","sourceRoot":"/","sources":["redis-cache.module.ts"],"names":[],"mappings":"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","sourcesContent":["import { DynamicModule, Global, Module } from '@nestjs/common';\nimport { RedisCacheService } from './redis-cache.service';\nimport {\n InternalRedisCacheOptions,\n RedisCacheModuleOptions,\n} from './interfaces/redis-cache-options.interface';\nimport { REDIS_CACHE_OPTIONS } from './redis-cache.constants';\n\n@Global()\n@Module({})\nexport class RedisCacheModule {\n static register(options: RedisCacheModuleOptions = {}): DynamicModule {\n const resolvedOptions = resolveOptions(options);\n const cacheOptionsProvider = {\n provide: REDIS_CACHE_OPTIONS,\n useValue: resolvedOptions,\n };\n\n return {\n module: RedisCacheModule,\n providers: [cacheOptionsProvider, RedisCacheService],\n exports: [RedisCacheService],\n };\n }\n}\n\nfunction resolveOptions(\n options: RedisCacheModuleOptions,\n): InternalRedisCacheOptions {\n return {\n ...options,\n resolvedClusterNodes:\n options.clusterNodes ??\n parseClusterNodes(\n process.env.VALKEY_CLUSTER_ENDPOINTS ??\n process.env.REDIS_CLUSTER_ENDPOINTS ??\n '',\n ),\n resolvedHost: options.host,\n resolvedPort: options.port ?? 6379,\n resolvedUrl: options.url,\n resolvedUsername: options.username,\n resolvedPassword: options.password,\n resolvedDb: options.db ?? 0,\n resolvedTtlMs: Number(\n options.ttlMs ?? process.env.REDIS_CACHE_TTL_MS ?? 300000,\n ),\n resolvedMax: Number(\n options.max ?? process.env.REDIS_CACHE_MAX_ITEMS ?? 1000,\n ),\n resolvedTlsEnabled: parseBoolean(\n options.tlsEnabled ?? process.env.REDIS_TLS_ENABLED ?? 'true',\n ),\n resolvedIsGlobal: options.isGlobal ?? true,\n };\n}\n\nfunction parseBoolean(value: unknown): boolean {\n if (typeof value === 'boolean') {\n return value;\n }\n if (typeof value === 'string') {\n return ['true', '1', 'yes'].includes(value.toLowerCase());\n }\n return Boolean(value);\n}\n\nfunction parseClusterNodes(\n value?: string,\n): Array<{ host: string; port?: number }> | undefined {\n if (!value) {\n return undefined;\n }\n const nodes = value\n .split(',')\n .map((token) => token.trim())\n .filter(Boolean)\n .map((token) => {\n const [host, port] = token.split(':');\n return {\n host,\n port: port ? Number(port) : undefined,\n };\n })\n .filter((node) => node.host);\n return nodes.length ? nodes : undefined;\n}\n"]}
@@ -0,0 +1,28 @@
1
+ import { OnModuleDestroy } from '@nestjs/common';
2
+ import { InternalRedisCacheOptions } from './interfaces/redis-cache-options.interface';
3
+ export interface CacheStats {
4
+ hits: number;
5
+ misses: number;
6
+ size: number;
7
+ maxSize: number;
8
+ hitRate: number;
9
+ }
10
+ export declare class RedisCacheService implements OnModuleDestroy {
11
+ private readonly options;
12
+ private readonly logger;
13
+ private readonly memoryCache;
14
+ private hits;
15
+ private misses;
16
+ constructor(options: InternalRedisCacheOptions);
17
+ onModuleDestroy(): Promise<void>;
18
+ get<T>(key: string): Promise<T | undefined>;
19
+ set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
20
+ del(key: string): Promise<void>;
21
+ delMany(keys: string[]): Promise<void>;
22
+ wrap<T>(key: string, fn: () => Promise<T>, ttlMs?: number): Promise<T>;
23
+ getStats(): CacheStats;
24
+ resetStats(): void;
25
+ clear(): void;
26
+ private enforceMemoryLimit;
27
+ }
28
+ //# sourceMappingURL=redis-cache.service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache.service.d.ts","sourceRoot":"/","sources":["redis-cache.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAA8B,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAE7E,OAAO,EAAE,yBAAyB,EAAE,MAAM,4CAA4C,CAAC;AAwBvF,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,qBACa,iBAAkB,YAAW,eAAe;IAUrD,OAAO,CAAC,QAAQ,CAAC,OAAO;IAT1B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAsC;IAC7D,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAuC;IAGnE,OAAO,CAAC,IAAI,CAAK;IACjB,OAAO,CAAC,MAAM,CAAK;gBAIA,OAAO,EAAE,yBAAyB;IAO/C,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC;IAShC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAyB3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB5D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAO/B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAOtC,IAAI,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAkB5E,QAAQ,IAAI,UAAU;IActB,UAAU,IAAI,IAAI;IAQlB,KAAK,IAAI,IAAI;IASb,OAAO,CAAC,kBAAkB;CAgC3B"}
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ var RedisCacheService_1;
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.RedisCacheService = void 0;
17
+ const common_1 = require("@nestjs/common");
18
+ const redis_cache_constants_1 = require("./redis-cache.constants");
19
+ let RedisCacheService = RedisCacheService_1 = class RedisCacheService {
20
+ constructor(options) {
21
+ this.options = options;
22
+ this.logger = new common_1.Logger(RedisCacheService_1.name);
23
+ this.memoryCache = new Map();
24
+ this.hits = 0;
25
+ this.misses = 0;
26
+ this.logger.log(`In-memory cache initialized (max items: ${this.options.resolvedMax}, default TTL: ${this.options.resolvedTtlMs}ms)`);
27
+ }
28
+ async onModuleDestroy() {
29
+ this.memoryCache.clear();
30
+ this.logger.log('In-memory cache cleared on module destroy');
31
+ }
32
+ async get(key) {
33
+ const entry = this.memoryCache.get(key);
34
+ if (!entry) {
35
+ this.misses++;
36
+ return undefined;
37
+ }
38
+ if (entry.expiresAt && entry.expiresAt <= Date.now()) {
39
+ this.memoryCache.delete(key);
40
+ this.misses++;
41
+ return undefined;
42
+ }
43
+ entry.lastAccessed = Date.now();
44
+ this.hits++;
45
+ return entry.value;
46
+ }
47
+ async set(key, value, ttlMs) {
48
+ const ttl = ttlMs ?? this.options.resolvedTtlMs;
49
+ const expiresAt = ttl ? Date.now() + ttl : undefined;
50
+ this.memoryCache.set(key, {
51
+ value,
52
+ expiresAt,
53
+ lastAccessed: Date.now(),
54
+ });
55
+ this.enforceMemoryLimit();
56
+ }
57
+ async del(key) {
58
+ this.memoryCache.delete(key);
59
+ }
60
+ async delMany(keys) {
61
+ keys.forEach((key) => this.memoryCache.delete(key));
62
+ }
63
+ async wrap(key, fn, ttlMs) {
64
+ const cached = await this.get(key);
65
+ if (cached !== undefined) {
66
+ return cached;
67
+ }
68
+ const value = await fn();
69
+ if (value !== undefined) {
70
+ await this.set(key, value, ttlMs);
71
+ }
72
+ else {
73
+ this.logger.warn(`Computed cache value for key ${key} is undefined`);
74
+ }
75
+ return value;
76
+ }
77
+ getStats() {
78
+ const total = this.hits + this.misses;
79
+ return {
80
+ hits: this.hits,
81
+ misses: this.misses,
82
+ size: this.memoryCache.size,
83
+ maxSize: this.options.resolvedMax ?? 1000,
84
+ hitRate: total > 0 ? this.hits / total : 0,
85
+ };
86
+ }
87
+ resetStats() {
88
+ this.hits = 0;
89
+ this.misses = 0;
90
+ }
91
+ clear() {
92
+ this.memoryCache.clear();
93
+ this.logger.log('In-memory cache cleared');
94
+ }
95
+ enforceMemoryLimit() {
96
+ const maxSize = this.options.resolvedMax ?? 1000;
97
+ if (this.memoryCache.size <= maxSize) {
98
+ return;
99
+ }
100
+ const now = Date.now();
101
+ for (const [key, entry] of this.memoryCache.entries()) {
102
+ if (entry.expiresAt && entry.expiresAt <= now) {
103
+ this.memoryCache.delete(key);
104
+ }
105
+ }
106
+ if (this.memoryCache.size <= maxSize) {
107
+ return;
108
+ }
109
+ const entries = Array.from(this.memoryCache.entries());
110
+ entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);
111
+ const entriesToRemove = entries.slice(0, this.memoryCache.size - maxSize + 1);
112
+ for (const [key] of entriesToRemove) {
113
+ this.memoryCache.delete(key);
114
+ }
115
+ }
116
+ };
117
+ exports.RedisCacheService = RedisCacheService;
118
+ exports.RedisCacheService = RedisCacheService = RedisCacheService_1 = __decorate([
119
+ (0, common_1.Injectable)(),
120
+ __param(0, (0, common_1.Inject)(redis_cache_constants_1.REDIS_CACHE_OPTIONS)),
121
+ __metadata("design:paramtypes", [Object])
122
+ ], RedisCacheService);
123
+ //# sourceMappingURL=redis-cache.service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redis-cache.service.js","sourceRoot":"/","sources":["redis-cache.service.ts"],"names":[],"mappings":"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","sourcesContent":["import { Inject, Injectable, Logger, OnModuleDestroy } from '@nestjs/common';\nimport { REDIS_CACHE_OPTIONS } from './redis-cache.constants';\nimport { InternalRedisCacheOptions } from './interfaces/redis-cache-options.interface';\n\n// NOTE: ioredis is kept as a dependency for potential future Redis/Valkey re-enablement\n// To re-enable Redis support:\n// 1. Uncomment the Redis imports and client code below\n// 2. Set REDIS_ENABLED=true environment variable\n// 3. Configure REDIS_HOST/VALKEY_CLUSTER_ENDPOINTS as needed\n\n// import Redis, { Cluster } from 'ioredis';\n// import { ValkeyProxyClient } from './valkey-proxy-client';\n// type RedisLikeClient = Redis | Cluster;\n\n/**\n * Cache entry with value, expiration time, and last access time for LRU eviction\n */\ntype MemoryCacheEntry = {\n value: unknown;\n expiresAt?: number;\n lastAccessed: number;\n};\n\n/**\n * Cache statistics for monitoring\n */\nexport interface CacheStats {\n hits: number;\n misses: number;\n size: number;\n maxSize: number;\n hitRate: number;\n}\n\n@Injectable()\nexport class RedisCacheService implements OnModuleDestroy {\n private readonly logger = new Logger(RedisCacheService.name);\n private readonly memoryCache = new Map<string, MemoryCacheEntry>();\n\n // Cache statistics\n private hits = 0;\n private misses = 0;\n\n constructor(\n @Inject(REDIS_CACHE_OPTIONS)\n private readonly options: InternalRedisCacheOptions,\n ) {\n this.logger.log(\n `In-memory cache initialized (max items: ${this.options.resolvedMax}, default TTL: ${this.options.resolvedTtlMs}ms)`,\n );\n }\n\n async onModuleDestroy(): Promise<void> {\n // Clear in-memory cache on module destroy\n this.memoryCache.clear();\n this.logger.log('In-memory cache cleared on module destroy');\n }\n\n /**\n * Get a cached value by key\n */\n async get<T>(key: string): Promise<T | undefined> {\n const entry = this.memoryCache.get(key);\n\n if (!entry) {\n this.misses++;\n return undefined;\n }\n\n // Check if entry has expired\n if (entry.expiresAt && entry.expiresAt <= Date.now()) {\n this.memoryCache.delete(key);\n this.misses++;\n return undefined;\n }\n\n // Update last accessed time for LRU tracking\n entry.lastAccessed = Date.now();\n this.hits++;\n\n return entry.value as T;\n }\n\n /**\n * Set a cached value with optional TTL\n */\n async set<T>(key: string, value: T, ttlMs?: number): Promise<void> {\n const ttl = ttlMs ?? this.options.resolvedTtlMs;\n const expiresAt = ttl ? Date.now() + ttl : undefined;\n\n this.memoryCache.set(key, {\n value,\n expiresAt,\n lastAccessed: Date.now(),\n });\n\n this.enforceMemoryLimit();\n }\n\n /**\n * Delete a cached value by key\n */\n async del(key: string): Promise<void> {\n this.memoryCache.delete(key);\n }\n\n /**\n * Delete multiple cached values by keys\n */\n async delMany(keys: string[]): Promise<void> {\n keys.forEach((key) => this.memoryCache.delete(key));\n }\n\n /**\n * Cache-aside pattern: get from cache or compute and store\n */\n async wrap<T>(key: string, fn: () => Promise<T>, ttlMs?: number): Promise<T> {\n const cached = await this.get<T>(key);\n if (cached !== undefined) {\n return cached;\n }\n\n const value = await fn();\n if (value !== undefined) {\n await this.set(key, value, ttlMs);\n } else {\n this.logger.warn(`Computed cache value for key ${key} is undefined`);\n }\n return value;\n }\n\n /**\n * Get cache statistics for monitoring\n */\n getStats(): CacheStats {\n const total = this.hits + this.misses;\n return {\n hits: this.hits,\n misses: this.misses,\n size: this.memoryCache.size,\n maxSize: this.options.resolvedMax ?? 1000,\n hitRate: total > 0 ? this.hits / total : 0,\n };\n }\n\n /**\n * Reset cache statistics\n */\n resetStats(): void {\n this.hits = 0;\n this.misses = 0;\n }\n\n /**\n * Clear all cached entries\n */\n clear(): void {\n this.memoryCache.clear();\n this.logger.log('In-memory cache cleared');\n }\n\n /**\n * Enforce memory limit using LRU eviction policy\n * Removes least recently accessed entries when cache exceeds max size\n */\n private enforceMemoryLimit(): void {\n const maxSize = this.options.resolvedMax ?? 1000;\n\n if (this.memoryCache.size <= maxSize) {\n return;\n }\n\n // First, remove expired entries\n const now = Date.now();\n for (const [key, entry] of this.memoryCache.entries()) {\n if (entry.expiresAt && entry.expiresAt <= now) {\n this.memoryCache.delete(key);\n }\n }\n\n // If still over limit, apply LRU eviction\n if (this.memoryCache.size <= maxSize) {\n return;\n }\n\n // Find and remove least recently accessed entries\n const entries = Array.from(this.memoryCache.entries());\n entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);\n\n const entriesToRemove = entries.slice(\n 0,\n this.memoryCache.size - maxSize + 1,\n );\n for (const [key] of entriesToRemove) {\n this.memoryCache.delete(key);\n }\n }\n}\n\n// =============================================================================\n// DISABLED: Redis/Valkey direct connection code\n// =============================================================================\n// To re-enable Redis support in the future:\n//\n// 1. Uncomment the imports at the top of this file:\n// import Redis, { Cluster } from 'ioredis';\n// import { ValkeyProxyClient } from './valkey-proxy-client';\n//\n// 2. Add these private fields to the class:\n// private readonly redisClient?: RedisLikeClient;\n// private readonly proxyClient?: ValkeyProxyClient;\n//\n// 3. Add Redis initialization to the constructor:\n// if (process.env.REDIS_ENABLED === 'true') {\n// if (process.env.VALKEY_PROXY_FUNCTION_NAME) {\n// this.proxyClient = new ValkeyProxyClient();\n// this.logger.log('Using Valkey Lambda Proxy');\n// } else if (options.resolvedUrl || options.resolvedHost || options.resolvedClusterNodes?.length) {\n// this.redisClient = this.createRedisClient(options);\n// }\n// }\n//\n// 4. Add Redis checks to get/set/del/delMany methods before in-memory fallback\n//\n// 5. Re-create the valkey-proxy-client.ts file if Lambda proxy is needed\n// =============================================================================\n"]}
@@ -0,0 +1,10 @@
1
+ export declare class ValkeyProxyClient {
2
+ private functionName;
3
+ constructor();
4
+ get<T>(key: string): Promise<T | undefined>;
5
+ set<T>(key: string, value: T, ttlMs?: number): Promise<void>;
6
+ del(key: string): Promise<void>;
7
+ delMany(keys: string[]): Promise<void>;
8
+ private invoke;
9
+ }
10
+ //# sourceMappingURL=valkey-proxy-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valkey-proxy-client.d.ts","sourceRoot":"/","sources":["valkey-proxy-client.ts"],"names":[],"mappings":"AAoBA,qBAAa,iBAAiB;IAC5B,OAAO,CAAC,YAAY,CAAS;;IASvB,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,SAAS,CAAC;IAK3C,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/B,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;YAI9B,MAAM;CAsBrB"}
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValkeyProxyClient = void 0;
4
+ const client_lambda_1 = require("@aws-sdk/client-lambda");
5
+ const lambdaClient = new client_lambda_1.Lambda({
6
+ region: process.env.CLIPPY_AWS_DEFAULT_REGION || 'ap-southeast-2',
7
+ });
8
+ class ValkeyProxyClient {
9
+ constructor() {
10
+ this.functionName = process.env.VALKEY_PROXY_FUNCTION_NAME;
11
+ if (!this.functionName) {
12
+ throw new Error('VALKEY_PROXY_FUNCTION_NAME environment variable not set');
13
+ }
14
+ }
15
+ async get(key) {
16
+ const response = await this.invoke({ operation: 'get', key });
17
+ return response.value;
18
+ }
19
+ async set(key, value, ttlMs) {
20
+ await this.invoke({ operation: 'set', key, value, ttlMs });
21
+ }
22
+ async del(key) {
23
+ await this.invoke({ operation: 'del', key });
24
+ }
25
+ async delMany(keys) {
26
+ await this.invoke({ operation: 'delMany', keys });
27
+ }
28
+ async invoke(payload) {
29
+ try {
30
+ const result = await lambdaClient.invoke({
31
+ FunctionName: this.functionName,
32
+ InvocationType: 'RequestResponse',
33
+ Payload: Buffer.from(JSON.stringify(payload)),
34
+ });
35
+ const responsePayload = JSON.parse(new TextDecoder().decode(result.Payload));
36
+ if (!responsePayload.success) {
37
+ throw new Error(`Valkey proxy error: ${responsePayload.error}`);
38
+ }
39
+ return responsePayload;
40
+ }
41
+ catch (error) {
42
+ console.error('Failed to invoke valkey-proxy:', error);
43
+ throw error;
44
+ }
45
+ }
46
+ }
47
+ exports.ValkeyProxyClient = ValkeyProxyClient;
48
+ //# sourceMappingURL=valkey-proxy-client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"valkey-proxy-client.js","sourceRoot":"/","sources":["valkey-proxy-client.ts"],"names":[],"mappings":";;;AAAA,0DAAgD;AAEhD,MAAM,YAAY,GAAG,IAAI,sBAAM,CAAC;IAC9B,MAAM,EAAE,OAAO,CAAC,GAAG,CAAC,yBAAyB,IAAI,gBAAgB;CAClE,CAAC,CAAC;AAgBH,MAAa,iBAAiB;IAG5B;QACE,IAAI,CAAC,YAAY,GAAG,OAAO,CAAC,GAAG,CAAC,0BAA0B,CAAC;QAC3D,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,CAAC;YACvB,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC;QAC7E,CAAC;IACH,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;QAC9D,OAAO,QAAQ,CAAC,KAAU,CAAC;IAC7B,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,KAAQ,EAAE,KAAc;QAChD,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC7D,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,IAAc;QAC1B,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,CAAC;IAEO,KAAK,CAAC,MAAM,CAAC,OAAqB;QACxC,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,YAAY,CAAC,MAAM,CAAC;gBACvC,YAAY,EAAE,IAAI,CAAC,YAAY;gBAC/B,cAAc,EAAE,iBAAiB;gBACjC,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;aAC9C,CAAC,CAAC;YAEH,MAAM,eAAe,GAAG,IAAI,CAAC,KAAK,CAChC,IAAI,WAAW,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CACxB,CAAC;YAEnB,IAAI,CAAC,eAAe,CAAC,OAAO,EAAE,CAAC;gBAC7B,MAAM,IAAI,KAAK,CAAC,uBAAuB,eAAe,CAAC,KAAK,EAAE,CAAC,CAAC;YAClE,CAAC;YAED,OAAO,eAAe,CAAC;QACzB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,KAAK,CAAC,gCAAgC,EAAE,KAAK,CAAC,CAAC;YACvD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF;AAjDD,8CAiDC","sourcesContent":["import { Lambda } from '@aws-sdk/client-lambda';\n\nconst lambdaClient = new Lambda({\n region: process.env.CLIPPY_AWS_DEFAULT_REGION || 'ap-southeast-2',\n});\n\ninterface CacheRequest {\n operation: 'get' | 'set' | 'del' | 'delMany';\n key?: string;\n keys?: string[];\n value?: unknown;\n ttlMs?: number;\n}\n\ninterface CacheResponse {\n success: boolean;\n value?: unknown;\n error?: string;\n}\n\nexport class ValkeyProxyClient {\n private functionName: string;\n\n constructor() {\n this.functionName = process.env.VALKEY_PROXY_FUNCTION_NAME;\n if (!this.functionName) {\n throw new Error('VALKEY_PROXY_FUNCTION_NAME environment variable not set');\n }\n }\n\n async get<T>(key: string): Promise<T | undefined> {\n const response = await this.invoke({ operation: 'get', key });\n return response.value as T;\n }\n\n async set<T>(key: string, value: T, ttlMs?: number): Promise<void> {\n await this.invoke({ operation: 'set', key, value, ttlMs });\n }\n\n async del(key: string): Promise<void> {\n await this.invoke({ operation: 'del', key });\n }\n\n async delMany(keys: string[]): Promise<void> {\n await this.invoke({ operation: 'delMany', keys });\n }\n\n private async invoke(payload: CacheRequest): Promise<CacheResponse> {\n try {\n const result = await lambdaClient.invoke({\n FunctionName: this.functionName,\n InvocationType: 'RequestResponse',\n Payload: Buffer.from(JSON.stringify(payload)),\n });\n\n const responsePayload = JSON.parse(\n new TextDecoder().decode(result.Payload),\n ) as CacheResponse;\n\n if (!responsePayload.success) {\n throw new Error(`Valkey proxy error: ${responsePayload.error}`);\n }\n\n return responsePayload;\n } catch (error) {\n console.error('Failed to invoke valkey-proxy:', error);\n throw error;\n }\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "clhq-cache-module",
3
+ "version": "1.1.0-alpha.100",
4
+ "description": "Reusable Redis cache module for NestJS services in the Clippy stack.",
5
+ "author": "clhq",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/reacthub-pricematch/clhq-api-monorepo"
14
+ },
15
+ "registry": "https://registry.npmjs.org/",
16
+ "publishConfig": {
17
+ "registry": "https://registry.npmjs.org/",
18
+ "access": "public"
19
+ },
20
+ "bugs": {
21
+ "url": "https://github.com/reacthub-pricematch/clhq-api-monorepo/issues"
22
+ },
23
+ "license": "MIT",
24
+ "scripts": {
25
+ "build": "yarn exec tsc -p tsconfig.json",
26
+ "clean:dist": "rimraf dist",
27
+ "clean:all": "yarn run clean:dist",
28
+ "format": "prettier --write \"src/**/*.ts\"",
29
+ "lint": "eslint \"src/**/*.ts\" --fix",
30
+ "version:upgrade:alpha": "standard-version --prerelease alpha",
31
+ "release": "npm publish --tag alpha",
32
+ "release:alpha": "npm publish --tag alpha",
33
+ "release:prealpha": "",
34
+ "clean:build": "rimraf .build",
35
+ "clean:webpack": "rimraf .webpack",
36
+ "clean:serverless": "rimraf .serverless",
37
+ "sls:deploy": "sls deploy",
38
+ "sls:deploy:dev": "env-cmd -f ../../.env.dev sls deploy --stage dev",
39
+ "sls:remove:dev": "env-cmd -f ../../.env.dev sls remove --stage dev",
40
+ "sls:offline": "sls offline",
41
+ "sls:package": "sls package",
42
+ "sls:package2": "npm run clean:all&& npm run sls:package",
43
+ "sls:remove": "sls remove"
44
+ },
45
+ "peerDependencies": {
46
+ "@nestjs/common": ">=11.0.0"
47
+ },
48
+ "dependencies": {
49
+ "ioredis": "^5.4.1"
50
+ },
51
+ "devDependencies": {
52
+ "@nestjs/common": "^11.1.5",
53
+ "@nestjs/platform-express": "^11.1.5",
54
+ "class-transformer": "0.5.1",
55
+ "class-validator": "0.14.2",
56
+ "prettier": "^3.6.1",
57
+ "rimraf": "^6.0.1",
58
+ "standard-version": "^9.5.0",
59
+ "typescript": "^5.8.3"
60
+ }
61
+ }