rediseed 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.
@@ -0,0 +1,89 @@
1
+ import { Redis } from 'ioredis';
2
+
3
+ type RedisStructure = "set" | "list" | "hash" | "string" | "zset";
4
+ interface RedisConnectionOptions {
5
+ url?: string;
6
+ host?: string;
7
+ port?: number;
8
+ password?: string;
9
+ db?: number;
10
+ tls?: boolean;
11
+ }
12
+ interface SeedOptions extends RedisConnectionOptions {
13
+ structure: RedisStructure;
14
+ keyName?: string;
15
+ column?: string;
16
+ valueTemplate?: string;
17
+ scoreColumn?: string;
18
+ batchSize?: number;
19
+ flushFirst?: boolean;
20
+ ttl?: number;
21
+ dryRun?: boolean;
22
+ }
23
+ interface SeedResult {
24
+ totalProcessed: number;
25
+ totalInserted: number;
26
+ elapsedMs: number;
27
+ opsPerSec: number;
28
+ structure: RedisStructure;
29
+ keysAffected: number;
30
+ }
31
+ interface ParsedRow {
32
+ [key: string]: any;
33
+ }
34
+
35
+ /**
36
+ * Parse a CSV string or file content into array of row objects.
37
+ */
38
+ declare function parseCSV(content: string, delimiter?: string): ParsedRow[];
39
+ /**
40
+ * Parse a JSON array, single JSON object, or JSONL string into array of row objects.
41
+ */
42
+ declare function parseJSONOrJSONL(content: string): ParsedRow[];
43
+ /**
44
+ * Parse plain text lines into { value: line } rows.
45
+ */
46
+ declare function parseLines(content: string): ParsedRow[];
47
+ /**
48
+ * Auto-detect file format from extension and contents.
49
+ */
50
+ declare function loadAndParseFile(filePath: string): ParsedRow[];
51
+
52
+ /**
53
+ * Render template string replacing `{{variable}}` with values from row.
54
+ */
55
+ declare function renderTemplate(template: string, row: ParsedRow): string;
56
+ /**
57
+ * Extract item value from row based on column or template.
58
+ */
59
+ declare function extractRowValue(row: ParsedRow, column?: string, valueTemplate?: string): string;
60
+
61
+ /**
62
+ * Generate ready-to-use k6 test script snippets using xk6-redis or HTTP mocks.
63
+ */
64
+ declare function generateK6Snippet(keyName: string, structure: RedisStructure): string;
65
+
66
+ /**
67
+ * Create a reusable ioredis client instance.
68
+ */
69
+ declare function createRedisClient(options?: RedisConnectionOptions): Redis;
70
+
71
+ /**
72
+ * Execute high-speed batched seeding into Redis using pipelines.
73
+ */
74
+ declare function executeSeed(redis: Redis, rows: ParsedRow[], options: SeedOptions): Promise<SeedResult>;
75
+
76
+ interface KeyInspection {
77
+ key: string;
78
+ type: string;
79
+ count: number;
80
+ memoryBytes?: number;
81
+ ttl: number;
82
+ samples: string[];
83
+ }
84
+ /**
85
+ * Inspect a Redis key (count, type, memory, and sample items).
86
+ */
87
+ declare function inspectKey(redis: Redis, key: string, sampleCount?: number): Promise<KeyInspection>;
88
+
89
+ export { type KeyInspection, type ParsedRow, type RedisConnectionOptions, type RedisStructure, type SeedOptions, type SeedResult, createRedisClient, executeSeed, extractRowValue, generateK6Snippet, inspectKey, loadAndParseFile, parseCSV, parseJSONOrJSONL, parseLines, renderTemplate };
@@ -0,0 +1,89 @@
1
+ import { Redis } from 'ioredis';
2
+
3
+ type RedisStructure = "set" | "list" | "hash" | "string" | "zset";
4
+ interface RedisConnectionOptions {
5
+ url?: string;
6
+ host?: string;
7
+ port?: number;
8
+ password?: string;
9
+ db?: number;
10
+ tls?: boolean;
11
+ }
12
+ interface SeedOptions extends RedisConnectionOptions {
13
+ structure: RedisStructure;
14
+ keyName?: string;
15
+ column?: string;
16
+ valueTemplate?: string;
17
+ scoreColumn?: string;
18
+ batchSize?: number;
19
+ flushFirst?: boolean;
20
+ ttl?: number;
21
+ dryRun?: boolean;
22
+ }
23
+ interface SeedResult {
24
+ totalProcessed: number;
25
+ totalInserted: number;
26
+ elapsedMs: number;
27
+ opsPerSec: number;
28
+ structure: RedisStructure;
29
+ keysAffected: number;
30
+ }
31
+ interface ParsedRow {
32
+ [key: string]: any;
33
+ }
34
+
35
+ /**
36
+ * Parse a CSV string or file content into array of row objects.
37
+ */
38
+ declare function parseCSV(content: string, delimiter?: string): ParsedRow[];
39
+ /**
40
+ * Parse a JSON array, single JSON object, or JSONL string into array of row objects.
41
+ */
42
+ declare function parseJSONOrJSONL(content: string): ParsedRow[];
43
+ /**
44
+ * Parse plain text lines into { value: line } rows.
45
+ */
46
+ declare function parseLines(content: string): ParsedRow[];
47
+ /**
48
+ * Auto-detect file format from extension and contents.
49
+ */
50
+ declare function loadAndParseFile(filePath: string): ParsedRow[];
51
+
52
+ /**
53
+ * Render template string replacing `{{variable}}` with values from row.
54
+ */
55
+ declare function renderTemplate(template: string, row: ParsedRow): string;
56
+ /**
57
+ * Extract item value from row based on column or template.
58
+ */
59
+ declare function extractRowValue(row: ParsedRow, column?: string, valueTemplate?: string): string;
60
+
61
+ /**
62
+ * Generate ready-to-use k6 test script snippets using xk6-redis or HTTP mocks.
63
+ */
64
+ declare function generateK6Snippet(keyName: string, structure: RedisStructure): string;
65
+
66
+ /**
67
+ * Create a reusable ioredis client instance.
68
+ */
69
+ declare function createRedisClient(options?: RedisConnectionOptions): Redis;
70
+
71
+ /**
72
+ * Execute high-speed batched seeding into Redis using pipelines.
73
+ */
74
+ declare function executeSeed(redis: Redis, rows: ParsedRow[], options: SeedOptions): Promise<SeedResult>;
75
+
76
+ interface KeyInspection {
77
+ key: string;
78
+ type: string;
79
+ count: number;
80
+ memoryBytes?: number;
81
+ ttl: number;
82
+ samples: string[];
83
+ }
84
+ /**
85
+ * Inspect a Redis key (count, type, memory, and sample items).
86
+ */
87
+ declare function inspectKey(redis: Redis, key: string, sampleCount?: number): Promise<KeyInspection>;
88
+
89
+ export { type KeyInspection, type ParsedRow, type RedisConnectionOptions, type RedisStructure, type SeedOptions, type SeedResult, createRedisClient, executeSeed, extractRowValue, generateK6Snippet, inspectKey, loadAndParseFile, parseCSV, parseJSONOrJSONL, parseLines, renderTemplate };
package/dist/index.js ADDED
@@ -0,0 +1,366 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __create = Object.create;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __getProtoOf = Object.getPrototypeOf;
8
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __export = (target, all) => {
10
+ for (var name in all)
11
+ __defProp(target, name, { get: all[name], enumerable: true });
12
+ };
13
+ var __copyProps = (to, from, except, desc) => {
14
+ if (from && typeof from === "object" || typeof from === "function") {
15
+ for (let key of __getOwnPropNames(from))
16
+ if (!__hasOwnProp.call(to, key) && key !== except)
17
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
18
+ }
19
+ return to;
20
+ };
21
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
22
+ // If the importer is in node compatibility mode or this is not an ESM
23
+ // file that has been converted to a CommonJS file using a Babel-
24
+ // compatible transform (i.e. "__esModule" has not been set), then set
25
+ // "default" to the CommonJS "module.exports" for node compatibility.
26
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
27
+ mod
28
+ ));
29
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
30
+
31
+ // src/index.ts
32
+ var src_exports = {};
33
+ __export(src_exports, {
34
+ createRedisClient: () => createRedisClient,
35
+ executeSeed: () => executeSeed,
36
+ extractRowValue: () => extractRowValue,
37
+ generateK6Snippet: () => generateK6Snippet,
38
+ inspectKey: () => inspectKey,
39
+ loadAndParseFile: () => loadAndParseFile,
40
+ parseCSV: () => parseCSV,
41
+ parseJSONOrJSONL: () => parseJSONOrJSONL,
42
+ parseLines: () => parseLines,
43
+ renderTemplate: () => renderTemplate
44
+ });
45
+ module.exports = __toCommonJS(src_exports);
46
+
47
+ // src/loaders/file.ts
48
+ var import_fs = __toESM(require("fs"));
49
+ var import_sync = require("csv-parse/sync");
50
+ function parseCSV(content, delimiter = ",") {
51
+ const records = (0, import_sync.parse)(content, {
52
+ columns: true,
53
+ skip_empty_lines: true,
54
+ trim: true,
55
+ delimiter,
56
+ cast: false
57
+ });
58
+ return records;
59
+ }
60
+ function parseJSONOrJSONL(content) {
61
+ const trimmed = content.trim();
62
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
63
+ const parsed = JSON.parse(trimmed);
64
+ if (!Array.isArray(parsed)) throw new Error("JSON root must be an array of objects.");
65
+ return parsed;
66
+ }
67
+ const lines = trimmed.split(/\r?\n/).map((l) => l.trim()).filter(Boolean);
68
+ const rows = [];
69
+ for (const line of lines) {
70
+ if (line.startsWith("{") && line.endsWith("}")) {
71
+ rows.push(JSON.parse(line));
72
+ } else {
73
+ rows.push({ value: line });
74
+ }
75
+ }
76
+ return rows;
77
+ }
78
+ function parseLines(content) {
79
+ return content.split(/\r?\n/).map((l) => l.trim()).filter(Boolean).map((l) => ({ value: l }));
80
+ }
81
+ function loadAndParseFile(filePath) {
82
+ if (!import_fs.default.existsSync(filePath)) {
83
+ throw new Error(`File not found: ${filePath}`);
84
+ }
85
+ const content = import_fs.default.readFileSync(filePath, "utf8");
86
+ const ext = filePath.split(".").pop()?.toLowerCase();
87
+ if (ext === "csv") {
88
+ return parseCSV(content);
89
+ } else if (ext === "tsv") {
90
+ return parseCSV(content, " ");
91
+ } else if (ext === "json" || ext === "jsonl" || ext === "ndjson") {
92
+ return parseJSONOrJSONL(content);
93
+ } else if (ext === "txt") {
94
+ return parseLines(content);
95
+ }
96
+ try {
97
+ return parseJSONOrJSONL(content);
98
+ } catch {
99
+ try {
100
+ return parseCSV(content);
101
+ } catch {
102
+ return parseLines(content);
103
+ }
104
+ }
105
+ }
106
+
107
+ // src/utils/template.ts
108
+ function renderTemplate(template, row) {
109
+ return template.replace(/\{\{\s*([a-zA-Z0-9_-]+)\s*\}\}/g, (_, key) => {
110
+ return row[key] !== void 0 ? String(row[key]) : "";
111
+ });
112
+ }
113
+ function extractRowValue(row, column, valueTemplate) {
114
+ if (valueTemplate) {
115
+ return renderTemplate(valueTemplate, row);
116
+ }
117
+ if (column && row[column] !== void 0) {
118
+ return String(row[column]);
119
+ }
120
+ const keys = Object.keys(row);
121
+ if (keys.length === 1) {
122
+ return String(row[keys[0]]);
123
+ }
124
+ return JSON.stringify(row);
125
+ }
126
+
127
+ // src/utils/k6.ts
128
+ function generateK6Snippet(keyName, structure) {
129
+ if (structure === "set") {
130
+ return `// ==========================================
131
+ // k6 Load Test - Redis Set Data Consumption
132
+ // Requirements: xk6-redis (k6 run --vus 50 --duration 1m script.js)
133
+ // ==========================================
134
+ import http from 'k6/http';
135
+ import { check, sleep } from 'k6';
136
+ import redis from 'k6/x/redis';
137
+
138
+ // Connect to Redis
139
+ const client = new redis.Client('redis://localhost:6379');
140
+
141
+ export const options = {
142
+ vus: 50,
143
+ duration: '1m',
144
+ };
145
+
146
+ export default function () {
147
+ // Option A: Pop unique token (guarantees NO collision between VUs)
148
+ const uniqueToken = client.spop('${keyName}');
149
+
150
+ // Option B: Random sample (if token is reusable across VUs)
151
+ // const uniqueToken = client.srandmember('${keyName}');
152
+
153
+ if (!uniqueToken) {
154
+ console.warn('Redis set "${keyName}" is exhausted!');
155
+ return;
156
+ }
157
+
158
+ // Execute authenticated HTTP request using seeded token
159
+ const res = http.get('https://api.example.com/v1/user/profile', {
160
+ headers: {
161
+ Authorization: \`Bearer \${uniqueToken}\`,
162
+ },
163
+ });
164
+
165
+ check(res, {
166
+ 'status is 200': (r) => r.status === 200,
167
+ });
168
+
169
+ sleep(0.1);
170
+ }
171
+ `;
172
+ }
173
+ if (structure === "list") {
174
+ return `// ==========================================
175
+ // k6 Load Test - Redis Queue / List FIFO
176
+ // Requirements: xk6-redis
177
+ // ==========================================
178
+ import http from 'k6/http';
179
+ import { check } from 'k6';
180
+ import redis from 'k6/x/redis';
181
+
182
+ const client = new redis.Client('redis://localhost:6379');
183
+
184
+ export default function () {
185
+ // Pop from FIFO queue
186
+ const item = client.lpop('${keyName}');
187
+
188
+ if (!item) {
189
+ console.warn('Queue "${keyName}" is empty!');
190
+ return;
191
+ }
192
+
193
+ const payload = JSON.parse(item);
194
+ const res = http.post('https://api.example.com/v1/orders', JSON.stringify(payload), {
195
+ headers: { 'Content-Type': 'application/json' },
196
+ });
197
+
198
+ check(res, { 'status is 201': (r) => r.status === 201 });
199
+ }
200
+ `;
201
+ }
202
+ return `// ==========================================
203
+ // k6 Load Test - Redis Key-Value Fetch
204
+ // Requirements: xk6-redis
205
+ // ==========================================
206
+ import http from 'k6/http';
207
+ import { check } from 'k6';
208
+ import redis from 'k6/x/redis';
209
+
210
+ const client = new redis.Client('redis://localhost:6379');
211
+
212
+ export default function () {
213
+ const value = client.get('${keyName}');
214
+ const res = http.get(\`https://api.example.com/v1/item/\${value}\`);
215
+ check(res, { 'status is 200': (r) => r.status === 200 });
216
+ }
217
+ `;
218
+ }
219
+
220
+ // src/engine/client.ts
221
+ var import_ioredis = require("ioredis");
222
+ function createRedisClient(options = {}) {
223
+ const url = options.url || process.env.REDIS_URL || process.env.REDIS_URI;
224
+ if (url) {
225
+ return new import_ioredis.Redis(url, {
226
+ lazyConnect: false,
227
+ maxRetriesPerRequest: 3,
228
+ enableReadyCheck: true
229
+ });
230
+ }
231
+ const redisConfig = {
232
+ host: options.host || process.env.REDIS_HOST || "127.0.0.1",
233
+ port: options.port || (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT, 10) : 6379),
234
+ password: options.password || process.env.REDIS_PASSWORD || void 0,
235
+ db: options.db !== void 0 ? options.db : process.env.REDIS_DB ? parseInt(process.env.REDIS_DB, 10) : 0,
236
+ maxRetriesPerRequest: 3,
237
+ lazyConnect: false
238
+ };
239
+ if (options.tls) {
240
+ redisConfig.tls = {};
241
+ }
242
+ return new import_ioredis.Redis(redisConfig);
243
+ }
244
+
245
+ // src/engine/seeder.ts
246
+ async function executeSeed(redis, rows, options) {
247
+ const startTime = Date.now();
248
+ const batchSize = options.batchSize || 1e3;
249
+ let totalInserted = 0;
250
+ const uniqueKeys = /* @__PURE__ */ new Set();
251
+ if (options.flushFirst && options.keyName && !options.keyName.includes("{{")) {
252
+ await redis.del(options.keyName);
253
+ }
254
+ for (let i = 0; i < rows.length; i += batchSize) {
255
+ const batch = rows.slice(i, i + batchSize);
256
+ const pipeline = redis.pipeline();
257
+ for (const row of batch) {
258
+ const targetKey = options.keyName ? renderTemplate(options.keyName, row) : "seed:data";
259
+ uniqueKeys.add(targetKey);
260
+ const val = extractRowValue(row, options.column, options.valueTemplate);
261
+ switch (options.structure) {
262
+ case "set":
263
+ pipeline.sadd(targetKey, val);
264
+ break;
265
+ case "list":
266
+ pipeline.rpush(targetKey, val);
267
+ break;
268
+ case "string":
269
+ if (options.ttl) {
270
+ pipeline.set(targetKey, val, "EX", options.ttl);
271
+ } else {
272
+ pipeline.set(targetKey, val);
273
+ }
274
+ break;
275
+ case "hash":
276
+ const hashFields = {};
277
+ for (const [k, v] of Object.entries(row)) {
278
+ hashFields[k] = typeof v === "object" ? JSON.stringify(v) : String(v);
279
+ }
280
+ pipeline.hset(targetKey, hashFields);
281
+ if (options.ttl) {
282
+ pipeline.expire(targetKey, options.ttl);
283
+ }
284
+ break;
285
+ case "zset":
286
+ const score = options.scoreColumn && row[options.scoreColumn] !== void 0 ? parseFloat(row[options.scoreColumn]) || 0 : Date.now();
287
+ pipeline.zadd(targetKey, score, val);
288
+ break;
289
+ }
290
+ }
291
+ await pipeline.exec();
292
+ totalInserted += batch.length;
293
+ }
294
+ const elapsedMs = Math.max(1, Date.now() - startTime);
295
+ const opsPerSec = Math.round(totalInserted / elapsedMs * 1e3);
296
+ return {
297
+ totalProcessed: rows.length,
298
+ totalInserted,
299
+ elapsedMs,
300
+ opsPerSec,
301
+ structure: options.structure,
302
+ keysAffected: uniqueKeys.size
303
+ };
304
+ }
305
+
306
+ // src/engine/inspector.ts
307
+ async function inspectKey(redis, key, sampleCount = 5) {
308
+ const [type, ttl] = await Promise.all([
309
+ redis.type(key),
310
+ redis.ttl(key)
311
+ ]);
312
+ let count = 0;
313
+ let samples = [];
314
+ switch (type) {
315
+ case "set":
316
+ count = await redis.scard(key);
317
+ samples = await redis.srandmember(key, sampleCount) || [];
318
+ break;
319
+ case "list":
320
+ count = await redis.llen(key);
321
+ samples = await redis.lrange(key, 0, sampleCount - 1) || [];
322
+ break;
323
+ case "hash":
324
+ count = await redis.hlen(key);
325
+ const hashData = await redis.hgetall(key);
326
+ samples = Object.entries(hashData).slice(0, sampleCount).map(([k, v]) => `${k}: ${v}`);
327
+ break;
328
+ case "zset":
329
+ count = await redis.zcard(key);
330
+ samples = await redis.zrange(key, 0, sampleCount - 1) || [];
331
+ break;
332
+ case "string":
333
+ count = 1;
334
+ const val = await redis.get(key);
335
+ samples = val ? [val] : [];
336
+ break;
337
+ }
338
+ let memoryBytes;
339
+ try {
340
+ const memory = await redis.memory("USAGE", key);
341
+ if (typeof memory === "number") memoryBytes = memory;
342
+ } catch {
343
+ }
344
+ return {
345
+ key,
346
+ type,
347
+ count,
348
+ memoryBytes,
349
+ ttl,
350
+ samples
351
+ };
352
+ }
353
+ // Annotate the CommonJS export names for ESM import in node:
354
+ 0 && (module.exports = {
355
+ createRedisClient,
356
+ executeSeed,
357
+ extractRowValue,
358
+ generateK6Snippet,
359
+ inspectKey,
360
+ loadAndParseFile,
361
+ parseCSV,
362
+ parseJSONOrJSONL,
363
+ parseLines,
364
+ renderTemplate
365
+ });
366
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/loaders/file.ts","../src/utils/template.ts","../src/utils/k6.ts","../src/engine/client.ts","../src/engine/seeder.ts","../src/engine/inspector.ts"],"sourcesContent":["export * from \"./types.js\";\nexport * from \"./loaders/file.js\";\nexport * from \"./utils/template.js\";\nexport * from \"./utils/k6.js\";\nexport * from \"./engine/client.js\";\nexport * from \"./engine/seeder.js\";\nexport * from \"./engine/inspector.js\";\n","import fs from \"fs\";\nimport { parse } from \"csv-parse/sync\";\nimport { ParsedRow } from \"../types.js\";\n\n/**\n * Parse a CSV string or file content into array of row objects.\n */\nexport function parseCSV(content: string, delimiter: string = \",\"): ParsedRow[] {\n const records = parse(content, {\n columns: true,\n skip_empty_lines: true,\n trim: true,\n delimiter,\n cast: false,\n });\n return records;\n}\n\n/**\n * Parse a JSON array, single JSON object, or JSONL string into array of row objects.\n */\nexport function parseJSONOrJSONL(content: string): ParsedRow[] {\n const trimmed = content.trim();\n if (trimmed.startsWith(\"[\") && trimmed.endsWith(\"]\")) {\n const parsed = JSON.parse(trimmed);\n if (!Array.isArray(parsed)) throw new Error(\"JSON root must be an array of objects.\");\n return parsed;\n }\n\n // Check if it is line-delimited JSON (JSONL)\n const lines = trimmed.split(/\\r?\\n/).map(l => l.trim()).filter(Boolean);\n const rows: ParsedRow[] = [];\n for (const line of lines) {\n if (line.startsWith(\"{\") && line.endsWith(\"}\")) {\n rows.push(JSON.parse(line));\n } else {\n // Plain text single column line\n rows.push({ value: line });\n }\n }\n\n return rows;\n}\n\n/**\n * Parse plain text lines into { value: line } rows.\n */\nexport function parseLines(content: string): ParsedRow[] {\n return content\n .split(/\\r?\\n/)\n .map(l => l.trim())\n .filter(Boolean)\n .map(l => ({ value: l }));\n}\n\n/**\n * Auto-detect file format from extension and contents.\n */\nexport function loadAndParseFile(filePath: string): ParsedRow[] {\n if (!fs.existsSync(filePath)) {\n throw new Error(`File not found: ${filePath}`);\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const ext = filePath.split(\".\").pop()?.toLowerCase();\n\n if (ext === \"csv\") {\n return parseCSV(content);\n } else if (ext === \"tsv\") {\n return parseCSV(content, \"\\t\");\n } else if (ext === \"json\" || ext === \"jsonl\" || ext === \"ndjson\") {\n return parseJSONOrJSONL(content);\n } else if (ext === \"txt\") {\n return parseLines(content);\n }\n\n // Fallback try JSON then CSV then lines\n try {\n return parseJSONOrJSONL(content);\n } catch {\n try {\n return parseCSV(content);\n } catch {\n return parseLines(content);\n }\n }\n}\n","import { ParsedRow } from \"../types.js\";\n\n/**\n * Render template string replacing `{{variable}}` with values from row.\n */\nexport function renderTemplate(template: string, row: ParsedRow): string {\n return template.replace(/\\{\\{\\s*([a-zA-Z0-9_-]+)\\s*\\}\\}/g, (_, key) => {\n return row[key] !== undefined ? String(row[key]) : \"\";\n });\n}\n\n/**\n * Extract item value from row based on column or template.\n */\nexport function extractRowValue(row: ParsedRow, column?: string, valueTemplate?: string): string {\n if (valueTemplate) {\n return renderTemplate(valueTemplate, row);\n }\n\n if (column && row[column] !== undefined) {\n return String(row[column]);\n }\n\n // If there's only 1 property in the row, use it\n const keys = Object.keys(row);\n if (keys.length === 1) {\n return String(row[keys[0]]);\n }\n\n // Otherwise return entire row as JSON string\n return JSON.stringify(row);\n}\n","import { RedisStructure } from \"../types.js\";\n\n/**\n * Generate ready-to-use k6 test script snippets using xk6-redis or HTTP mocks.\n */\nexport function generateK6Snippet(keyName: string, structure: RedisStructure): string {\n if (structure === \"set\") {\n return `// ==========================================\n// k6 Load Test - Redis Set Data Consumption\n// Requirements: xk6-redis (k6 run --vus 50 --duration 1m script.js)\n// ==========================================\nimport http from 'k6/http';\nimport { check, sleep } from 'k6';\nimport redis from 'k6/x/redis';\n\n// Connect to Redis\nconst client = new redis.Client('redis://localhost:6379');\n\nexport const options = {\n vus: 50,\n duration: '1m',\n};\n\nexport default function () {\n // Option A: Pop unique token (guarantees NO collision between VUs)\n const uniqueToken = client.spop('${keyName}');\n\n // Option B: Random sample (if token is reusable across VUs)\n // const uniqueToken = client.srandmember('${keyName}');\n\n if (!uniqueToken) {\n console.warn('Redis set \"${keyName}\" is exhausted!');\n return;\n }\n\n // Execute authenticated HTTP request using seeded token\n const res = http.get('https://api.example.com/v1/user/profile', {\n headers: {\n Authorization: \\`Bearer \\${uniqueToken}\\`,\n },\n });\n\n check(res, {\n 'status is 200': (r) => r.status === 200,\n });\n\n sleep(0.1);\n}\n`;\n }\n\n if (structure === \"list\") {\n return `// ==========================================\n// k6 Load Test - Redis Queue / List FIFO\n// Requirements: xk6-redis\n// ==========================================\nimport http from 'k6/http';\nimport { check } from 'k6';\nimport redis from 'k6/x/redis';\n\nconst client = new redis.Client('redis://localhost:6379');\n\nexport default function () {\n // Pop from FIFO queue\n const item = client.lpop('${keyName}');\n\n if (!item) {\n console.warn('Queue \"${keyName}\" is empty!');\n return;\n }\n\n const payload = JSON.parse(item);\n const res = http.post('https://api.example.com/v1/orders', JSON.stringify(payload), {\n headers: { 'Content-Type': 'application/json' },\n });\n\n check(res, { 'status is 201': (r) => r.status === 201 });\n}\n`;\n }\n\n // Default string / hash snippet\n return `// ==========================================\n// k6 Load Test - Redis Key-Value Fetch\n// Requirements: xk6-redis\n// ==========================================\nimport http from 'k6/http';\nimport { check } from 'k6';\nimport redis from 'k6/x/redis';\n\nconst client = new redis.Client('redis://localhost:6379');\n\nexport default function () {\n const value = client.get('${keyName}');\n const res = http.get(\\`https://api.example.com/v1/item/\\${value}\\`);\n check(res, { 'status is 200': (r) => r.status === 200 });\n}\n`;\n}\n","import { Redis, RedisOptions } from \"ioredis\";\nimport { RedisConnectionOptions } from \"../types.js\";\n\n/**\n * Create a reusable ioredis client instance.\n */\nexport function createRedisClient(options: RedisConnectionOptions = {}): Redis {\n const url = options.url || process.env.REDIS_URL || process.env.REDIS_URI;\n\n if (url) {\n return new Redis(url, {\n lazyConnect: false,\n maxRetriesPerRequest: 3,\n enableReadyCheck: true,\n });\n }\n\n const redisConfig: RedisOptions = {\n host: options.host || process.env.REDIS_HOST || \"127.0.0.1\",\n port: options.port || (process.env.REDIS_PORT ? parseInt(process.env.REDIS_PORT, 10) : 6379),\n password: options.password || process.env.REDIS_PASSWORD || undefined,\n db: options.db !== undefined ? options.db : (process.env.REDIS_DB ? parseInt(process.env.REDIS_DB, 10) : 0),\n maxRetriesPerRequest: 3,\n lazyConnect: false,\n };\n\n if (options.tls) {\n redisConfig.tls = {};\n }\n\n return new Redis(redisConfig);\n}\n","import { Redis } from \"ioredis\";\nimport { SeedOptions, SeedResult, ParsedRow } from \"../types.js\";\nimport { renderTemplate, extractRowValue } from \"../utils/template.js\";\n\n/**\n * Execute high-speed batched seeding into Redis using pipelines.\n */\nexport async function executeSeed(\n redis: Redis,\n rows: ParsedRow[],\n options: SeedOptions\n): Promise<SeedResult> {\n const startTime = Date.now();\n const batchSize = options.batchSize || 1000;\n let totalInserted = 0;\n const uniqueKeys = new Set<string>();\n\n // If flushFirst is enabled and static keyName is provided, delete it first\n if (options.flushFirst && options.keyName && !options.keyName.includes(\"{{\")) {\n await redis.del(options.keyName);\n }\n\n // Process in batches using pipeline\n for (let i = 0; i < rows.length; i += batchSize) {\n const batch = rows.slice(i, i + batchSize);\n const pipeline = redis.pipeline();\n\n for (const row of batch) {\n const targetKey = options.keyName\n ? renderTemplate(options.keyName, row)\n : \"seed:data\";\n\n uniqueKeys.add(targetKey);\n const val = extractRowValue(row, options.column, options.valueTemplate);\n\n switch (options.structure) {\n case \"set\":\n pipeline.sadd(targetKey, val);\n break;\n\n case \"list\":\n pipeline.rpush(targetKey, val);\n break;\n\n case \"string\":\n if (options.ttl) {\n pipeline.set(targetKey, val, \"EX\", options.ttl);\n } else {\n pipeline.set(targetKey, val);\n }\n break;\n\n case \"hash\":\n // If row has multiple columns, store full hash fields\n const hashFields: Record<string, string> = {};\n for (const [k, v] of Object.entries(row)) {\n hashFields[k] = typeof v === \"object\" ? JSON.stringify(v) : String(v);\n }\n pipeline.hset(targetKey, hashFields);\n if (options.ttl) {\n pipeline.expire(targetKey, options.ttl);\n }\n break;\n\n case \"zset\":\n const score = options.scoreColumn && row[options.scoreColumn] !== undefined\n ? parseFloat(row[options.scoreColumn]) || 0\n : Date.now();\n pipeline.zadd(targetKey, score, val);\n break;\n }\n }\n\n await pipeline.exec();\n totalInserted += batch.length;\n }\n\n const elapsedMs = Math.max(1, Date.now() - startTime);\n const opsPerSec = Math.round((totalInserted / elapsedMs) * 1000);\n\n return {\n totalProcessed: rows.length,\n totalInserted,\n elapsedMs,\n opsPerSec,\n structure: options.structure,\n keysAffected: uniqueKeys.size,\n };\n}\n","import { Redis } from \"ioredis\";\n\nexport interface KeyInspection {\n key: string;\n type: string;\n count: number;\n memoryBytes?: number;\n ttl: number;\n samples: string[];\n}\n\n/**\n * Inspect a Redis key (count, type, memory, and sample items).\n */\nexport async function inspectKey(redis: Redis, key: string, sampleCount: number = 5): Promise<KeyInspection> {\n const [type, ttl] = await Promise.all([\n redis.type(key),\n redis.ttl(key),\n ]);\n\n let count = 0;\n let samples: string[] = [];\n\n switch (type) {\n case \"set\":\n count = await redis.scard(key);\n samples = await redis.srandmember(key, sampleCount) || [];\n break;\n\n case \"list\":\n count = await redis.llen(key);\n samples = await redis.lrange(key, 0, sampleCount - 1) || [];\n break;\n\n case \"hash\":\n count = await redis.hlen(key);\n const hashData = await redis.hgetall(key);\n samples = Object.entries(hashData).slice(0, sampleCount).map(([k, v]) => `${k}: ${v}`);\n break;\n\n case \"zset\":\n count = await redis.zcard(key);\n samples = await redis.zrange(key, 0, sampleCount - 1) || [];\n break;\n\n case \"string\":\n count = 1;\n const val = await redis.get(key);\n samples = val ? [val] : [];\n break;\n }\n\n let memoryBytes: number | undefined;\n try {\n const memory = await redis.memory(\"USAGE\", key) as number;\n if (typeof memory === \"number\") memoryBytes = memory;\n } catch {\n // MEMORY USAGE might not be supported on all Redis clusters/proxies\n }\n\n return {\n key,\n type,\n count,\n memoryBytes,\n ttl,\n samples,\n };\n}\n"],"mappings":"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","names":["fs"]}