@vreko/cli 3.0.1
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/LICENSE +201 -0
- package/README.md +45 -0
- package/dist/CeremonyView-LQS7FTMK.js +134 -0
- package/dist/CeremonyView-LQS7FTMK.js.map +1 -0
- package/dist/InitApp-7K5DTYSW.js +1479 -0
- package/dist/InitApp-7K5DTYSW.js.map +1 -0
- package/dist/SkippedTestDetector-PJSKSOZR.js +7 -0
- package/dist/SkippedTestDetector-PJSKSOZR.js.map +1 -0
- package/dist/TuiApp-FX23XQBK.js +8 -0
- package/dist/TuiApp-FX23XQBK.js.map +1 -0
- package/dist/analysis-ABEO6RTN.js +8 -0
- package/dist/analysis-ABEO6RTN.js.map +1 -0
- package/dist/auth-XNBEBNPY.js +7669 -0
- package/dist/auth-XNBEBNPY.js.map +1 -0
- package/dist/ceremony-M7CXVBVA.js +45 -0
- package/dist/ceremony-M7CXVBVA.js.map +1 -0
- package/dist/chunk-A3QSZJPD.js +3147 -0
- package/dist/chunk-A3QSZJPD.js.map +1 -0
- package/dist/chunk-ASGZ5B6C.js +3969 -0
- package/dist/chunk-ASGZ5B6C.js.map +1 -0
- package/dist/chunk-DMXC2JTC.js +58 -0
- package/dist/chunk-DMXC2JTC.js.map +1 -0
- package/dist/chunk-EEBSK2IH.js +161 -0
- package/dist/chunk-EEBSK2IH.js.map +1 -0
- package/dist/chunk-EWOJGXRX.js +22 -0
- package/dist/chunk-EWOJGXRX.js.map +1 -0
- package/dist/chunk-F7GEJLP7.js +2389 -0
- package/dist/chunk-F7GEJLP7.js.map +1 -0
- package/dist/chunk-GOYL3F4T.js +605 -0
- package/dist/chunk-GOYL3F4T.js.map +1 -0
- package/dist/chunk-GRMRYWYS.js +17 -0
- package/dist/chunk-GRMRYWYS.js.map +1 -0
- package/dist/chunk-GSUGROXB.js +1951 -0
- package/dist/chunk-GSUGROXB.js.map +1 -0
- package/dist/chunk-H7773ONB.js +50 -0
- package/dist/chunk-H7773ONB.js.map +1 -0
- package/dist/chunk-HFQHU5LC.js +445 -0
- package/dist/chunk-HFQHU5LC.js.map +1 -0
- package/dist/chunk-IVHUBLJD.js +318 -0
- package/dist/chunk-IVHUBLJD.js.map +1 -0
- package/dist/chunk-KJWKY4L4.js +14 -0
- package/dist/chunk-KJWKY4L4.js.map +1 -0
- package/dist/chunk-MJVY2XUN.js +1793 -0
- package/dist/chunk-MJVY2XUN.js.map +1 -0
- package/dist/chunk-QWZVCJII.js +1797 -0
- package/dist/chunk-QWZVCJII.js.map +1 -0
- package/dist/chunk-VTSNRV3V.js +3237 -0
- package/dist/chunk-VTSNRV3V.js.map +1 -0
- package/dist/chunk-W5B4GTXR.js +1466 -0
- package/dist/chunk-W5B4GTXR.js.map +1 -0
- package/dist/chunk-WZEZLVOW.js +4995 -0
- package/dist/chunk-WZEZLVOW.js.map +1 -0
- package/dist/chunk-YPTTIXKC.js +199 -0
- package/dist/chunk-YPTTIXKC.js.map +1 -0
- package/dist/chunk-Z55UGM6X.js +6360 -0
- package/dist/chunk-Z55UGM6X.js.map +1 -0
- package/dist/chunk-ZIIRQODJ.js +110 -0
- package/dist/chunk-ZIIRQODJ.js.map +1 -0
- package/dist/chunk-ZSUQ4FMB.js +77 -0
- package/dist/chunk-ZSUQ4FMB.js.map +1 -0
- package/dist/client-JMTSZS3V.js +10 -0
- package/dist/client-JMTSZS3V.js.map +1 -0
- package/dist/deprecated-snap.js +19 -0
- package/dist/deprecated-snap.js.map +1 -0
- package/dist/dist-2KWBZFLA.js +14 -0
- package/dist/dist-2KWBZFLA.js.map +1 -0
- package/dist/dist-5ZYKNNU3.js +7 -0
- package/dist/dist-5ZYKNNU3.js.map +1 -0
- package/dist/dist-CP3RFHPI.js +11 -0
- package/dist/dist-CP3RFHPI.js.map +1 -0
- package/dist/gecko-53ITAGG6.js +56 -0
- package/dist/gecko-53ITAGG6.js.map +1 -0
- package/dist/guards-QAFC64NO.js +7 -0
- package/dist/guards-QAFC64NO.js.map +1 -0
- package/dist/index.js +57785 -0
- package/dist/index.js.map +1 -0
- package/dist/init-command-246JIVXM.js +7 -0
- package/dist/init-command-246JIVXM.js.map +1 -0
- package/dist/init-core-KAI7LCXZ.js +12 -0
- package/dist/init-core-KAI7LCXZ.js.map +1 -0
- package/dist/init-scan-RZNYDTUV.js +1919 -0
- package/dist/init-scan-RZNYDTUV.js.map +1 -0
- package/dist/local-service-adapter-6KNN6WQL.js +8 -0
- package/dist/local-service-adapter-6KNN6WQL.js.map +1 -0
- package/dist/secure-credentials-JXWAQLS2.js +306 -0
- package/dist/secure-credentials-JXWAQLS2.js.map +1 -0
- package/dist/tui-TPJPUS2R.js +111 -0
- package/dist/tui-TPJPUS2R.js.map +1 -0
- package/dist/vreko-dir-O3RLG7PI.js +8 -0
- package/dist/vreko-dir-O3RLG7PI.js.map +1 -0
- package/package.json +132 -0
- package/scripts/check-banned-words.ts +152 -0
- package/scripts/hooks/posttooluse-file-notify.sh +108 -0
- package/scripts/hooks/pretooluse-fragile-guard.sh +82 -0
- package/scripts/post-install-notice.js +24 -0
- package/scripts/postinstall.mjs +84 -0
- package/scripts/preuninstall.mjs +34 -0
- package/scripts/verify-jsx-transform.mjs +55 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createLogger, LogLevel } from './chunk-WZEZLVOW.js';
|
|
3
|
+
import { __name } from './chunk-EWOJGXRX.js';
|
|
4
|
+
import { createClient } from 'redis';
|
|
5
|
+
|
|
6
|
+
process.env.VREKO_CLI='true';process.env.NODE_NO_WARNINGS='1';
|
|
7
|
+
createLogger({
|
|
8
|
+
name: "redis-factory",
|
|
9
|
+
level: LogLevel.INFO
|
|
10
|
+
});
|
|
11
|
+
function isSocketTimeoutError(cause) {
|
|
12
|
+
if (!cause) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return cause?.name === "SocketTimeoutError" || cause?.message?.includes("socket timeout");
|
|
16
|
+
}
|
|
17
|
+
__name(isSocketTimeoutError, "isSocketTimeoutError");
|
|
18
|
+
|
|
19
|
+
// ../../packages/platform/dist/cache/redis-metrics.js
|
|
20
|
+
var LATENCY_THRESHOLDS = {
|
|
21
|
+
/** Under 100ms is healthy */
|
|
22
|
+
healthy: 100,
|
|
23
|
+
/** Under 500ms is degraded */
|
|
24
|
+
degraded: 500
|
|
25
|
+
};
|
|
26
|
+
var RedisMetricsCollector = class {
|
|
27
|
+
static {
|
|
28
|
+
__name(this, "RedisMetricsCollector");
|
|
29
|
+
}
|
|
30
|
+
reconnectAttempts = 0;
|
|
31
|
+
lastSuccessAt = null;
|
|
32
|
+
lastErrorAt = null;
|
|
33
|
+
lastError = null;
|
|
34
|
+
keyPrefix;
|
|
35
|
+
constructor(keyPrefix = "") {
|
|
36
|
+
this.keyPrefix = keyPrefix;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Record a successful operation
|
|
40
|
+
*/
|
|
41
|
+
recordSuccess() {
|
|
42
|
+
this.lastSuccessAt = Date.now();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Record an error
|
|
46
|
+
*/
|
|
47
|
+
recordError(error) {
|
|
48
|
+
this.lastErrorAt = Date.now();
|
|
49
|
+
this.lastError = error.message;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Record a reconnection attempt
|
|
53
|
+
*/
|
|
54
|
+
recordReconnect() {
|
|
55
|
+
this.reconnectAttempts++;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reset reconnection counter (after successful connection)
|
|
59
|
+
*/
|
|
60
|
+
resetReconnectCount() {
|
|
61
|
+
this.reconnectAttempts = 0;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Measure Redis latency using PING command
|
|
65
|
+
*/
|
|
66
|
+
async measureLatency(client) {
|
|
67
|
+
if (!client || !client.isReady) {
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
const start = Date.now();
|
|
72
|
+
await client.ping();
|
|
73
|
+
const latency = Date.now() - start;
|
|
74
|
+
this.recordSuccess();
|
|
75
|
+
return latency;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
this.recordError(error);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Collect comprehensive metrics
|
|
83
|
+
*/
|
|
84
|
+
async collect(client) {
|
|
85
|
+
const isConnected = client?.isReady ?? false;
|
|
86
|
+
const isOpen = client?.isOpen ?? false;
|
|
87
|
+
const latency = await this.measureLatency(client);
|
|
88
|
+
let status;
|
|
89
|
+
let message;
|
|
90
|
+
if (!isConnected) {
|
|
91
|
+
status = "unhealthy";
|
|
92
|
+
message = "Redis client not connected";
|
|
93
|
+
} else if (latency === null) {
|
|
94
|
+
status = "unhealthy";
|
|
95
|
+
message = "Redis PING failed";
|
|
96
|
+
} else if (latency < LATENCY_THRESHOLDS.healthy) {
|
|
97
|
+
status = "healthy";
|
|
98
|
+
message = `Redis latency: ${latency}ms`;
|
|
99
|
+
} else if (latency < LATENCY_THRESHOLDS.degraded) {
|
|
100
|
+
status = "degraded";
|
|
101
|
+
message = `Redis latency elevated: ${latency}ms`;
|
|
102
|
+
} else {
|
|
103
|
+
status = "unhealthy";
|
|
104
|
+
message = `Redis latency too high: ${latency}ms`;
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
isConnected,
|
|
108
|
+
isOpen,
|
|
109
|
+
latency,
|
|
110
|
+
status,
|
|
111
|
+
message,
|
|
112
|
+
lastSuccessAt: this.lastSuccessAt,
|
|
113
|
+
lastErrorAt: this.lastErrorAt,
|
|
114
|
+
lastError: this.lastError,
|
|
115
|
+
reconnectAttempts: this.reconnectAttempts,
|
|
116
|
+
keyPrefix: this.keyPrefix
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get current reconnection attempt count
|
|
121
|
+
*/
|
|
122
|
+
getReconnectAttempts() {
|
|
123
|
+
return this.reconnectAttempts;
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
var metricsCollectors = /* @__PURE__ */ new Map();
|
|
127
|
+
function getMetricsCollector(keyPrefix = "") {
|
|
128
|
+
let collector = metricsCollectors.get(keyPrefix);
|
|
129
|
+
if (!collector) {
|
|
130
|
+
collector = new RedisMetricsCollector(keyPrefix);
|
|
131
|
+
metricsCollectors.set(keyPrefix, collector);
|
|
132
|
+
}
|
|
133
|
+
return collector;
|
|
134
|
+
}
|
|
135
|
+
__name(getMetricsCollector, "getMetricsCollector");
|
|
136
|
+
|
|
137
|
+
// ../../packages/platform/dist/cache/redis-client.js
|
|
138
|
+
var logger2 = createLogger({
|
|
139
|
+
name: "redis-client",
|
|
140
|
+
level: LogLevel.INFO
|
|
141
|
+
});
|
|
142
|
+
var KEY_PREFIX = "cache:";
|
|
143
|
+
var redisClient = null;
|
|
144
|
+
var redisAvailable = false;
|
|
145
|
+
var initializationPromise = null;
|
|
146
|
+
var metricsCollector = getMetricsCollector(KEY_PREFIX);
|
|
147
|
+
async function initializeRedis() {
|
|
148
|
+
if (initializationPromise) {
|
|
149
|
+
return initializationPromise;
|
|
150
|
+
}
|
|
151
|
+
initializationPromise = (async () => {
|
|
152
|
+
const redisUrl = process.env.REDIS_URL;
|
|
153
|
+
if (!redisUrl) {
|
|
154
|
+
logger2.warn("REDIS_URL not configured - using in-memory fallback for caching");
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
redisClient = createClient({
|
|
159
|
+
url: redisUrl,
|
|
160
|
+
socket: {
|
|
161
|
+
// Connection timeout - how long to wait for initial connection
|
|
162
|
+
connectTimeout: 1e4,
|
|
163
|
+
// TCP keepalive - prevents silent connection drops
|
|
164
|
+
keepAlive: 5e3,
|
|
165
|
+
// Reconnection strategy with exponential backoff + jitter
|
|
166
|
+
reconnectStrategy: /* @__PURE__ */ __name((retries, cause) => {
|
|
167
|
+
if (isSocketTimeoutError(cause)) {
|
|
168
|
+
logger2.warn("Redis socket timeout - not reconnecting", {
|
|
169
|
+
cause: cause?.message
|
|
170
|
+
});
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
if (retries > 20) {
|
|
174
|
+
logger2.error("Redis max retries exceeded", {
|
|
175
|
+
retries,
|
|
176
|
+
cause: cause?.message
|
|
177
|
+
});
|
|
178
|
+
return new Error("Redis connection failed");
|
|
179
|
+
}
|
|
180
|
+
const baseDelay = Math.min(2 ** retries * 100, 3e4);
|
|
181
|
+
const jitter = Math.floor(Math.random() * 200);
|
|
182
|
+
return baseDelay + jitter;
|
|
183
|
+
}, "reconnectStrategy")
|
|
184
|
+
},
|
|
185
|
+
// Application-level ping to keep connection alive
|
|
186
|
+
pingInterval: 6e4
|
|
187
|
+
});
|
|
188
|
+
redisClient.on("error", (err) => {
|
|
189
|
+
if (err.message.includes("ECONNRESET") || err.message.includes("ECONNREFUSED")) {
|
|
190
|
+
logger2.debug("Redis connection error (will reconnect)", {
|
|
191
|
+
error: err.message
|
|
192
|
+
});
|
|
193
|
+
} else {
|
|
194
|
+
logger2.warn("Redis client error", {
|
|
195
|
+
error: err.message
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
redisAvailable = false;
|
|
199
|
+
metricsCollector.recordError(err);
|
|
200
|
+
});
|
|
201
|
+
redisClient.on("connect", () => {
|
|
202
|
+
redisAvailable = true;
|
|
203
|
+
metricsCollector.resetReconnectCount();
|
|
204
|
+
if (process.env.NODE_ENV !== "production") {
|
|
205
|
+
logger2.info("Redis connected for platform caching");
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
redisClient.on("ready", () => {
|
|
209
|
+
redisAvailable = true;
|
|
210
|
+
logger2.debug("Redis client ready for platform caching");
|
|
211
|
+
});
|
|
212
|
+
redisClient.on("reconnecting", () => {
|
|
213
|
+
metricsCollector.recordReconnect();
|
|
214
|
+
logger2.debug("Redis reconnecting for platform caching");
|
|
215
|
+
});
|
|
216
|
+
await redisClient.connect();
|
|
217
|
+
redisAvailable = true;
|
|
218
|
+
if (process.env.NODE_ENV !== "production") {
|
|
219
|
+
logger2.info("\u2705 Redis client initialized for platform caching with production config");
|
|
220
|
+
}
|
|
221
|
+
} catch (error) {
|
|
222
|
+
logger2.error("Redis initialization failed", {
|
|
223
|
+
error: error instanceof Error ? error.message : String(error)
|
|
224
|
+
});
|
|
225
|
+
redisAvailable = false;
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
return initializationPromise;
|
|
229
|
+
}
|
|
230
|
+
__name(initializeRedis, "initializeRedis");
|
|
231
|
+
async function getCache(key) {
|
|
232
|
+
await initializeRedis();
|
|
233
|
+
if (!redisAvailable || !redisClient) {
|
|
234
|
+
return null;
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const value = await redisClient.get(key);
|
|
238
|
+
if (!value) {
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
return JSON.parse(value);
|
|
242
|
+
} catch (error) {
|
|
243
|
+
logger2.error("Redis GET failed", {
|
|
244
|
+
key,
|
|
245
|
+
error
|
|
246
|
+
});
|
|
247
|
+
return null;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
__name(getCache, "getCache");
|
|
251
|
+
async function setCache(key, value, ttlSeconds) {
|
|
252
|
+
await initializeRedis();
|
|
253
|
+
if (!redisAvailable || !redisClient) {
|
|
254
|
+
return false;
|
|
255
|
+
}
|
|
256
|
+
try {
|
|
257
|
+
const serialized = JSON.stringify(value);
|
|
258
|
+
if (ttlSeconds) {
|
|
259
|
+
await redisClient.set(key, serialized, {
|
|
260
|
+
EX: ttlSeconds
|
|
261
|
+
});
|
|
262
|
+
} else {
|
|
263
|
+
await redisClient.set(key, serialized);
|
|
264
|
+
}
|
|
265
|
+
return true;
|
|
266
|
+
} catch (error) {
|
|
267
|
+
logger2.error("Redis SET failed", {
|
|
268
|
+
key,
|
|
269
|
+
error
|
|
270
|
+
});
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
__name(setCache, "setCache");
|
|
275
|
+
async function deleteCache(key) {
|
|
276
|
+
await initializeRedis();
|
|
277
|
+
if (!redisAvailable || !redisClient) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
try {
|
|
281
|
+
await redisClient.del(key);
|
|
282
|
+
return true;
|
|
283
|
+
} catch (error) {
|
|
284
|
+
logger2.error("Redis DEL failed", {
|
|
285
|
+
key,
|
|
286
|
+
error
|
|
287
|
+
});
|
|
288
|
+
return false;
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
__name(deleteCache, "deleteCache");
|
|
292
|
+
async function closeRedis() {
|
|
293
|
+
if (redisClient) {
|
|
294
|
+
try {
|
|
295
|
+
await redisClient.quit();
|
|
296
|
+
logger2.info("Redis connection closed");
|
|
297
|
+
} catch (error) {
|
|
298
|
+
logger2.error("Error closing Redis connection", {
|
|
299
|
+
error
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
redisClient = null;
|
|
303
|
+
redisAvailable = false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
__name(closeRedis, "closeRedis");
|
|
307
|
+
function isRedisAvailable() {
|
|
308
|
+
return redisAvailable;
|
|
309
|
+
}
|
|
310
|
+
__name(isRedisAvailable, "isRedisAvailable");
|
|
311
|
+
function getRedisClient() {
|
|
312
|
+
return redisClient;
|
|
313
|
+
}
|
|
314
|
+
__name(getRedisClient, "getRedisClient");
|
|
315
|
+
|
|
316
|
+
export { closeRedis, deleteCache, getCache, getRedisClient, initializeRedis, isRedisAvailable, setCache };
|
|
317
|
+
//# sourceMappingURL=chunk-IVHUBLJD.js.map
|
|
318
|
+
//# sourceMappingURL=chunk-IVHUBLJD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/platform/dist/cache/redis-factory.js","../../../packages/platform/dist/cache/redis-metrics.js","../../../packages/platform/dist/cache/redis-client.js"],"names":["createLogger","name","level","LogLevel","INFO","isSocketTimeoutError","cause","message","includes","LATENCY_THRESHOLDS","healthy","degraded","RedisMetricsCollector","reconnectAttempts","lastSuccessAt","lastErrorAt","lastError","keyPrefix","recordSuccess","Date","now","recordError","error","recordReconnect","resetReconnectCount","measureLatency","client","isReady","start","ping","latency","collect","isConnected","isOpen","status","getReconnectAttempts","metricsCollectors","Map","getMetricsCollector","collector","get","set","logger","KEY_PREFIX","redisClient","redisAvailable","initializationPromise","metricsCollector","initializeRedis","redisUrl","process","env","REDIS_URL","warn","createClient","url","socket","connectTimeout","keepAlive","reconnectStrategy","retries","Error","baseDelay","Math","min","jitter","floor","random","pingInterval","on","err","debug","NODE_ENV","info","connect","String","getCache","key","value","JSON","parse","setCache","ttlSeconds","serialized","stringify","EX","deleteCache","del","closeRedis","quit","isRedisAvailable","getRedisClient"],"mappings":"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","file":"chunk-IVHUBLJD.js","sourcesContent":["/**\n * Redis Client Factory - Production-Ready Configuration\n *\n * Provides a unified factory for creating Redis clients with:\n * - Exponential backoff with jitter for reconnection\n * - Connection keepalive to prevent silent drops\n * - Application-level ping interval\n * - Key namespacing for logical separation\n *\n * @module @vreko/platform/cache\n */\nimport { createLogger, LogLevel } from \"@vreko/contracts\";\nimport { createClient } from \"redis\";\nconst logger = createLogger({ name: \"redis-factory\", level: LogLevel.INFO });\n/**\n * Check if an error is a socket timeout error\n * Socket timeouts indicate network/firewall issues, not transient Redis failures\n */\nexport function isSocketTimeoutError(cause) {\n if (!cause) {\n return false;\n }\n return cause?.name === \"SocketTimeoutError\" || cause?.message?.includes(\"socket timeout\");\n}\n/**\n * Default Redis client options optimized for production\n */\nexport const DEFAULT_REDIS_OPTIONS = {\n keyPrefix: \"\",\n maxRetries: 20,\n pingInterval: 60000,\n connectTimeout: 10000,\n keepAlive: 5000,\n};\n/**\n * Create a production-ready Redis client with optimal configuration\n *\n * Features:\n * - Exponential backoff with jitter for reconnection\n * - Connection keepalive to prevent silent drops\n * - Application-level ping interval\n * - Proper error handling and logging\n * - Key namespacing utility for logical separation\n *\n * @example\n * ```typescript\n * // Create client for auth module\n * const authRedis = createRedisClient({\n * pingInterval: 60000,\n * onConnect: () => process.stdout.write('Auth Redis connected'),\n * });\n *\n * await authRedis.connect();\n *\n * // Use key namespacing for logical separation\n * await authRedis.set(withKeyPrefix('auth:', 'session:123'), data);\n * ```\n */\nexport function createRedisClient(options = {}) {\n const config = { ...DEFAULT_REDIS_OPTIONS, ...options };\n const redisUrl = process.env.REDIS_URL;\n if (!redisUrl) {\n logger.warn(\"REDIS_URL not configured - Redis client will not be available\");\n return null;\n }\n // Validate URL format\n if (!redisUrl.startsWith(\"redis://\") && !redisUrl.startsWith(\"rediss://\")) {\n logger.error(\"Invalid REDIS_URL format\", {\n error: \"REDIS_URL must start with 'redis://' or 'rediss://' protocol\",\n example: \"redis://localhost:6379\",\n });\n return null;\n }\n const client = createClient({\n url: redisUrl,\n socket: {\n // Connection timeout - how long to wait for initial connection\n connectTimeout: config.connectTimeout,\n // TCP keepalive - prevents silent connection drops\n keepAlive: config.keepAlive,\n // Reconnection strategy with exponential backoff + jitter\n reconnectStrategy: (retries, cause) => {\n // Don't reconnect on socket timeout - indicates a different issue\n // (e.g., firewall, network partition) that won't be fixed by reconnecting\n if (isSocketTimeoutError(cause)) {\n logger.warn(\"Redis socket timeout - not reconnecting\", { cause: cause?.message });\n return false;\n }\n // Give up after max retries\n if (retries > config.maxRetries) {\n logger.error(\"Redis max retries exceeded\", {\n retries,\n maxRetries: config.maxRetries,\n cause: cause?.message,\n });\n return new Error(`Redis connection failed after ${config.maxRetries} retries`);\n }\n // Exponential backoff with jitter\n // 100ms, 200ms, 400ms, 800ms, 1600ms, 3200ms, ... max 30s\n const baseDelay = Math.min(2 ** retries * 100, 30000);\n const jitter = Math.floor(Math.random() * 200);\n const delay = baseDelay + jitter;\n logger.debug(\"Redis reconnecting\", { retries, delay, nextRetryIn: `${delay}ms` });\n return delay;\n },\n },\n // Application-level ping to keep connection alive\n pingInterval: config.pingInterval,\n });\n // Set up event handlers\n client.on(\"error\", (err) => {\n // Don't log ECONNRESET as error - it's expected during reconnection\n if (err.message.includes(\"ECONNRESET\") || err.message.includes(\"ECONNREFUSED\")) {\n logger.debug(\"Redis connection error (will reconnect)\", { error: err.message });\n }\n else {\n logger.warn(\"Redis client error\", { error: err.message });\n }\n options.onDisconnect?.(err);\n });\n client.on(\"connect\", () => {\n logger.debug(\"Redis connection initiated\");\n });\n client.on(\"ready\", () => {\n logger.info(\"Redis client ready\", { keyPrefix: config.keyPrefix || \"none\" });\n options.onConnect?.();\n });\n client.on(\"reconnecting\", () => {\n logger.debug(\"Redis client reconnecting\");\n options.onReconnecting?.();\n });\n client.on(\"end\", () => {\n logger.debug(\"Redis connection closed\");\n });\n return client;\n}\n/**\n * Utility function to add key prefix for logical namespace separation\n *\n * @param prefix - The namespace prefix (e.g., 'auth:', 'cache:', 'ratelimit:')\n * @param key - The key to prefix\n * @returns The prefixed key\n *\n * @example\n * ```typescript\n * const key = withKeyPrefix('auth:', 'session:123'); // 'auth:session:123'\n * ```\n */\nexport function withKeyPrefix(prefix, key) {\n return `${prefix}${key}`;\n}\n/**\n * Predefined key prefixes for consistent namespacing\n */\nexport const REDIS_KEY_PREFIXES = {\n AUTH: \"auth:\",\n AUTH_RATE_LIMIT: \"auth:ratelimit:\",\n CACHE: \"cache:\",\n ENTITLEMENTS: \"cache:entitlements:\",\n WAITLIST: \"cache:waitlist:\",\n RATE_LIMIT: \"ratelimit:\",\n IDEMPOTENCY: \"idempotency:\",\n SESSION: \"session:\",\n};\n","/**\n * Redis Metrics Collection\n *\n * Provides utilities for monitoring Redis connection health,\n * latency, and reconnection statistics.\n *\n * @module @vreko/platform/cache\n */\n/**\n * Latency thresholds for health status\n */\nexport const LATENCY_THRESHOLDS = {\n /** Under 100ms is healthy */\n healthy: 100,\n /** Under 500ms is degraded */\n degraded: 500,\n};\n/**\n * Track Redis metrics for a client\n */\nexport class RedisMetricsCollector {\n reconnectAttempts = 0;\n lastSuccessAt = null;\n lastErrorAt = null;\n lastError = null;\n keyPrefix;\n constructor(keyPrefix = \"\") {\n this.keyPrefix = keyPrefix;\n }\n /**\n * Record a successful operation\n */\n recordSuccess() {\n this.lastSuccessAt = Date.now();\n }\n /**\n * Record an error\n */\n recordError(error) {\n this.lastErrorAt = Date.now();\n this.lastError = error.message;\n }\n /**\n * Record a reconnection attempt\n */\n recordReconnect() {\n this.reconnectAttempts++;\n }\n /**\n * Reset reconnection counter (after successful connection)\n */\n resetReconnectCount() {\n this.reconnectAttempts = 0;\n }\n /**\n * Measure Redis latency using PING command\n */\n async measureLatency(client) {\n if (!client || !client.isReady) {\n return null;\n }\n try {\n const start = Date.now();\n await client.ping();\n const latency = Date.now() - start;\n this.recordSuccess();\n return latency;\n }\n catch (error) {\n this.recordError(error);\n return null;\n }\n }\n /**\n * Collect comprehensive metrics\n */\n async collect(client) {\n const isConnected = client?.isReady ?? false;\n const isOpen = client?.isOpen ?? false;\n const latency = await this.measureLatency(client);\n // Determine health status\n let status;\n let message;\n if (!isConnected) {\n status = \"unhealthy\";\n message = \"Redis client not connected\";\n }\n else if (latency === null) {\n status = \"unhealthy\";\n message = \"Redis PING failed\";\n }\n else if (latency < LATENCY_THRESHOLDS.healthy) {\n status = \"healthy\";\n message = `Redis latency: ${latency}ms`;\n }\n else if (latency < LATENCY_THRESHOLDS.degraded) {\n status = \"degraded\";\n message = `Redis latency elevated: ${latency}ms`;\n }\n else {\n status = \"unhealthy\";\n message = `Redis latency too high: ${latency}ms`;\n }\n return {\n isConnected,\n isOpen,\n latency,\n status,\n message,\n lastSuccessAt: this.lastSuccessAt,\n lastErrorAt: this.lastErrorAt,\n lastError: this.lastError,\n reconnectAttempts: this.reconnectAttempts,\n keyPrefix: this.keyPrefix,\n };\n }\n /**\n * Get current reconnection attempt count\n */\n getReconnectAttempts() {\n return this.reconnectAttempts;\n }\n}\n/**\n * Global metrics collectors by key prefix\n */\nconst metricsCollectors = new Map();\n/**\n * Get or create a metrics collector for a key prefix\n */\nexport function getMetricsCollector(keyPrefix = \"\") {\n let collector = metricsCollectors.get(keyPrefix);\n if (!collector) {\n collector = new RedisMetricsCollector(keyPrefix);\n metricsCollectors.set(keyPrefix, collector);\n }\n return collector;\n}\n/**\n * Collect metrics from all registered collectors\n */\nexport async function collectAllMetrics(clients) {\n const results = new Map();\n for (const [keyPrefix, client] of clients) {\n const collector = getMetricsCollector(keyPrefix);\n const metrics = await collector.collect(client);\n results.set(keyPrefix, metrics);\n }\n return results;\n}\n/**\n * Check if Redis metrics indicate a healthy state\n */\nexport function isHealthy(metrics) {\n return metrics.status === \"healthy\" || metrics.status === \"degraded\";\n}\n/**\n * Get a summary of Redis health for logging/monitoring\n */\nexport function getHealthSummary(metrics) {\n return `[${metrics.keyPrefix || \"default\"}] ${metrics.status.toUpperCase()}: ${metrics.message}`;\n}\n","/**\n * Redis Client for Caching and Idempotency\n *\n * Provides Redis-backed caching for:\n * - Entitlements caching (reduce database load)\n * - Pioneer action idempotency (distributed deduplication)\n * - Attribution fingerprint lookups\n *\n * Includes graceful degradation to in-memory when Redis unavailable\n *\n * Production Features:\n * - Exponential backoff with jitter (up to 20 retries)\n * - TCP keepalive (5s) to prevent silent drops\n * - Application-level ping (60s) to keep connection alive\n * - Key namespacing for logical separation\n */\nimport { createLogger, LogLevel } from \"@vreko/contracts\";\nimport { createClient } from \"redis\";\nimport { isSocketTimeoutError } from \"./redis-factory.js\";\nimport { getMetricsCollector } from \"./redis-metrics.js\";\nconst logger = createLogger({ name: \"redis-client\", level: LogLevel.INFO });\n// Key prefix for platform caching operations\nconst KEY_PREFIX = \"cache:\";\n// Redis client instance\nlet redisClient = null;\nlet redisAvailable = false;\nlet initializationPromise = null;\n// Metrics collector for this client\nconst metricsCollector = getMetricsCollector(KEY_PREFIX);\n/**\n * Initialize Redis client connection with production-ready configuration\n */\nexport async function initializeRedis() {\n if (initializationPromise) {\n return initializationPromise;\n }\n initializationPromise = (async () => {\n const redisUrl = process.env.REDIS_URL;\n if (!redisUrl) {\n logger.warn(\"REDIS_URL not configured - using in-memory fallback for caching\");\n return;\n }\n try {\n // Production-ready Redis client configuration\n redisClient = createClient({\n url: redisUrl,\n socket: {\n // Connection timeout - how long to wait for initial connection\n connectTimeout: 10000, // 10s\n // TCP keepalive - prevents silent connection drops\n keepAlive: 5000, // 5s\n // Reconnection strategy with exponential backoff + jitter\n reconnectStrategy: (retries, cause) => {\n // Don't reconnect on socket timeout - indicates a different issue\n // (e.g., firewall, network partition) that won't be fixed by reconnecting\n if (isSocketTimeoutError(cause)) {\n logger.warn(\"Redis socket timeout - not reconnecting\", { cause: cause?.message });\n return false;\n }\n // Give up after 20 retries (increased from 3)\n if (retries > 20) {\n logger.error(\"Redis max retries exceeded\", { retries, cause: cause?.message });\n return new Error(\"Redis connection failed\");\n }\n // Exponential backoff with jitter\n // 100ms, 200ms, 400ms, 800ms, ... max 30s\n const baseDelay = Math.min(2 ** retries * 100, 30000);\n const jitter = Math.floor(Math.random() * 200);\n return baseDelay + jitter;\n },\n },\n // Application-level ping to keep connection alive\n pingInterval: 60000, // 60s\n });\n redisClient.on(\"error\", (err) => {\n // Don't log ECONNRESET as warning - it's expected during reconnection\n if (err.message.includes(\"ECONNRESET\") || err.message.includes(\"ECONNREFUSED\")) {\n logger.debug(\"Redis connection error (will reconnect)\", { error: err.message });\n }\n else {\n logger.warn(\"Redis client error\", { error: err.message });\n }\n redisAvailable = false;\n metricsCollector.recordError(err);\n });\n redisClient.on(\"connect\", () => {\n redisAvailable = true;\n metricsCollector.resetReconnectCount();\n if (process.env.NODE_ENV !== \"production\") {\n logger.info(\"Redis connected for platform caching\");\n }\n });\n redisClient.on(\"ready\", () => {\n redisAvailable = true;\n logger.debug(\"Redis client ready for platform caching\");\n });\n redisClient.on(\"reconnecting\", () => {\n metricsCollector.recordReconnect();\n logger.debug(\"Redis reconnecting for platform caching\");\n });\n await redisClient.connect();\n redisAvailable = true;\n if (process.env.NODE_ENV !== \"production\") {\n logger.info(\"✅ Redis client initialized for platform caching with production config\");\n }\n }\n catch (error) {\n logger.error(\"Redis initialization failed\", {\n error: error instanceof Error ? error.message : String(error),\n });\n redisAvailable = false;\n }\n })();\n return initializationPromise;\n}\n/**\n * Get value from Redis cache\n */\nexport async function getCache(key) {\n await initializeRedis();\n if (!redisAvailable || !redisClient) {\n return null;\n }\n try {\n const value = await redisClient.get(key);\n if (!value) {\n return null;\n }\n return JSON.parse(value);\n }\n catch (error) {\n logger.error(\"Redis GET failed\", { key, error });\n return null;\n }\n}\n/**\n * Set value in Redis cache with optional TTL\n */\nexport async function setCache(key, value, ttlSeconds) {\n await initializeRedis();\n if (!redisAvailable || !redisClient) {\n return false;\n }\n try {\n const serialized = JSON.stringify(value);\n if (ttlSeconds) {\n await redisClient.set(key, serialized, { EX: ttlSeconds });\n }\n else {\n await redisClient.set(key, serialized);\n }\n return true;\n }\n catch (error) {\n logger.error(\"Redis SET failed\", { key, error });\n return false;\n }\n}\n/**\n * Delete value from Redis cache\n */\nexport async function deleteCache(key) {\n await initializeRedis();\n if (!redisAvailable || !redisClient) {\n return false;\n }\n try {\n await redisClient.del(key);\n return true;\n }\n catch (error) {\n logger.error(\"Redis DEL failed\", { key, error });\n return false;\n }\n}\n/**\n * Check if key exists in Redis\n */\nexport async function hasCache(key) {\n await initializeRedis();\n if (!redisAvailable || !redisClient) {\n return false;\n }\n try {\n const exists = await redisClient.exists(key);\n return exists === 1;\n }\n catch (error) {\n logger.error(\"Redis EXISTS failed\", { key, error });\n return false;\n }\n}\n/**\n * Set if not exists (atomic operation for idempotency)\n */\nexport async function setNX(key, value, ttlSeconds) {\n await initializeRedis();\n if (!redisAvailable || !redisClient) {\n return false;\n }\n try {\n const serialized = JSON.stringify(value);\n const result = ttlSeconds\n ? await redisClient.set(key, serialized, { NX: true, EX: ttlSeconds })\n : await redisClient.set(key, serialized, { NX: true });\n return result === \"OK\";\n }\n catch (error) {\n logger.error(\"Redis SETNX failed\", { key, error });\n return false;\n }\n}\n/**\n * Get multiple keys at once (batch operation)\n */\nexport async function mGet(keys) {\n if (keys.length === 0) {\n return [];\n }\n await initializeRedis();\n if (!redisAvailable || !redisClient) {\n return keys.map(() => null);\n }\n try {\n const values = await redisClient.mGet(keys);\n return values.map((v) => (v ? JSON.parse(v) : null));\n }\n catch (error) {\n logger.error(\"Redis MGET failed\", { keys, error });\n return keys.map(() => null);\n }\n}\n/**\n * Close Redis connection\n */\nexport async function closeRedis() {\n if (redisClient) {\n try {\n await redisClient.quit();\n logger.info(\"Redis connection closed\");\n }\n catch (error) {\n logger.error(\"Error closing Redis connection\", { error });\n }\n redisClient = null;\n redisAvailable = false;\n }\n}\n/**\n * Check if Redis is available\n */\nexport function isRedisAvailable() {\n return redisAvailable;\n}\n/**\n * Get Redis client (for advanced operations)\n */\nexport function getRedisClient() {\n return redisClient;\n}\n/**\n * Get metrics for this Redis client\n */\nexport async function getRedisMetrics() {\n if (!redisClient) {\n return {\n isConnected: false,\n latency: null,\n lastError: \"Redis client not initialized\",\n reconnectAttempts: 0,\n };\n }\n return metricsCollector.collect(redisClient);\n}\n// Lazy initialization - Redis is initialized on first cache access, not module load\n// This ensures environment variables (e.g., from Doppler) are available\n"]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { __name } from './chunk-EWOJGXRX.js';
|
|
3
|
+
import { nanoid } from 'nanoid';
|
|
4
|
+
|
|
5
|
+
process.env.VREKO_CLI='true';process.env.NODE_NO_WARNINGS='1';
|
|
6
|
+
function generateId(prefix) {
|
|
7
|
+
const id = nanoid();
|
|
8
|
+
return prefix ? `${prefix}-${id}` : id;
|
|
9
|
+
}
|
|
10
|
+
__name(generateId, "generateId");
|
|
11
|
+
|
|
12
|
+
export { generateId };
|
|
13
|
+
//# sourceMappingURL=chunk-KJWKY4L4.js.map
|
|
14
|
+
//# sourceMappingURL=chunk-KJWKY4L4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../packages/contracts/dist/id-generator.js"],"names":["generateId","prefix","id","nanoid"],"mappings":";;;;;AAsDO,SAASA,WAAWC,MAAAA,EAAM;AAC7B,EAAA,MAAMC,KAAKC,MAAAA,EAAAA;AACX,EAAA,OAAOF,MAAAA,GAAS,CAAA,EAAGA,MAAAA,CAAAA,CAAAA,EAAUC,EAAAA,CAAAA,CAAAA,GAAOA,EAAAA;AACxC;AAHgBF,MAAAA,CAAAA,UAAAA,EAAAA,YAAAA,CAAAA","file":"chunk-KJWKY4L4.js","sourcesContent":["import { nanoid } from \"nanoid\";\nimport { AuditId as makeAuditId, SessionId as makeSessionId, SnapshotId as makeSnapshotId } from \"./types/branded.js\";\n// =============================================================================\n// ID PREFIXES\n// =============================================================================\n/**\n * ID prefixes for different entity types\n */\nexport const ID_PREFIX = {\n SESSION: \"sess\",\n SNAPSHOT: \"vreko\",\n AUDIT: \"audit\",\n CHECKPOINT: \"cp\",\n};\n// =============================================================================\n// INTERNAL HELPERS\n// =============================================================================\n/**\n * Generate a cryptographically random alphanumeric suffix\n * Note: This is a private helper, not exported, so no special annotation needed\n */\nfunction randomSuffix(length = 6) {\n const chars = \"abcdefghijklmnopqrstuvwxyz0123456789\";\n let result = \"\";\n const bytes = new Uint8Array(length);\n // Use crypto.getRandomValues for browser/Node compatibility\n crypto.getRandomValues(bytes);\n for (let i = 0; i < length; i++) {\n result += chars[bytes[i] % chars.length];\n }\n return result;\n}\n/**\n * Slugify a description for use in snapshot IDs\n * Converts \"Before fixing auth flow\" to \"before-fixing-auth-flow\"\n */\nfunction slugify(description, maxLength = 30) {\n return description\n .toLowerCase()\n .trim()\n .replace(/[^a-z0-9\\s-]/g, \"\") // Remove non-alphanumeric\n .replace(/\\s+/g, \"-\") // Spaces to hyphens\n .replace(/-+/g, \"-\") // Collapse hyphens\n .replace(/^-|-$/g, \"\") // Trim hyphens\n .slice(0, maxLength);\n}\n// =============================================================================\n// ID GENERATION FUNCTIONS\n// =============================================================================\n/**\n * Generate a unique ID with optional prefix\n * @param prefix Optional prefix for the ID (e.g., 'user', 'session')\n * @returns Unique ID string\n */\nexport function generateId(prefix) {\n const id = nanoid();\n return prefix ? `${prefix}-${id}` : id;\n}\n/**\n * Generate a session ID with unified format\n *\n * Format: sess-<timestamp>-<random>\n * Example: sess-1733657123456-a1b2c3\n *\n * @returns SessionId branded type\n */\nexport function generateSessionId() {\n return makeSessionId(`${ID_PREFIX.SESSION}-${Date.now()}-${randomSuffix()}`);\n}\n/**\n * Generate a snapshot ID in the standard format\n * Format with description: snapshot-<slug>-<timestamp>-<random>\n * Format without: snapshot-<timestamp>-<random>\n * @param description Optional human-readable description\n * @returns SnapshotId branded type\n */\nexport function generateSnapshotId(description) {\n if (description && description.length > 0) {\n const slug = slugify(description);\n if (slug.length > 0) {\n return makeSnapshotId(`snapshot-${slug}-${Date.now()}-${nanoid(9)}`);\n }\n }\n return makeSnapshotId(`snapshot-${Date.now()}-${nanoid(9)}`);\n}\n/**\n * Generate an audit entry ID with unified format\n *\n * Format: audit-<timestamp>-<random>\n * Example: audit-1733657123456-d4e5f6\n *\n * @returns AuditId branded type\n */\nexport function generateAuditId() {\n return makeAuditId(`${ID_PREFIX.AUDIT}-${Date.now()}-${randomSuffix()}`);\n}\n/**\n * Generate a checkpoint ID with unified format\n *\n * Format: cp-<timestamp>-<random>\n * Example: cp-1733657123456-g7h8i9\n *\n * @returns Checkpoint ID string\n */\nexport function generateCheckpointId() {\n return `${ID_PREFIX.CHECKPOINT}-${Date.now()}-${randomSuffix()}`;\n}\n/**\n * Generate a random alphanumeric ID string\n *\n * @param length - Length of the ID (default: 6)\n * @returns Lowercase alphanumeric string\n */\nexport function randomId(length = 6) {\n return randomSuffix(length);\n}\n// =============================================================================\n// ID PARSING FUNCTIONS\n// =============================================================================\n/**\n * Parse timestamp from a generated ID\n *\n * Handles all ID formats:\n * - sess-<timestamp>-<random>\n * - audit-<timestamp>-<random>\n * - cp-<timestamp>-<random>\n * - vreko-<timestamp>-<random>\n * - vreko-<slug>-<timestamp>-<random>\n * - snapshot-<timestamp>-<random> (legacy)\n * - snapshot-<slug>-<timestamp>-<random> (legacy)\n *\n * @param id - ID string to parse\n * @returns Timestamp in milliseconds, or null if invalid format\n */\nexport function parseIdTimestamp(id) {\n // Validate known prefix (includes legacy \"snapshot\" for backward compatibility)\n if (!id.match(/^(?:sess|vreko|snapshot|audit|cp)-/)) {\n return null;\n }\n // Match 13-digit timestamp before the final random suffix\n // Works for both `prefix-<timestamp>-<random>` and `prefix-<slug>-<timestamp>-<random>`\n const match = id.match(/-(\\d{13})-[a-zA-Z0-9_-]+$/);\n if (!match) {\n return null;\n }\n const timestamp = Number.parseInt(match[1], 10);\n return Number.isNaN(timestamp) ? null : timestamp;\n}\n/**\n * Extract the prefix from a generated ID\n *\n * @param id - ID string to parse\n * @returns ID prefix or null if invalid format\n */\nexport function parseIdPrefix(id) {\n // Handle legacy \"snapshot\" prefix as \"vreko\"\n if (id.startsWith(\"snapshot-\")) {\n return \"vreko\";\n }\n const match = id.match(/^(sess|vreko|audit|cp)-/);\n return match ? match[1] : null;\n}\n/**\n * Validate that an ID matches the expected format\n *\n * Handles formats:\n * - sess/audit/cp-<timestamp>-<6 random chars>\n * - vreko-<timestamp>-<9 random chars>\n * - vreko-<slug>-<timestamp>-<9 random chars>\n * - snapshot-* (legacy format, also valid)\n *\n * @param id - ID string to validate\n * @param expectedPrefix - Expected prefix (optional, validates any if not specified)\n * @returns True if valid, false otherwise\n */\nexport function isValidId(id, expectedPrefix) {\n // Snapshot IDs can have optional slug and use 9-char suffix\n // Also handle legacy \"snapshot-\" prefix\n const isSnapshotId = id.startsWith(\"vreko-\") || id.startsWith(\"snapshot-\");\n if (expectedPrefix === \"vreko\" || (!expectedPrefix && isSnapshotId)) {\n return /^(?:vreko|snapshot)-(?:[a-z0-9]+-)?(\\d{13})-[a-zA-Z0-9_-]{9}$/.test(id);\n }\n // Non-snapshot IDs use 6-char suffix\n if (expectedPrefix) {\n const regex = new RegExp(`^${expectedPrefix}-\\\\d{13}-[a-z0-9]{6}$`);\n return regex.test(id);\n }\n return /^(?:sess|audit|cp)-\\d{13}-[a-z0-9]{6}$/.test(id);\n}\n"]}
|