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.
- package/dist/__tests__/autopilot.test.d.ts +7 -0
- package/dist/__tests__/autopilot.test.d.ts.map +1 -0
- package/dist/__tests__/autopilot.test.js +156 -0
- package/dist/__tests__/tier-config.test.d.ts +9 -0
- package/dist/__tests__/tier-config.test.d.ts.map +1 -0
- package/dist/__tests__/tier-config.test.js +230 -0
- package/dist/__tests__/utils/hash-inline.test.d.ts +2 -0
- package/dist/__tests__/utils/hash-inline.test.d.ts.map +1 -0
- package/dist/__tests__/utils/hash-inline.test.js +62 -0
- package/dist/__tests__/utils/hash.test.d.ts +3 -0
- package/dist/__tests__/utils/hash.test.d.ts.map +1 -0
- package/dist/__tests__/utils/hash.test.js +95 -0
- package/dist/__tests__/utils/simple.test.d.ts +1 -0
- package/dist/__tests__/utils/simple.test.d.ts.map +1 -0
- package/dist/__tests__/utils/simple.test.js +10 -0
- package/dist/__tests__/utils/utils-simple.test.d.ts +1 -0
- package/dist/__tests__/utils/utils-simple.test.d.ts.map +1 -0
- package/dist/__tests__/utils/utils-simple.test.js +6 -0
- package/dist/__tests__/utils/utils.test.d.ts +15 -0
- package/dist/__tests__/utils/utils.test.d.ts.map +1 -0
- package/dist/__tests__/utils/utils.test.js +172 -0
- package/dist/autopilot/autopilot-runner.d.ts +33 -0
- package/dist/autopilot/autopilot-runner.d.ts.map +1 -0
- package/dist/autopilot/autopilot-runner.js +479 -0
- package/dist/autopilot/index.d.ts +6 -0
- package/dist/autopilot/index.d.ts.map +1 -0
- package/dist/autopilot/index.js +25 -0
- package/dist/autopilot/types.d.ts +102 -0
- package/dist/autopilot/types.d.ts.map +1 -0
- package/dist/autopilot/types.js +18 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +22 -0
- package/dist/cache/redis-cache.d.ts +145 -0
- package/dist/cache/redis-cache.d.ts.map +1 -0
- package/dist/cache/redis-cache.js +459 -0
- package/dist/ci/github-actions.d.ts +77 -0
- package/dist/ci/github-actions.d.ts.map +1 -0
- package/dist/ci/github-actions.js +277 -0
- package/dist/ci/index.d.ts +12 -0
- package/dist/ci/index.d.ts.map +1 -0
- package/dist/ci/index.js +27 -0
- package/dist/ci/pre-commit.d.ts +65 -0
- package/dist/ci/pre-commit.d.ts.map +1 -0
- package/dist/ci/pre-commit.js +286 -0
- package/dist/entitlements.d.ts +149 -0
- package/dist/entitlements.d.ts.map +1 -0
- package/dist/entitlements.js +464 -0
- package/dist/env.d.ts +113 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +204 -0
- package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts +7 -0
- package/dist/fix-packs/__tests__/generate-fix-packs.test.d.ts.map +1 -0
- package/dist/fix-packs/__tests__/generate-fix-packs.test.js +250 -0
- package/dist/fix-packs/generate-fix-packs.d.ts +15 -0
- package/dist/fix-packs/generate-fix-packs.d.ts.map +1 -0
- package/dist/fix-packs/generate-fix-packs.js +505 -0
- package/dist/fix-packs/index.d.ts +8 -0
- package/dist/fix-packs/index.d.ts.map +1 -0
- package/dist/fix-packs/index.js +23 -0
- package/dist/fix-packs/types.d.ts +113 -0
- package/dist/fix-packs/types.d.ts.map +1 -0
- package/dist/fix-packs/types.js +71 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/metrics/prometheus.d.ts +99 -0
- package/dist/metrics/prometheus.d.ts.map +1 -0
- package/dist/metrics/prometheus.js +306 -0
- package/dist/quota-ledger.d.ts +119 -0
- package/dist/quota-ledger.d.ts.map +1 -0
- package/dist/quota-ledger.js +462 -0
- package/dist/rbac/__tests__/permissions.test.d.ts +8 -0
- package/dist/rbac/__tests__/permissions.test.d.ts.map +1 -0
- package/dist/rbac/__tests__/permissions.test.js +350 -0
- package/dist/rbac/index.d.ts +9 -0
- package/dist/rbac/index.d.ts.map +1 -0
- package/dist/rbac/index.js +32 -0
- package/dist/rbac/permissions.d.ts +71 -0
- package/dist/rbac/permissions.d.ts.map +1 -0
- package/dist/rbac/permissions.js +247 -0
- package/dist/rbac/types.d.ts +69 -0
- package/dist/rbac/types.d.ts.map +1 -0
- package/dist/rbac/types.js +213 -0
- package/dist/tier-config.d.ts +203 -0
- package/dist/tier-config.d.ts.map +1 -0
- package/dist/tier-config.js +675 -0
- package/dist/types.d.ts +365 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/utils.d.ts +36 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +127 -0
- package/dist/verified-autofix/__tests__/format-validator.test.d.ts +11 -0
- package/dist/verified-autofix/__tests__/format-validator.test.d.ts.map +1 -0
- package/dist/verified-autofix/__tests__/format-validator.test.js +285 -0
- package/dist/verified-autofix/__tests__/pipeline.test.d.ts +11 -0
- package/dist/verified-autofix/__tests__/pipeline.test.d.ts.map +1 -0
- package/dist/verified-autofix/__tests__/pipeline.test.js +389 -0
- package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts +11 -0
- package/dist/verified-autofix/__tests__/repo-fingerprint.test.d.ts.map +1 -0
- package/dist/verified-autofix/__tests__/repo-fingerprint.test.js +236 -0
- package/dist/verified-autofix/__tests__/workspace.test.d.ts +11 -0
- package/dist/verified-autofix/__tests__/workspace.test.d.ts.map +1 -0
- package/dist/verified-autofix/__tests__/workspace.test.js +314 -0
- package/dist/verified-autofix/format-validator.d.ts +101 -0
- package/dist/verified-autofix/format-validator.d.ts.map +1 -0
- package/dist/verified-autofix/format-validator.js +446 -0
- package/dist/verified-autofix/index.d.ts +14 -0
- package/dist/verified-autofix/index.d.ts.map +1 -0
- package/dist/verified-autofix/index.js +39 -0
- package/dist/verified-autofix/pipeline.d.ts +68 -0
- package/dist/verified-autofix/pipeline.d.ts.map +1 -0
- package/dist/verified-autofix/pipeline.js +330 -0
- package/dist/verified-autofix/repo-fingerprint.d.ts +56 -0
- package/dist/verified-autofix/repo-fingerprint.d.ts.map +1 -0
- package/dist/verified-autofix/repo-fingerprint.js +396 -0
- package/dist/verified-autofix/workspace.d.ts +83 -0
- package/dist/verified-autofix/workspace.d.ts.map +1 -0
- package/dist/verified-autofix/workspace.js +454 -0
- package/dist/verified-autofix.d.ts +182 -0
- package/dist/verified-autofix.d.ts.map +1 -0
- package/dist/verified-autofix.js +1021 -0
- package/dist/visualization/dependency-graph.d.ts +79 -0
- package/dist/visualization/dependency-graph.d.ts.map +1 -0
- package/dist/visualization/dependency-graph.js +399 -0
- package/dist/visualization/index.d.ts +5 -0
- package/dist/visualization/index.d.ts.map +1 -0
- package/dist/visualization/index.js +20 -0
- package/package.json +29 -0
- package/src/__tests__/autopilot.test.ts +196 -0
- package/src/__tests__/tier-config.test.ts +289 -0
- package/src/__tests__/utils/hash-inline.test.ts +76 -0
- package/src/__tests__/utils/hash.test.ts +119 -0
- package/src/__tests__/utils/simple.test.ts +10 -0
- package/src/__tests__/utils/utils-simple.test.ts +5 -0
- package/src/__tests__/utils/utils.test.ts +203 -0
- package/src/autopilot/autopilot-runner.ts +503 -0
- package/src/autopilot/index.ts +6 -0
- package/src/autopilot/types.ts +119 -0
- package/src/cache/index.ts +7 -0
- package/src/cache/redis-cache.d.ts +155 -0
- package/src/cache/redis-cache.d.ts.map +1 -0
- package/src/cache/redis-cache.ts +517 -0
- package/src/ci/github-actions.ts +335 -0
- package/src/ci/index.ts +12 -0
- package/src/ci/pre-commit.ts +338 -0
- package/src/db/usage-schema.prisma +114 -0
- package/src/entitlements.ts +570 -0
- package/src/env.d.ts +68 -0
- package/src/env.d.ts.map +1 -0
- package/src/env.ts +247 -0
- package/src/fix-packs/__tests__/generate-fix-packs.test.ts +317 -0
- package/src/fix-packs/generate-fix-packs.ts +577 -0
- package/src/fix-packs/index.ts +8 -0
- package/src/fix-packs/types.ts +206 -0
- package/src/index.d.ts +7 -0
- package/src/index.d.ts.map +1 -0
- package/src/index.ts +12 -0
- package/src/metrics/prometheus.d.ts +104 -0
- package/src/metrics/prometheus.d.ts.map +1 -0
- package/src/metrics/prometheus.ts +446 -0
- package/src/quota-ledger.ts +548 -0
- package/src/rbac/__tests__/permissions.test.ts +446 -0
- package/src/rbac/index.ts +46 -0
- package/src/rbac/permissions.ts +301 -0
- package/src/rbac/types.ts +298 -0
- package/src/tier-config.json +157 -0
- package/src/tier-config.ts +815 -0
- package/src/types.d.ts +365 -0
- package/src/types.d.ts.map +1 -0
- package/src/types.ts +441 -0
- package/src/utils.d.ts +36 -0
- package/src/utils.d.ts.map +1 -0
- package/src/utils.ts +140 -0
- package/src/verified-autofix/__tests__/format-validator.test.ts +335 -0
- package/src/verified-autofix/__tests__/pipeline.test.ts +419 -0
- package/src/verified-autofix/__tests__/repo-fingerprint.test.ts +241 -0
- package/src/verified-autofix/__tests__/workspace.test.ts +373 -0
- package/src/verified-autofix/format-validator.ts +517 -0
- package/src/verified-autofix/index.ts +63 -0
- package/src/verified-autofix/pipeline.ts +403 -0
- package/src/verified-autofix/repo-fingerprint.ts +459 -0
- package/src/verified-autofix/workspace.ts +531 -0
- package/src/verified-autofix.ts +1187 -0
- package/src/visualization/dependency-graph.d.ts +85 -0
- package/src/visualization/dependency-graph.d.ts.map +1 -0
- package/src/visualization/dependency-graph.ts +495 -0
- 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();
|