@crashlab/redis-mock 0.1.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/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @crashlab/redis-mock
2
+
3
+ Redis RESP protocol mock for CrashLab. Supports 16 commands: GET, SET, DEL, EXPIRE, TTL, INCR, DECR, LPUSH, RPUSH, LPOP, RPOP, HSET, HGET, SMEMBERS, SADD, ZADD, ZRANGE. TTL integrates with virtual clock. Throws `CrashLabUnsupportedRedisCommand` for unsupported commands.
package/dist/index.cjs ADDED
@@ -0,0 +1,198 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ RedisMock: () => RedisMock
34
+ });
35
+ module.exports = __toCommonJS(index_exports);
36
+ var import_ioredis_mock = __toESM(require("ioredis-mock"), 1);
37
+ function parseRESPArray(buf, offset) {
38
+ if (offset >= buf.length) return null;
39
+ if (buf[offset] !== 42) {
40
+ const lineEnd = buf.indexOf("\r\n", offset);
41
+ if (lineEnd < 0) return null;
42
+ const line = buf.toString("utf8", offset, lineEnd);
43
+ return { args: line.split(/\s+/), consumed: lineEnd + 2 - offset };
44
+ }
45
+ const firstLine = buf.indexOf("\r\n", offset);
46
+ if (firstLine < 0) return null;
47
+ const count = parseInt(buf.toString("utf8", offset + 1, firstLine));
48
+ if (count < 0) return { args: [], consumed: firstLine + 2 - offset };
49
+ let pos = firstLine + 2;
50
+ const args = [];
51
+ for (let i = 0; i < count; i++) {
52
+ if (pos >= buf.length) return null;
53
+ if (buf[pos] === 36) {
54
+ const lineEnd = buf.indexOf("\r\n", pos);
55
+ if (lineEnd < 0) return null;
56
+ const len = parseInt(buf.toString("utf8", pos + 1, lineEnd));
57
+ if (len < 0) {
58
+ args.push("");
59
+ pos = lineEnd + 2;
60
+ continue;
61
+ }
62
+ const dataStart = lineEnd + 2;
63
+ const dataEnd = dataStart + len;
64
+ if (dataEnd + 2 > buf.length) return null;
65
+ args.push(buf.toString("utf8", dataStart, dataEnd));
66
+ pos = dataEnd + 2;
67
+ } else {
68
+ const lineEnd = buf.indexOf("\r\n", pos);
69
+ if (lineEnd < 0) return null;
70
+ args.push(buf.toString("utf8", pos, lineEnd));
71
+ pos = lineEnd + 2;
72
+ }
73
+ }
74
+ return { args, consumed: pos - offset };
75
+ }
76
+ function parseAllCommands(buf) {
77
+ const commands = [];
78
+ let offset = 0;
79
+ while (offset < buf.length) {
80
+ const result = parseRESPArray(buf, offset);
81
+ if (!result) break;
82
+ if (result.args.length > 0) commands.push(result.args);
83
+ offset += result.consumed;
84
+ }
85
+ return commands;
86
+ }
87
+ function encodeBulkString(s) {
88
+ return `$${Buffer.byteLength(s)}\r
89
+ ${s}\r
90
+ `;
91
+ }
92
+ function encodeResp(value) {
93
+ if (value === null || value === void 0) return "$-1\r\n";
94
+ if (typeof value === "number") return `:${Math.floor(value)}\r
95
+ `;
96
+ if (typeof value === "string") return encodeBulkString(value);
97
+ if (value instanceof Error) return `-ERR ${value.message}\r
98
+ `;
99
+ if (Array.isArray(value)) {
100
+ const parts = [`*${value.length}\r
101
+ `];
102
+ for (const el of value) parts.push(encodeResp(el));
103
+ return parts.join("");
104
+ }
105
+ return encodeBulkString(String(value));
106
+ }
107
+ var SIMPLE_STRING_COMMANDS = /* @__PURE__ */ new Set([
108
+ "SET",
109
+ "MSET",
110
+ "FLUSHDB",
111
+ "FLUSHALL",
112
+ "SELECT",
113
+ "PSETEX",
114
+ "SETEX",
115
+ "RENAME",
116
+ "RESTORE",
117
+ "HMSET",
118
+ "LSET",
119
+ "LTRIM",
120
+ "DISCARD",
121
+ "MULTI",
122
+ "EXEC",
123
+ "WATCH",
124
+ "UNWATCH",
125
+ "AUTH",
126
+ "QUIT",
127
+ "OK"
128
+ ]);
129
+ function encodeCommandResult(cmd, value) {
130
+ if (value === "OK" && SIMPLE_STRING_COMMANDS.has(cmd.toUpperCase())) {
131
+ return "+OK\r\n";
132
+ }
133
+ if (cmd.toUpperCase() === "PING" && value === "PONG") {
134
+ return "+PONG\r\n";
135
+ }
136
+ return encodeResp(value);
137
+ }
138
+ var RedisMock = class {
139
+ _redis;
140
+ /** Queued seedData pairs applied before first command. */
141
+ _seedPairs = [];
142
+ _seeded = false;
143
+ constructor(opts) {
144
+ this._redis = new import_ioredis_mock.default({ data: opts?.data ?? {} });
145
+ }
146
+ /**
147
+ * Seed a key-value pair into the store.
148
+ */
149
+ seedData(key, value) {
150
+ this._seedPairs.push([key, value]);
151
+ }
152
+ /**
153
+ * Flush the database.
154
+ * Called during teardown for clean isolation.
155
+ */
156
+ async flush() {
157
+ await this._redis.flushdb();
158
+ this._seeded = false;
159
+ }
160
+ // ── TCP handler ─────────────────────────────────────────────────────────
161
+ createHandler() {
162
+ return async (data, _ctx) => {
163
+ if (!this._seeded) {
164
+ this._seeded = true;
165
+ for (const [k, v] of this._seedPairs) {
166
+ await this._redis.set(k, v);
167
+ }
168
+ }
169
+ const commands = parseAllCommands(data);
170
+ if (commands.length === 0) return null;
171
+ const responseParts = [];
172
+ for (const [cmd, ...args] of commands) {
173
+ const upper = cmd.toUpperCase();
174
+ const lower = cmd.toLowerCase();
175
+ try {
176
+ const fn = this._redis[lower];
177
+ if (typeof fn !== "function") {
178
+ responseParts.push(`-ERR unknown command '${upper}'\r
179
+ `);
180
+ continue;
181
+ }
182
+ const result = await fn.apply(this._redis, args);
183
+ responseParts.push(encodeCommandResult(upper, result));
184
+ } catch (err) {
185
+ const msg = err instanceof Error ? err.message : String(err);
186
+ responseParts.push(`-ERR ${msg}\r
187
+ `);
188
+ }
189
+ }
190
+ return Buffer.from(responseParts.join(""));
191
+ };
192
+ }
193
+ };
194
+ // Annotate the CommonJS export names for ESM import in node:
195
+ 0 && (module.exports = {
196
+ RedisMock
197
+ });
198
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @crashlab/redis-mock\n *\n * Pure in-memory Redis mock backed by ioredis-mock. No external Redis\n * binary or compilation step is needed. Incoming RESP commands from the\n * TCP interceptor are parsed, executed against ioredis-mock, and the\n * results are encoded back into RESP wire format.\n */\n\nimport type { TcpMockHandler, TcpMockContext, TcpHandlerResult } from '@crashlab/tcp';\nimport Redis from 'ioredis-mock';\n\n// ---------------------------------------------------------------------------\n// RESP parser — extract [command, ...args] arrays from raw bytes\n// ---------------------------------------------------------------------------\n\n/** Measure the byte-length of one complete RESP value at `offset`, or -1 if incomplete. */\nfunction respValueLength(buf: Buffer, offset: number): number {\n if (offset >= buf.length) return -1;\n const type = buf[offset];\n const lineEnd = buf.indexOf('\\r\\n', offset);\n if (lineEnd < 0) return -1;\n\n switch (type) {\n case 0x2b: // + Simple string\n case 0x2d: // - Error\n case 0x3a: // : Integer\n return lineEnd - offset + 2;\n\n case 0x24: { // $ Bulk string\n const len = parseInt(buf.toString('utf8', offset + 1, lineEnd));\n if (len < 0) return lineEnd - offset + 2; // $-1\\r\\n (null)\n const dataEnd = lineEnd + 2 + len + 2;\n if (dataEnd > buf.length) return -1;\n return dataEnd - offset;\n }\n\n case 0x2a: { // * Array\n const count = parseInt(buf.toString('utf8', offset + 1, lineEnd));\n if (count < 0) return lineEnd - offset + 2; // *-1\\r\\n (null array)\n let pos = lineEnd + 2;\n for (let i = 0; i < count; i++) {\n const elemLen = respValueLength(buf, pos);\n if (elemLen < 0) return -1;\n pos += elemLen;\n }\n return pos - offset;\n }\n\n default:\n return lineEnd - offset + 2;\n }\n}\n\n/** Parse a single RESP array command into a string[] of [command, ...args]. */\nfunction parseRESPArray(buf: Buffer, offset: number): { args: string[]; consumed: number } | null {\n if (offset >= buf.length) return null;\n\n // Inline command (plain text line)\n if (buf[offset] !== 0x2a /* '*' */) {\n const lineEnd = buf.indexOf('\\r\\n', offset);\n if (lineEnd < 0) return null;\n const line = buf.toString('utf8', offset, lineEnd);\n return { args: line.split(/\\s+/), consumed: lineEnd + 2 - offset };\n }\n\n const firstLine = buf.indexOf('\\r\\n', offset);\n if (firstLine < 0) return null;\n const count = parseInt(buf.toString('utf8', offset + 1, firstLine));\n if (count < 0) return { args: [], consumed: firstLine + 2 - offset };\n\n let pos = firstLine + 2;\n const args: string[] = [];\n\n for (let i = 0; i < count; i++) {\n if (pos >= buf.length) return null;\n if (buf[pos] === 0x24 /* '$' */) {\n const lineEnd = buf.indexOf('\\r\\n', pos);\n if (lineEnd < 0) return null;\n const len = parseInt(buf.toString('utf8', pos + 1, lineEnd));\n if (len < 0) { args.push(''); pos = lineEnd + 2; continue; }\n const dataStart = lineEnd + 2;\n const dataEnd = dataStart + len;\n if (dataEnd + 2 > buf.length) return null;\n args.push(buf.toString('utf8', dataStart, dataEnd));\n pos = dataEnd + 2;\n } else {\n // Non-bulk inline element\n const lineEnd = buf.indexOf('\\r\\n', pos);\n if (lineEnd < 0) return null;\n args.push(buf.toString('utf8', pos, lineEnd));\n pos = lineEnd + 2;\n }\n }\n\n return { args, consumed: pos - offset };\n}\n\n/** Parse all RESP commands from a buffer. */\nfunction parseAllCommands(buf: Buffer): string[][] {\n const commands: string[][] = [];\n let offset = 0;\n while (offset < buf.length) {\n const result = parseRESPArray(buf, offset);\n if (!result) break;\n if (result.args.length > 0) commands.push(result.args);\n offset += result.consumed;\n }\n return commands;\n}\n\n// ---------------------------------------------------------------------------\n// RESP encoder — convert JS values back to RESP wire format\n// ---------------------------------------------------------------------------\n\nfunction encodeBulkString(s: string): string {\n return `$${Buffer.byteLength(s)}\\r\\n${s}\\r\\n`;\n}\n\n/** Encode a JS value to RESP wire format. */\nfunction encodeResp(value: unknown): string {\n if (value === null || value === undefined) return '$-1\\r\\n';\n if (typeof value === 'number') return `:${Math.floor(value)}\\r\\n`;\n if (typeof value === 'string') return encodeBulkString(value);\n if (value instanceof Error) return `-ERR ${value.message}\\r\\n`;\n if (Array.isArray(value)) {\n const parts = [`*${value.length}\\r\\n`];\n for (const el of value) parts.push(encodeResp(el));\n return parts.join('');\n }\n return encodeBulkString(String(value));\n}\n\n// Map of commands whose successful response should be a simple string (+OK)\n// rather than a bulk string ($2\\r\\nOK\\r\\n).\nconst SIMPLE_STRING_COMMANDS = new Set([\n 'SET', 'MSET', 'FLUSHDB', 'FLUSHALL', 'SELECT', 'PSETEX', 'SETEX',\n 'RENAME', 'RESTORE', 'HMSET', 'LSET', 'LTRIM', 'DISCARD', 'MULTI',\n 'EXEC', 'WATCH', 'UNWATCH', 'AUTH', 'QUIT', 'OK',\n]);\n\n/** Encode a command result, using +OK for commands that return simple strings. */\nfunction encodeCommandResult(cmd: string, value: unknown): string {\n if (value === 'OK' && SIMPLE_STRING_COMMANDS.has(cmd.toUpperCase())) {\n return '+OK\\r\\n';\n }\n // PING returns simple string\n if (cmd.toUpperCase() === 'PING' && value === 'PONG') {\n return '+PONG\\r\\n';\n }\n return encodeResp(value);\n}\n\n// ---------------------------------------------------------------------------\n// RedisMock — public API\n// ---------------------------------------------------------------------------\n\nexport interface RedisMockOpts {\n /** Initial data to pre-populate (optional). */\n data?: Record<string, unknown>;\n}\n\nexport class RedisMock {\n private _redis: InstanceType<typeof Redis>;\n /** Queued seedData pairs applied before first command. */\n private _seedPairs: Array<[string, string]> = [];\n private _seeded = false;\n\n constructor(opts?: RedisMockOpts) {\n this._redis = new Redis({ data: opts?.data ?? {} });\n }\n\n /**\n * Seed a key-value pair into the store.\n */\n seedData(key: string, value: string): void {\n this._seedPairs.push([key, value]);\n }\n\n /**\n * Flush the database.\n * Called during teardown for clean isolation.\n */\n async flush(): Promise<void> {\n await this._redis.flushdb();\n this._seeded = false;\n }\n\n // ── TCP handler ─────────────────────────────────────────────────────────\n\n createHandler(): TcpMockHandler {\n return async (data: Buffer, _ctx: TcpMockContext): Promise<TcpHandlerResult> => {\n // Apply seed data on first interaction\n if (!this._seeded) {\n this._seeded = true;\n for (const [k, v] of this._seedPairs) {\n await this._redis.set(k, v);\n }\n }\n\n const commands = parseAllCommands(data);\n if (commands.length === 0) return null;\n\n const responseParts: string[] = [];\n\n for (const [cmd, ...args] of commands) {\n const upper = cmd.toUpperCase();\n const lower = cmd.toLowerCase();\n try {\n const fn = (this._redis as any)[lower];\n if (typeof fn !== 'function') {\n responseParts.push(`-ERR unknown command '${upper}'\\r\\n`);\n continue;\n }\n const result = await fn.apply(this._redis, args);\n responseParts.push(encodeCommandResult(upper, result));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n responseParts.push(`-ERR ${msg}\\r\\n`);\n }\n }\n\n return Buffer.from(responseParts.join(''));\n };\n }\n}\n"],"mappings":"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","names":["Redis"]}
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @crashlab/redis-mock
3
+ *
4
+ * Pure in-memory Redis mock backed by ioredis-mock. No external Redis
5
+ * binary or compilation step is needed. Incoming RESP commands from the
6
+ * TCP interceptor are parsed, executed against ioredis-mock, and the
7
+ * results are encoded back into RESP wire format.
8
+ */
9
+ import type { TcpMockHandler } from '@crashlab/tcp';
10
+ export interface RedisMockOpts {
11
+ /** Initial data to pre-populate (optional). */
12
+ data?: Record<string, unknown>;
13
+ }
14
+ export declare class RedisMock {
15
+ private _redis;
16
+ /** Queued seedData pairs applied before first command. */
17
+ private _seedPairs;
18
+ private _seeded;
19
+ constructor(opts?: RedisMockOpts);
20
+ /**
21
+ * Seed a key-value pair into the store.
22
+ */
23
+ seedData(key: string, value: string): void;
24
+ /**
25
+ * Flush the database.
26
+ * Called during teardown for clean isolation.
27
+ */
28
+ flush(): Promise<void>;
29
+ createHandler(): TcpMockHandler;
30
+ }
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAoC,MAAM,eAAe,CAAC;AAoJtF,MAAM,WAAW,aAAa;IAC5B,+CAA+C;IAC/C,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAChC;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAA6B;IAC3C,0DAA0D;IAC1D,OAAO,CAAC,UAAU,CAA+B;IACjD,OAAO,CAAC,OAAO,CAAS;gBAEZ,IAAI,CAAC,EAAE,aAAa;IAIhC;;OAEG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI;IAI1C;;;OAGG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAO5B,aAAa,IAAI,cAAc;CAmChC"}
package/dist/index.js ADDED
@@ -0,0 +1,163 @@
1
+ // src/index.ts
2
+ import Redis from "ioredis-mock";
3
+ function parseRESPArray(buf, offset) {
4
+ if (offset >= buf.length) return null;
5
+ if (buf[offset] !== 42) {
6
+ const lineEnd = buf.indexOf("\r\n", offset);
7
+ if (lineEnd < 0) return null;
8
+ const line = buf.toString("utf8", offset, lineEnd);
9
+ return { args: line.split(/\s+/), consumed: lineEnd + 2 - offset };
10
+ }
11
+ const firstLine = buf.indexOf("\r\n", offset);
12
+ if (firstLine < 0) return null;
13
+ const count = parseInt(buf.toString("utf8", offset + 1, firstLine));
14
+ if (count < 0) return { args: [], consumed: firstLine + 2 - offset };
15
+ let pos = firstLine + 2;
16
+ const args = [];
17
+ for (let i = 0; i < count; i++) {
18
+ if (pos >= buf.length) return null;
19
+ if (buf[pos] === 36) {
20
+ const lineEnd = buf.indexOf("\r\n", pos);
21
+ if (lineEnd < 0) return null;
22
+ const len = parseInt(buf.toString("utf8", pos + 1, lineEnd));
23
+ if (len < 0) {
24
+ args.push("");
25
+ pos = lineEnd + 2;
26
+ continue;
27
+ }
28
+ const dataStart = lineEnd + 2;
29
+ const dataEnd = dataStart + len;
30
+ if (dataEnd + 2 > buf.length) return null;
31
+ args.push(buf.toString("utf8", dataStart, dataEnd));
32
+ pos = dataEnd + 2;
33
+ } else {
34
+ const lineEnd = buf.indexOf("\r\n", pos);
35
+ if (lineEnd < 0) return null;
36
+ args.push(buf.toString("utf8", pos, lineEnd));
37
+ pos = lineEnd + 2;
38
+ }
39
+ }
40
+ return { args, consumed: pos - offset };
41
+ }
42
+ function parseAllCommands(buf) {
43
+ const commands = [];
44
+ let offset = 0;
45
+ while (offset < buf.length) {
46
+ const result = parseRESPArray(buf, offset);
47
+ if (!result) break;
48
+ if (result.args.length > 0) commands.push(result.args);
49
+ offset += result.consumed;
50
+ }
51
+ return commands;
52
+ }
53
+ function encodeBulkString(s) {
54
+ return `$${Buffer.byteLength(s)}\r
55
+ ${s}\r
56
+ `;
57
+ }
58
+ function encodeResp(value) {
59
+ if (value === null || value === void 0) return "$-1\r\n";
60
+ if (typeof value === "number") return `:${Math.floor(value)}\r
61
+ `;
62
+ if (typeof value === "string") return encodeBulkString(value);
63
+ if (value instanceof Error) return `-ERR ${value.message}\r
64
+ `;
65
+ if (Array.isArray(value)) {
66
+ const parts = [`*${value.length}\r
67
+ `];
68
+ for (const el of value) parts.push(encodeResp(el));
69
+ return parts.join("");
70
+ }
71
+ return encodeBulkString(String(value));
72
+ }
73
+ var SIMPLE_STRING_COMMANDS = /* @__PURE__ */ new Set([
74
+ "SET",
75
+ "MSET",
76
+ "FLUSHDB",
77
+ "FLUSHALL",
78
+ "SELECT",
79
+ "PSETEX",
80
+ "SETEX",
81
+ "RENAME",
82
+ "RESTORE",
83
+ "HMSET",
84
+ "LSET",
85
+ "LTRIM",
86
+ "DISCARD",
87
+ "MULTI",
88
+ "EXEC",
89
+ "WATCH",
90
+ "UNWATCH",
91
+ "AUTH",
92
+ "QUIT",
93
+ "OK"
94
+ ]);
95
+ function encodeCommandResult(cmd, value) {
96
+ if (value === "OK" && SIMPLE_STRING_COMMANDS.has(cmd.toUpperCase())) {
97
+ return "+OK\r\n";
98
+ }
99
+ if (cmd.toUpperCase() === "PING" && value === "PONG") {
100
+ return "+PONG\r\n";
101
+ }
102
+ return encodeResp(value);
103
+ }
104
+ var RedisMock = class {
105
+ _redis;
106
+ /** Queued seedData pairs applied before first command. */
107
+ _seedPairs = [];
108
+ _seeded = false;
109
+ constructor(opts) {
110
+ this._redis = new Redis({ data: opts?.data ?? {} });
111
+ }
112
+ /**
113
+ * Seed a key-value pair into the store.
114
+ */
115
+ seedData(key, value) {
116
+ this._seedPairs.push([key, value]);
117
+ }
118
+ /**
119
+ * Flush the database.
120
+ * Called during teardown for clean isolation.
121
+ */
122
+ async flush() {
123
+ await this._redis.flushdb();
124
+ this._seeded = false;
125
+ }
126
+ // ── TCP handler ─────────────────────────────────────────────────────────
127
+ createHandler() {
128
+ return async (data, _ctx) => {
129
+ if (!this._seeded) {
130
+ this._seeded = true;
131
+ for (const [k, v] of this._seedPairs) {
132
+ await this._redis.set(k, v);
133
+ }
134
+ }
135
+ const commands = parseAllCommands(data);
136
+ if (commands.length === 0) return null;
137
+ const responseParts = [];
138
+ for (const [cmd, ...args] of commands) {
139
+ const upper = cmd.toUpperCase();
140
+ const lower = cmd.toLowerCase();
141
+ try {
142
+ const fn = this._redis[lower];
143
+ if (typeof fn !== "function") {
144
+ responseParts.push(`-ERR unknown command '${upper}'\r
145
+ `);
146
+ continue;
147
+ }
148
+ const result = await fn.apply(this._redis, args);
149
+ responseParts.push(encodeCommandResult(upper, result));
150
+ } catch (err) {
151
+ const msg = err instanceof Error ? err.message : String(err);
152
+ responseParts.push(`-ERR ${msg}\r
153
+ `);
154
+ }
155
+ }
156
+ return Buffer.from(responseParts.join(""));
157
+ };
158
+ }
159
+ };
160
+ export {
161
+ RedisMock
162
+ };
163
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["/**\n * @crashlab/redis-mock\n *\n * Pure in-memory Redis mock backed by ioredis-mock. No external Redis\n * binary or compilation step is needed. Incoming RESP commands from the\n * TCP interceptor are parsed, executed against ioredis-mock, and the\n * results are encoded back into RESP wire format.\n */\n\nimport type { TcpMockHandler, TcpMockContext, TcpHandlerResult } from '@crashlab/tcp';\nimport Redis from 'ioredis-mock';\n\n// ---------------------------------------------------------------------------\n// RESP parser — extract [command, ...args] arrays from raw bytes\n// ---------------------------------------------------------------------------\n\n/** Measure the byte-length of one complete RESP value at `offset`, or -1 if incomplete. */\nfunction respValueLength(buf: Buffer, offset: number): number {\n if (offset >= buf.length) return -1;\n const type = buf[offset];\n const lineEnd = buf.indexOf('\\r\\n', offset);\n if (lineEnd < 0) return -1;\n\n switch (type) {\n case 0x2b: // + Simple string\n case 0x2d: // - Error\n case 0x3a: // : Integer\n return lineEnd - offset + 2;\n\n case 0x24: { // $ Bulk string\n const len = parseInt(buf.toString('utf8', offset + 1, lineEnd));\n if (len < 0) return lineEnd - offset + 2; // $-1\\r\\n (null)\n const dataEnd = lineEnd + 2 + len + 2;\n if (dataEnd > buf.length) return -1;\n return dataEnd - offset;\n }\n\n case 0x2a: { // * Array\n const count = parseInt(buf.toString('utf8', offset + 1, lineEnd));\n if (count < 0) return lineEnd - offset + 2; // *-1\\r\\n (null array)\n let pos = lineEnd + 2;\n for (let i = 0; i < count; i++) {\n const elemLen = respValueLength(buf, pos);\n if (elemLen < 0) return -1;\n pos += elemLen;\n }\n return pos - offset;\n }\n\n default:\n return lineEnd - offset + 2;\n }\n}\n\n/** Parse a single RESP array command into a string[] of [command, ...args]. */\nfunction parseRESPArray(buf: Buffer, offset: number): { args: string[]; consumed: number } | null {\n if (offset >= buf.length) return null;\n\n // Inline command (plain text line)\n if (buf[offset] !== 0x2a /* '*' */) {\n const lineEnd = buf.indexOf('\\r\\n', offset);\n if (lineEnd < 0) return null;\n const line = buf.toString('utf8', offset, lineEnd);\n return { args: line.split(/\\s+/), consumed: lineEnd + 2 - offset };\n }\n\n const firstLine = buf.indexOf('\\r\\n', offset);\n if (firstLine < 0) return null;\n const count = parseInt(buf.toString('utf8', offset + 1, firstLine));\n if (count < 0) return { args: [], consumed: firstLine + 2 - offset };\n\n let pos = firstLine + 2;\n const args: string[] = [];\n\n for (let i = 0; i < count; i++) {\n if (pos >= buf.length) return null;\n if (buf[pos] === 0x24 /* '$' */) {\n const lineEnd = buf.indexOf('\\r\\n', pos);\n if (lineEnd < 0) return null;\n const len = parseInt(buf.toString('utf8', pos + 1, lineEnd));\n if (len < 0) { args.push(''); pos = lineEnd + 2; continue; }\n const dataStart = lineEnd + 2;\n const dataEnd = dataStart + len;\n if (dataEnd + 2 > buf.length) return null;\n args.push(buf.toString('utf8', dataStart, dataEnd));\n pos = dataEnd + 2;\n } else {\n // Non-bulk inline element\n const lineEnd = buf.indexOf('\\r\\n', pos);\n if (lineEnd < 0) return null;\n args.push(buf.toString('utf8', pos, lineEnd));\n pos = lineEnd + 2;\n }\n }\n\n return { args, consumed: pos - offset };\n}\n\n/** Parse all RESP commands from a buffer. */\nfunction parseAllCommands(buf: Buffer): string[][] {\n const commands: string[][] = [];\n let offset = 0;\n while (offset < buf.length) {\n const result = parseRESPArray(buf, offset);\n if (!result) break;\n if (result.args.length > 0) commands.push(result.args);\n offset += result.consumed;\n }\n return commands;\n}\n\n// ---------------------------------------------------------------------------\n// RESP encoder — convert JS values back to RESP wire format\n// ---------------------------------------------------------------------------\n\nfunction encodeBulkString(s: string): string {\n return `$${Buffer.byteLength(s)}\\r\\n${s}\\r\\n`;\n}\n\n/** Encode a JS value to RESP wire format. */\nfunction encodeResp(value: unknown): string {\n if (value === null || value === undefined) return '$-1\\r\\n';\n if (typeof value === 'number') return `:${Math.floor(value)}\\r\\n`;\n if (typeof value === 'string') return encodeBulkString(value);\n if (value instanceof Error) return `-ERR ${value.message}\\r\\n`;\n if (Array.isArray(value)) {\n const parts = [`*${value.length}\\r\\n`];\n for (const el of value) parts.push(encodeResp(el));\n return parts.join('');\n }\n return encodeBulkString(String(value));\n}\n\n// Map of commands whose successful response should be a simple string (+OK)\n// rather than a bulk string ($2\\r\\nOK\\r\\n).\nconst SIMPLE_STRING_COMMANDS = new Set([\n 'SET', 'MSET', 'FLUSHDB', 'FLUSHALL', 'SELECT', 'PSETEX', 'SETEX',\n 'RENAME', 'RESTORE', 'HMSET', 'LSET', 'LTRIM', 'DISCARD', 'MULTI',\n 'EXEC', 'WATCH', 'UNWATCH', 'AUTH', 'QUIT', 'OK',\n]);\n\n/** Encode a command result, using +OK for commands that return simple strings. */\nfunction encodeCommandResult(cmd: string, value: unknown): string {\n if (value === 'OK' && SIMPLE_STRING_COMMANDS.has(cmd.toUpperCase())) {\n return '+OK\\r\\n';\n }\n // PING returns simple string\n if (cmd.toUpperCase() === 'PING' && value === 'PONG') {\n return '+PONG\\r\\n';\n }\n return encodeResp(value);\n}\n\n// ---------------------------------------------------------------------------\n// RedisMock — public API\n// ---------------------------------------------------------------------------\n\nexport interface RedisMockOpts {\n /** Initial data to pre-populate (optional). */\n data?: Record<string, unknown>;\n}\n\nexport class RedisMock {\n private _redis: InstanceType<typeof Redis>;\n /** Queued seedData pairs applied before first command. */\n private _seedPairs: Array<[string, string]> = [];\n private _seeded = false;\n\n constructor(opts?: RedisMockOpts) {\n this._redis = new Redis({ data: opts?.data ?? {} });\n }\n\n /**\n * Seed a key-value pair into the store.\n */\n seedData(key: string, value: string): void {\n this._seedPairs.push([key, value]);\n }\n\n /**\n * Flush the database.\n * Called during teardown for clean isolation.\n */\n async flush(): Promise<void> {\n await this._redis.flushdb();\n this._seeded = false;\n }\n\n // ── TCP handler ─────────────────────────────────────────────────────────\n\n createHandler(): TcpMockHandler {\n return async (data: Buffer, _ctx: TcpMockContext): Promise<TcpHandlerResult> => {\n // Apply seed data on first interaction\n if (!this._seeded) {\n this._seeded = true;\n for (const [k, v] of this._seedPairs) {\n await this._redis.set(k, v);\n }\n }\n\n const commands = parseAllCommands(data);\n if (commands.length === 0) return null;\n\n const responseParts: string[] = [];\n\n for (const [cmd, ...args] of commands) {\n const upper = cmd.toUpperCase();\n const lower = cmd.toLowerCase();\n try {\n const fn = (this._redis as any)[lower];\n if (typeof fn !== 'function') {\n responseParts.push(`-ERR unknown command '${upper}'\\r\\n`);\n continue;\n }\n const result = await fn.apply(this._redis, args);\n responseParts.push(encodeCommandResult(upper, result));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n responseParts.push(`-ERR ${msg}\\r\\n`);\n }\n }\n\n return Buffer.from(responseParts.join(''));\n };\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@crashlab/redis-mock",
3
+ "version": "0.1.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup && tsc --build tsconfig.json --force",
21
+ "clean": "rimraf dist",
22
+ "prepack": "npm run build",
23
+ "build:pkg": "tsup && tsc --build tsconfig.json --force"
24
+ },
25
+ "dependencies": {
26
+ "ioredis-mock": "^8.13.1"
27
+ }
28
+ }