guardrail-core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. package/dist/__tests__/autopilot.test.d.ts +7 -0
  2. package/dist/__tests__/autopilot.test.d.ts.map +1 -0
  3. package/dist/__tests__/autopilot.test.js +156 -0
  4. package/dist/__tests__/tier-config.test.d.ts +9 -0
  5. package/dist/__tests__/tier-config.test.d.ts.map +1 -0
  6. package/dist/__tests__/tier-config.test.js +230 -0
  7. package/dist/__tests__/utils/hash-inline.test.d.ts +2 -0
  8. package/dist/__tests__/utils/hash-inline.test.d.ts.map +1 -0
  9. package/dist/__tests__/utils/hash-inline.test.js +62 -0
  10. package/dist/__tests__/utils/hash.test.d.ts +3 -0
  11. package/dist/__tests__/utils/hash.test.d.ts.map +1 -0
  12. package/dist/__tests__/utils/hash.test.js +95 -0
  13. package/dist/__tests__/utils/simple.test.d.ts +1 -0
  14. package/dist/__tests__/utils/simple.test.d.ts.map +1 -0
  15. package/dist/__tests__/utils/simple.test.js +10 -0
  16. package/dist/__tests__/utils/utils-simple.test.d.ts +1 -0
  17. package/dist/__tests__/utils/utils-simple.test.d.ts.map +1 -0
  18. package/dist/__tests__/utils/utils-simple.test.js +6 -0
  19. package/dist/__tests__/utils/utils.test.d.ts +15 -0
  20. package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
  21. package/dist/__tests__/utils/utils.test.js +172 -0
  22. package/dist/autopilot/autopilot-runner.d.ts +33 -0
  23. package/dist/autopilot/autopilot-runner.d.ts.map +1 -0
  24. package/dist/autopilot/autopilot-runner.js +479 -0
  25. package/dist/autopilot/index.d.ts +6 -0
  26. package/dist/autopilot/index.d.ts.map +1 -0
  27. package/dist/autopilot/index.js +25 -0
  28. package/dist/autopilot/types.d.ts +102 -0
  29. package/dist/autopilot/types.d.ts.map +1 -0
  30. package/dist/autopilot/types.js +18 -0
  31. package/dist/cache/index.d.ts +7 -0
  32. package/dist/cache/index.d.ts.map +1 -0
  33. package/dist/cache/index.js +22 -0
  34. package/dist/cache/redis-cache.d.ts +145 -0
  35. package/dist/cache/redis-cache.d.ts.map +1 -0
  36. package/dist/cache/redis-cache.js +459 -0
  37. package/dist/ci/github-actions.d.ts +77 -0
  38. package/dist/ci/github-actions.d.ts.map +1 -0
  39. package/dist/ci/github-actions.js +277 -0
  40. package/dist/ci/index.d.ts +12 -0
  41. package/dist/ci/index.d.ts.map +1 -0
  42. package/dist/ci/index.js +27 -0
  43. package/dist/ci/pre-commit.d.ts +65 -0
  44. package/dist/ci/pre-commit.d.ts.map +1 -0
  45. package/dist/ci/pre-commit.js +286 -0
  46. package/dist/entitlements.d.ts +149 -0
  47. package/dist/entitlements.d.ts.map +1 -0
  48. package/dist/entitlements.js +464 -0
  49. package/dist/env.d.ts +113 -0
  50. package/dist/env.d.ts.map +1 -0
  51. package/dist/env.js +204 -0
  52. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts +7 -0
  53. package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts.map +1 -0
  54. package/dist/fix-packs/__tests__/generate-fix-packs.test.js +250 -0
  55. package/dist/fix-packs/generate-fix-packs.d.ts +15 -0
  56. package/dist/fix-packs/generate-fix-packs.d.ts.map +1 -0
  57. package/dist/fix-packs/generate-fix-packs.js +505 -0
  58. package/dist/fix-packs/index.d.ts +8 -0
  59. package/dist/fix-packs/index.d.ts.map +1 -0
  60. package/dist/fix-packs/index.js +23 -0
  61. package/dist/fix-packs/types.d.ts +113 -0
  62. package/dist/fix-packs/types.d.ts.map +1 -0
  63. package/dist/fix-packs/types.js +71 -0
  64. package/dist/index.d.ts +13 -0
  65. package/dist/index.d.ts.map +1 -0
  66. package/dist/index.js +28 -0
  67. package/dist/metrics/prometheus.d.ts +99 -0
  68. package/dist/metrics/prometheus.d.ts.map +1 -0
  69. package/dist/metrics/prometheus.js +306 -0
  70. package/dist/quota-ledger.d.ts +119 -0
  71. package/dist/quota-ledger.d.ts.map +1 -0
  72. package/dist/quota-ledger.js +462 -0
  73. package/dist/rbac/__tests__/permissions.test.d.ts +8 -0
  74. package/dist/rbac/__tests__/permissions.test.d.ts.map +1 -0
  75. package/dist/rbac/__tests__/permissions.test.js +350 -0
  76. package/dist/rbac/index.d.ts +9 -0
  77. package/dist/rbac/index.d.ts.map +1 -0
  78. package/dist/rbac/index.js +32 -0
  79. package/dist/rbac/permissions.d.ts +71 -0
  80. package/dist/rbac/permissions.d.ts.map +1 -0
  81. package/dist/rbac/permissions.js +247 -0
  82. package/dist/rbac/types.d.ts +69 -0
  83. package/dist/rbac/types.d.ts.map +1 -0
  84. package/dist/rbac/types.js +213 -0
  85. package/dist/tier-config.d.ts +203 -0
  86. package/dist/tier-config.d.ts.map +1 -0
  87. package/dist/tier-config.js +675 -0
  88. package/dist/types.d.ts +365 -0
  89. package/dist/types.d.ts.map +1 -0
  90. package/dist/types.js +5 -0
  91. package/dist/utils.d.ts +36 -0
  92. package/dist/utils.d.ts.map +1 -0
  93. package/dist/utils.js +127 -0
  94. package/dist/verified-autofix/__tests__/format-validator.test.d.ts +11 -0
  95. package/dist/verified-autofix/__tests__/format-validator.test.d.ts.map +1 -0
  96. package/dist/verified-autofix/__tests__/format-validator.test.js +285 -0
  97. package/dist/verified-autofix/__tests__/pipeline.test.d.ts +11 -0
  98. package/dist/verified-autofix/__tests__/pipeline.test.d.ts.map +1 -0
  99. package/dist/verified-autofix/__tests__/pipeline.test.js +389 -0
  100. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts +11 -0
  101. package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts.map +1 -0
  102. package/dist/verified-autofix/__tests__/repo-fingerprint.test.js +236 -0
  103. package/dist/verified-autofix/__tests__/workspace.test.d.ts +11 -0
  104. package/dist/verified-autofix/__tests__/workspace.test.d.ts.map +1 -0
  105. package/dist/verified-autofix/__tests__/workspace.test.js +314 -0
  106. package/dist/verified-autofix/format-validator.d.ts +101 -0
  107. package/dist/verified-autofix/format-validator.d.ts.map +1 -0
  108. package/dist/verified-autofix/format-validator.js +446 -0
  109. package/dist/verified-autofix/index.d.ts +14 -0
  110. package/dist/verified-autofix/index.d.ts.map +1 -0
  111. package/dist/verified-autofix/index.js +39 -0
  112. package/dist/verified-autofix/pipeline.d.ts +68 -0
  113. package/dist/verified-autofix/pipeline.d.ts.map +1 -0
  114. package/dist/verified-autofix/pipeline.js +330 -0
  115. package/dist/verified-autofix/repo-fingerprint.d.ts +56 -0
  116. package/dist/verified-autofix/repo-fingerprint.d.ts.map +1 -0
  117. package/dist/verified-autofix/repo-fingerprint.js +396 -0
  118. package/dist/verified-autofix/workspace.d.ts +83 -0
  119. package/dist/verified-autofix/workspace.d.ts.map +1 -0
  120. package/dist/verified-autofix/workspace.js +454 -0
  121. package/dist/verified-autofix.d.ts +182 -0
  122. package/dist/verified-autofix.d.ts.map +1 -0
  123. package/dist/verified-autofix.js +1021 -0
  124. package/dist/visualization/dependency-graph.d.ts +79 -0
  125. package/dist/visualization/dependency-graph.d.ts.map +1 -0
  126. package/dist/visualization/dependency-graph.js +399 -0
  127. package/dist/visualization/index.d.ts +5 -0
  128. package/dist/visualization/index.d.ts.map +1 -0
  129. package/dist/visualization/index.js +20 -0
  130. package/package.json +29 -0
  131. package/src/__tests__/autopilot.test.ts +196 -0
  132. package/src/__tests__/tier-config.test.ts +289 -0
  133. package/src/__tests__/utils/hash-inline.test.ts +76 -0
  134. package/src/__tests__/utils/hash.test.ts +119 -0
  135. package/src/__tests__/utils/simple.test.ts +10 -0
  136. package/src/__tests__/utils/utils-simple.test.ts +5 -0
  137. package/src/__tests__/utils/utils.test.ts +203 -0
  138. package/src/autopilot/autopilot-runner.ts +503 -0
  139. package/src/autopilot/index.ts +6 -0
  140. package/src/autopilot/types.ts +119 -0
  141. package/src/cache/index.ts +7 -0
  142. package/src/cache/redis-cache.d.ts +155 -0
  143. package/src/cache/redis-cache.d.ts.map +1 -0
  144. package/src/cache/redis-cache.ts +517 -0
  145. package/src/ci/github-actions.ts +335 -0
  146. package/src/ci/index.ts +12 -0
  147. package/src/ci/pre-commit.ts +338 -0
  148. package/src/db/usage-schema.prisma +114 -0
  149. package/src/entitlements.ts +570 -0
  150. package/src/env.d.ts +68 -0
  151. package/src/env.d.ts.map +1 -0
  152. package/src/env.ts +247 -0
  153. package/src/fix-packs/__tests__/generate-fix-packs.test.ts +317 -0
  154. package/src/fix-packs/generate-fix-packs.ts +577 -0
  155. package/src/fix-packs/index.ts +8 -0
  156. package/src/fix-packs/types.ts +206 -0
  157. package/src/index.d.ts +7 -0
  158. package/src/index.d.ts.map +1 -0
  159. package/src/index.ts +12 -0
  160. package/src/metrics/prometheus.d.ts +104 -0
  161. package/src/metrics/prometheus.d.ts.map +1 -0
  162. package/src/metrics/prometheus.ts +446 -0
  163. package/src/quota-ledger.ts +548 -0
  164. package/src/rbac/__tests__/permissions.test.ts +446 -0
  165. package/src/rbac/index.ts +46 -0
  166. package/src/rbac/permissions.ts +301 -0
  167. package/src/rbac/types.ts +298 -0
  168. package/src/tier-config.json +157 -0
  169. package/src/tier-config.ts +815 -0
  170. package/src/types.d.ts +365 -0
  171. package/src/types.d.ts.map +1 -0
  172. package/src/types.ts +441 -0
  173. package/src/utils.d.ts +36 -0
  174. package/src/utils.d.ts.map +1 -0
  175. package/src/utils.ts +140 -0
  176. package/src/verified-autofix/__tests__/format-validator.test.ts +335 -0
  177. package/src/verified-autofix/__tests__/pipeline.test.ts +419 -0
  178. package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +241 -0
  179. package/src/verified-autofix/__tests__/workspace.test.ts +373 -0
  180. package/src/verified-autofix/format-validator.ts +517 -0
  181. package/src/verified-autofix/index.ts +63 -0
  182. package/src/verified-autofix/pipeline.ts +403 -0
  183. package/src/verified-autofix/repo-fingerprint.ts +459 -0
  184. package/src/verified-autofix/workspace.ts +531 -0
  185. package/src/verified-autofix.ts +1187 -0
  186. package/src/visualization/dependency-graph.d.ts +85 -0
  187. package/src/visualization/dependency-graph.d.ts.map +1 -0
  188. package/src/visualization/dependency-graph.ts +495 -0
  189. package/src/visualization/index.ts +5 -0
