js-valkey-server 0.3.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/LICENSE +21 -0
- package/README.md +558 -0
- package/dist/cli.js +17420 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.mts +651 -0
- package/dist/core.d.ts +651 -0
- package/dist/core.js +17929 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +17691 -0
- package/dist/core.mjs.map +1 -0
- package/dist/index.d.mts +502 -0
- package/dist/index.d.ts +502 -0
- package/dist/index.js +18845 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +18749 -0
- package/dist/index.mjs.map +1 -0
- package/dist/redis-error-C96iHLJ-.d.mts +1392 -0
- package/dist/redis-error-C96iHLJ-.d.ts +1392 -0
- package/package.json +119 -0
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
import { R as RedisCluster, a as RedisServerState, C as CompatibilitySpec, L as Logger, b as RedisClusterNodeHandle, c as Resp2Server, d as CommandExecutor, e as RedisValue } from './redis-error-C96iHLJ-.mjs';
|
|
2
|
+
export { f as CompatibilityProfile, g as CountGreaterThanZeroError, D as DiscardWithoutMultiError, E as ExecWithoutMultiError, h as ExpectedFloatError, i as ExpectedIntegerError, F as FeatureId, H as HashValueNotFloatError, j as HashValueNotIntegerError, I as IndexOutOfRangeError, k as InvalidExpireTimeError, l as LimitCantBeNegativeError, M as MinMaxNotFloatError, N as NoAuthError, m as NoPasswordConfiguredError, n as NoScriptError, o as NoSuchKeyError, p as NumKeysGreaterThanZeroError, O as OffsetOutOfRangeError, P as PositiveCountError, q as RedisClusterDownError, r as RedisClusterOptions, s as RedisCommandError, t as RedisCrossSlotError, u as RedisFlavor, v as RedisMovedError, w as RedisSyntaxError, x as ResultingScoreNaNError, S as ScriptCallNoCommandError, y as ScriptDebugModeError, z as ScriptFlushOptionError, A as ScriptUnknownCommandError, B as StreamElementTooLargeError, G as StreamIdExhaustedError, T as TransactionDiscardedError, U as UnknownClusterSubcommandError, J as UnknownRedisCommandError, K as UnknownScriptSubcommandError, V as VersionGate, W as WatchInsideMultiError, Q as WrongNumberOfArgumentsError, X as WrongNumberOfKeysError, Y as WrongPassError, Z as WrongTypeRedisError, _ as ZaddGtLtNxConflictError, $ as ZaddIncrPairError, a0 as ZaddNxXxConflictError, a1 as buildRedisCluster, a2 as computeSlotRange, a3 as createRedisCluster, a4 as gateSatisfied, a5 as resolveCompatibilityProfile } from './redis-error-C96iHLJ-.mjs';
|
|
3
|
+
import { Redis, Cluster } from 'ioredis';
|
|
4
|
+
import 'net';
|
|
5
|
+
import 'lua-redis-wasm';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Public seeding contract for {@link RedisMock}. Callers describe data with
|
|
9
|
+
* plain keys/types/values (plus optional `ttlMs` and `db`); the mock owns the
|
|
10
|
+
* conversion into the internal {@link RedisDataValue} representation and the
|
|
11
|
+
* placement onto the right database (and, in cluster mode, the right node).
|
|
12
|
+
*
|
|
13
|
+
* Streams are intentionally not seedable yet — their public entry shape (entry
|
|
14
|
+
* ids, fields, consumer groups) is not finalized, and exposing the internal
|
|
15
|
+
* stream state would leak implementation details.
|
|
16
|
+
*/
|
|
17
|
+
type SeedEntry = {
|
|
18
|
+
key: string;
|
|
19
|
+
type: 'string';
|
|
20
|
+
value: string | number;
|
|
21
|
+
ttlMs?: number;
|
|
22
|
+
db?: number;
|
|
23
|
+
} | {
|
|
24
|
+
key: string;
|
|
25
|
+
type: 'hash';
|
|
26
|
+
value: Record<string, string | number>;
|
|
27
|
+
ttlMs?: number;
|
|
28
|
+
db?: number;
|
|
29
|
+
} | {
|
|
30
|
+
key: string;
|
|
31
|
+
type: 'list';
|
|
32
|
+
value: (string | number)[];
|
|
33
|
+
ttlMs?: number;
|
|
34
|
+
db?: number;
|
|
35
|
+
} | {
|
|
36
|
+
key: string;
|
|
37
|
+
type: 'set';
|
|
38
|
+
value: (string | number)[];
|
|
39
|
+
ttlMs?: number;
|
|
40
|
+
db?: number;
|
|
41
|
+
} | {
|
|
42
|
+
key: string;
|
|
43
|
+
type: 'zset';
|
|
44
|
+
value: Record<string, number>;
|
|
45
|
+
ttlMs?: number;
|
|
46
|
+
db?: number;
|
|
47
|
+
};
|
|
48
|
+
declare function seedStandalone(state: RedisServerState, entries: readonly SeedEntry[]): Promise<void>;
|
|
49
|
+
/**
|
|
50
|
+
* The minimal cluster shape `seedCluster` needs: a topology to slot keys and the
|
|
51
|
+
* per-node `RedisServerState` to write into. Both the socket-backed
|
|
52
|
+
* {@link RedisCluster} (`.nodes[].server`) and the TCP-free `buildClusterNodes`
|
|
53
|
+
* product satisfy this structurally, so the in-memory ioredis mock can reuse the
|
|
54
|
+
* same seeder.
|
|
55
|
+
*/
|
|
56
|
+
type SeedClusterTarget = {
|
|
57
|
+
topology: RedisCluster['topology'];
|
|
58
|
+
nodes: readonly {
|
|
59
|
+
id: string;
|
|
60
|
+
server: RedisServerState;
|
|
61
|
+
}[];
|
|
62
|
+
};
|
|
63
|
+
declare function seedCluster(cluster: SeedClusterTarget, entries: readonly SeedEntry[]): Promise<void>;
|
|
64
|
+
|
|
65
|
+
type RedisAddress = {
|
|
66
|
+
host: string;
|
|
67
|
+
port: number;
|
|
68
|
+
};
|
|
69
|
+
type CreateRedisServerOptions = {
|
|
70
|
+
/** Port to bind. Defaults to `0` (OS-assigned free port). */
|
|
71
|
+
port?: number;
|
|
72
|
+
/** Logical database count. Defaults to 16, matching real Redis. */
|
|
73
|
+
databaseCount?: number;
|
|
74
|
+
compatibility?: CompatibilitySpec;
|
|
75
|
+
logger?: Pick<Logger, 'error'>;
|
|
76
|
+
};
|
|
77
|
+
type CreateRedisServerClusterOptions = {
|
|
78
|
+
/** Build a (listening) cluster instead of a standalone server. */
|
|
79
|
+
cluster: RedisMockClusterOptions;
|
|
80
|
+
/** Cluster base port. Defaults to `0` (each node OS-assigned). */
|
|
81
|
+
basePort?: number;
|
|
82
|
+
/** Databases per cluster node. Defaults to 1. */
|
|
83
|
+
databasesPerNode?: number;
|
|
84
|
+
compatibility?: CompatibilitySpec;
|
|
85
|
+
logger?: Pick<Logger, 'error'>;
|
|
86
|
+
};
|
|
87
|
+
type RedisServerHandle = {
|
|
88
|
+
host: string;
|
|
89
|
+
port: number;
|
|
90
|
+
state: RedisServerState;
|
|
91
|
+
server: Resp2Server;
|
|
92
|
+
close(): Promise<void>;
|
|
93
|
+
};
|
|
94
|
+
/**
|
|
95
|
+
* Runs a real, **listening** Redis server you connect a normal client library
|
|
96
|
+
* to. Standalone by default (one {@link RedisServerState} + executor +
|
|
97
|
+
* {@link Resp2Server}, 16 databases); pass `cluster` to build and start a whole
|
|
98
|
+
* cluster instead — the returned {@link RedisCluster} is already listening.
|
|
99
|
+
*
|
|
100
|
+
* For tests prefer {@link createRedisMock}, which wraps this and adds seeding,
|
|
101
|
+
* reset-between-tests, and an in-process socketless client. Drop to
|
|
102
|
+
* `js-redis-server/core` only when you need to assemble the pipeline by hand.
|
|
103
|
+
*
|
|
104
|
+
* @see {@link createRedisMock} — the test-mock facade (use this for test suites)
|
|
105
|
+
*/
|
|
106
|
+
declare function createRedisServer(options?: CreateRedisServerOptions): Promise<RedisServerHandle>;
|
|
107
|
+
declare function createRedisServer(options: CreateRedisServerClusterOptions): Promise<RedisCluster>;
|
|
108
|
+
type RedisMockClusterOptions = {
|
|
109
|
+
masters: number;
|
|
110
|
+
replicas?: number;
|
|
111
|
+
};
|
|
112
|
+
type CreateRedisMockOptions = {
|
|
113
|
+
/** When set, builds a cluster mock instead of a standalone one. */
|
|
114
|
+
cluster?: RedisMockClusterOptions;
|
|
115
|
+
/** Standalone-only: logical database count (defaults to 16). */
|
|
116
|
+
databaseCount?: number;
|
|
117
|
+
/** Standalone bind port. Defaults to `0` (OS-assigned). */
|
|
118
|
+
port?: number;
|
|
119
|
+
/** Cluster base port. Defaults to `0` (each node OS-assigned). */
|
|
120
|
+
basePort?: number;
|
|
121
|
+
compatibility?: CompatibilitySpec;
|
|
122
|
+
logger?: Pick<Logger, 'error'>;
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Friendly test-mock facade over a standalone server or a cluster. Exposes
|
|
126
|
+
* connection helpers, seeding, reset-between-tests, and escape hatches to the
|
|
127
|
+
* underlying state/nodes for power users.
|
|
128
|
+
*
|
|
129
|
+
* Need a socketless in-process client (no TCP, no RESP)? Use the standalone
|
|
130
|
+
* {@link createInMemoryClient} builder instead.
|
|
131
|
+
*/
|
|
132
|
+
interface RedisMock {
|
|
133
|
+
readonly host: string;
|
|
134
|
+
readonly port: number;
|
|
135
|
+
readonly url: string;
|
|
136
|
+
/**
|
|
137
|
+
* Node addresses as `{ host, port }[]` — a single entry for standalone mocks,
|
|
138
|
+
* every node for cluster mocks. Client-agnostic: index `[0]` for a single
|
|
139
|
+
* client, or pass the whole array to a cluster client.
|
|
140
|
+
*
|
|
141
|
+
* @example new Redis(mock.addresses()[0]) // ioredis, standalone
|
|
142
|
+
* @example new Redis.Cluster(mock.addresses()) // ioredis, cluster
|
|
143
|
+
*/
|
|
144
|
+
addresses(): RedisAddress[];
|
|
145
|
+
seed(entries: readonly SeedEntry[]): Promise<void>;
|
|
146
|
+
flush(): Promise<void>;
|
|
147
|
+
/** Alias for {@link RedisMock.flush}. */
|
|
148
|
+
reset(): Promise<void>;
|
|
149
|
+
close(): Promise<void>;
|
|
150
|
+
/** Escape hatch: the underlying state (standalone mocks only). */
|
|
151
|
+
readonly state?: RedisServerState;
|
|
152
|
+
/** Escape hatch: the underlying node handles (cluster mocks only). */
|
|
153
|
+
readonly nodes?: readonly RedisClusterNodeHandle[];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Creates a {@link RedisMock} — the entry point for test suites. Standalone by
|
|
157
|
+
* default; pass `{ cluster: { masters } }` for a cluster mock. Adds seeding and
|
|
158
|
+
* reset-between-tests on top of a real server/cluster.
|
|
159
|
+
*
|
|
160
|
+
* To run a long-running server a separate process connects to (a CLI, a dev
|
|
161
|
+
* tool) rather than drive it from test code, use {@link createRedisServer}
|
|
162
|
+
* instead (it takes the same `cluster` option).
|
|
163
|
+
*
|
|
164
|
+
* @see {@link createRedisServer} — run a real server/cluster without test helpers
|
|
165
|
+
*/
|
|
166
|
+
declare function createRedisMock(options?: CreateRedisMockOptions): Promise<RedisMock>;
|
|
167
|
+
/** Alternative name for the same factory; compatibility defaults are unchanged. */
|
|
168
|
+
declare const createValkeyMock: typeof createRedisMock;
|
|
169
|
+
|
|
170
|
+
type CreateIoredisMockOptions = {
|
|
171
|
+
cluster?: false;
|
|
172
|
+
databaseCount?: number;
|
|
173
|
+
seed?: readonly SeedEntry[];
|
|
174
|
+
} | {
|
|
175
|
+
cluster: {
|
|
176
|
+
masters: number;
|
|
177
|
+
replicasPerMaster?: number;
|
|
178
|
+
};
|
|
179
|
+
seed?: readonly SeedEntry[];
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* A drop-in, socketless `ioredis` client (standalone or `Cluster`) backed by the
|
|
183
|
+
* in-memory server pipeline. The **real** ioredis client drives a fake
|
|
184
|
+
* `net.Socket` ({@link createVirtualConnection}) over real RESP, so reply
|
|
185
|
+
* shaping, pipelines, `multi`, pub/sub, and `scanStream` all work for free — no
|
|
186
|
+
* TCP socket and no port bind.
|
|
187
|
+
*
|
|
188
|
+
* `ioredis` is an optional peer dependency, imported lazily, so the core stays
|
|
189
|
+
* dependency-free.
|
|
190
|
+
*/
|
|
191
|
+
declare function createIoredisMock(options?: CreateIoredisMockOptions): Promise<Redis | Cluster>;
|
|
192
|
+
|
|
193
|
+
/** A command argument the in-memory client accepts. */
|
|
194
|
+
type RedisCommandArgument = string | number | Buffer;
|
|
195
|
+
/** Native JS value an in-memory reply decodes to. */
|
|
196
|
+
type RedisNativeReply = string | number | bigint | boolean | Buffer | null | RedisNativeReply[] | {
|
|
197
|
+
[key: string]: RedisNativeReply;
|
|
198
|
+
};
|
|
199
|
+
type InMemoryRedisClientOptions = {
|
|
200
|
+
server: RedisServerState;
|
|
201
|
+
executor: CommandExecutor;
|
|
202
|
+
/** Initial selected database (default 0). */
|
|
203
|
+
database?: number;
|
|
204
|
+
/** Return `Buffer`s for bulk-string/verbatim replies instead of utf8 strings. */
|
|
205
|
+
returnBuffers?: boolean;
|
|
206
|
+
/** Called once when the client is closed — used to tear down owned state. */
|
|
207
|
+
onClose?: () => void;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* Socketless, high-level client that drives the **same** command pipeline as a
|
|
211
|
+
* networked client through an in-process {@link ClientSession} — bypassing both
|
|
212
|
+
* the TCP loopback and RESP encoding/decoding. Intended for tests that just need
|
|
213
|
+
* to issue commands and read native JS replies without pulling in a real client
|
|
214
|
+
* library.
|
|
215
|
+
*
|
|
216
|
+
* Streaming commands (SUBSCRIBE / PSUBSCRIBE / MONITOR) are not supported here;
|
|
217
|
+
* use a real client for those.
|
|
218
|
+
*/
|
|
219
|
+
declare class InMemoryRedisClient {
|
|
220
|
+
private readonly session;
|
|
221
|
+
private readonly returnBuffers;
|
|
222
|
+
private readonly onClose?;
|
|
223
|
+
private closed;
|
|
224
|
+
/** Aborted on close — tears down any active stream and push readers. */
|
|
225
|
+
private readonly lifetime;
|
|
226
|
+
/**
|
|
227
|
+
* Set once a streaming command (e.g. MONITOR) hands back a `ResponseStream`.
|
|
228
|
+
* `command()` consumes its first frame as the immediate reply; {@link pushes}
|
|
229
|
+
* drains the rest. Pub/sub doesn't set this — its messages flow through the
|
|
230
|
+
* session push channel instead (see {@link pushes}).
|
|
231
|
+
*/
|
|
232
|
+
private activeStream?;
|
|
233
|
+
private streamFrames?;
|
|
234
|
+
constructor(options: InMemoryRedisClientOptions);
|
|
235
|
+
/**
|
|
236
|
+
* Run a single command (e.g. `client.command('SET', 'k', 'v')`) and resolve
|
|
237
|
+
* to its native reply. Throws a {@link RedisCommandError} for `-ERR` replies,
|
|
238
|
+
* mirroring what a real client surfaces.
|
|
239
|
+
*
|
|
240
|
+
* Streaming commands (MONITOR / SUBSCRIBE / …) resolve to their *immediate*
|
|
241
|
+
* reply (MONITOR's `OK`, the subscribe confirmation); their server-initiated
|
|
242
|
+
* frames are delivered through {@link pushes}.
|
|
243
|
+
*/
|
|
244
|
+
command(name: string, ...args: RedisCommandArgument[]): Promise<RedisNativeReply>;
|
|
245
|
+
/** Alias for {@link InMemoryRedisClient.command}. */
|
|
246
|
+
send(name: string, ...args: RedisCommandArgument[]): Promise<RedisNativeReply>;
|
|
247
|
+
/**
|
|
248
|
+
* True once the last command put the connection into *push mode* — an active
|
|
249
|
+
* MONITOR stream, or a SUBSCRIBE/PSUBSCRIBE/SSUBSCRIBE that entered subscribed
|
|
250
|
+
* mode. Consumers should switch to draining {@link pushes} while this holds.
|
|
251
|
+
*/
|
|
252
|
+
get streaming(): boolean;
|
|
253
|
+
/**
|
|
254
|
+
* Server-initiated frames for a connection in *push mode* — pub/sub messages
|
|
255
|
+
* (via the session push channel) and the tail of a MONITOR stream — decoded to
|
|
256
|
+
* native replies. Iterate it after issuing SUBSCRIBE/PSUBSCRIBE/MONITOR. Ends
|
|
257
|
+
* when `signal` (or the connection) is closed.
|
|
258
|
+
*/
|
|
259
|
+
pushes(signal?: AbortSignal): AsyncIterable<RedisNativeReply>;
|
|
260
|
+
close(): void;
|
|
261
|
+
private decode;
|
|
262
|
+
}
|
|
263
|
+
type CreateInMemoryRedisOptions = {
|
|
264
|
+
/** Logical database count (defaults to 16, matching real Redis). */
|
|
265
|
+
databaseCount?: number;
|
|
266
|
+
/** Pre-populate the keyspace before any connection is opened. */
|
|
267
|
+
seed?: readonly SeedEntry[];
|
|
268
|
+
/** Redis/Valkey version to emulate (defaults to `redis-8.0`). */
|
|
269
|
+
compatibility?: CompatibilitySpec;
|
|
270
|
+
};
|
|
271
|
+
/** Per-connection options for {@link InMemoryRedis.connect}. */
|
|
272
|
+
type ConnectOptions = {
|
|
273
|
+
/** Initial selected database (default 0). */
|
|
274
|
+
database?: number;
|
|
275
|
+
/** Return `Buffer`s for bulk-string/verbatim replies instead of utf8 strings. */
|
|
276
|
+
returnBuffers?: boolean;
|
|
277
|
+
};
|
|
278
|
+
/**
|
|
279
|
+
* A socketless in-memory Redis *instance* — one shared keyspace + command
|
|
280
|
+
* pipeline that many {@link InMemoryRedisClient} connections can drive at once.
|
|
281
|
+
* Because connections share the underlying {@link RedisServerState}, a `MONITOR`
|
|
282
|
+
* / `SUBSCRIBE` on one connection observes commands run on another, and a
|
|
283
|
+
* `BLPOP` blocks until another connection writes the key — exactly like real
|
|
284
|
+
* Redis. `close()` tears the whole instance down.
|
|
285
|
+
*/
|
|
286
|
+
declare class InMemoryRedis {
|
|
287
|
+
private readonly state;
|
|
288
|
+
private readonly executor;
|
|
289
|
+
constructor(state: RedisServerState, executor: CommandExecutor);
|
|
290
|
+
/** Open a new connection (its own {@link ClientSession}) over this keyspace. */
|
|
291
|
+
connect(options?: ConnectOptions): InMemoryRedisClient;
|
|
292
|
+
close(): void;
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* Build a socketless {@link InMemoryRedis} instance with its own keyspace +
|
|
296
|
+
* command pipeline. Open one or more connections with {@link InMemoryRedis.connect}.
|
|
297
|
+
*/
|
|
298
|
+
declare function createInMemoryRedis(options?: CreateInMemoryRedisOptions): Promise<InMemoryRedis>;
|
|
299
|
+
type CreateInMemoryClientOptions = CreateInMemoryRedisOptions & ConnectOptions;
|
|
300
|
+
/**
|
|
301
|
+
* Convenience wrapper: an {@link InMemoryRedis} instance with a single owned
|
|
302
|
+
* connection. `client.close()` tears the whole instance down. For multiple
|
|
303
|
+
* connections over one keyspace (pub/sub, MONITOR, cross-connection blocking),
|
|
304
|
+
* use {@link createInMemoryRedis} and call `connect()` per connection.
|
|
305
|
+
*/
|
|
306
|
+
declare function createInMemoryClient(options?: CreateInMemoryClientOptions): Promise<InMemoryRedisClient>;
|
|
307
|
+
|
|
308
|
+
/** A command argument node-redis accepts on the wire. */
|
|
309
|
+
type NodeRedisCommandArgument = string | Buffer;
|
|
310
|
+
/** Native JS value a reply decodes to (mirrors node-redis RESP2 defaults). */
|
|
311
|
+
type NodeRedisReply = string | number | bigint | boolean | Buffer | null | NodeRedisReply[] | {
|
|
312
|
+
[key: string]: NodeRedisReply;
|
|
313
|
+
};
|
|
314
|
+
type NodeRedisMockClusterOptions = {
|
|
315
|
+
masters: number;
|
|
316
|
+
replicas?: number;
|
|
317
|
+
};
|
|
318
|
+
type CreateNodeRedisMockOptions = {
|
|
319
|
+
cluster?: undefined;
|
|
320
|
+
databaseCount?: number;
|
|
321
|
+
} | {
|
|
322
|
+
cluster: NodeRedisMockClusterOptions;
|
|
323
|
+
};
|
|
324
|
+
/** A single `{ score, value }` member for ZADD-style methods. */
|
|
325
|
+
type NodeRedisZMember = {
|
|
326
|
+
score: number;
|
|
327
|
+
value: string;
|
|
328
|
+
};
|
|
329
|
+
/** Listener invoked for each delivered pub/sub message: `(message, channel)`. */
|
|
330
|
+
type NodeRedisPubSubListener = (message: string, channel: string) => void;
|
|
331
|
+
/**
|
|
332
|
+
* Creates an in-memory node-redis-shaped client (standalone) or cluster client.
|
|
333
|
+
* Pass `{ cluster: { masters } }` for a {@link NodeRedisMockCluster}; otherwise
|
|
334
|
+
* a {@link NodeRedisMockClient} backed by a single in-memory pipeline.
|
|
335
|
+
*/
|
|
336
|
+
declare function createNodeRedisMock(options?: CreateNodeRedisMockOptions): Promise<NodeRedisMockClient | NodeRedisMockCluster>;
|
|
337
|
+
type FacadeBackend = {
|
|
338
|
+
state: RedisServerState;
|
|
339
|
+
executor: CommandExecutor;
|
|
340
|
+
};
|
|
341
|
+
/**
|
|
342
|
+
* Shared command-method surface implemented by both the standalone client and
|
|
343
|
+
* the cluster client. Standalone runs every command on its own session; the
|
|
344
|
+
* cluster routes by slot to the owning node's session — but the curated method
|
|
345
|
+
* bodies are identical, so they live in this base and dispatch through the
|
|
346
|
+
* abstract {@link CommandRunner.run}.
|
|
347
|
+
*/
|
|
348
|
+
declare abstract class CommandRunner {
|
|
349
|
+
/**
|
|
350
|
+
* Execute one already-tokenised command and return its decoded reply.
|
|
351
|
+
* Implementations pick the session (standalone: the only one; cluster: the
|
|
352
|
+
* slot owner for the command's keys).
|
|
353
|
+
*/
|
|
354
|
+
protected abstract run(args: NodeRedisCommandArgument[]): Promise<RedisValue>;
|
|
355
|
+
/** Generic escape hatch for any command, decoded to a native JS reply. */
|
|
356
|
+
sendCommand(args: NodeRedisCommandArgument[]): Promise<NodeRedisReply>;
|
|
357
|
+
get(key: string): Promise<string | null>;
|
|
358
|
+
set(key: string, value: string | number, ...rest: NodeRedisCommandArgument[]): Promise<string | null>;
|
|
359
|
+
del(...keys: string[]): Promise<number>;
|
|
360
|
+
exists(...keys: string[]): Promise<number>;
|
|
361
|
+
incr(key: string): Promise<number>;
|
|
362
|
+
expire(key: string, seconds: number): Promise<number>;
|
|
363
|
+
ttl(key: string): Promise<number>;
|
|
364
|
+
hSet(key: string, field: string, value: string): Promise<number>;
|
|
365
|
+
hGet(key: string, field: string): Promise<string | null>;
|
|
366
|
+
hGetAll(key: string): Promise<{
|
|
367
|
+
[field: string]: string;
|
|
368
|
+
}>;
|
|
369
|
+
lPush(key: string, ...values: string[]): Promise<number>;
|
|
370
|
+
rPush(key: string, ...values: string[]): Promise<number>;
|
|
371
|
+
lRange(key: string, start: number, stop: number): Promise<string[]>;
|
|
372
|
+
sAdd(key: string, ...members: string[]): Promise<number>;
|
|
373
|
+
sMembers(key: string): Promise<string[]>;
|
|
374
|
+
zAdd(key: string, members: NodeRedisZMember | NodeRedisZMember[]): Promise<number>;
|
|
375
|
+
zRange(key: string, start: number, stop: number): Promise<string[]>;
|
|
376
|
+
}
|
|
377
|
+
type NodeRedisMockClientInit = FacadeBackend & {
|
|
378
|
+
database?: number;
|
|
379
|
+
/** True only for the client that created the state — it owns its teardown. */
|
|
380
|
+
ownsState?: boolean;
|
|
381
|
+
};
|
|
382
|
+
/**
|
|
383
|
+
* Standalone in-memory node-redis facade. Drives one {@link ClientSession}
|
|
384
|
+
* against a single keyspace; pub/sub uses a *dedicated* session that drains
|
|
385
|
+
* pushes off {@link ClientSession.readPushes}, mirroring node-redis' rule that a
|
|
386
|
+
* subscribed connection is reserved for pub/sub.
|
|
387
|
+
*/
|
|
388
|
+
declare class NodeRedisMockClient extends CommandRunner {
|
|
389
|
+
private readonly emitter;
|
|
390
|
+
private readonly backend;
|
|
391
|
+
private readonly database?;
|
|
392
|
+
private readonly ownsState;
|
|
393
|
+
private readonly session;
|
|
394
|
+
/**
|
|
395
|
+
* Serializes commands on this client the way a real single node-redis
|
|
396
|
+
* connection does, so a concurrent (un-awaited) call cannot interleave between
|
|
397
|
+
* the MULTI and EXEC of a transaction sharing {@link session}.
|
|
398
|
+
*/
|
|
399
|
+
private commandLock;
|
|
400
|
+
/** Dedicated session + push-reader loop, created lazily on first subscribe. */
|
|
401
|
+
private pubsub?;
|
|
402
|
+
private closed;
|
|
403
|
+
constructor(init: NodeRedisMockClientInit);
|
|
404
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
405
|
+
once(event: string, listener: (...args: unknown[]) => void): this;
|
|
406
|
+
off(event: string, listener: (...args: unknown[]) => void): this;
|
|
407
|
+
/** node-redis clients require an explicit connect(); here it is a no-op. */
|
|
408
|
+
connect(): Promise<this>;
|
|
409
|
+
/** A fresh, independent client over the **same** shared keyspace. */
|
|
410
|
+
duplicate(): Promise<NodeRedisMockClient>;
|
|
411
|
+
protected run(args: NodeRedisCommandArgument[]): Promise<RedisValue>;
|
|
412
|
+
/** Run `fn` after any in-flight command/transaction on this client settles. */
|
|
413
|
+
private runExclusive;
|
|
414
|
+
publish(channel: string, message: string): Promise<number>;
|
|
415
|
+
subscribe(channel: string, listener: NodeRedisPubSubListener): Promise<void>;
|
|
416
|
+
pSubscribe(pattern: string, listener: NodeRedisPubSubListener): Promise<void>;
|
|
417
|
+
unsubscribe(channel?: string): Promise<void>;
|
|
418
|
+
pUnsubscribe(pattern?: string): Promise<void>;
|
|
419
|
+
watch(...keys: string[]): Promise<string>;
|
|
420
|
+
unwatch(): Promise<string>;
|
|
421
|
+
/** Begin a MULTI transaction. Commands are queued, then replayed on exec(). */
|
|
422
|
+
multi(): NodeRedisMockMulti;
|
|
423
|
+
/** Replay MULTI → queued commands → EXEC on the shared session as one span. */
|
|
424
|
+
private runTransactionSpan;
|
|
425
|
+
/** Gracefully close: tear down pub/sub + the command session. */
|
|
426
|
+
quit(): Promise<string>;
|
|
427
|
+
/** Hard close (node-redis `disconnect()`). Same teardown as quit(). */
|
|
428
|
+
disconnect(): Promise<void>;
|
|
429
|
+
/**
|
|
430
|
+
* node-redis exposes a synchronous destroy(); abort everything immediately.
|
|
431
|
+
* The push-drain loop settles on the next tick via the abort signal.
|
|
432
|
+
*/
|
|
433
|
+
destroy(): void;
|
|
434
|
+
private teardown;
|
|
435
|
+
private ensurePubSub;
|
|
436
|
+
private drainPushes;
|
|
437
|
+
private dispatchPush;
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* MULTI builder mirroring node-redis' chainable transaction API. Queues curated
|
|
441
|
+
* commands and replays them with a real MULTI/EXEC on the owning session, so the
|
|
442
|
+
* server's transaction + WATCH semantics drive the result (EXEC returns an array
|
|
443
|
+
* of replies, or `null` when a watched key changed).
|
|
444
|
+
*/
|
|
445
|
+
declare class NodeRedisMockMulti {
|
|
446
|
+
private readonly runTransaction;
|
|
447
|
+
private readonly queued;
|
|
448
|
+
private settled;
|
|
449
|
+
constructor(runTransaction: (queued: NodeRedisCommandArgument[][]) => Promise<RedisValue>);
|
|
450
|
+
set(key: string, value: string | number): this;
|
|
451
|
+
get(key: string): this;
|
|
452
|
+
del(...keys: string[]): this;
|
|
453
|
+
incr(key: string): this;
|
|
454
|
+
hSet(key: string, field: string, value: string): this;
|
|
455
|
+
hGet(key: string, field: string): this;
|
|
456
|
+
/** Generic escape hatch: queue any raw command. */
|
|
457
|
+
addCommand(args: NodeRedisCommandArgument[]): this;
|
|
458
|
+
private queue;
|
|
459
|
+
/**
|
|
460
|
+
* Replay the queued commands inside a real MULTI/EXEC and return the array of
|
|
461
|
+
* decoded replies. Matching node-redis, a watch-aborted transaction throws a
|
|
462
|
+
* `WatchError` (never returns null), and per-command errors are aggregated
|
|
463
|
+
* into a single `MultiErrorReply` carrying every reply + the error indexes.
|
|
464
|
+
*/
|
|
465
|
+
exec(): Promise<NodeRedisReply[]>;
|
|
466
|
+
/** Cancel the transaction without running the queued commands. */
|
|
467
|
+
discard(): Promise<void>;
|
|
468
|
+
private assertOpen;
|
|
469
|
+
}
|
|
470
|
+
/**
|
|
471
|
+
* In-memory node-redis cluster facade. Reuses {@link buildClusterNodes} for a
|
|
472
|
+
* TCP-free cluster, then routes each command to the slot owner's session
|
|
473
|
+
* (computed via {@link RedisClusterTopology.calculateSlotForKeys}). The curated
|
|
474
|
+
* method surface is inherited unchanged from {@link CommandRunner}.
|
|
475
|
+
*/
|
|
476
|
+
declare class NodeRedisMockCluster extends CommandRunner {
|
|
477
|
+
private readonly emitter;
|
|
478
|
+
private readonly topology;
|
|
479
|
+
private readonly masters;
|
|
480
|
+
private readonly sessions;
|
|
481
|
+
private readonly replicationLinks;
|
|
482
|
+
private closed;
|
|
483
|
+
private constructor();
|
|
484
|
+
static create(options: NodeRedisMockClusterOptions): NodeRedisMockCluster;
|
|
485
|
+
on(event: string, listener: (...args: unknown[]) => void): this;
|
|
486
|
+
once(event: string, listener: (...args: unknown[]) => void): this;
|
|
487
|
+
off(event: string, listener: (...args: unknown[]) => void): this;
|
|
488
|
+
connect(): Promise<this>;
|
|
489
|
+
protected run(args: NodeRedisCommandArgument[]): Promise<RedisValue>;
|
|
490
|
+
quit(): Promise<string>;
|
|
491
|
+
disconnect(): Promise<void>;
|
|
492
|
+
destroy(): void;
|
|
493
|
+
private teardown;
|
|
494
|
+
/**
|
|
495
|
+
* Resolve (and cache) a session on the master that owns the slot for the
|
|
496
|
+
* command's keys. Keyless commands run on the first master.
|
|
497
|
+
*/
|
|
498
|
+
private sessionForCommand;
|
|
499
|
+
private sessionFor;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
export { CompatibilitySpec, type ConnectOptions, type CreateInMemoryClientOptions, type CreateInMemoryRedisOptions, type CreateIoredisMockOptions, type CreateNodeRedisMockOptions, type CreateRedisMockOptions, type CreateRedisServerClusterOptions, type CreateRedisServerOptions, InMemoryRedis, InMemoryRedisClient, type InMemoryRedisClientOptions, Logger, type NodeRedisCommandArgument, NodeRedisMockClient, NodeRedisMockCluster, type NodeRedisMockClusterOptions, NodeRedisMockMulti, type NodeRedisPubSubListener, type NodeRedisReply, type NodeRedisZMember, type RedisAddress, RedisCluster, RedisClusterNodeHandle, type RedisCommandArgument, type RedisMock, type RedisMockClusterOptions, type RedisNativeReply, type RedisServerHandle, type SeedEntry, createInMemoryClient, createInMemoryRedis, createIoredisMock, createNodeRedisMock, createRedisMock, createRedisServer, createValkeyMock, seedCluster, seedStandalone };
|