@@ -0,0 +1,517 @@
1
+ /**
2
+ * Redis Caching Layer
3
+ *
4
+ * Production-ready caching implementation with:
5
+ * - Connection pooling
6
+ * - Automatic serialization/deserialization
7
+ * - TTL management
8
+ * - Cache invalidation patterns
9
+ * - Metrics tracking
10
+ */
11
+
12
+ export interface RedisCacheConfig {
13
+ host: string;
14
+ port: number;
15
+ password?: string;
16
+ db?: number;
17
+ keyPrefix?: string;
18
+ defaultTTL?: number;
19
+ maxRetries?: number;
20
+ retryDelay?: number;
21
+ enableMetrics?: boolean;
22
+ }
23
+
24
+ export interface CacheEntry<T> {
25
+ value: T;
26
+ createdAt: number;
27
+ expiresAt: number;
28
+ tags?: string[];
29
+ }
30
+
31
+ export interface CacheStats {
32
+ hits: number;
33
+ misses: number;
34
+ sets: number;
35
+ deletes: number;
36
+ errors: number;
37
+ hitRate: number;
38
+ avgLatency: number;
39
+ }
40
+
41
+ type RedisClient = {
42
+ get: (key: string) => Promise<string | null>;
43
+ set: (
44
+ key: string,
45
+ value: string,
46
+ options?: { EX?: number },
47
+ ) => Promise<string | null>;
48
+ del: (key: string | string[]) => Promise<number>;
49
+ keys: (pattern: string) => Promise<string[]>;
50
+ expire: (key: string, seconds: number) => Promise<number>;
51
+ ttl: (key: string) => Promise<number>;
52
+ exists: (key: string) => Promise<number>;
53
+ flushdb: () => Promise<string>;
54
+ ping: () => Promise<string>;
55
+ quit: () => Promise<string>;
56
+ };
57
+
58
+ export class RedisCache {
59
+ private client: RedisClient | null = null;
60
+ private config: RedisCacheConfig;
61
+ private stats: CacheStats = {
62
+ hits: 0,
63
+ misses: 0,
64
+ sets: 0,
65
+ deletes: 0,
66
+ errors: 0,
67
+ hitRate: 0,
68
+ avgLatency: 0,
69
+ };
70
+ private latencies: number[] = [];
71
+ private connected = false;
72
+ private memoryFallback: Map<string, { value: string; expiresAt: number }> =
73
+ new Map();
74
+
75
+ constructor(config: Partial<RedisCacheConfig> = {}) {
76
+ this.config = {
77
+ host: config.host || process.env["REDIS_HOST"] || "localhost",
78
+ port: config.port || parseInt(process.env["REDIS_PORT"] || "6379"),
79
+ password: config.password || process.env["REDIS_PASSWORD"],
80
+ db: config.db || 0,
81
+ keyPrefix: config.keyPrefix || "Guardrail:",
82
+ defaultTTL: config.defaultTTL || 3600, // 1 hour
83
+ maxRetries: config.maxRetries || 3,
84
+ retryDelay: config.retryDelay || 1000,
85
+ enableMetrics: config.enableMetrics ?? true,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Connect to Redis
91
+ */
92
+ async connect(): Promise<void> {
93
+ try {
94
+ // Dynamic import to avoid errors if redis is not installed
95
+ // @ts-ignore
96
+ const { createClient } = await import("redis");
97
+
98
+ this.client = createClient({
99
+ socket: {
100
+ host: this.config.host,
101
+ port: this.config.port,
102
+ },
103
+ password: this.config.password,
104
+ database: this.config.db,
105
+ }) as unknown as RedisClient;
106
+
107
+ await (this.client as any).connect();
108
+ this.connected = true;
109
+ console.log("Redis cache connected");
110
+ } catch (error) {
111
+ console.warn("Redis connection failed, using in-memory fallback:", error);
112
+ this.connected = false;
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Disconnect from Redis
118
+ */
119
+ async disconnect(): Promise<void> {
120
+ if (this.client && this.connected) {
121
+ await this.client.quit();
122
+ this.connected = false;
123
+ }
124
+ }
125
+
126
+ /**
127
+ * Get prefixed key
128
+ */
129
+ private getKey(key: string): string {
130
+ return `${this.config.keyPrefix}${key}`;
131
+ }
132
+
133
+ /**
134
+ * Get value from cache
135
+ */
136
+ async get<T>(key: string): Promise<T | null> {
137
+ const startTime = Date.now();
138
+ const prefixedKey = this.getKey(key);
139
+
140
+ try {
141
+ let data: string | null = null;
142
+
143
+ if (this.connected && this.client) {
144
+ data = await this.client.get(prefixedKey);
145
+ } else {
146
+ const entry = this.memoryFallback.get(prefixedKey);
147
+ if (entry && entry.expiresAt > Date.now()) {
148
+ data = entry.value;
149
+ } else if (entry) {
150
+ this.memoryFallback.delete(prefixedKey);
151
+ }
152
+ }
153
+
154
+ this.recordLatency(Date.now() - startTime);
155
+
156
+ if (data) {
157
+ this.stats.hits++;
158
+ this.updateHitRate();
159
+ const entry: CacheEntry<T> = JSON.parse(data);
160
+ return entry.value;
161
+ }
162
+
163
+ this.stats.misses++;
164
+ this.updateHitRate();
165
+ return null;
166
+ } catch (error) {
167
+ this.stats.errors++;
168
+ console.error("Cache get error:", error);
169
+ return null;
170
+ }
171
+ }
172
+
173
+ /**
174
+ * Set value in cache
175
+ */
176
+ async set<T>(
177
+ key: string,
178
+ value: T,
179
+ ttl?: number,
180
+ tags?: string[],
181
+ ): Promise<boolean> {
182
+ const startTime = Date.now();
183
+ const prefixedKey = this.getKey(key);
184
+ const expiresIn = ttl || this.config.defaultTTL || 3600;
185
+
186
+ try {
187
+ const entry: CacheEntry<T> = {
188
+ value,
189
+ createdAt: Date.now(),
190
+ expiresAt: Date.now() + expiresIn * 1000,
191
+ tags,
192
+ };
193
+
194
+ const serialized = JSON.stringify(entry);
195
+
196
+ if (this.connected && this.client) {
197
+ await this.client.set(prefixedKey, serialized, { EX: expiresIn });
198
+ } else {
199
+ this.memoryFallback.set(prefixedKey, {
200
+ value: serialized,
201
+ expiresAt: Date.now() + expiresIn * 1000,
202
+ });
203
+ }
204
+
205
+ // Store tag mappings for invalidation
206
+ if (tags && tags.length > 0) {
207
+ for (const tag of tags) {
208
+ await this.addKeyToTag(tag, prefixedKey);
209
+ }
210
+ }
211
+
212
+ this.stats.sets++;
213
+ this.recordLatency(Date.now() - startTime);
214
+ return true;
215
+ } catch (error) {
216
+ this.stats.errors++;
217
+ console.error("Cache set error:", error);
218
+ return false;
219
+ }
220
+ }
221
+
222
+ /**
223
+ * Delete value from cache
224
+ */
225
+ async delete(key: string): Promise<boolean> {
226
+ const prefixedKey = this.getKey(key);
227
+
228
+ try {
229
+ if (this.connected && this.client) {
230
+ await this.client.del(prefixedKey);
231
+ } else {
232
+ this.memoryFallback.delete(prefixedKey);
233
+ }
234
+
235
+ this.stats.deletes++;
236
+ return true;
237
+ } catch (error) {
238
+ this.stats.errors++;
239
+ console.error("Cache delete error:", error);
240
+ return false;
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Delete multiple keys by pattern
246
+ */
247
+ async deletePattern(pattern: string): Promise<number> {
248
+ const prefixedPattern = this.getKey(pattern);
249
+
250
+ try {
251
+ if (this.connected && this.client) {
252
+ const keys = await this.client.keys(prefixedPattern);
253
+ if (keys.length > 0) {
254
+ await this.client.del(keys);
255
+ this.stats.deletes += keys.length;
256
+ return keys.length;
257
+ }
258
+ } else {
259
+ const regex = new RegExp(prefixedPattern.replace(/\*/g, ".*"));
260
+ let count = 0;
261
+ for (const key of this.memoryFallback.keys()) {
262
+ if (regex.test(key)) {
263
+ this.memoryFallback.delete(key);
264
+ count++;
265
+ }
266
+ }
267
+ this.stats.deletes += count;
268
+ return count;
269
+ }
270
+ return 0;
271
+ } catch (error) {
272
+ this.stats.errors++;
273
+ console.error("Cache deletePattern error:", error);
274
+ return 0;
275
+ }
276
+ }
277
+
278
+ /**
279
+ * Invalidate cache by tag
280
+ */
281
+ async invalidateByTag(tag: string): Promise<number> {
282
+ const tagKey = this.getKey(`tag:${tag}`);
283
+
284
+ try {
285
+ if (this.connected && this.client) {
286
+ const keysData = await this.client.get(tagKey);
287
+ if (keysData) {
288
+ const keys: string[] = JSON.parse(keysData);
289
+ if (keys.length > 0) {
290
+ await this.client.del(keys);
291
+ await this.client.del(tagKey);
292
+ this.stats.deletes += keys.length;
293
+ return keys.length;
294
+ }
295
+ }
296
+ }
297
+ return 0;
298
+ } catch (error) {
299
+ this.stats.errors++;
300
+ console.error("Cache invalidateByTag error:", error);
301
+ return 0;
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Add key to tag set
307
+ */
308
+ private async addKeyToTag(tag: string, key: string): Promise<void> {
309
+ const tagKey = this.getKey(`tag:${tag}`);
310
+
311
+ try {
312
+ if (this.connected && this.client) {
313
+ const existing = await this.client.get(tagKey);
314
+ const keys: string[] = existing ? JSON.parse(existing) : [];
315
+ if (!keys.includes(key)) {
316
+ keys.push(key);
317
+ await this.client.set(tagKey, JSON.stringify(keys), { EX: 86400 }); // 24 hour TTL for tags
318
+ }
319
+ }
320
+ } catch (error) {
321
+ console.error("Cache addKeyToTag error:", error);
322
+ }
323
+ }
324
+
325
+ /**
326
+ * Check if key exists
327
+ */
328
+ async exists(key: string): Promise<boolean> {
329
+ const prefixedKey = this.getKey(key);
330
+
331
+ try {
332
+ if (this.connected && this.client) {
333
+ const result = await this.client.exists(prefixedKey);
334
+ return result === 1;
335
+ } else {
336
+ const entry = this.memoryFallback.get(prefixedKey);
337
+ return entry !== undefined && entry.expiresAt > Date.now();
338
+ }
339
+ } catch (error) {
340
+ return false;
341
+ }
342
+ }
343
+
344
+ /**
345
+ * Get remaining TTL
346
+ */
347
+ async getTTL(key: string): Promise<number> {
348
+ const prefixedKey = this.getKey(key);
349
+
350
+ try {
351
+ if (this.connected && this.client) {
352
+ return await this.client.ttl(prefixedKey);
353
+ } else {
354
+ const entry = this.memoryFallback.get(prefixedKey);
355
+ if (entry) {
356
+ return Math.max(0, Math.floor((entry.expiresAt - Date.now()) / 1000));
357
+ }
358
+ return -2;
359
+ }
360
+ } catch (error) {
361
+ return -1;
362
+ }
363
+ }
364
+
365
+ /**
366
+ * Get or set (cache-aside pattern)
367
+ */
368
+ async getOrSet<T>(
369
+ key: string,
370
+ factory: () => Promise<T>,
371
+ ttl?: number,
372
+ tags?: string[],
373
+ ): Promise<T> {
374
+ const cached = await this.get<T>(key);
375
+ if (cached !== null) {
376
+ return cached;
377
+ }
378
+
379
+ const value = await factory();
380
+ await this.set(key, value, ttl, tags);
381
+ return value;
382
+ }
383
+
384
+ /**
385
+ * Flush all cache
386
+ */
387
+ async flush(): Promise<boolean> {
388
+ try {
389
+ if (this.connected && this.client) {
390
+ await this.client.flushdb();
391
+ } else {
392
+ this.memoryFallback.clear();
393
+ }
394
+ return true;
395
+ } catch (error) {
396
+ this.stats.errors++;
397
+ return false;
398
+ }
399
+ }
400
+
401
+ /**
402
+ * Health check
403
+ */
404
+ async ping(): Promise<boolean> {
405
+ try {
406
+ if (this.connected && this.client) {
407
+ const result = await this.client.ping();
408
+ return result === "PONG";
409
+ }
410
+ return true; // Memory fallback always available
411
+ } catch (error) {
412
+ return false;
413
+ }
414
+ }
415
+
416
+ /**
417
+ * Get cache statistics
418
+ */
419
+ getStats(): CacheStats {
420
+ return { ...this.stats };
421
+ }
422
+
423
+ /**
424
+ * Reset statistics
425
+ */
426
+ resetStats(): void {
427
+ this.stats = {
428
+ hits: 0,
429
+ misses: 0,
430
+ sets: 0,
431
+ deletes: 0,
432
+ errors: 0,
433
+ hitRate: 0,
434
+ avgLatency: 0,
435
+ };
436
+ this.latencies = [];
437
+ }
438
+
439
+ /**
440
+ * Record latency for metrics
441
+ */
442
+ private recordLatency(ms: number): void {
443
+ if (!this.config.enableMetrics) return;
444
+
445
+ this.latencies.push(ms);
446
+ if (this.latencies.length > 1000) {
447
+ this.latencies.shift();
448
+ }
449
+
450
+ this.stats.avgLatency =
451
+ this.latencies.reduce((a, b) => a + b, 0) / this.latencies.length;
452
+ }
453
+
454
+ /**
455
+ * Update hit rate
456
+ */
457
+ private updateHitRate(): void {
458
+ const total = this.stats.hits + this.stats.misses;
459
+ this.stats.hitRate = total > 0 ? this.stats.hits / total : 0;
460
+ }
461
+
462
+ /**
463
+ * Check if connected to Redis
464
+ */
465
+ isConnected(): boolean {
466
+ return this.connected;
467
+ }
468
+ }
469
+
470
+ // Cache key generators for common use cases
471
+ export const CacheKeys = {
472
+ // Scan results
473
+ scanResult: (projectId: string, scanType: string) =>
474
+ `scan:${projectId}:${scanType}`,
475
+
476
+ // Knowledge base
477
+ knowledgeBase: (projectPath: string) =>
478
+ `knowledge:${Buffer.from(projectPath).toString("base64").slice(0, 32)}`,
479
+
480
+ // Embeddings
481
+ embedding: (contentHash: string) => `embedding:${contentHash}`,
482
+
483
+ // Vulnerability data
484
+ vulnerability: (packageName: string, version: string) =>
485
+ `vuln:${packageName}:${version}`,
486
+
487
+ // License data
488
+ license: (packageName: string) => `license:${packageName}`,
489
+
490
+ // Compliance assessment
491
+ compliance: (projectId: string, framework: string) =>
492
+ `compliance:${projectId}:${framework}`,
493
+
494
+ // User session
495
+ session: (sessionId: string) => `session:${sessionId}`,
496
+
497
+ // Rate limiting
498
+ rateLimit: (identifier: string, action: string) =>
499
+ `ratelimit:${identifier}:${action}`,
500
+ };
501
+
502
+ // Cache TTL presets (in seconds)
503
+ export const CacheTTL = {
504
+ SHORT: 300, // 5 minutes
505
+ MEDIUM: 3600, // 1 hour
506
+ LONG: 86400, // 24 hours
507
+ WEEK: 604800, // 7 days
508
+ SCAN_RESULT: 1800, // 30 minutes
509
+ KNOWLEDGE: 3600, // 1 hour
510
+ EMBEDDING: 604800, // 7 days
511
+ VULNERABILITY: 21600, // 6 hours
512
+ LICENSE: 604800, // 7 days
513
+ SESSION: 86400, // 24 hours
514
+ };
515
+
516
+ // Export singleton
517
+ export const cache = new RedisCache();