@crvouga/mockingbird-service-redis 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.
@@ -0,0 +1,4767 @@
1
+ // src/clock.ts
2
+ function manualClock(start = 0) {
3
+ let current = start;
4
+ const listeners = /* @__PURE__ */ new Set();
5
+ return {
6
+ manual: true,
7
+ now: () => current,
8
+ advance(ms) {
9
+ current += ms;
10
+ for (const listener of [...listeners]) listener();
11
+ },
12
+ subscribe(listener) {
13
+ listeners.add(listener);
14
+ return () => listeners.delete(listener);
15
+ }
16
+ };
17
+ }
18
+ function wallClock() {
19
+ let offset = 0;
20
+ const listeners = /* @__PURE__ */ new Set();
21
+ return {
22
+ manual: false,
23
+ now: () => Date.now() + offset,
24
+ advance(ms) {
25
+ offset += ms;
26
+ for (const listener of [...listeners]) listener();
27
+ },
28
+ subscribe(listener) {
29
+ listeners.add(listener);
30
+ return () => listeners.delete(listener);
31
+ }
32
+ };
33
+ }
34
+
35
+ // src/bytes.ts
36
+ function latin1(bytes) {
37
+ let out = "";
38
+ for (let i = 0; i < bytes.length; i++) out += String.fromCharCode(bytes[i] ?? 0);
39
+ return out;
40
+ }
41
+ function fromLatin1(value) {
42
+ const out = new Uint8Array(value.length);
43
+ for (let i = 0; i < value.length; i++) out[i] = value.charCodeAt(i) & 255;
44
+ return out;
45
+ }
46
+ function utf8(value) {
47
+ return new TextEncoder().encode(value);
48
+ }
49
+ function utf8Text(bytes) {
50
+ return new TextDecoder("utf-8", { fatal: false }).decode(bytes);
51
+ }
52
+ function cmpBytes(a, b) {
53
+ const n = Math.min(a.length, b.length);
54
+ for (let i = 0; i < n; i++) {
55
+ const d = (a[i] ?? 0) - (b[i] ?? 0);
56
+ if (d !== 0) return d;
57
+ }
58
+ return a.length - b.length;
59
+ }
60
+ var INT64_MAX = 9223372036854775807n;
61
+ var INT64_MIN = -9223372036854775808n;
62
+ function parseI64(bytes) {
63
+ const text2 = latin1(bytes);
64
+ if (!/^-?\d+$/.test(text2)) return null;
65
+ try {
66
+ const value = BigInt(text2);
67
+ if (value > INT64_MAX || value < INT64_MIN) return null;
68
+ return value;
69
+ } catch {
70
+ return null;
71
+ }
72
+ }
73
+ function intReply(value) {
74
+ if (value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(Number.MIN_SAFE_INTEGER)) {
75
+ return Number(value);
76
+ }
77
+ return value;
78
+ }
79
+ function globMatch(pattern, value) {
80
+ const match = (p, v) => {
81
+ if (p === pattern.length) return v === value.length;
82
+ const ch = pattern[p];
83
+ if (ch === "\\") {
84
+ if (p + 1 >= pattern.length) return false;
85
+ if (v >= value.length || value[v] !== pattern[p + 1]) return false;
86
+ return match(p + 2, v + 1);
87
+ }
88
+ if (ch === "*") {
89
+ for (let i = v; i <= value.length; i++) if (match(p + 1, i)) return true;
90
+ return false;
91
+ }
92
+ if (ch === "?") {
93
+ if (v >= value.length) return false;
94
+ return match(p + 1, v + 1);
95
+ }
96
+ if (ch === "[") {
97
+ if (v >= value.length) return false;
98
+ let i = p + 1;
99
+ let negate = false;
100
+ if (pattern[i] === "^" || pattern[i] === "!") {
101
+ negate = true;
102
+ i++;
103
+ }
104
+ let ok2 = false;
105
+ if (pattern[i] === "]") return false;
106
+ while (i < pattern.length && pattern[i] !== "]") {
107
+ if (pattern[i] === "\\" && i + 1 < pattern.length) i++;
108
+ const start = pattern[i] ?? "";
109
+ if (pattern[i + 1] === "-" && pattern[i + 2] !== void 0 && pattern[i + 2] !== "]") {
110
+ const end = pattern[i + 2] ?? "";
111
+ const cur = value[v] ?? "";
112
+ if (cur >= start && cur <= end) ok2 = true;
113
+ i += 3;
114
+ } else {
115
+ if (value[v] === start) ok2 = true;
116
+ i++;
117
+ }
118
+ }
119
+ if (pattern[i] !== "]") return false;
120
+ if (ok2 === negate) return false;
121
+ return match(i + 1, v + 1);
122
+ }
123
+ if (v >= value.length || value[v] !== ch) return false;
124
+ return match(p + 1, v + 1);
125
+ };
126
+ return match(0, 0);
127
+ }
128
+
129
+ // src/protocol.ts
130
+ var ok = () => ({ t: "simple", v: "OK" });
131
+ var queued = () => ({ t: "simple", v: "QUEUED" });
132
+ var simple = (v) => ({ t: "simple", v });
133
+ var err = (v) => ({ t: "error", v });
134
+ var int = (v) => ({ t: "int", v });
135
+ var bulk = (v) => ({
136
+ t: "bulk",
137
+ v: v === null ? null : typeof v === "string" ? utf8(v) : v
138
+ });
139
+ var arr = (v) => ({ t: "array", v });
140
+ var nil = () => ({ t: "bulk", v: null });
141
+ var nilArray = () => ({ t: "array", v: null });
142
+ var push = (v) => ({ t: "push", v });
143
+ var map = (v) => ({ t: "map", v });
144
+ var wrongType = () => err("WRONGTYPE Operation against a key holding the wrong kind of value");
145
+ function arityError(name) {
146
+ return err(`ERR wrong number of arguments for '${name}' command`);
147
+ }
148
+ var syntaxError = () => err("ERR syntax error");
149
+ function unknownCommand(name, args) {
150
+ const shown = args.slice(0, 10).map((arg) => `'${latin1(arg)}'`).join(" ");
151
+ const extra = shown.length > 0 ? `${shown} ` : "";
152
+ return err(`ERR unknown command '${name}', with args beginning with: ${extra}`);
153
+ }
154
+ var notInteger = () => err("ERR value is not an integer or out of range");
155
+ var notFloat = () => err("ERR value is not a valid float");
156
+ var noScript = () => err("NOSCRIPT No matching script. Please use EVAL.");
157
+ var readonlyError = () => err("READONLY You can't write against a read only replica.");
158
+ var oomError = () => err("OOM command not allowed when used memory > 'maxmemory'.");
159
+ var busyError = () => err("BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE.");
160
+ var noAuth = () => err("NOAUTH Authentication required.");
161
+ var wrongPass = () => err("WRONGPASS invalid username-password pair or user is disabled.");
162
+ function authWithoutPassword(password) {
163
+ return err(
164
+ `ERR AUTH <${password}> called without any password configured for the default user. Are you sure your configuration is correct?`
165
+ );
166
+ }
167
+ var clusterDisabled = () => err("ERR This instance has cluster support disabled");
168
+ var noSuchKey = () => err("ERR no such key");
169
+ var execWithoutMulti = () => err("ERR EXEC without MULTI");
170
+ var discardWithoutMulti = () => err("ERR DISCARD without MULTI");
171
+ var nestedMulti = () => err("ERR MULTI calls can not be nested");
172
+ var execAbort = () => err("EXECABORT Transaction discarded because of previous errors.");
173
+ var scriptDenied = () => err("ERR This Redis command is not allowed from scripts");
174
+ var notBusy = () => err("NOTBUSY No scripts in execution right now.");
175
+ function pubsubOnly(name) {
176
+ return err(
177
+ `ERR Can't execute '${name}': only (P|S)SUBSCRIBE / (P|S)UNSUBSCRIBE / PING / QUIT / RESET are allowed in this context`
178
+ );
179
+ }
180
+ function noPerm(name) {
181
+ return err(`NOPERM this user has no permissions to run the '${name}' command`);
182
+ }
183
+ var CRLF = utf8("\r\n");
184
+ function concat(parts) {
185
+ let size = 0;
186
+ for (const part of parts) size += part.length;
187
+ const out = new Uint8Array(size);
188
+ let offset = 0;
189
+ for (const part of parts) {
190
+ out.set(part, offset);
191
+ offset += part.length;
192
+ }
193
+ return out;
194
+ }
195
+ function formatDouble(value) {
196
+ if (Number.isNaN(value)) return "nan";
197
+ if (value === Infinity) return "inf";
198
+ if (value === -Infinity) return "-inf";
199
+ if (Object.is(value, -0)) return "-0";
200
+ if (Number.isInteger(value)) return String(value);
201
+ return String(value);
202
+ }
203
+ function encodeReply(reply, protocol = 2) {
204
+ const parts = [];
205
+ const text2 = (value) => {
206
+ parts.push(utf8(value));
207
+ };
208
+ const walk = (value) => {
209
+ switch (value.t) {
210
+ case "simple":
211
+ text2(`+${value.v}\r
212
+ `);
213
+ return;
214
+ case "error":
215
+ text2(`-${value.v}\r
216
+ `);
217
+ return;
218
+ case "int":
219
+ text2(`:${value.v.toString()}\r
220
+ `);
221
+ return;
222
+ case "bulk":
223
+ if (value.v === null) {
224
+ text2(protocol === 3 ? "_\r\n" : "$-1\r\n");
225
+ return;
226
+ }
227
+ text2(`$${value.v.length}\r
228
+ `);
229
+ parts.push(value.v);
230
+ parts.push(CRLF);
231
+ return;
232
+ case "array":
233
+ if (value.v === null) {
234
+ text2(protocol === 3 ? "_\r\n" : "*-1\r\n");
235
+ return;
236
+ }
237
+ text2(`*${value.v.length}\r
238
+ `);
239
+ for (const item of value.v) walk(item);
240
+ return;
241
+ case "null":
242
+ text2(protocol === 3 ? "_\r\n" : "$-1\r\n");
243
+ return;
244
+ case "bool":
245
+ if (protocol === 3) text2(value.v ? "#t\r\n" : "#f\r\n");
246
+ else text2(value.v ? ":1\r\n" : ":0\r\n");
247
+ return;
248
+ case "double":
249
+ if (protocol === 3) text2(`,${formatDouble(value.v)}\r
250
+ `);
251
+ else walk(bulk(formatDouble(value.v)));
252
+ return;
253
+ case "map":
254
+ if (protocol === 3) {
255
+ text2(`%${value.v.length}\r
256
+ `);
257
+ for (const [key, item] of value.v) {
258
+ walk(key);
259
+ walk(item);
260
+ }
261
+ return;
262
+ }
263
+ text2(`*${value.v.length * 2}\r
264
+ `);
265
+ for (const [key, item] of value.v) {
266
+ walk(key);
267
+ walk(item);
268
+ }
269
+ return;
270
+ case "push":
271
+ text2(protocol === 3 ? `>${value.v.length}\r
272
+ ` : `*${value.v.length}\r
273
+ `);
274
+ for (const item of value.v) walk(item);
275
+ return;
276
+ default: {
277
+ const never = value;
278
+ throw new Error(`unknown reply ${never.t}`);
279
+ }
280
+ }
281
+ };
282
+ walk(reply);
283
+ return concat(parts);
284
+ }
285
+ var RespParser = class {
286
+ buf = new Uint8Array(0);
287
+ pos = 0;
288
+ push(chunk) {
289
+ if (this.pos > 0) this.buf = this.buf.slice(this.pos);
290
+ this.pos = 0;
291
+ if (chunk.length > 0) {
292
+ const next = new Uint8Array(this.buf.length + chunk.length);
293
+ next.set(this.buf);
294
+ next.set(chunk, this.buf.length);
295
+ this.buf = next;
296
+ }
297
+ const out = [];
298
+ while (this.pos < this.buf.length) {
299
+ const saved = this.pos;
300
+ const reply = this.tryOne();
301
+ if (reply === void 0) {
302
+ this.pos = saved;
303
+ break;
304
+ }
305
+ out.push(reply);
306
+ }
307
+ return out;
308
+ }
309
+ tryOne() {
310
+ if (this.pos >= this.buf.length) return void 0;
311
+ const marker = this.buf[this.pos] ?? 0;
312
+ if (marker !== 43 && marker !== 45 && marker !== 58 && marker !== 36 && marker !== 42 && marker !== 95 && marker !== 35 && marker !== 44 && marker !== 37 && marker !== 126 && marker !== 62 && marker !== 33) {
313
+ return this.inline();
314
+ }
315
+ this.pos++;
316
+ if (marker === 43) {
317
+ const line = this.readLine();
318
+ if (line === null) return void 0;
319
+ return simple(line);
320
+ }
321
+ if (marker === 45) {
322
+ const line = this.readLine();
323
+ if (line === null) return void 0;
324
+ return err(line);
325
+ }
326
+ if (marker === 58) {
327
+ const line = this.readLine();
328
+ if (line === null) return void 0;
329
+ if (!/^-?\d+$/.test(line)) return err("ERR protocol error: invalid integer");
330
+ const value = BigInt(line);
331
+ return int(
332
+ value <= BigInt(Number.MAX_SAFE_INTEGER) && value >= BigInt(Number.MIN_SAFE_INTEGER) ? Number(value) : value
333
+ );
334
+ }
335
+ if (marker === 36) return this.bulk();
336
+ if (marker === 42) return this.aggregate("array");
337
+ if (marker === 95) {
338
+ const line = this.readLine();
339
+ if (line === null) return void 0;
340
+ return { t: "null" };
341
+ }
342
+ if (marker === 35) {
343
+ const line = this.readLine();
344
+ if (line === null) return void 0;
345
+ return { t: "bool", v: line === "t" };
346
+ }
347
+ if (marker === 44) {
348
+ const line = this.readLine();
349
+ if (line === null) return void 0;
350
+ return { t: "double", v: Number(line) };
351
+ }
352
+ if (marker === 37) return this.map();
353
+ if (marker === 126) return this.aggregate("set");
354
+ if (marker === 62) return this.aggregate("push");
355
+ if (marker === 33) return this.bulk();
356
+ return void 0;
357
+ }
358
+ inline() {
359
+ const line = this.readLine();
360
+ if (line === null) return void 0;
361
+ if (line.length === 0) return arr([]);
362
+ const parts = [];
363
+ let current = "";
364
+ let quote = null;
365
+ for (let i = 0; i < line.length; i++) {
366
+ const ch = line[i];
367
+ if (quote) {
368
+ if (ch === quote) quote = null;
369
+ else current += ch ?? "";
370
+ continue;
371
+ }
372
+ if (ch === "'" || ch === '"') {
373
+ quote = ch;
374
+ continue;
375
+ }
376
+ if (ch === " ") {
377
+ if (current.length > 0) {
378
+ parts.push(current);
379
+ current = "";
380
+ }
381
+ continue;
382
+ }
383
+ current += ch ?? "";
384
+ }
385
+ if (current.length > 0) parts.push(current);
386
+ return arr(parts.map((part) => bulk(fromLatin1(part))));
387
+ }
388
+ readLine() {
389
+ for (let i = this.pos; i < this.buf.length; i++) {
390
+ if (this.buf[i] !== 10) continue;
391
+ let end = i;
392
+ if (end > this.pos && this.buf[end - 1] === 13) end--;
393
+ const line = latin1(this.buf.subarray(this.pos, end));
394
+ this.pos = i + 1;
395
+ return line;
396
+ }
397
+ return null;
398
+ }
399
+ bulk() {
400
+ const line = this.readLine();
401
+ if (line === null) return void 0;
402
+ const size = Number(line);
403
+ if (!Number.isInteger(size)) return err("ERR protocol error: invalid bulk length");
404
+ if (size < 0) return nil();
405
+ if (this.buf.length < this.pos + size + 2) return void 0;
406
+ const value = this.buf.slice(this.pos, this.pos + size);
407
+ this.pos += size + 2;
408
+ return { t: "bulk", v: value };
409
+ }
410
+ aggregate(kind) {
411
+ const line = this.readLine();
412
+ if (line === null) return void 0;
413
+ const count = Number(line);
414
+ if (!Number.isInteger(count)) return err("ERR protocol error: invalid multibulk length");
415
+ if (count < 0) return nilArray();
416
+ const items = [];
417
+ for (let i = 0; i < count; i++) {
418
+ const item = this.tryOne();
419
+ if (item === void 0) return void 0;
420
+ items.push(item);
421
+ }
422
+ if (kind === "push") return push(items);
423
+ return arr(items);
424
+ }
425
+ map() {
426
+ const line = this.readLine();
427
+ if (line === null) return void 0;
428
+ const count = Number(line);
429
+ if (!Number.isInteger(count) || count < 0) return err("ERR protocol error: invalid map length");
430
+ const pairs = [];
431
+ for (let i = 0; i < count; i++) {
432
+ const key = this.tryOne();
433
+ if (key === void 0) return void 0;
434
+ const value = this.tryOne();
435
+ if (value === void 0) return void 0;
436
+ pairs.push([key, value]);
437
+ }
438
+ return map(pairs);
439
+ }
440
+ };
441
+ function asCommand(reply) {
442
+ if (reply.t !== "array" || reply.v === null || reply.v.length === 0) {
443
+ return err("ERR protocol error: expected command array");
444
+ }
445
+ const name = reply.v[0];
446
+ if (name?.t !== "bulk" || name.v === null) return err("ERR protocol error: expected command name");
447
+ const args = [];
448
+ for (let i = 1; i < reply.v.length; i++) {
449
+ const arg = reply.v[i];
450
+ if (arg?.t !== "bulk" || arg.v === null)
451
+ return err("ERR protocol error: expected bulk argument");
452
+ args.push(arg.v);
453
+ }
454
+ return { name: latin1(name.v), args };
455
+ }
456
+
457
+ // src/sha1.ts
458
+ var K = [1518500249, 1859775393, 2400959708, 3395469782];
459
+ function rotl(value, bits) {
460
+ return (value << bits | value >>> 32 - bits) >>> 0;
461
+ }
462
+ function sha1Hex(data) {
463
+ const bitLength = data.length * 8;
464
+ const padded = new Uint8Array((data.length + 9 + 63 & ~63) >>> 0);
465
+ padded.set(data);
466
+ padded[data.length] = 128;
467
+ const view = new DataView(padded.buffer);
468
+ view.setUint32(padded.length - 8, Math.floor(bitLength / 4294967296), false);
469
+ view.setUint32(padded.length - 4, bitLength >>> 0, false);
470
+ let h0 = 1732584193;
471
+ let h1 = 4023233417;
472
+ let h2 = 2562383102;
473
+ let h3 = 271733878;
474
+ let h4 = 3285377520;
475
+ const w = new Uint32Array(80);
476
+ for (let offset = 0; offset < padded.length; offset += 64) {
477
+ for (let i = 0; i < 16; i++) w[i] = view.getUint32(offset + i * 4, false);
478
+ for (let i = 16; i < 80; i++) {
479
+ w[i] = rotl((w[i - 3] ?? 0) ^ (w[i - 8] ?? 0) ^ (w[i - 14] ?? 0) ^ (w[i - 16] ?? 0), 1);
480
+ }
481
+ let a = h0;
482
+ let b = h1;
483
+ let c = h2;
484
+ let d = h3;
485
+ let e = h4;
486
+ for (let i = 0; i < 80; i++) {
487
+ const stage = Math.floor(i / 20);
488
+ let f = 0;
489
+ if (stage === 0) f = b & c | ~b & d;
490
+ else if (stage === 1 || stage === 3) f = b ^ c ^ d;
491
+ else f = b & c | b & d | c & d;
492
+ const temp = rotl(a, 5) + f + e + (K[stage] ?? 0) + (w[i] ?? 0) >>> 0;
493
+ e = d;
494
+ d = c;
495
+ c = rotl(b, 30);
496
+ b = a;
497
+ a = temp;
498
+ }
499
+ h0 = h0 + a >>> 0;
500
+ h1 = h1 + b >>> 0;
501
+ h2 = h2 + c >>> 0;
502
+ h3 = h3 + d >>> 0;
503
+ h4 = h4 + e >>> 0;
504
+ }
505
+ return [h0, h1, h2, h3, h4].map((part) => part.toString(16).padStart(8, "0")).join("");
506
+ }
507
+
508
+ // src/lua.ts
509
+ var LuaTable = class {
510
+ array = /* @__PURE__ */ new Map();
511
+ map = /* @__PURE__ */ new Map();
512
+ set(key, value) {
513
+ if (typeof key === "number" && Number.isInteger(key) && key >= 1) {
514
+ if (value === null) this.array.delete(key);
515
+ else this.array.set(key, value);
516
+ return;
517
+ }
518
+ const name = keyString(key);
519
+ if (value === null) this.map.delete(name);
520
+ else this.map.set(name, value);
521
+ }
522
+ get(key) {
523
+ if (typeof key === "number" && Number.isInteger(key) && key >= 1) {
524
+ return this.array.has(key) ? this.array.get(key) ?? null : null;
525
+ }
526
+ const name = keyString(key);
527
+ return this.map.has(name) ? this.map.get(name) ?? null : null;
528
+ }
529
+ has(key) {
530
+ if (typeof key === "number" && Number.isInteger(key) && key >= 1) return this.array.has(key);
531
+ return this.map.has(keyString(key));
532
+ }
533
+ length() {
534
+ let n = 0;
535
+ while (this.array.has(n + 1)) n++;
536
+ return n;
537
+ }
538
+ };
539
+ var LuaStatus = class {
540
+ constructor(ok2) {
541
+ this.ok = ok2;
542
+ }
543
+ ok;
544
+ };
545
+ var LuaFailure = class {
546
+ constructor(err2) {
547
+ this.err = err2;
548
+ }
549
+ err;
550
+ };
551
+ var LuaClosure = class {
552
+ constructor(params, vararg, body, env) {
553
+ this.params = params;
554
+ this.vararg = vararg;
555
+ this.body = body;
556
+ this.env = env;
557
+ }
558
+ params;
559
+ vararg;
560
+ body;
561
+ env;
562
+ };
563
+ function keyString(key) {
564
+ if (typeof key === "string") return key;
565
+ if (typeof key === "number") return String(key);
566
+ if (typeof key === "boolean") return key ? "true" : "false";
567
+ if (key === null) throw new Error("table index is nil");
568
+ return `value:${typeof key}`;
569
+ }
570
+ var Env = class {
571
+ constructor(parent) {
572
+ this.parent = parent;
573
+ }
574
+ parent;
575
+ vars = /* @__PURE__ */ new Map();
576
+ varargs = [];
577
+ get(name) {
578
+ if (this.vars.has(name)) return this.vars.get(name) ?? null;
579
+ return this.parent ? this.parent.get(name) : null;
580
+ }
581
+ define(name, value) {
582
+ this.vars.set(name, value);
583
+ }
584
+ set(name, value) {
585
+ if (this.vars.has(name) || this.parent === null) {
586
+ this.vars.set(name, value);
587
+ return;
588
+ }
589
+ this.parent.set(name, value);
590
+ }
591
+ };
592
+ var NEXT = { k: "next" };
593
+ function truthy(value) {
594
+ return value !== null && value !== false;
595
+ }
596
+ function luaEqual(a, b) {
597
+ if (typeof a === "number" && typeof b === "string") return numberEqualsString(a, b);
598
+ if (typeof b === "number" && typeof a === "string") return numberEqualsString(b, a);
599
+ if (a instanceof LuaTable || b instanceof LuaTable) return a === b;
600
+ return a === b;
601
+ }
602
+ function numberEqualsString(value, text2) {
603
+ const trimmed = text2.trim();
604
+ if (!/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?$/.test(trimmed)) return false;
605
+ return Number(trimmed) === value;
606
+ }
607
+ function asNumber(value) {
608
+ if (typeof value === "number") return value;
609
+ if (typeof value === "string" && /^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?$/.test(value.trim())) {
610
+ return Number(value.trim());
611
+ }
612
+ throw new Error("attempt to perform arithmetic on a non-number");
613
+ }
614
+ function asString(value) {
615
+ if (typeof value === "string") return value;
616
+ if (typeof value === "number") return Number.isInteger(value) ? String(value) : String(value);
617
+ if (typeof value === "boolean") return value ? "true" : "false";
618
+ if (value === null) throw new Error("attempt to concatenate a nil value");
619
+ throw new Error("attempt to concatenate a non-string value");
620
+ }
621
+ function execBlock(body, env) {
622
+ for (const stmt of body) {
623
+ const flow = exec(stmt, env);
624
+ if (flow.k !== "next") return flow;
625
+ }
626
+ return NEXT;
627
+ }
628
+ function exec(stmt, env) {
629
+ switch (stmt.k) {
630
+ case "if": {
631
+ for (const arm of stmt.arms) {
632
+ if (truthy(first(evalExpr(arm.cond, env)))) return execBlock(arm.body, new Env(env));
633
+ }
634
+ return execBlock(stmt.elseBody, new Env(env));
635
+ }
636
+ case "while": {
637
+ while (truthy(first(evalExpr(stmt.cond, env)))) {
638
+ const flow = execBlock(stmt.body, new Env(env));
639
+ if (flow.k === "return") return flow;
640
+ if (flow.k === "break") break;
641
+ }
642
+ return NEXT;
643
+ }
644
+ case "for": {
645
+ const start = asNumber(first(evalExpr(stmt.start, env)));
646
+ const stop = asNumber(first(evalExpr(stmt.stop, env)));
647
+ const step = stmt.step ? asNumber(first(evalExpr(stmt.step, env))) : 1;
648
+ if (step === 0) throw new Error("'for' step is zero");
649
+ for (let i = start; step > 0 ? i <= stop : i >= stop; i += step) {
650
+ const local = new Env(env);
651
+ local.define(stmt.name, i);
652
+ const flow = execBlock(stmt.body, local);
653
+ if (flow.k === "return") return flow;
654
+ if (flow.k === "break") break;
655
+ }
656
+ return NEXT;
657
+ }
658
+ case "forin": {
659
+ const produced = evalList(stmt.iter, env);
660
+ const iter = produced[0] ?? null;
661
+ const state = produced[1] ?? null;
662
+ let control = produced[2] ?? null;
663
+ if (typeof iter !== "function" && !(iter instanceof LuaClosure)) {
664
+ throw new Error("attempt to call a non-function");
665
+ }
666
+ for (; ; ) {
667
+ const values = callValue(iter, [state, control]);
668
+ const next = values[0] ?? null;
669
+ if (next === null) break;
670
+ control = next;
671
+ const local = new Env(env);
672
+ stmt.names.forEach((name, index) => {
673
+ local.define(name, values[index] ?? null);
674
+ });
675
+ const flow = execBlock(stmt.body, local);
676
+ if (flow.k === "return") return flow;
677
+ if (flow.k === "break") break;
678
+ }
679
+ return NEXT;
680
+ }
681
+ case "do":
682
+ return execBlock(stmt.body, new Env(env));
683
+ case "return":
684
+ return { k: "return", values: evalList(stmt.values, env) };
685
+ case "local": {
686
+ const values = evalList(stmt.values, env);
687
+ stmt.names.forEach((name, index) => {
688
+ env.define(name, values[index] ?? null);
689
+ });
690
+ return NEXT;
691
+ }
692
+ case "localfn": {
693
+ const closure = new LuaClosure(stmt.params, stmt.vararg, stmt.body, env);
694
+ env.define(stmt.name, closure);
695
+ return NEXT;
696
+ }
697
+ case "assign": {
698
+ const values = evalList(stmt.values, env);
699
+ stmt.targets.forEach((target, index) => {
700
+ assign(target, values[index] ?? null, env);
701
+ });
702
+ return NEXT;
703
+ }
704
+ case "call":
705
+ evalExpr(stmt.expr, env);
706
+ return NEXT;
707
+ case "break":
708
+ return { k: "break" };
709
+ default: {
710
+ const never = stmt;
711
+ return never;
712
+ }
713
+ }
714
+ }
715
+ function assign(target, value, env) {
716
+ if (target.k === "name") {
717
+ env.set(target.name, value);
718
+ return;
719
+ }
720
+ const table = first(evalExpr(target.k === "index" ? target.table : target.table, env));
721
+ if (!(table instanceof LuaTable)) throw new Error("attempt to index a non-table");
722
+ if (target.k === "field") table.set(target.name, value);
723
+ else table.set(first(evalExpr(target.key, env)), value);
724
+ }
725
+ function evalList(exprs, env) {
726
+ if (exprs.length === 0) return [];
727
+ const out = [];
728
+ for (let i = 0; i < exprs.length; i++) {
729
+ const expr = exprs[i];
730
+ if (!expr) continue;
731
+ const values = evalExpr(expr, env);
732
+ if (i === exprs.length - 1) out.push(...values);
733
+ else out.push(values[0] ?? null);
734
+ }
735
+ return out;
736
+ }
737
+ function first(values) {
738
+ return values[0] ?? null;
739
+ }
740
+ function evalExpr(expr, env) {
741
+ switch (expr.k) {
742
+ case "nil":
743
+ return [null];
744
+ case "bool":
745
+ return [expr.v];
746
+ case "num":
747
+ return [expr.v];
748
+ case "str":
749
+ return [expr.v];
750
+ case "name":
751
+ return [env.get(expr.v)];
752
+ case "dots":
753
+ return env.varargs;
754
+ case "un": {
755
+ const value = first(evalExpr(expr.e, env));
756
+ if (expr.op === "not") return [!truthy(value)];
757
+ if (expr.op === "#") {
758
+ if (typeof value === "string") return [value.length];
759
+ if (value instanceof LuaTable) return [value.length()];
760
+ throw new Error("attempt to get length of a non-table");
761
+ }
762
+ return [-asNumber(value)];
763
+ }
764
+ case "bin": {
765
+ if (expr.op === "..") {
766
+ return [asString(first(evalExpr(expr.l, env))) + asString(first(evalExpr(expr.r, env)))];
767
+ }
768
+ if (expr.op === "==" || expr.op === "~=") {
769
+ const equal = luaEqual(first(evalExpr(expr.l, env)), first(evalExpr(expr.r, env)));
770
+ return [expr.op === "==" ? equal : !equal];
771
+ }
772
+ if (expr.op === "<" || expr.op === ">" || expr.op === "<=" || expr.op === ">=") {
773
+ const op = expr.op;
774
+ return [
775
+ compare(
776
+ expr.l,
777
+ expr.r,
778
+ env,
779
+ (a, b) => op === "<" ? a < b : op === ">" ? a > b : op === "<=" ? a <= b : a >= b
780
+ )
781
+ ];
782
+ }
783
+ const left = asNumber(first(evalExpr(expr.l, env)));
784
+ const right = asNumber(first(evalExpr(expr.r, env)));
785
+ switch (expr.op) {
786
+ case "+":
787
+ return [left + right];
788
+ case "-":
789
+ return [left - right];
790
+ case "*":
791
+ return [left * right];
792
+ case "/":
793
+ return [left / right];
794
+ case "%":
795
+ return [left % right];
796
+ case "^":
797
+ return [left ** right];
798
+ default:
799
+ throw new Error(`unexpected operator ${expr.op}`);
800
+ }
801
+ }
802
+ case "and": {
803
+ const left = evalExpr(expr.l, env);
804
+ if (!truthy(first(left))) return [first(left)];
805
+ return evalExpr(expr.r, env);
806
+ }
807
+ case "or": {
808
+ const left = evalExpr(expr.l, env);
809
+ if (truthy(first(left))) return [first(left)];
810
+ return evalExpr(expr.r, env);
811
+ }
812
+ case "index": {
813
+ const table = first(evalExpr(expr.t, env));
814
+ if (!(table instanceof LuaTable)) throw new Error("attempt to index a non-table");
815
+ return [table.get(first(evalExpr(expr.i, env)))];
816
+ }
817
+ case "field": {
818
+ const table = first(evalExpr(expr.t, env));
819
+ if (!(table instanceof LuaTable)) throw new Error("attempt to index a non-table");
820
+ return [table.get(expr.n)];
821
+ }
822
+ case "call":
823
+ return callExpr(expr, env);
824
+ case "table":
825
+ return [buildTable(expr, env)];
826
+ case "fn":
827
+ return [new LuaClosure(expr.params, expr.vararg, expr.body, env)];
828
+ default: {
829
+ const never = expr;
830
+ return never;
831
+ }
832
+ }
833
+ }
834
+ function compare(left, right, env, op) {
835
+ const a = first(evalExpr(left, env));
836
+ const b = first(evalExpr(right, env));
837
+ if (typeof a === "number" && typeof b === "number") return op(a, b);
838
+ if (typeof a === "string" && typeof b === "string") return op(a, b);
839
+ throw new Error("attempt to compare incompatible values");
840
+ }
841
+ function callExpr(expr, env) {
842
+ if (expr.method) {
843
+ const self = first(evalExpr(expr.callee, env));
844
+ if (!(self instanceof LuaTable)) throw new Error("attempt to index a non-table");
845
+ const fn2 = self.get(expr.method);
846
+ const args = evalList(expr.args, env);
847
+ return callValue(fn2, [self, ...args]);
848
+ }
849
+ const fn = first(evalExpr(expr.callee, env));
850
+ return callValue(fn, evalList(expr.args, env));
851
+ }
852
+ function callValue(fn, args) {
853
+ if (fn instanceof LuaClosure) {
854
+ const local = new Env(fn.env);
855
+ fn.params.forEach((name, index) => {
856
+ local.define(name, args[index] ?? null);
857
+ });
858
+ local.varargs = fn.vararg ? args.slice(fn.params.length) : [];
859
+ const flow = execBlock(fn.body, local);
860
+ return flow.k === "return" ? flow.values : [null];
861
+ }
862
+ if (typeof fn === "function") return fn(args);
863
+ throw new Error("attempt to call a non-function");
864
+ }
865
+ function buildTable(expr, env) {
866
+ const table = new LuaTable();
867
+ let index = 1;
868
+ expr.fields.forEach((field, fieldIndex) => {
869
+ if (field.name !== void 0) {
870
+ table.set(field.name, first(evalExpr(field.value, env)));
871
+ return;
872
+ }
873
+ if (field.key) {
874
+ table.set(first(evalExpr(field.key, env)), first(evalExpr(field.value, env)));
875
+ return;
876
+ }
877
+ const values = evalExpr(field.value, env);
878
+ const last = fieldIndex === expr.fields.length - 1;
879
+ const produced = last ? values : [values[0] ?? null];
880
+ if (produced.length === 0) return;
881
+ for (const value of produced) {
882
+ if (value !== null) table.set(index, value);
883
+ index++;
884
+ }
885
+ });
886
+ return table;
887
+ }
888
+ function tokenize(source) {
889
+ const toks = [];
890
+ let i = 0;
891
+ let line = 1;
892
+ const peek = (offset = 0) => source[i + offset];
893
+ while (i < source.length) {
894
+ const ch = peek();
895
+ if (ch === "\n") {
896
+ line++;
897
+ i++;
898
+ continue;
899
+ }
900
+ if (ch === " " || ch === " " || ch === "\r") {
901
+ i++;
902
+ continue;
903
+ }
904
+ if (ch === "-" && peek(1) === "-") {
905
+ if (peek(2) === "[") {
906
+ const close = longBracket(source, i + 2);
907
+ if (close) {
908
+ const end = source.indexOf(close.end, close.next);
909
+ if (end < 0) throw new Error(`unfinished long comment at line ${line}`);
910
+ const skipped = source.slice(i, end);
911
+ line += skipped.split("\n").length - 1;
912
+ i = end + close.end.length;
913
+ continue;
914
+ }
915
+ }
916
+ while (i < source.length && source[i] !== "\n") i++;
917
+ continue;
918
+ }
919
+ if (ch === "'" || ch === '"') {
920
+ const quote = ch;
921
+ i++;
922
+ let value = "";
923
+ while (i < source.length && source[i] !== quote) {
924
+ if (source[i] === "\n") line++;
925
+ if (source[i] === "\\") {
926
+ i++;
927
+ const esc = source[i];
928
+ const map2 = {
929
+ n: "\n",
930
+ r: "\r",
931
+ t: " ",
932
+ a: "\x07",
933
+ b: "\b",
934
+ f: "\f",
935
+ v: "\v",
936
+ "\\": "\\",
937
+ "'": "'",
938
+ '"': '"',
939
+ "\n": "\n"
940
+ };
941
+ if (esc !== void 0 && map2[esc] !== void 0) value += map2[esc];
942
+ else if (esc !== void 0 && esc >= "0" && esc <= "9") {
943
+ let digits = esc;
944
+ for (let n = 0; n < 2; n++) {
945
+ const next = source[i + 1];
946
+ if (next === void 0 || next < "0" || next > "9") break;
947
+ i++;
948
+ digits += next;
949
+ }
950
+ value += String.fromCharCode(Number(digits) & 255);
951
+ } else value += esc ?? "";
952
+ i++;
953
+ continue;
954
+ }
955
+ value += source[i] ?? "";
956
+ i++;
957
+ }
958
+ if (source[i] !== quote) throw new Error(`unfinished string at line ${line}`);
959
+ i++;
960
+ toks.push({ k: "str", v: value, line });
961
+ continue;
962
+ }
963
+ if (ch === "[") {
964
+ const bracket = longBracket(source, i);
965
+ if (bracket) {
966
+ let start = bracket.next;
967
+ if (source[start] === "\n") {
968
+ start++;
969
+ line++;
970
+ }
971
+ const end = source.indexOf(bracket.end, start);
972
+ if (end < 0) throw new Error(`unfinished long string at line ${line}`);
973
+ const value = source.slice(start, end);
974
+ line += value.split("\n").length - 1;
975
+ i = end + bracket.end.length;
976
+ toks.push({ k: "str", v: value, line });
977
+ continue;
978
+ }
979
+ }
980
+ const digit = peek(1);
981
+ if (ch !== void 0 && ch >= "0" && ch <= "9" || ch === "." && digit !== void 0 && digit >= "0" && digit <= "9") {
982
+ const start = i;
983
+ if (ch === "0" && (peek(1) === "x" || peek(1) === "X")) {
984
+ i += 2;
985
+ while (i < source.length && /[0-9a-fA-F]/.test(source[i] ?? "")) i++;
986
+ toks.push({ k: "num", v: Number(source.slice(start, i)), line });
987
+ continue;
988
+ }
989
+ while (i < source.length && /[0-9]/.test(source[i] ?? "")) i++;
990
+ if (source[i] === ".") {
991
+ i++;
992
+ while (i < source.length && /[0-9]/.test(source[i] ?? "")) i++;
993
+ }
994
+ if (source[i] === "e" || source[i] === "E") {
995
+ i++;
996
+ if (source[i] === "+" || source[i] === "-") i++;
997
+ while (i < source.length && /[0-9]/.test(source[i] ?? "")) i++;
998
+ }
999
+ toks.push({ k: "num", v: Number(source.slice(start, i)), line });
1000
+ continue;
1001
+ }
1002
+ if (ch !== void 0 && /[A-Za-z_]/.test(ch)) {
1003
+ const start = i;
1004
+ i++;
1005
+ while (i < source.length && /[A-Za-z0-9_]/.test(source[i] ?? "")) i++;
1006
+ toks.push({ k: "name", v: source.slice(start, i), line });
1007
+ continue;
1008
+ }
1009
+ const three = source.slice(i, i + 3);
1010
+ const two = source.slice(i, i + 2);
1011
+ if (three === "...") {
1012
+ toks.push({ k: "sym", v: "...", line });
1013
+ i += 3;
1014
+ continue;
1015
+ }
1016
+ if (["==", "~=", "<=", ">=", ".."].includes(two)) {
1017
+ toks.push({ k: "sym", v: two, line });
1018
+ i += 2;
1019
+ continue;
1020
+ }
1021
+ toks.push({ k: "sym", v: ch ?? "", line });
1022
+ i++;
1023
+ }
1024
+ toks.push({ k: "eof", line });
1025
+ return toks;
1026
+ }
1027
+ function longBracket(source, at) {
1028
+ if (source[at] !== "[") return null;
1029
+ let i = at + 1;
1030
+ while (source[i] === "=") i++;
1031
+ if (source[i] !== "[") return null;
1032
+ const marks = i - (at + 1);
1033
+ return { next: i + 1, end: `]${"=".repeat(marks)}]` };
1034
+ }
1035
+ var Parser = class {
1036
+ constructor(toks) {
1037
+ this.toks = toks;
1038
+ }
1039
+ toks;
1040
+ i = 0;
1041
+ cur() {
1042
+ return this.toks[this.i] ?? { k: "eof", line: 0 };
1043
+ }
1044
+ line() {
1045
+ return this.cur().line;
1046
+ }
1047
+ eat() {
1048
+ const tok = this.cur();
1049
+ this.i++;
1050
+ return tok;
1051
+ }
1052
+ eatSym(value) {
1053
+ const tok = this.cur();
1054
+ if (tok.k === "sym" && tok.v === value) {
1055
+ this.i++;
1056
+ return true;
1057
+ }
1058
+ return false;
1059
+ }
1060
+ expectSym(value) {
1061
+ if (!this.eatSym(value)) throw new Error(`expected '${value}' at line ${this.line()}`);
1062
+ }
1063
+ isName(value) {
1064
+ const tok = this.cur();
1065
+ return tok.k === "name" && (value === void 0 || tok.v === value);
1066
+ }
1067
+ eatName(value) {
1068
+ if (!this.isName(value)) return null;
1069
+ const tok = this.eat();
1070
+ return tok.k === "name" ? tok.v : null;
1071
+ }
1072
+ expectName() {
1073
+ const name = this.eatName();
1074
+ if (name === null) throw new Error(`expected name at line ${this.line()}`);
1075
+ return name;
1076
+ }
1077
+ parseChunk() {
1078
+ const body = this.parseBlock();
1079
+ if (this.cur().k !== "eof") throw new Error(`unexpected token at line ${this.line()}`);
1080
+ return body;
1081
+ }
1082
+ parseBlock() {
1083
+ const body = [];
1084
+ while (!this.blockEnd()) {
1085
+ if (this.isName("return")) {
1086
+ body.push(this.parseReturn());
1087
+ this.eatSym(";");
1088
+ break;
1089
+ }
1090
+ body.push(this.parseStmt());
1091
+ this.eatSym(";");
1092
+ }
1093
+ return body;
1094
+ }
1095
+ blockEnd() {
1096
+ return this.cur().k === "eof" || this.isName("end") || this.isName("else") || this.isName("elseif") || this.isName("until");
1097
+ }
1098
+ parseStmt() {
1099
+ if (this.isName("if")) return this.parseIf();
1100
+ if (this.isName("while")) return this.parseWhile();
1101
+ if (this.isName("for")) return this.parseFor();
1102
+ if (this.isName("do")) {
1103
+ this.eat();
1104
+ const body = this.parseBlock();
1105
+ if (!this.eatName("end")) throw new Error(`expected 'end' at line ${this.line()}`);
1106
+ return { k: "do", body };
1107
+ }
1108
+ if (this.isName("local")) {
1109
+ this.eat();
1110
+ if (this.isName("function")) {
1111
+ this.eat();
1112
+ const name = this.expectName();
1113
+ const fn = this.parseFunctionTail();
1114
+ return { k: "localfn", name, params: fn.params, vararg: fn.vararg, body: fn.body };
1115
+ }
1116
+ const names = [this.expectName()];
1117
+ while (this.eatSym(",")) names.push(this.expectName());
1118
+ const values = [];
1119
+ if (this.eatSym("=")) values.push(...this.parseExprList());
1120
+ return { k: "local", names, values };
1121
+ }
1122
+ if (this.isName("function")) {
1123
+ this.eat();
1124
+ const name = this.expectName();
1125
+ const fn = this.parseFunctionTail();
1126
+ return {
1127
+ k: "assign",
1128
+ targets: [{ k: "name", name }],
1129
+ values: [{ k: "fn", params: fn.params, vararg: fn.vararg, body: fn.body }]
1130
+ };
1131
+ }
1132
+ if (this.isName("break")) {
1133
+ this.eat();
1134
+ return { k: "break" };
1135
+ }
1136
+ const expr = this.parsePrefix();
1137
+ if (this.cur().k === "sym" && this.cur().k === "sym" && (this.cur().v === "=" || this.cur().v === ",")) {
1138
+ const targets = [this.asTarget(expr)];
1139
+ while (this.eatSym(",")) targets.push(this.asTarget(this.parsePrefix()));
1140
+ this.expectSym("=");
1141
+ return { k: "assign", targets, values: this.parseExprList() };
1142
+ }
1143
+ if (expr.k !== "call") throw new Error(`expected statement at line ${this.line()}`);
1144
+ return { k: "call", expr };
1145
+ }
1146
+ parseReturn() {
1147
+ this.eat();
1148
+ if (this.blockEnd() || this.cur().k === "sym" && this.cur().k === "sym" && this.cur().v === ";") {
1149
+ return { k: "return", values: [] };
1150
+ }
1151
+ return { k: "return", values: this.parseExprList() };
1152
+ }
1153
+ parseIf() {
1154
+ this.eat();
1155
+ const arms = [];
1156
+ arms.push({ cond: this.parseExpr(), body: this.afterThen() });
1157
+ while (this.eatName("elseif")) arms.push({ cond: this.parseExpr(), body: this.afterThen() });
1158
+ let elseBody = [];
1159
+ if (this.eatName("else")) elseBody = this.parseBlock();
1160
+ if (!this.eatName("end")) throw new Error(`expected 'end' at line ${this.line()}`);
1161
+ return { k: "if", arms, elseBody };
1162
+ }
1163
+ afterThen() {
1164
+ if (!this.eatName("then")) throw new Error(`expected 'then' at line ${this.line()}`);
1165
+ return this.parseBlock();
1166
+ }
1167
+ parseWhile() {
1168
+ this.eat();
1169
+ const cond = this.parseExpr();
1170
+ if (!this.eatName("do")) throw new Error(`expected 'do' at line ${this.line()}`);
1171
+ const body = this.parseBlock();
1172
+ if (!this.eatName("end")) throw new Error(`expected 'end' at line ${this.line()}`);
1173
+ return { k: "while", cond, body };
1174
+ }
1175
+ parseFor() {
1176
+ this.eat();
1177
+ const name = this.expectName();
1178
+ if (this.eatSym("=")) {
1179
+ const start = this.parseExpr();
1180
+ this.expectSym(",");
1181
+ const stop = this.parseExpr();
1182
+ const step = this.eatSym(",") ? this.parseExpr() : null;
1183
+ if (!this.eatName("do")) throw new Error(`expected 'do' at line ${this.line()}`);
1184
+ const body2 = this.parseBlock();
1185
+ if (!this.eatName("end")) throw new Error(`expected 'end' at line ${this.line()}`);
1186
+ return { k: "for", name, start, stop, step, body: body2 };
1187
+ }
1188
+ const names = [name];
1189
+ while (this.eatSym(",")) names.push(this.expectName());
1190
+ if (!this.eatName("in")) throw new Error(`expected 'in' at line ${this.line()}`);
1191
+ const iter = this.parseExprList();
1192
+ if (!this.eatName("do")) throw new Error(`expected 'do' at line ${this.line()}`);
1193
+ const body = this.parseBlock();
1194
+ if (!this.eatName("end")) throw new Error(`expected 'end' at line ${this.line()}`);
1195
+ return { k: "forin", names, iter, body };
1196
+ }
1197
+ asTarget(expr) {
1198
+ if (expr.k === "name") return { k: "name", name: expr.v };
1199
+ if (expr.k === "index") return { k: "index", table: expr.t, key: expr.i };
1200
+ if (expr.k === "field") return { k: "field", table: expr.t, name: expr.n };
1201
+ throw new Error(`invalid assignment target at line ${this.line()}`);
1202
+ }
1203
+ parseExprList() {
1204
+ const exprs = [this.parseExpr()];
1205
+ while (this.eatSym(",")) exprs.push(this.parseExpr());
1206
+ return exprs;
1207
+ }
1208
+ parseExpr() {
1209
+ return this.parseOr();
1210
+ }
1211
+ parseOr() {
1212
+ let left = this.parseAnd();
1213
+ while (this.eatName("or")) left = { k: "or", l: left, r: this.parseAnd() };
1214
+ return left;
1215
+ }
1216
+ parseAnd() {
1217
+ let left = this.parseCmp();
1218
+ while (this.eatName("and")) left = { k: "and", l: left, r: this.parseCmp() };
1219
+ return left;
1220
+ }
1221
+ parseCmp() {
1222
+ let left = this.parseConcat();
1223
+ const tok = this.cur();
1224
+ if (tok.k === "sym" && ["==", "~=", "<", ">", "<=", ">="].includes(tok.v)) {
1225
+ this.eat();
1226
+ left = { k: "bin", op: tok.v, l: left, r: this.parseConcat() };
1227
+ }
1228
+ return left;
1229
+ }
1230
+ parseConcat() {
1231
+ const left = this.parseAdd();
1232
+ if (this.eatSym("..")) return { k: "bin", op: "..", l: left, r: this.parseConcat() };
1233
+ return left;
1234
+ }
1235
+ parseAdd() {
1236
+ let left = this.parseMul();
1237
+ while (this.cur().k === "sym" && this.cur().k === "sym" && (this.cur().v === "+" || this.cur().v === "-")) {
1238
+ const op = this.eat().v;
1239
+ left = { k: "bin", op, l: left, r: this.parseMul() };
1240
+ }
1241
+ return left;
1242
+ }
1243
+ parseMul() {
1244
+ let left = this.parseUnary();
1245
+ while (this.cur().k === "sym" && ["*", "/", "%"].includes(this.cur().v)) {
1246
+ const op = this.eat().v;
1247
+ left = { k: "bin", op, l: left, r: this.parseUnary() };
1248
+ }
1249
+ return left;
1250
+ }
1251
+ parseUnary() {
1252
+ if (this.eatName("not")) return { k: "un", op: "not", e: this.parseUnary() };
1253
+ if (this.eatSym("#")) return { k: "un", op: "#", e: this.parseUnary() };
1254
+ if (this.eatSym("-")) return { k: "un", op: "-", e: this.parseUnary() };
1255
+ return this.parsePower();
1256
+ }
1257
+ parsePower() {
1258
+ const left = this.parsePrimary();
1259
+ if (this.eatSym("^")) return { k: "bin", op: "^", l: left, r: this.parseUnary() };
1260
+ return left;
1261
+ }
1262
+ parsePrimary() {
1263
+ if (this.eatName("nil")) return { k: "nil" };
1264
+ if (this.eatName("false")) return { k: "bool", v: false };
1265
+ if (this.eatName("true")) return { k: "bool", v: true };
1266
+ if (this.eatSym("...")) return { k: "dots" };
1267
+ const tok = this.cur();
1268
+ if (tok.k === "num") {
1269
+ this.eat();
1270
+ return { k: "num", v: tok.v };
1271
+ }
1272
+ if (tok.k === "str") {
1273
+ this.eat();
1274
+ return { k: "str", v: tok.v };
1275
+ }
1276
+ if (this.isName("function")) {
1277
+ this.eat();
1278
+ const fn = this.parseFunctionTail();
1279
+ return { k: "fn", params: fn.params, vararg: fn.vararg, body: fn.body };
1280
+ }
1281
+ if (this.eatSym("{")) return this.parseTable();
1282
+ return this.parsePrefix();
1283
+ }
1284
+ parsePrefix() {
1285
+ let expr;
1286
+ if (this.eatSym("(")) {
1287
+ expr = this.parseExpr();
1288
+ this.expectSym(")");
1289
+ } else if (this.cur().k === "name") {
1290
+ expr = { k: "name", v: this.expectName() };
1291
+ } else throw new Error(`expected expression at line ${this.line()}`);
1292
+ for (; ; ) {
1293
+ if (this.eatSym(".")) {
1294
+ expr = { k: "field", t: expr, n: this.expectName() };
1295
+ continue;
1296
+ }
1297
+ if (this.eatSym("[")) {
1298
+ const index = this.parseExpr();
1299
+ this.expectSym("]");
1300
+ expr = { k: "index", t: expr, i: index };
1301
+ continue;
1302
+ }
1303
+ if (this.eatSym(":")) {
1304
+ const method = this.expectName();
1305
+ const args = this.parseCallArgs();
1306
+ expr = { k: "call", callee: expr, args, method };
1307
+ continue;
1308
+ }
1309
+ if (this.startsArgs()) {
1310
+ expr = { k: "call", callee: expr, args: this.parseCallArgs() };
1311
+ continue;
1312
+ }
1313
+ return expr;
1314
+ }
1315
+ }
1316
+ startsArgs() {
1317
+ return this.eatSymPeek("(") || this.eatSymPeek("{") || this.cur().k === "str";
1318
+ }
1319
+ eatSymPeek(value) {
1320
+ const tok = this.cur();
1321
+ return tok.k === "sym" && tok.v === value;
1322
+ }
1323
+ parseCallArgs() {
1324
+ if (this.eatSym("(")) {
1325
+ const args = this.cur().k === "sym" && this.cur().v === ")" ? [] : this.parseExprList();
1326
+ this.expectSym(")");
1327
+ return args;
1328
+ }
1329
+ if (this.eatSym("{")) return [this.parseTable()];
1330
+ const tok = this.cur();
1331
+ if (tok.k === "str") {
1332
+ this.eat();
1333
+ return [{ k: "str", v: tok.v }];
1334
+ }
1335
+ throw new Error(`expected arguments at line ${this.line()}`);
1336
+ }
1337
+ parseFunctionTail() {
1338
+ this.expectSym("(");
1339
+ const params = [];
1340
+ let vararg = false;
1341
+ if (!this.eatSym(")")) {
1342
+ for (; ; ) {
1343
+ if (this.eatSym("...")) {
1344
+ vararg = true;
1345
+ break;
1346
+ }
1347
+ params.push(this.expectName());
1348
+ if (!this.eatSym(",")) break;
1349
+ }
1350
+ this.expectSym(")");
1351
+ }
1352
+ const body = this.parseBlock();
1353
+ if (!this.eatName("end")) throw new Error(`expected 'end' at line ${this.line()}`);
1354
+ return { params, vararg, body };
1355
+ }
1356
+ parseTable() {
1357
+ const fields = [];
1358
+ while (!(this.cur().k === "sym" && this.cur().v === "}")) {
1359
+ if (this.eatSym("[")) {
1360
+ const key = this.parseExpr();
1361
+ this.expectSym("]");
1362
+ this.expectSym("=");
1363
+ fields.push({ key, value: this.parseExpr() });
1364
+ } else if (this.cur().k === "name" && this.toks[this.i + 1]?.k === "sym" && this.toks[this.i + 1].v === "=") {
1365
+ const name = this.expectName();
1366
+ this.expectSym("=");
1367
+ fields.push({ name, value: this.parseExpr() });
1368
+ } else fields.push({ value: this.parseExpr() });
1369
+ if (!this.eatSym(",") && !this.eatSym(";")) break;
1370
+ }
1371
+ this.expectSym("}");
1372
+ return { k: "table", fields };
1373
+ }
1374
+ };
1375
+ function stringSub(value, startRaw, endRaw) {
1376
+ const n = value.length;
1377
+ const normalize = (index) => index < 0 ? n + index + 1 : index;
1378
+ const start = normalize(startRaw);
1379
+ const end = endRaw === void 0 ? n : normalize(endRaw);
1380
+ if (start > end) return "";
1381
+ return value.slice(Math.max(start, 1) - 1, Math.min(end, n));
1382
+ }
1383
+ function toJson(value) {
1384
+ if (value === null) return null;
1385
+ if (typeof value === "boolean" || typeof value === "number") return value;
1386
+ if (typeof value === "string") return utf8Text(fromLatin1(value));
1387
+ if (value instanceof LuaTable) {
1388
+ const named = [...value.map.keys()];
1389
+ if (named.length === 0 && value.length() === 0) return {};
1390
+ if (named.length === 0) {
1391
+ const list = [];
1392
+ for (let i = 1; i <= value.length(); i++) list.push(toJson(value.get(i)));
1393
+ return list;
1394
+ }
1395
+ const object = {};
1396
+ for (const [key, item] of value.map) object[utf8Text(fromLatin1(key))] = toJson(item);
1397
+ return object;
1398
+ }
1399
+ return null;
1400
+ }
1401
+ function fromJson(value) {
1402
+ if (value === null || value === void 0) return null;
1403
+ if (typeof value === "boolean" || typeof value === "number") return value;
1404
+ if (typeof value === "string") return latin1(utf8(value));
1405
+ if (Array.isArray(value)) {
1406
+ const table = new LuaTable();
1407
+ value.forEach((item, index) => {
1408
+ table.set(index + 1, fromJson(item));
1409
+ });
1410
+ return table;
1411
+ }
1412
+ if (typeof value === "object") {
1413
+ const table = new LuaTable();
1414
+ for (const [key, item] of Object.entries(value)) table.set(key, fromJson(item));
1415
+ return table;
1416
+ }
1417
+ return null;
1418
+ }
1419
+ function luaGlobals(host) {
1420
+ const env = new Env(null);
1421
+ const redis = new LuaTable();
1422
+ redis.set("call", host.call);
1423
+ redis.set("pcall", host.pcall);
1424
+ redis.set("error_reply", (args) => [new LuaFailure(asString(args[0] ?? null))]);
1425
+ redis.set("status_reply", (args) => [new LuaStatus(asString(args[0] ?? null))]);
1426
+ redis.set("sha1hex", (args) => [sha1Hex(fromLatin1(asString(args[0] ?? null)))]);
1427
+ redis.set("log", () => [null]);
1428
+ redis.set("replicate_commands", () => [1]);
1429
+ redis.set("set_repl", () => [null]);
1430
+ redis.set("LOG_DEBUG", 0);
1431
+ redis.set("LOG_VERBOSE", 1);
1432
+ redis.set("LOG_NOTICE", 2);
1433
+ redis.set("LOG_WARNING", 3);
1434
+ env.define("redis", redis);
1435
+ const stringLib = new LuaTable();
1436
+ stringLib.set("len", (args) => [asString(args[0] ?? null).length]);
1437
+ stringLib.set("sub", (args) => [
1438
+ stringSub(
1439
+ asString(args[0] ?? null),
1440
+ asNumber(args[1] ?? 1),
1441
+ args[2] === void 0 ? void 0 : asNumber(args[2] ?? 0)
1442
+ )
1443
+ ]);
1444
+ stringLib.set("byte", (args) => {
1445
+ const value = asString(args[0] ?? null);
1446
+ const start = args[1] === void 0 ? 1 : asNumber(args[1] ?? 1);
1447
+ const end = args[2] === void 0 ? start : asNumber(args[2] ?? start);
1448
+ const slice = stringSub(value, start, end);
1449
+ return [...slice].map((ch) => ch.charCodeAt(0));
1450
+ });
1451
+ stringLib.set("char", (args) => [
1452
+ args.map((arg) => String.fromCharCode(asNumber(arg) & 255)).join("")
1453
+ ]);
1454
+ stringLib.set("format", (args) => [formatString(asString(args[0] ?? ""), args.slice(1))]);
1455
+ stringLib.set("find", (args) => {
1456
+ const hay = asString(args[0] ?? null);
1457
+ const needle = asString(args[1] ?? "");
1458
+ const init = args[2] === void 0 ? 1 : asNumber(args[2] ?? 1);
1459
+ const at = hay.indexOf(needle, Math.max(0, init - 1));
1460
+ if (at < 0) return [null];
1461
+ return [at + 1, at + needle.length];
1462
+ });
1463
+ stringLib.set("lower", (args) => [asString(args[0] ?? null).toLowerCase()]);
1464
+ stringLib.set("upper", (args) => [asString(args[0] ?? null).toUpperCase()]);
1465
+ stringLib.set("rep", (args) => [asString(args[0] ?? "").repeat(asNumber(args[1] ?? 0))]);
1466
+ env.define("string", stringLib);
1467
+ const tableLib = new LuaTable();
1468
+ tableLib.set("insert", (args) => {
1469
+ const table = args[0];
1470
+ if (!(table instanceof LuaTable)) throw new Error("bad argument to table.insert");
1471
+ if (args.length >= 3) {
1472
+ const pos = asNumber(args[1] ?? 1);
1473
+ const value = args[2] ?? null;
1474
+ for (let i = table.length(); i >= pos; i--) table.set(i + 1, table.get(i));
1475
+ table.set(pos, value);
1476
+ } else table.set(table.length() + 1, args[1] ?? null);
1477
+ return [null];
1478
+ });
1479
+ tableLib.set("remove", (args) => {
1480
+ const table = args[0];
1481
+ if (!(table instanceof LuaTable)) throw new Error("bad argument to table.remove");
1482
+ const pos = args[1] === void 0 ? table.length() : asNumber(args[1] ?? 1);
1483
+ const value = table.get(pos);
1484
+ const len = table.length();
1485
+ for (let i = pos; i < len; i++) table.set(i, table.get(i + 1));
1486
+ table.set(len, null);
1487
+ return [value];
1488
+ });
1489
+ tableLib.set("concat", (args) => {
1490
+ const table = args[0];
1491
+ if (!(table instanceof LuaTable)) throw new Error("bad argument to table.concat");
1492
+ const sep = args[1] === void 0 || args[1] === null ? "" : asString(args[1]);
1493
+ const start = args[2] === void 0 ? 1 : asNumber(args[2] ?? 1);
1494
+ const finish = args[3] === void 0 ? table.length() : asNumber(args[3] ?? 0);
1495
+ const parts = [];
1496
+ for (let i = start; i <= finish; i++) parts.push(asString(table.get(i)));
1497
+ return [parts.join(sep)];
1498
+ });
1499
+ tableLib.set("unpack", (args) => unpackTable(args));
1500
+ env.define("table", tableLib);
1501
+ const mathLib = new LuaTable();
1502
+ mathLib.set("floor", (args) => [Math.floor(asNumber(args[0] ?? 0))]);
1503
+ mathLib.set("ceil", (args) => [Math.ceil(asNumber(args[0] ?? 0))]);
1504
+ mathLib.set("abs", (args) => [Math.abs(asNumber(args[0] ?? 0))]);
1505
+ mathLib.set("max", (args) => [Math.max(...args.map((arg) => asNumber(arg)))]);
1506
+ mathLib.set("min", (args) => [Math.min(...args.map((arg) => asNumber(arg)))]);
1507
+ mathLib.set("huge", Infinity);
1508
+ env.define("math", mathLib);
1509
+ const cjson = new LuaTable();
1510
+ cjson.set("encode", (args) => [latin1(utf8(JSON.stringify(toJson(args[0] ?? null)) ?? "null"))]);
1511
+ cjson.set("decode", (args) => {
1512
+ const text2 = utf8Text(fromLatin1(asString(args[0] ?? "null")));
1513
+ return [fromJson(JSON.parse(text2))];
1514
+ });
1515
+ env.define("cjson", cjson);
1516
+ env.define("tonumber", (args) => {
1517
+ const value = args[0] ?? null;
1518
+ if (typeof value === "number") return [value];
1519
+ if (typeof value !== "string") return [null];
1520
+ if (!/^[-+]?\d+(\.\d+)?([eE][-+]?\d+)?$/.test(value.trim())) return [null];
1521
+ return [Number(value.trim())];
1522
+ });
1523
+ env.define("tostring", (args) => {
1524
+ const value = args[0] ?? null;
1525
+ if (value === null) return ["nil"];
1526
+ if (typeof value === "boolean") return [value ? "true" : "false"];
1527
+ if (typeof value === "number" || typeof value === "string") return [String(value)];
1528
+ if (value instanceof LuaTable) return ["table"];
1529
+ if (value instanceof LuaStatus) return [value.ok];
1530
+ return ["userdata"];
1531
+ });
1532
+ env.define("type", (args) => {
1533
+ const value = args[0] ?? null;
1534
+ if (value === null) return ["nil"];
1535
+ if (typeof value === "boolean") return ["boolean"];
1536
+ if (typeof value === "number") return ["number"];
1537
+ if (typeof value === "string") return ["string"];
1538
+ if (value instanceof LuaTable) return ["table"];
1539
+ if (typeof value === "function" || value instanceof LuaClosure) return ["function"];
1540
+ return ["userdata"];
1541
+ });
1542
+ env.define("unpack", (args) => unpackTable(args));
1543
+ env.define("ipairs", (args) => {
1544
+ const table = args[0];
1545
+ const iter = (callArgs) => {
1546
+ const subject = callArgs[0];
1547
+ const index = (typeof callArgs[1] === "number" ? callArgs[1] : 0) + 1;
1548
+ if (!(subject instanceof LuaTable) || !subject.has(index)) return [null];
1549
+ return [index, subject.get(index)];
1550
+ };
1551
+ return [iter, table ?? null, 0];
1552
+ });
1553
+ env.define("pairs", (args) => {
1554
+ const table = args[0];
1555
+ if (!(table instanceof LuaTable)) throw new Error("bad argument to pairs");
1556
+ const keys = [...table.array.keys(), ...table.map.keys()];
1557
+ let cursor = 0;
1558
+ const iter = () => {
1559
+ const key = keys[cursor];
1560
+ cursor++;
1561
+ if (key === void 0) return [null];
1562
+ return [key, table.get(key)];
1563
+ };
1564
+ return [iter, table, null];
1565
+ });
1566
+ env.define("error", (args) => {
1567
+ throw new Error(asString(args[0] ?? "error"));
1568
+ });
1569
+ env.define("select", (args) => {
1570
+ const index = args[0];
1571
+ const rest = args.slice(1);
1572
+ if (index === "#") return [rest.length];
1573
+ const at = asNumber(index ?? 1);
1574
+ if (at < 1) throw new Error("bad argument to select");
1575
+ return rest.slice(at - 1);
1576
+ });
1577
+ env.define("next", () => {
1578
+ throw new Error("next is not implemented");
1579
+ });
1580
+ return env;
1581
+ }
1582
+ function unpackTable(args) {
1583
+ const table = args[0];
1584
+ if (!(table instanceof LuaTable)) throw new Error("bad argument to unpack");
1585
+ const start = args[1] === void 0 ? 1 : asNumber(args[1] ?? 1);
1586
+ const finish = args[2] === void 0 ? table.length() : asNumber(args[2] ?? 0);
1587
+ const out = [];
1588
+ for (let i = start; i <= finish; i++) out.push(table.get(i));
1589
+ return out;
1590
+ }
1591
+ function formatString(fmt, args) {
1592
+ let out = "";
1593
+ let arg = 0;
1594
+ for (let i = 0; i < fmt.length; i++) {
1595
+ if (fmt[i] !== "%") {
1596
+ out += fmt[i] ?? "";
1597
+ continue;
1598
+ }
1599
+ const spec = fmt[++i];
1600
+ if (spec === "%") {
1601
+ out += "%";
1602
+ continue;
1603
+ }
1604
+ const value = args[arg++] ?? null;
1605
+ if (spec === "s") out += value === null ? "nil" : asString(value);
1606
+ else if (spec === "d" || spec === "i") out += String(Math.trunc(asNumber(value)));
1607
+ else if (spec === "f") out += String(asNumber(value));
1608
+ else if (spec === "q")
1609
+ out += `"${asString(value).replaceAll("\\", "\\\\").replaceAll('"', '\\"')}"`;
1610
+ else out += spec ?? "";
1611
+ }
1612
+ return out;
1613
+ }
1614
+ function runLua(source, env) {
1615
+ let body;
1616
+ try {
1617
+ body = new Parser(tokenize(source)).parseChunk();
1618
+ } catch (error) {
1619
+ const message = error instanceof Error ? error.message : String(error);
1620
+ throw new Error(`Error compiling script: ${message}`);
1621
+ }
1622
+ const flow = execBlock(body, env);
1623
+ return flow.k === "return" ? flow.values : [null];
1624
+ }
1625
+
1626
+ // src/store.ts
1627
+ function emptyStream() {
1628
+ return { entries: [], groups: /* @__PURE__ */ new Map(), lastMs: -1n, lastSeq: 0n };
1629
+ }
1630
+ function parseStreamId(id) {
1631
+ const dash = id.indexOf("-");
1632
+ if (dash <= 0 || dash === id.length - 1) return null;
1633
+ const msText = id.slice(0, dash);
1634
+ const seqText = id.slice(dash + 1);
1635
+ if (!/^\d+$/.test(msText) || !/^\d+$/.test(seqText)) return null;
1636
+ return { ms: BigInt(msText), seq: BigInt(seqText) };
1637
+ }
1638
+ function cmpId(a, b) {
1639
+ const left = parseStreamId(a);
1640
+ const right = parseStreamId(b);
1641
+ if (!left || !right) return a < b ? -1 : a > b ? 1 : 0;
1642
+ if (left.ms !== right.ms) return left.ms < right.ms ? -1 : 1;
1643
+ if (left.seq !== right.seq) return left.seq < right.seq ? -1 : 1;
1644
+ return 0;
1645
+ }
1646
+ var Database = class {
1647
+ constructor(clock) {
1648
+ this.clock = clock;
1649
+ }
1650
+ clock;
1651
+ values = /* @__PURE__ */ new Map();
1652
+ expires = /* @__PURE__ */ new Map();
1653
+ gens = /* @__PURE__ */ new Map();
1654
+ lru = /* @__PURE__ */ new Map();
1655
+ order = [];
1656
+ tick = 0;
1657
+ now() {
1658
+ return this.clock.now();
1659
+ }
1660
+ bump(key) {
1661
+ this.gens.set(key, (this.gens.get(key) ?? 0) + 1);
1662
+ }
1663
+ sweep(key) {
1664
+ const expiry = this.expires.get(key);
1665
+ if (expiry === void 0 || expiry > this.clock.now()) return;
1666
+ this.expires.delete(key);
1667
+ if (this.values.delete(key)) {
1668
+ this.bump(key);
1669
+ this.order = this.order.filter((item) => item !== key);
1670
+ this.lru.delete(key);
1671
+ }
1672
+ }
1673
+ generation(key) {
1674
+ this.sweep(key);
1675
+ return this.gens.get(key) ?? 0;
1676
+ }
1677
+ get(key) {
1678
+ this.sweep(key);
1679
+ const value = this.values.get(key);
1680
+ if (value) this.lru.set(key, ++this.tick);
1681
+ return value;
1682
+ }
1683
+ has(key) {
1684
+ return this.get(key) !== void 0;
1685
+ }
1686
+ put(key, value) {
1687
+ this.sweep(key);
1688
+ if (!this.values.has(key)) this.order.push(key);
1689
+ this.values.set(key, value);
1690
+ this.bump(key);
1691
+ this.lru.set(key, ++this.tick);
1692
+ }
1693
+ /** Remove the key when a collection has no elements left. Streams are kept. */
1694
+ dropIfEmpty(key) {
1695
+ const value = this.values.get(key);
1696
+ if (!value) return;
1697
+ const empty = value.kind === "list" && value.items.length === 0 || value.kind === "set" && value.members.size === 0 || value.kind === "zset" && value.members.size === 0 || value.kind === "hash" && value.fields.size === 0;
1698
+ if (empty) this.delete(key);
1699
+ }
1700
+ setExpiry(key, at) {
1701
+ if (!this.values.has(key)) return;
1702
+ if (at === null) this.expires.delete(key);
1703
+ else this.expires.set(key, at);
1704
+ this.bump(key);
1705
+ }
1706
+ expiry(key) {
1707
+ this.sweep(key);
1708
+ return this.expires.get(key) ?? null;
1709
+ }
1710
+ pttl(key) {
1711
+ this.sweep(key);
1712
+ if (!this.values.has(key)) return -2;
1713
+ const expiry = this.expires.get(key);
1714
+ if (expiry === void 0) return -1;
1715
+ return Math.max(0, expiry - this.clock.now());
1716
+ }
1717
+ delete(key) {
1718
+ this.sweep(key);
1719
+ this.expires.delete(key);
1720
+ const had = this.values.delete(key);
1721
+ if (had) {
1722
+ this.bump(key);
1723
+ this.order = this.order.filter((item) => item !== key);
1724
+ this.lru.delete(key);
1725
+ }
1726
+ return had;
1727
+ }
1728
+ rename(source, dest) {
1729
+ const value = this.get(source);
1730
+ if (!value) return false;
1731
+ const expiry = this.expires.get(source) ?? null;
1732
+ this.delete(source);
1733
+ this.put(dest, value);
1734
+ if (expiry !== null) this.expires.set(dest, expiry);
1735
+ return true;
1736
+ }
1737
+ keys() {
1738
+ const live = [];
1739
+ for (const key of this.order) {
1740
+ this.sweep(key);
1741
+ if (this.values.has(key)) live.push(key);
1742
+ }
1743
+ this.order = live;
1744
+ return live;
1745
+ }
1746
+ matching(pattern) {
1747
+ return this.keys().filter((key) => globMatch(pattern, key));
1748
+ }
1749
+ clear() {
1750
+ for (const key of [...this.values.keys()]) this.delete(key);
1751
+ this.values.clear();
1752
+ this.expires.clear();
1753
+ this.order = [];
1754
+ this.lru.clear();
1755
+ }
1756
+ memory() {
1757
+ let total = 0;
1758
+ for (const [key, value] of this.values) total += key.length + valueBytes(value);
1759
+ return total;
1760
+ }
1761
+ evictLru() {
1762
+ let best = null;
1763
+ let bestTick = Number.POSITIVE_INFINITY;
1764
+ for (const [key, tick] of this.lru) {
1765
+ if (!this.values.has(key)) continue;
1766
+ if (tick < bestTick) {
1767
+ best = key;
1768
+ bestTick = tick;
1769
+ }
1770
+ }
1771
+ if (best === null) return null;
1772
+ this.delete(best);
1773
+ return best;
1774
+ }
1775
+ zsetOrdered(value) {
1776
+ return [...value.members.values()].sort((a, b) => {
1777
+ if (a.score !== b.score) return a.score - b.score;
1778
+ return cmpBytes(a.member, b.member);
1779
+ });
1780
+ }
1781
+ };
1782
+ function valueBytes(value) {
1783
+ switch (value.kind) {
1784
+ case "string":
1785
+ return value.bytes.length;
1786
+ case "list":
1787
+ return value.items.reduce((sum, item) => sum + item.length, 0);
1788
+ case "set":
1789
+ return [...value.members.values()].reduce((sum, item) => sum + item.length, 0);
1790
+ case "zset":
1791
+ return [...value.members.values()].reduce((sum, item) => sum + item.member.length + 8, 0);
1792
+ case "hash":
1793
+ return [...value.fields.entries()].reduce(
1794
+ (sum, [field, item]) => sum + field.length + item.length,
1795
+ 0
1796
+ );
1797
+ case "stream":
1798
+ return value.stream.entries.reduce(
1799
+ (sum, entry) => sum + entry.id.length + entry.fields.reduce((inner, field) => inner + field.length, 0),
1800
+ 0
1801
+ );
1802
+ default: {
1803
+ const never = value;
1804
+ return never;
1805
+ }
1806
+ }
1807
+ }
1808
+ function memberKey(bytes) {
1809
+ return latin1(bytes);
1810
+ }
1811
+
1812
+ // src/engine.ts
1813
+ var RedisConnectionError = class extends Error {
1814
+ constructor(message) {
1815
+ super(message);
1816
+ this.name = "RedisConnectionError";
1817
+ }
1818
+ };
1819
+ var RedisReplyError = class extends Error {
1820
+ constructor(message) {
1821
+ super(message);
1822
+ this.name = "RedisReplyError";
1823
+ }
1824
+ };
1825
+ var PREAUTH = /* @__PURE__ */ new Set(["auth", "hello", "quit", "reset"]);
1826
+ var PUBSUB_OK = /* @__PURE__ */ new Set([
1827
+ "subscribe",
1828
+ "unsubscribe",
1829
+ "psubscribe",
1830
+ "punsubscribe",
1831
+ "ssubscribe",
1832
+ "sunsubscribe",
1833
+ "ping",
1834
+ "quit",
1835
+ "reset"
1836
+ ]);
1837
+ var IMMEDIATE = /* @__PURE__ */ new Set([
1838
+ "exec",
1839
+ "discard",
1840
+ "multi",
1841
+ "watch",
1842
+ "unwatch",
1843
+ "quit",
1844
+ "reset",
1845
+ "subscribe",
1846
+ "unsubscribe",
1847
+ "psubscribe",
1848
+ "punsubscribe"
1849
+ ]);
1850
+ var WRITES = /* @__PURE__ */ new Set([
1851
+ "set",
1852
+ "setnx",
1853
+ "setex",
1854
+ "psetex",
1855
+ "mset",
1856
+ "append",
1857
+ "setrange",
1858
+ "incr",
1859
+ "incrby",
1860
+ "incrbyfloat",
1861
+ "decr",
1862
+ "decrby",
1863
+ "getset",
1864
+ "getdel",
1865
+ "getex",
1866
+ "del",
1867
+ "unlink",
1868
+ "expire",
1869
+ "pexpire",
1870
+ "expireat",
1871
+ "pexpireat",
1872
+ "persist",
1873
+ "rename",
1874
+ "renamenx",
1875
+ "copy",
1876
+ "flushdb",
1877
+ "flushall",
1878
+ "hset",
1879
+ "hmset",
1880
+ "hdel",
1881
+ "hincrby",
1882
+ "hincrbyfloat",
1883
+ "hsetnx",
1884
+ "lpush",
1885
+ "rpush",
1886
+ "lpop",
1887
+ "rpop",
1888
+ "lset",
1889
+ "lrem",
1890
+ "ltrim",
1891
+ "linsert",
1892
+ "lmove",
1893
+ "rpoplpush",
1894
+ "blpop",
1895
+ "brpop",
1896
+ "sadd",
1897
+ "srem",
1898
+ "spop",
1899
+ "smove",
1900
+ "zadd",
1901
+ "zrem",
1902
+ "zincrby",
1903
+ "zpopmin",
1904
+ "zpopmax",
1905
+ "bzpopmin",
1906
+ "bzpopmax",
1907
+ "zremrangebyrank",
1908
+ "zremrangebyscore",
1909
+ "xadd",
1910
+ "xdel",
1911
+ "xtrim",
1912
+ "xgroup",
1913
+ "xreadgroup",
1914
+ "xack",
1915
+ "xclaim",
1916
+ "xautoclaim",
1917
+ "eval",
1918
+ "evalsha"
1919
+ ]);
1920
+ var SCRIPT_BLOCKED = /* @__PURE__ */ new Set([
1921
+ "blpop",
1922
+ "brpop",
1923
+ "bzpopmin",
1924
+ "bzpopmax",
1925
+ "auth",
1926
+ "select",
1927
+ "watch",
1928
+ "multi",
1929
+ "exec",
1930
+ "discard",
1931
+ "subscribe",
1932
+ "unsubscribe",
1933
+ "psubscribe",
1934
+ "punsubscribe",
1935
+ "shutdown"
1936
+ ]);
1937
+ var VERSION = "7.2.4";
1938
+ function text(bytes) {
1939
+ return bytes ? latin1(bytes) : "";
1940
+ }
1941
+ function upper(bytes) {
1942
+ return text(bytes).toUpperCase();
1943
+ }
1944
+ function scoreText(score) {
1945
+ if (Number.isNaN(score)) return "nan";
1946
+ if (score === Infinity) return "inf";
1947
+ if (score === -Infinity) return "-inf";
1948
+ if (Number.isInteger(score)) return String(score);
1949
+ return String(score);
1950
+ }
1951
+ function parseScore(bytes) {
1952
+ if (!bytes) return null;
1953
+ const value = text(bytes).trim().toLowerCase();
1954
+ if (value === "+inf" || value === "inf") return Infinity;
1955
+ if (value === "-inf") return -Infinity;
1956
+ if (!/^[-+]?(?:\d+\.?\d*|\.\d+)(?:[eE][-+]?\d+)?$/.test(value)) return null;
1957
+ const score = Number(value);
1958
+ return Number.isFinite(score) ? score : null;
1959
+ }
1960
+ function parseTimeout(bytes) {
1961
+ if (!bytes) return null;
1962
+ const value = Number(text(bytes));
1963
+ if (!Number.isFinite(value) || value < 0) return null;
1964
+ return value;
1965
+ }
1966
+ function decodeReply(reply) {
1967
+ switch (reply.t) {
1968
+ case "simple":
1969
+ return reply.v;
1970
+ case "error":
1971
+ throw new RedisReplyError(reply.v);
1972
+ case "int":
1973
+ return typeof reply.v === "bigint" ? intReply(reply.v) : reply.v;
1974
+ case "bulk":
1975
+ return reply.v === null ? null : utf8Text(reply.v);
1976
+ case "array":
1977
+ return reply.v === null ? null : reply.v.map((item) => decodeReply(item));
1978
+ case "null":
1979
+ return null;
1980
+ case "bool":
1981
+ return reply.v;
1982
+ case "double":
1983
+ return reply.v;
1984
+ case "map": {
1985
+ const record = {};
1986
+ for (const [key, value] of reply.v) record[String(decodeReply(key))] = decodeReply(value);
1987
+ return record;
1988
+ }
1989
+ case "push":
1990
+ return reply.v.map((item) => decodeReply(item));
1991
+ default: {
1992
+ const never = reply;
1993
+ return never;
1994
+ }
1995
+ }
1996
+ }
1997
+ function luaToBytes(value) {
1998
+ if (typeof value === "string") return fromLatin1(value);
1999
+ if (typeof value === "number")
2000
+ return utf8(Number.isInteger(value) ? String(value) : String(value));
2001
+ if (typeof value === "boolean") return utf8(value ? "1" : "");
2002
+ if (value === null) return utf8("");
2003
+ if (value instanceof LuaStatus) return utf8(value.ok);
2004
+ return utf8("");
2005
+ }
2006
+ function replyToLua(reply) {
2007
+ switch (reply.t) {
2008
+ case "error":
2009
+ throw new Error(reply.v);
2010
+ case "simple":
2011
+ return new LuaStatus(reply.v);
2012
+ case "int":
2013
+ return typeof reply.v === "bigint" ? Number(reply.v) : reply.v;
2014
+ case "bulk":
2015
+ return reply.v === null ? false : latin1(reply.v);
2016
+ case "array":
2017
+ case "push": {
2018
+ if (reply.t === "array" && reply.v === null) return false;
2019
+ const items = reply.v ?? [];
2020
+ const table = new LuaTable();
2021
+ items.forEach((item, index) => {
2022
+ table.set(index + 1, replyToLua(item));
2023
+ });
2024
+ return table;
2025
+ }
2026
+ case "null":
2027
+ return false;
2028
+ case "bool":
2029
+ return reply.v;
2030
+ case "double":
2031
+ return reply.v;
2032
+ case "map": {
2033
+ const table = new LuaTable();
2034
+ for (const [key, value] of reply.v) {
2035
+ const luaKey = replyToLua(key);
2036
+ table.set(luaKey === false ? "" : luaKey, replyToLua(value));
2037
+ }
2038
+ return table;
2039
+ }
2040
+ default: {
2041
+ const never = reply;
2042
+ return never;
2043
+ }
2044
+ }
2045
+ }
2046
+ function luaToReply(value) {
2047
+ if (value === null || value === false) return nil();
2048
+ if (value === true) return int(1);
2049
+ if (value instanceof LuaStatus) return simple(value.ok);
2050
+ if (value instanceof LuaFailure) return err(value.err);
2051
+ if (typeof value === "number") {
2052
+ if (Number.isInteger(value)) return int(value);
2053
+ return bulk(String(value));
2054
+ }
2055
+ if (typeof value === "string") return { t: "bulk", v: fromLatin1(value) };
2056
+ if (value instanceof LuaTable) {
2057
+ const okField = value.get("ok");
2058
+ const errField = value.get("err");
2059
+ if (typeof okField === "string") return simple(okField);
2060
+ if (typeof errField === "string") return err(errField);
2061
+ const items = [];
2062
+ for (let i = 1; value.has(i); i++) items.push(luaToReply(value.get(i)));
2063
+ return arr(items);
2064
+ }
2065
+ return nil();
2066
+ }
2067
+ function entryFields(entry) {
2068
+ return arr([bulk(entry.id), arr(entry.fields.map((field) => ({ t: "bulk", v: field })))]);
2069
+ }
2070
+ var Redis = class {
2071
+ clock;
2072
+ dbs;
2073
+ password;
2074
+ users;
2075
+ authRequired;
2076
+ maxmemory;
2077
+ policy;
2078
+ rngState;
2079
+ faultValue = null;
2080
+ tail = Promise.resolve();
2081
+ sessions = /* @__PURE__ */ new Set();
2082
+ nextClientId = 1;
2083
+ waiters = [];
2084
+ channelSubs = /* @__PURE__ */ new Map();
2085
+ patternSubs = /* @__PURE__ */ new Map();
2086
+ scripts = /* @__PURE__ */ new Map();
2087
+ invoked = "";
2088
+ scriptDepth = 0;
2089
+ commandCount = 0;
2090
+ closed = false;
2091
+ consumersPaused = false;
2092
+ disconnectListeners = /* @__PURE__ */ new Set();
2093
+ constructor(options = {}) {
2094
+ this.clock = options.clock ?? wallClock();
2095
+ const count = options.databases ?? 16;
2096
+ this.dbs = Array.from({ length: count }, () => new Database(this.clock));
2097
+ this.password = options.password;
2098
+ this.users = options.users ?? [];
2099
+ this.authRequired = options.password !== void 0 || this.users.length > 0;
2100
+ this.maxmemory = options.maxmemory ?? 0;
2101
+ this.policy = options.maxmemoryPolicy ?? "noeviction";
2102
+ this.rngState = options.seed ?? 1;
2103
+ }
2104
+ client() {
2105
+ return new RedisClient(this, this.createSession());
2106
+ }
2107
+ advance(ms) {
2108
+ this.clock.advance(ms);
2109
+ }
2110
+ fault(value) {
2111
+ this.faultValue = value;
2112
+ if (value === "busy") return;
2113
+ if (value === "disconnect" || value === "timeout") {
2114
+ for (const session of [...this.sessions]) this.dropSession(session, value);
2115
+ }
2116
+ }
2117
+ pauseConsumers(paused = true) {
2118
+ this.consumersPaused = paused;
2119
+ if (!paused) this.enqueue(() => this.wakeAll());
2120
+ }
2121
+ waiterCount() {
2122
+ return this.waiters.length;
2123
+ }
2124
+ inspect(db = 0) {
2125
+ const database = this.database(db);
2126
+ return database.keys().map((key) => ({
2127
+ key,
2128
+ type: database.get(key)?.kind ?? "none",
2129
+ pttl: database.pttl(key)
2130
+ }));
2131
+ }
2132
+ reset() {
2133
+ for (const database of this.dbs) database.clear();
2134
+ this.scripts.clear();
2135
+ for (const waiter of [...this.waiters]) {
2136
+ this.finishWaiter(waiter, nilArray());
2137
+ }
2138
+ for (const session of this.sessions) {
2139
+ session.multi = null;
2140
+ session.multiFailed = false;
2141
+ session.watching = [];
2142
+ }
2143
+ }
2144
+ close() {
2145
+ this.closed = true;
2146
+ for (const waiter of [...this.waiters]) {
2147
+ this.failWaiter(waiter, new RedisConnectionError("Connection is closed"));
2148
+ }
2149
+ for (const session of [...this.sessions]) session.dead = true;
2150
+ }
2151
+ onDisconnect(listener) {
2152
+ this.disconnectListeners.add(listener);
2153
+ return () => this.disconnectListeners.delete(listener);
2154
+ }
2155
+ createSession() {
2156
+ const session = {
2157
+ id: this.nextClientId++,
2158
+ db: 0,
2159
+ protocol: 2,
2160
+ name: "",
2161
+ libName: "",
2162
+ libVer: "",
2163
+ authenticated: !this.authRequired,
2164
+ username: "default",
2165
+ readOnly: false,
2166
+ multi: null,
2167
+ multiFailed: false,
2168
+ watching: [],
2169
+ channels: /* @__PURE__ */ new Set(),
2170
+ patterns: /* @__PURE__ */ new Set(),
2171
+ dead: false,
2172
+ quit: false,
2173
+ inExec: false,
2174
+ onPush: () => void 0
2175
+ };
2176
+ this.sessions.add(session);
2177
+ return session;
2178
+ }
2179
+ perform(session, name, args) {
2180
+ if (session.dead || this.closed) {
2181
+ return Promise.reject(new RedisConnectionError("Connection is closed"));
2182
+ }
2183
+ return new Promise((resolve, reject) => {
2184
+ this.enqueue(async () => {
2185
+ try {
2186
+ if (session.dead || this.closed) {
2187
+ reject(new RedisConnectionError("Connection is closed"));
2188
+ return;
2189
+ }
2190
+ if (this.faultValue === "disconnect" || this.faultValue === "timeout") {
2191
+ const message = this.faultValue === "timeout" ? "Command timed out" : "Connection reset";
2192
+ this.dropSession(session, this.faultValue);
2193
+ reject(new RedisConnectionError(message));
2194
+ return;
2195
+ }
2196
+ await this.applyLatency();
2197
+ if (session.dead || this.closed) {
2198
+ reject(new RedisConnectionError("Connection is closed"));
2199
+ return;
2200
+ }
2201
+ const result = this.dispatch(session, name, args);
2202
+ if (result instanceof Promise) {
2203
+ result.then(resolve, reject);
2204
+ return;
2205
+ }
2206
+ resolve(result);
2207
+ } catch (error) {
2208
+ reject(error instanceof Error ? error : new Error(String(error)));
2209
+ }
2210
+ });
2211
+ });
2212
+ }
2213
+ enqueue(task) {
2214
+ this.tail = this.tail.then(task, task).then(
2215
+ () => void 0,
2216
+ () => void 0
2217
+ );
2218
+ }
2219
+ applyLatency() {
2220
+ const fault = this.faultValue;
2221
+ if (fault === null || typeof fault === "string") return Promise.resolve();
2222
+ const target = this.clock.now() + fault.latencyMs;
2223
+ return new Promise((resolve) => {
2224
+ let timer;
2225
+ const finish = () => {
2226
+ if (this.clock.now() < target) return;
2227
+ unsub();
2228
+ if (timer !== void 0) clearTimeout(timer);
2229
+ resolve();
2230
+ };
2231
+ const unsub = this.clock.subscribe(finish);
2232
+ if (!this.clock.manual) timer = setTimeout(finish, fault.latencyMs);
2233
+ finish();
2234
+ });
2235
+ }
2236
+ dropSession(session, reason) {
2237
+ session.dead = true;
2238
+ const message = reason === "timeout" ? "Command timed out" : "Connection reset";
2239
+ for (const waiter of [...this.waiters]) {
2240
+ if (waiter.db >= 0 && this.sessionOf(waiter) === session) {
2241
+ this.failWaiter(waiter, new RedisConnectionError(message));
2242
+ }
2243
+ }
2244
+ this.leavePubsub(session);
2245
+ this.sessions.delete(session);
2246
+ for (const listener of this.disconnectListeners) listener(session);
2247
+ }
2248
+ sessionOf(waiter) {
2249
+ for (const session of this.sessions) {
2250
+ if (session === waiterSession.get(waiter)) return session;
2251
+ }
2252
+ return waiterSession.get(waiter) ?? null;
2253
+ }
2254
+ database(index) {
2255
+ const database = this.dbs[index];
2256
+ if (!database) throw new Error("db index out of range");
2257
+ return database;
2258
+ }
2259
+ random() {
2260
+ this.rngState = Math.imul(1664525, this.rngState) + 1013904223 >>> 0;
2261
+ return this.rngState / 4294967296;
2262
+ }
2263
+ memory() {
2264
+ return this.dbs.reduce((sum, database) => sum + database.memory(), 0);
2265
+ }
2266
+ dispatch(session, nameRaw, args) {
2267
+ const command = nameRaw.toLowerCase();
2268
+ this.invoked = nameRaw;
2269
+ this.commandCount++;
2270
+ if (this.authRequired && !session.authenticated && !PREAUTH.has(command)) return noAuth();
2271
+ if (session.channels.size + session.patterns.size > 0 && !PUBSUB_OK.has(command)) {
2272
+ return pubsubOnly(command);
2273
+ }
2274
+ if (this.faultValue === "busy" && command !== "script" && command !== "shutdown")
2275
+ return busyError();
2276
+ if (session.multi && !IMMEDIATE.has(command)) {
2277
+ const checked = this.execCmd(session, command, args, false);
2278
+ if (checked instanceof Promise || checked.t === "error") {
2279
+ session.multiFailed = true;
2280
+ return checked instanceof Promise ? syntaxError() : checked;
2281
+ }
2282
+ session.multi.push({
2283
+ name: command,
2284
+ args: args.map((arg) => Uint8Array.from(arg))
2285
+ });
2286
+ return queued();
2287
+ }
2288
+ return this.execCmd(session, command, args, true);
2289
+ }
2290
+ execCmd(session, command, args, apply) {
2291
+ if (apply && this.scriptDepth > 0 && SCRIPT_BLOCKED.has(command)) return scriptDenied();
2292
+ if (apply && command === "xread" && args.some((arg) => text(arg).toUpperCase() === "BLOCK") && this.scriptDepth > 0) {
2293
+ return scriptDenied();
2294
+ }
2295
+ if (apply && command === "xreadgroup" && args.some((arg) => text(arg).toUpperCase() === "BLOCK") && this.scriptDepth > 0) {
2296
+ return scriptDenied();
2297
+ }
2298
+ const denied = this.guardWrite(session, command, apply);
2299
+ if (denied) return denied;
2300
+ switch (command) {
2301
+ case "ping":
2302
+ return this.cmdPing(session, args);
2303
+ case "echo":
2304
+ return args.length === 1 ? bulk(args[0] ?? null) : arityError("echo");
2305
+ case "quit":
2306
+ session.quit = true;
2307
+ return ok();
2308
+ case "auth":
2309
+ return this.cmdAuth(session, args);
2310
+ case "hello":
2311
+ return this.cmdHello(session, args);
2312
+ case "select":
2313
+ return this.cmdSelect(session, args);
2314
+ case "reset":
2315
+ return this.cmdReset(session);
2316
+ case "client":
2317
+ return this.cmdClient(session, args, apply);
2318
+ case "info":
2319
+ return bulk(this.info(session, args[0] ? text(args[0]) : "default"));
2320
+ case "time":
2321
+ return this.cmdTime();
2322
+ case "role":
2323
+ return arr([simple("master"), int(0), arr([])]);
2324
+ case "readonly":
2325
+ case "asking":
2326
+ return clusterDisabled();
2327
+ case "cluster":
2328
+ return clusterDisabled();
2329
+ case "module":
2330
+ return args.length >= 1 && upper(args[0]) === "LIST" ? arr([]) : arityError("module");
2331
+ case "command":
2332
+ return arr([]);
2333
+ case "config":
2334
+ return this.cmdConfig(args, apply);
2335
+ case "dbsize":
2336
+ return int(this.database(session.db).keys().length);
2337
+ case "flushdb":
2338
+ if (!apply) return queued();
2339
+ this.database(session.db).clear();
2340
+ return ok();
2341
+ case "flushall":
2342
+ if (!apply) return queued();
2343
+ for (const database of this.dbs) database.clear();
2344
+ return ok();
2345
+ case "randomkey":
2346
+ return this.cmdRandomKey(session);
2347
+ case "scan":
2348
+ return this.cmdScan(session, args);
2349
+ case "keys":
2350
+ return this.cmdKeys(session, args);
2351
+ case "type":
2352
+ return this.cmdType(session, args);
2353
+ case "exists":
2354
+ return this.cmdExists(session, args);
2355
+ case "del":
2356
+ case "unlink":
2357
+ return this.cmdDel(session, args, apply);
2358
+ case "rename":
2359
+ return this.cmdRename(session, args, apply, false);
2360
+ case "renamenx":
2361
+ return this.cmdRename(session, args, apply, true);
2362
+ case "expire":
2363
+ case "pexpire":
2364
+ case "expireat":
2365
+ case "pexpireat":
2366
+ return this.cmdExpire(session, command, args, apply);
2367
+ case "ttl":
2368
+ case "pttl":
2369
+ return this.cmdTtl(session, command, args);
2370
+ case "persist":
2371
+ return this.cmdPersist(session, args, apply);
2372
+ case "get":
2373
+ return this.cmdGet(session, args);
2374
+ case "set":
2375
+ return this.cmdSet(session, args, apply);
2376
+ case "setnx":
2377
+ return args.length === 2 ? this.cmdSet(session, [args[0] ?? utf8(""), args[1] ?? utf8(""), utf8("NX")], apply) : arityError("setnx");
2378
+ case "setex":
2379
+ return args.length === 3 ? this.cmdSet(
2380
+ session,
2381
+ [args[0] ?? utf8(""), args[2] ?? utf8(""), utf8("EX"), args[1] ?? utf8("")],
2382
+ apply
2383
+ ) : arityError("setex");
2384
+ case "psetex":
2385
+ return args.length === 3 ? this.cmdSet(
2386
+ session,
2387
+ [args[0] ?? utf8(""), args[2] ?? utf8(""), utf8("PX"), args[1] ?? utf8("")],
2388
+ apply
2389
+ ) : arityError("psetex");
2390
+ case "mget":
2391
+ return this.cmdMget(session, args);
2392
+ case "mset":
2393
+ return this.cmdMset(session, args, apply);
2394
+ case "append":
2395
+ return this.cmdAppend(session, args, apply);
2396
+ case "strlen":
2397
+ return this.cmdStrlen(session, args);
2398
+ case "getrange":
2399
+ return this.cmdGetRange(session, args);
2400
+ case "setrange":
2401
+ return this.cmdSetRange(session, args, apply);
2402
+ case "incr":
2403
+ return this.cmdIncr(session, args, 1n, apply);
2404
+ case "decr":
2405
+ return this.cmdIncr(session, args, -1n, apply);
2406
+ case "incrby":
2407
+ case "decrby":
2408
+ return this.cmdIncrBy(session, command, args, apply);
2409
+ case "incrbyfloat":
2410
+ return this.cmdIncrByFloat(session, args, apply, false);
2411
+ case "getset":
2412
+ return args.length === 2 ? this.cmdSet(session, [args[0] ?? utf8(""), args[1] ?? utf8(""), utf8("GET")], apply) : arityError("getset");
2413
+ case "getdel":
2414
+ return this.cmdGetDel(session, args, apply);
2415
+ case "getex":
2416
+ return this.cmdGetEx(session, args, apply);
2417
+ case "hset":
2418
+ case "hmset":
2419
+ case "hsetnx":
2420
+ case "hget":
2421
+ case "hdel":
2422
+ case "hexists":
2423
+ case "hgetall":
2424
+ case "hkeys":
2425
+ case "hvals":
2426
+ case "hlen":
2427
+ case "hmget":
2428
+ case "hincrby":
2429
+ case "hincrbyfloat":
2430
+ case "hstrlen":
2431
+ return this.cmdHash(session, command, args, apply);
2432
+ case "lpush":
2433
+ case "rpush":
2434
+ case "lpop":
2435
+ case "rpop":
2436
+ case "llen":
2437
+ case "lrange":
2438
+ case "lindex":
2439
+ case "lset":
2440
+ case "lrem":
2441
+ case "ltrim":
2442
+ case "linsert":
2443
+ case "lmove":
2444
+ case "rpoplpush":
2445
+ return this.cmdList(session, command, args, apply);
2446
+ case "blpop":
2447
+ return this.cmdBlockingList(session, args, apply, "left");
2448
+ case "brpop":
2449
+ return this.cmdBlockingList(session, args, apply, "right");
2450
+ case "sadd":
2451
+ case "srem":
2452
+ case "sismember":
2453
+ case "smismember":
2454
+ case "smembers":
2455
+ case "scard":
2456
+ case "spop":
2457
+ case "srandmember":
2458
+ case "sinter":
2459
+ case "sunion":
2460
+ case "sdiff":
2461
+ case "smove":
2462
+ return this.cmdSetFamily(session, command, args, apply);
2463
+ case "zadd":
2464
+ case "zrem":
2465
+ case "zcard":
2466
+ case "zscore":
2467
+ case "zmscore":
2468
+ case "zrank":
2469
+ case "zrevrank":
2470
+ case "zrange":
2471
+ case "zrevrange":
2472
+ case "zrangebyscore":
2473
+ case "zrevrangebyscore":
2474
+ case "zcount":
2475
+ case "zpopmin":
2476
+ case "zpopmax":
2477
+ case "zincrby":
2478
+ case "zremrangebyscore":
2479
+ case "zremrangebyrank":
2480
+ return this.cmdZset(session, command, args, apply);
2481
+ case "bzpopmin":
2482
+ return this.cmdBlockingZset(session, args, apply, false);
2483
+ case "bzpopmax":
2484
+ return this.cmdBlockingZset(session, args, apply, true);
2485
+ case "xadd":
2486
+ case "xlen":
2487
+ case "xrange":
2488
+ case "xrevrange":
2489
+ case "xdel":
2490
+ case "xtrim":
2491
+ case "xgroup":
2492
+ case "xread":
2493
+ case "xreadgroup":
2494
+ case "xack":
2495
+ case "xpending":
2496
+ case "xclaim":
2497
+ case "xautoclaim":
2498
+ case "xinfo":
2499
+ return this.cmdStream(session, command, args, apply);
2500
+ case "subscribe":
2501
+ case "unsubscribe":
2502
+ case "psubscribe":
2503
+ case "punsubscribe":
2504
+ return this.cmdSub(session, command, args);
2505
+ case "publish":
2506
+ return this.cmdPublish(args);
2507
+ case "pubsub":
2508
+ return this.cmdPubsub(args);
2509
+ case "multi":
2510
+ if (session.multi) return nestedMulti();
2511
+ session.multi = [];
2512
+ session.multiFailed = false;
2513
+ return ok();
2514
+ case "exec":
2515
+ return this.cmdExec(session);
2516
+ case "discard":
2517
+ return this.cmdDiscard(session);
2518
+ case "watch":
2519
+ return this.cmdWatch(session, args);
2520
+ case "unwatch":
2521
+ session.watching = [];
2522
+ return ok();
2523
+ case "eval":
2524
+ case "evalsha":
2525
+ return this.cmdEval(session, command, args, apply);
2526
+ case "script":
2527
+ return this.cmdScript(args, apply);
2528
+ case "shutdown":
2529
+ return ok();
2530
+ default:
2531
+ return unknownCommand(this.invoked, args);
2532
+ }
2533
+ }
2534
+ guardWrite(session, command, apply) {
2535
+ if (!apply || !WRITES.has(command)) return null;
2536
+ if (session.readOnly) return noPerm(command);
2537
+ if (this.faultValue === "readonly") return readonlyError();
2538
+ if (this.faultValue === "oom") return oomError();
2539
+ if (this.maxmemory > 0 && this.memory() > this.maxmemory && this.policy === "noeviction") {
2540
+ return oomError();
2541
+ }
2542
+ if (this.maxmemory > 0 && this.policy === "allkeys-lru") {
2543
+ while (this.memory() > this.maxmemory) {
2544
+ let evicted = false;
2545
+ for (const database of this.dbs) {
2546
+ if (database.evictLru()) {
2547
+ evicted = true;
2548
+ break;
2549
+ }
2550
+ }
2551
+ if (!evicted) break;
2552
+ }
2553
+ }
2554
+ return null;
2555
+ }
2556
+ cmdPing(session, args) {
2557
+ if (args.length > 1) return arityError("ping");
2558
+ if (session.channels.size + session.patterns.size > 0) {
2559
+ return push([bulk("pong"), args[0] ? { t: "bulk", v: args[0] } : bulk("")]);
2560
+ }
2561
+ if (args.length === 1) return { t: "bulk", v: args[0] ?? null };
2562
+ return simple("PONG");
2563
+ }
2564
+ cmdAuth(session, args) {
2565
+ if (args.length !== 1 && args.length !== 2) return arityError("auth");
2566
+ const user = args.length === 2 ? text(args[0]) : "default";
2567
+ const password = text(args.length === 2 ? args[1] : args[0]);
2568
+ if (!this.authRequired) return authWithoutPassword(password);
2569
+ const account = user === "default" ? {
2570
+ password: this.users.find((item) => item.name === "default")?.password ?? this.password,
2571
+ readOnly: this.users.find((item) => item.name === "default")?.readOnly === true
2572
+ } : this.users.find((item) => item.name === user);
2573
+ if (!account || account.password === void 0 || account.password !== password)
2574
+ return wrongPass();
2575
+ session.authenticated = true;
2576
+ session.username = user;
2577
+ session.readOnly = account.readOnly === true;
2578
+ return ok();
2579
+ }
2580
+ cmdHello(session, args) {
2581
+ let protocol = session.protocol;
2582
+ let index = 0;
2583
+ if (args.length > 0) {
2584
+ const requested = Number(text(args[0]));
2585
+ if (requested !== 2 && requested !== 3) return err("NOPROTO unsupported protocol version");
2586
+ protocol = requested;
2587
+ index = 1;
2588
+ }
2589
+ while (index < args.length) {
2590
+ const option = upper(args[index]);
2591
+ if (option === "AUTH") {
2592
+ const user = args[index + 1];
2593
+ const pass = args[index + 2];
2594
+ if (!user || !pass) return syntaxError();
2595
+ const result = this.cmdAuth(session, [user, pass]);
2596
+ if (result.t === "error") return result;
2597
+ index += 3;
2598
+ continue;
2599
+ }
2600
+ if (option === "SETNAME") {
2601
+ const name = args[index + 1];
2602
+ if (!name) return syntaxError();
2603
+ session.name = text(name);
2604
+ index += 2;
2605
+ continue;
2606
+ }
2607
+ return syntaxError();
2608
+ }
2609
+ session.protocol = protocol;
2610
+ return map([
2611
+ [bulk("server"), bulk("redis")],
2612
+ [bulk("version"), bulk(VERSION)],
2613
+ [bulk("proto"), int(protocol)],
2614
+ [bulk("id"), int(session.id)],
2615
+ [bulk("mode"), bulk("standalone")],
2616
+ [bulk("role"), bulk("master")],
2617
+ [bulk("modules"), arr([])]
2618
+ ]);
2619
+ }
2620
+ cmdSelect(session, args) {
2621
+ if (args.length !== 1) return arityError("select");
2622
+ const index = parseI64(args[0] ?? utf8(""));
2623
+ if (index === null) return notInteger();
2624
+ if (index < 0n || index >= BigInt(this.dbs.length)) return err("ERR DB index is out of range");
2625
+ session.db = Number(index);
2626
+ return ok();
2627
+ }
2628
+ cmdReset(session) {
2629
+ session.multi = null;
2630
+ session.multiFailed = false;
2631
+ session.watching = [];
2632
+ this.leavePubsub(session);
2633
+ session.name = "";
2634
+ session.db = 0;
2635
+ session.protocol = 2;
2636
+ session.authenticated = !this.authRequired;
2637
+ session.readOnly = false;
2638
+ return simple("RESET");
2639
+ }
2640
+ cmdClient(session, args, apply) {
2641
+ const sub = upper(args[0]);
2642
+ if (sub === "SETNAME") {
2643
+ if (args.length !== 2) return arityError("client|setname");
2644
+ if (!apply) return queued();
2645
+ session.name = text(args[1]);
2646
+ return ok();
2647
+ }
2648
+ if (sub === "GETNAME") return session.name ? bulk(session.name) : nil();
2649
+ if (sub === "SETINFO") {
2650
+ if (args.length !== 3) return arityError("client|setinfo");
2651
+ const kind = upper(args[1]);
2652
+ if (kind !== "LIB-NAME" && kind !== "LIB-VER") return syntaxError();
2653
+ if (!apply) return queued();
2654
+ if (kind === "LIB-NAME") session.libName = text(args[2]);
2655
+ else session.libVer = text(args[2]);
2656
+ return ok();
2657
+ }
2658
+ if (sub === "ID") return int(session.id);
2659
+ if (sub === "INFO") return bulk(this.clientInfo(session));
2660
+ if (sub === "LIST") {
2661
+ return bulk([...this.sessions].map((item) => this.clientInfo(item)).join("\n"));
2662
+ }
2663
+ if (sub === "PAUSE" || sub === "UNPAUSE") return ok();
2664
+ if (!sub) return arityError("client");
2665
+ return err(`ERR unknown subcommand '${text(args[0]).toLowerCase()}'. Try CLIENT HELP.`);
2666
+ }
2667
+ clientInfo(session) {
2668
+ return `id=${session.id} addr=127.0.0.1:${session.id} laddr=127.0.0.1:6379 name=${session.name} db=${session.db} sub=${session.channels.size} psub=${session.patterns.size} multi=${session.multi ? session.multi.length : -1} lib-name=${session.libName} lib-ver=${session.libVer}`;
2669
+ }
2670
+ info(session, section) {
2671
+ const wanted = section.toLowerCase();
2672
+ const blocks = [
2673
+ [
2674
+ "server",
2675
+ [
2676
+ "# Server",
2677
+ `redis_version:${VERSION}`,
2678
+ "redis_mode:standalone",
2679
+ "os:mockingbird",
2680
+ "arch_bits:64",
2681
+ "tcp_port:6379",
2682
+ "uptime_in_seconds:0"
2683
+ ]
2684
+ ],
2685
+ ["clients", ["# Clients", `connected_clients:${this.sessions.size}`]],
2686
+ [
2687
+ "memory",
2688
+ [
2689
+ "# Memory",
2690
+ `used_memory:${this.memory()}`,
2691
+ `maxmemory:${this.maxmemory}`,
2692
+ `maxmemory_policy:${this.policy}`
2693
+ ]
2694
+ ],
2695
+ [
2696
+ "persistence",
2697
+ ["# Persistence", "loading:0", "rdb_changes_since_last_save:0", "aof_enabled:0"]
2698
+ ],
2699
+ ["stats", ["# Stats", `total_commands_processed:${this.commandCount}`]],
2700
+ ["replication", ["# Replication", "role:master", "connected_slaves:0"]],
2701
+ ["keyspace", ["# Keyspace", ...this.keyspaceLines()]]
2702
+ ];
2703
+ const chosen = wanted === "default" || wanted === "all" || wanted === "" ? blocks : blocks.filter((block) => block[0] === wanted);
2704
+ const lines = chosen.flatMap((block) => block[1]);
2705
+ if (session.id < 0) return "";
2706
+ return `${lines.join("\r\n")}\r
2707
+ `;
2708
+ }
2709
+ keyspaceLines() {
2710
+ const lines = [];
2711
+ this.dbs.forEach((database, index) => {
2712
+ const keys = database.keys();
2713
+ if (keys.length === 0) return;
2714
+ let expires = 0;
2715
+ for (const key of keys) if (database.pttl(key) >= 0) expires++;
2716
+ lines.push(`db${index}:keys=${keys.length},expires=${expires},avg_ttl=0`);
2717
+ });
2718
+ return lines;
2719
+ }
2720
+ cmdTime() {
2721
+ const now = Math.trunc(this.clock.now());
2722
+ const seconds = Math.trunc(now / 1e3);
2723
+ const micros = now % 1e3 * 1e3;
2724
+ return arr([bulk(String(seconds)), bulk(String(micros))]);
2725
+ }
2726
+ cmdConfig(args, apply) {
2727
+ const sub = upper(args[0]);
2728
+ if (sub === "GET") {
2729
+ if (args.length !== 2) return arityError("config|get");
2730
+ const key = text(args[1]).toLowerCase();
2731
+ const value = key === "maxmemory" ? String(this.maxmemory) : key === "maxmemory-policy" ? this.policy : key === "timeout" ? "0" : key === "databases" ? String(this.dbs.length) : null;
2732
+ if (value === null) return arr([]);
2733
+ return arr([bulk(key), bulk(value)]);
2734
+ }
2735
+ if (sub === "SET") {
2736
+ if (args.length !== 3) return arityError("config|set");
2737
+ if (!apply) return queued();
2738
+ const key = text(args[1]).toLowerCase();
2739
+ const value = text(args[2]);
2740
+ if (key === "maxmemory") {
2741
+ const parsed = Number(value);
2742
+ if (!Number.isFinite(parsed) || parsed < 0) return err("ERR Invalid argument");
2743
+ return ok();
2744
+ }
2745
+ if (key === "maxmemory-policy") {
2746
+ if (value !== "noeviction" && value !== "allkeys-lru") return err("ERR Invalid argument");
2747
+ this.policy = value;
2748
+ return ok();
2749
+ }
2750
+ return err("ERR Unsupported CONFIG parameter");
2751
+ }
2752
+ return arityError("config");
2753
+ }
2754
+ cmdRandomKey(session) {
2755
+ const keys = this.database(session.db).keys();
2756
+ if (keys.length === 0) return nil();
2757
+ const key = keys[Math.floor(this.random() * keys.length)] ?? keys[0];
2758
+ return key === void 0 ? nil() : { t: "bulk", v: fromLatin1(key) };
2759
+ }
2760
+ cmdScan(session, args) {
2761
+ if (args.length < 1) return arityError("scan");
2762
+ const cursor = Number(text(args[0]));
2763
+ if (!Number.isInteger(cursor) || cursor < 0) return err("ERR invalid cursor");
2764
+ let pattern = "*";
2765
+ let count = 10;
2766
+ for (let i = 1; i < args.length; i++) {
2767
+ const option = upper(args[i]);
2768
+ if (option === "MATCH") {
2769
+ const value = args[++i];
2770
+ if (!value) return syntaxError();
2771
+ pattern = text(value);
2772
+ } else if (option === "COUNT") {
2773
+ const value = args[++i];
2774
+ const parsed = value ? parseI64(value) : null;
2775
+ if (parsed === null || parsed <= 0n) return syntaxError();
2776
+ count = Number(parsed);
2777
+ } else return syntaxError();
2778
+ }
2779
+ const keys = this.database(session.db).matching(pattern);
2780
+ const slice = keys.slice(cursor, cursor + count);
2781
+ const next = cursor + count >= keys.length ? 0 : cursor + count;
2782
+ return arr([
2783
+ bulk(String(next)),
2784
+ arr(slice.map((key) => ({ t: "bulk", v: fromLatin1(key) })))
2785
+ ]);
2786
+ }
2787
+ cmdKeys(session, args) {
2788
+ if (args.length !== 1) return arityError("keys");
2789
+ const keys = this.database(session.db).matching(text(args[0]));
2790
+ return arr(keys.map((key) => ({ t: "bulk", v: fromLatin1(key) })));
2791
+ }
2792
+ cmdType(session, args) {
2793
+ if (args.length !== 1) return arityError("type");
2794
+ const value = this.database(session.db).get(text(args[0]));
2795
+ return simple(value?.kind ?? "none");
2796
+ }
2797
+ cmdExists(session, args) {
2798
+ if (args.length < 1) return arityError("exists");
2799
+ const database = this.database(session.db);
2800
+ return int(args.filter((arg) => database.has(text(arg))).length);
2801
+ }
2802
+ cmdDel(session, args, apply) {
2803
+ if (args.length < 1) return arityError("del");
2804
+ if (!apply) return queued();
2805
+ const database = this.database(session.db);
2806
+ let removed = 0;
2807
+ for (const arg of args) if (database.delete(text(arg))) removed++;
2808
+ return int(removed);
2809
+ }
2810
+ cmdRename(session, args, apply, nx) {
2811
+ if (args.length !== 2) return arityError(nx ? "renamenx" : "rename");
2812
+ const source = text(args[0]);
2813
+ const dest = text(args[1]);
2814
+ if (source === dest) return err("ERR source and destination objects are the same");
2815
+ const database = this.database(session.db);
2816
+ if (!database.has(source)) return noSuchKey();
2817
+ if (nx && database.has(dest)) return int(0);
2818
+ if (!apply) return queued();
2819
+ database.rename(source, dest);
2820
+ return nx ? int(1) : ok();
2821
+ }
2822
+ cmdExpire(session, command, args, apply) {
2823
+ if (args.length < 2) return arityError(command);
2824
+ const key = text(args[0]);
2825
+ const amount = parseI64(args[1] ?? utf8(""));
2826
+ if (amount === null) return notInteger();
2827
+ let flag = "";
2828
+ if (args.length === 3) flag = upper(args[2]);
2829
+ else if (args.length > 3) return syntaxError();
2830
+ if (flag && !["NX", "XX", "GT", "LT"].includes(flag)) return syntaxError();
2831
+ const database = this.database(session.db);
2832
+ if (!database.has(key)) return int(0);
2833
+ if (!apply) return queued();
2834
+ if (amount < 0n && (command === "expire" || command === "pexpire")) {
2835
+ database.delete(key);
2836
+ return int(1);
2837
+ }
2838
+ const now = database.now();
2839
+ const absolute = command === "expire" ? now + Number(amount) * 1e3 : command === "pexpire" ? now + Number(amount) : command === "expireat" ? Number(amount) * 1e3 : Number(amount);
2840
+ const current = database.expiry(key);
2841
+ if (flag === "NX" && current !== null) return int(0);
2842
+ if (flag === "XX" && current === null) return int(0);
2843
+ if ((flag === "GT" || flag === "LT") && current === null) return int(0);
2844
+ if (flag === "GT" && current !== null && absolute <= current) return int(0);
2845
+ if (flag === "LT" && current !== null && absolute >= current) return int(0);
2846
+ database.setExpiry(key, absolute);
2847
+ return int(1);
2848
+ }
2849
+ cmdTtl(session, command, args) {
2850
+ if (args.length !== 1) return arityError(command);
2851
+ const pttl = this.database(session.db).pttl(text(args[0]));
2852
+ if (pttl < 0) return int(pttl);
2853
+ return int(command === "pttl" ? pttl : Math.floor(pttl / 1e3));
2854
+ }
2855
+ cmdPersist(session, args, apply) {
2856
+ if (args.length !== 1) return arityError("persist");
2857
+ const database = this.database(session.db);
2858
+ const key = text(args[0]);
2859
+ if (database.pttl(key) < 0) return int(0);
2860
+ if (!apply) return queued();
2861
+ database.setExpiry(key, null);
2862
+ return int(1);
2863
+ }
2864
+ stringAt(database, key) {
2865
+ const value = database.get(key);
2866
+ if (!value) return null;
2867
+ if (value.kind !== "string") return wrongType();
2868
+ return value.bytes;
2869
+ }
2870
+ cmdGet(session, args) {
2871
+ if (args.length !== 1) return arityError("get");
2872
+ const value = this.stringAt(this.database(session.db), text(args[0]));
2873
+ if (value instanceof Uint8Array) return { t: "bulk", v: value };
2874
+ if (value === null) return nil();
2875
+ return value;
2876
+ }
2877
+ cmdSet(session, args, apply) {
2878
+ if (args.length < 2) return arityError("set");
2879
+ const key = text(args[0]);
2880
+ const value = args[1];
2881
+ if (!value) return arityError("set");
2882
+ let nx = false;
2883
+ let xx = false;
2884
+ let get = false;
2885
+ let mode = null;
2886
+ let amount = 0;
2887
+ for (let i = 2; i < args.length; i++) {
2888
+ const option = upper(args[i]);
2889
+ if (option === "NX") nx = true;
2890
+ else if (option === "XX") xx = true;
2891
+ else if (option === "GET") get = true;
2892
+ else if (option === "KEEPTTL") {
2893
+ if (mode) return syntaxError();
2894
+ mode = "keep";
2895
+ } else if (option === "EX" || option === "PX" || option === "EXAT" || option === "PXAT") {
2896
+ if (mode) return syntaxError();
2897
+ const raw = args[++i];
2898
+ const parsed = raw ? parseI64(raw) : null;
2899
+ if (parsed === null) return notInteger();
2900
+ mode = option.toLowerCase();
2901
+ amount = Number(parsed);
2902
+ } else return syntaxError();
2903
+ }
2904
+ if (nx && xx) return syntaxError();
2905
+ const database = this.database(session.db);
2906
+ const existing = database.get(key);
2907
+ if (existing && existing.kind !== "string") return wrongType();
2908
+ const previous = existing ? { t: "bulk", v: existing.bytes } : nil();
2909
+ if (nx && existing || xx && !existing) return get ? previous : nil();
2910
+ if (!apply) return queued();
2911
+ const kept = mode === "keep" ? database.expiry(key) : null;
2912
+ database.put(key, { kind: "string", bytes: value });
2913
+ const now = database.now();
2914
+ if (mode === "ex") database.setExpiry(key, now + amount * 1e3);
2915
+ else if (mode === "px") database.setExpiry(key, now + amount);
2916
+ else if (mode === "exat") database.setExpiry(key, amount * 1e3);
2917
+ else if (mode === "pxat") database.setExpiry(key, amount);
2918
+ else if (mode === "keep") {
2919
+ if (kept !== null) database.setExpiry(key, kept);
2920
+ } else database.setExpiry(key, null);
2921
+ return get ? previous : ok();
2922
+ }
2923
+ cmdMget(session, args) {
2924
+ if (args.length < 1) return arityError("mget");
2925
+ return arr(args.map((arg) => this.cmdGet(session, [arg])));
2926
+ }
2927
+ cmdMset(session, args, apply) {
2928
+ if (args.length < 2 || args.length % 2 !== 0) return arityError("mset");
2929
+ if (!apply) return queued();
2930
+ for (let i = 0; i < args.length; i += 2) {
2931
+ const result = this.cmdSet(session, [args[i] ?? utf8(""), args[i + 1] ?? utf8("")], true);
2932
+ if (result.t === "error") return result;
2933
+ }
2934
+ return ok();
2935
+ }
2936
+ cmdAppend(session, args, apply) {
2937
+ if (args.length !== 2) return arityError("append");
2938
+ const database = this.database(session.db);
2939
+ const key = text(args[0]);
2940
+ const extra = args[1] ?? utf8("");
2941
+ const current = this.stringAt(database, key);
2942
+ if (current && !(current instanceof Uint8Array)) return current;
2943
+ if (!apply) return queued();
2944
+ const bytes = current instanceof Uint8Array ? concatBytes(current, extra) : extra;
2945
+ database.put(key, { kind: "string", bytes });
2946
+ return int(bytes.length);
2947
+ }
2948
+ cmdStrlen(session, args) {
2949
+ if (args.length !== 1) return arityError("strlen");
2950
+ const value = this.stringAt(this.database(session.db), text(args[0]));
2951
+ if (value instanceof Uint8Array) return int(value.length);
2952
+ if (value === null) return int(0);
2953
+ return value;
2954
+ }
2955
+ cmdGetRange(session, args) {
2956
+ if (args.length !== 3) return arityError("getrange");
2957
+ const value = this.stringAt(this.database(session.db), text(args[0]));
2958
+ if (value && !(value instanceof Uint8Array)) return value;
2959
+ const bytes = value instanceof Uint8Array ? value : utf8("");
2960
+ const [start, end] = indexes(bytes.length, args[1], args[2]);
2961
+ if (start > end) return bulk("");
2962
+ return { t: "bulk", v: bytes.slice(start, end + 1) };
2963
+ }
2964
+ cmdSetRange(session, args, apply) {
2965
+ if (args.length !== 3) return arityError("setrange");
2966
+ const offset = parseI64(args[1] ?? utf8(""));
2967
+ if (offset === null || offset < 0n) return err("ERR offset is out of range");
2968
+ const database = this.database(session.db);
2969
+ const key = text(args[0]);
2970
+ const current = this.stringAt(database, key);
2971
+ if (current && !(current instanceof Uint8Array)) return current;
2972
+ if (!apply) return queued();
2973
+ const patch = args[2] ?? utf8("");
2974
+ const base = current instanceof Uint8Array ? current : new Uint8Array();
2975
+ const next = new Uint8Array(Math.max(base.length, Number(offset) + patch.length));
2976
+ next.set(base);
2977
+ next.set(patch, Number(offset));
2978
+ database.put(key, { kind: "string", bytes: next });
2979
+ return int(next.length);
2980
+ }
2981
+ cmdIncr(session, args, delta, apply) {
2982
+ if (args.length !== 1) return arityError(delta < 0n ? "decr" : "incr");
2983
+ return this.addInteger(session, text(args[0]), delta, apply);
2984
+ }
2985
+ cmdIncrBy(session, command, args, apply) {
2986
+ if (args.length !== 2) return arityError(command);
2987
+ const delta = parseI64(args[1] ?? utf8(""));
2988
+ if (delta === null) return notInteger();
2989
+ return this.addInteger(session, text(args[0]), command === "decrby" ? -delta : delta, apply);
2990
+ }
2991
+ addInteger(session, key, delta, apply) {
2992
+ const database = this.database(session.db);
2993
+ const current = this.stringAt(database, key);
2994
+ if (current && !(current instanceof Uint8Array)) return current;
2995
+ let value = 0n;
2996
+ if (current instanceof Uint8Array) {
2997
+ const parsed = parseI64(current);
2998
+ if (parsed === null) return notInteger();
2999
+ value = parsed;
3000
+ }
3001
+ const next = value + delta;
3002
+ if (next > 9223372036854775807n || next < -9223372036854775808n) return notInteger();
3003
+ if (!apply) return queued();
3004
+ database.put(key, { kind: "string", bytes: utf8(next.toString()) });
3005
+ return int(intReply(next));
3006
+ }
3007
+ cmdIncrByFloat(session, args, apply, hash) {
3008
+ if (hash) return syntaxError();
3009
+ if (args.length !== 2) return arityError("incrbyfloat");
3010
+ const delta = parseScore(args[1]);
3011
+ if (delta === null) return notFloat();
3012
+ const database = this.database(session.db);
3013
+ const key = text(args[0]);
3014
+ const current = this.stringAt(database, key);
3015
+ if (current && !(current instanceof Uint8Array)) return current;
3016
+ const base = current instanceof Uint8Array ? parseScore(current) : 0;
3017
+ if (base === null) return notFloat();
3018
+ if (!apply) return queued();
3019
+ const next = scoreText(base + delta);
3020
+ database.put(key, { kind: "string", bytes: utf8(next) });
3021
+ return bulk(next);
3022
+ }
3023
+ cmdGetDel(session, args, apply) {
3024
+ if (args.length !== 1) return arityError("getdel");
3025
+ const got = this.cmdGet(session, args);
3026
+ if (got.t === "error" || got.t !== "bulk" || got.v === null) return got;
3027
+ if (!apply) return queued();
3028
+ this.database(session.db).delete(text(args[0]));
3029
+ return got;
3030
+ }
3031
+ cmdGetEx(session, args, apply) {
3032
+ if (args.length < 1) return arityError("getex");
3033
+ const got = this.cmdGet(session, [args[0] ?? utf8("")]);
3034
+ if (got.t === "error") return got;
3035
+ if (args.length === 1) return got;
3036
+ if (!apply) return queued();
3037
+ const option = upper(args[1]);
3038
+ if (option === "PERSIST") {
3039
+ this.database(session.db).setExpiry(text(args[0]), null);
3040
+ return got;
3041
+ }
3042
+ const result = this.cmdExpire(
3043
+ session,
3044
+ option === "EX" ? "expire" : option === "PX" ? "pexpire" : option === "EXAT" ? "expireat" : "pexpireat",
3045
+ [args[0] ?? utf8(""), args[2] ?? utf8("")],
3046
+ true
3047
+ );
3048
+ if (result.t === "error") return result;
3049
+ return got;
3050
+ }
3051
+ requireHash(database, key, create) {
3052
+ const value = database.get(key);
3053
+ if (!value) {
3054
+ if (!create) return null;
3055
+ const created = { kind: "hash", fields: /* @__PURE__ */ new Map() };
3056
+ database.put(key, created);
3057
+ return created;
3058
+ }
3059
+ if (value.kind !== "hash") return wrongType();
3060
+ return value;
3061
+ }
3062
+ cmdHash(session, command, args, apply) {
3063
+ const database = this.database(session.db);
3064
+ const key = text(args[0]);
3065
+ if (command === "hset" || command === "hmset" || command === "hsetnx") {
3066
+ if (args.length < 3 || (args.length - 1) % 2 !== 0) return arityError(command);
3067
+ const hash = this.requireHash(database, key, false);
3068
+ if (hash && !(hash instanceof Object && "fields" in hash) && hash !== null)
3069
+ return hash;
3070
+ if (!apply) return queued();
3071
+ const target = this.requireHash(database, key, true);
3072
+ if (!target || !("fields" in target)) return target ?? wrongType();
3073
+ let added = 0;
3074
+ for (let i = 1; i < args.length; i += 2) {
3075
+ const field = memberKey(args[i] ?? utf8(""));
3076
+ const value = args[i + 1] ?? utf8("");
3077
+ if (command === "hsetnx" && target.fields.has(field)) continue;
3078
+ if (!target.fields.has(field)) added++;
3079
+ target.fields.set(field, value);
3080
+ }
3081
+ database.put(key, target);
3082
+ return command === "hmset" ? ok() : int(command === "hsetnx" ? added > 0 ? 1 : 0 : added);
3083
+ }
3084
+ if (command === "hget") {
3085
+ if (args.length !== 2) return arityError("hget");
3086
+ const hash = this.requireHash(database, key, false);
3087
+ if (hash === null) return nil();
3088
+ if (!("fields" in hash)) return hash;
3089
+ const value = hash.fields.get(memberKey(args[1] ?? utf8("")));
3090
+ return value ? { t: "bulk", v: value } : nil();
3091
+ }
3092
+ if (command === "hdel") {
3093
+ if (args.length < 2) return arityError("hdel");
3094
+ const hash = this.requireHash(database, key, false);
3095
+ if (hash === null) return int(0);
3096
+ if (!("fields" in hash)) return hash;
3097
+ if (!apply) return queued();
3098
+ let removed = 0;
3099
+ for (let i = 1; i < args.length; i++) {
3100
+ if (hash.fields.delete(memberKey(args[i] ?? utf8("")))) removed++;
3101
+ }
3102
+ database.dropIfEmpty(key);
3103
+ if (database.has(key)) database.put(key, hash);
3104
+ return int(removed);
3105
+ }
3106
+ if (command === "hexists") {
3107
+ if (args.length !== 2) return arityError("hexists");
3108
+ const hash = this.requireHash(database, key, false);
3109
+ if (hash === null) return int(0);
3110
+ if (!("fields" in hash)) return hash;
3111
+ return int(hash.fields.has(memberKey(args[1] ?? utf8(""))) ? 1 : 0);
3112
+ }
3113
+ if (command === "hlen") {
3114
+ if (args.length !== 1) return arityError("hlen");
3115
+ const hash = this.requireHash(database, key, false);
3116
+ if (hash === null) return int(0);
3117
+ if (!("fields" in hash)) return hash;
3118
+ return int(hash.fields.size);
3119
+ }
3120
+ if (command === "hstrlen") {
3121
+ if (args.length !== 2) return arityError("hstrlen");
3122
+ const hash = this.requireHash(database, key, false);
3123
+ if (hash === null) return int(0);
3124
+ if (!("fields" in hash)) return hash;
3125
+ return int(hash.fields.get(memberKey(args[1] ?? utf8("")))?.length ?? 0);
3126
+ }
3127
+ if (command === "hkeys" || command === "hvals" || command === "hgetall") {
3128
+ if (args.length !== 1) return arityError(command);
3129
+ const hash = this.requireHash(database, key, false);
3130
+ if (hash === null) return arr([]);
3131
+ if (!("fields" in hash)) return hash;
3132
+ const items = [];
3133
+ for (const [field, value] of hash.fields) {
3134
+ if (command !== "hvals") items.push({ t: "bulk", v: fromLatin1(field) });
3135
+ if (command !== "hkeys") items.push({ t: "bulk", v: value });
3136
+ }
3137
+ return arr(items);
3138
+ }
3139
+ if (command === "hmget") {
3140
+ if (args.length < 2) return arityError("hmget");
3141
+ const hash = this.requireHash(database, key, false);
3142
+ if (hash && !("fields" in hash)) return hash;
3143
+ return arr(
3144
+ args.slice(1).map((field) => {
3145
+ const value = hash && "fields" in hash ? hash.fields.get(memberKey(field)) : void 0;
3146
+ return value ? { t: "bulk", v: value } : nil();
3147
+ })
3148
+ );
3149
+ }
3150
+ if (command === "hincrby" || command === "hincrbyfloat") {
3151
+ if (args.length !== 3) return arityError(command);
3152
+ const hash = this.requireHash(database, key, false);
3153
+ if (hash && !("fields" in hash)) return hash;
3154
+ const field = memberKey(args[1] ?? utf8(""));
3155
+ const current = hash && "fields" in hash ? hash.fields.get(field) : void 0;
3156
+ if (command === "hincrby") {
3157
+ const delta2 = parseI64(args[2] ?? utf8(""));
3158
+ if (delta2 === null) return notInteger();
3159
+ const base2 = current ? parseI64(current) : 0n;
3160
+ if (base2 === null) return notInteger();
3161
+ if (!apply) return queued();
3162
+ const target2 = this.requireHash(database, key, true);
3163
+ if (!target2 || !("fields" in target2)) return target2 ?? wrongType();
3164
+ const next2 = base2 + delta2;
3165
+ target2.fields.set(field, utf8(next2.toString()));
3166
+ database.put(key, target2);
3167
+ return int(intReply(next2));
3168
+ }
3169
+ const delta = parseScore(args[2]);
3170
+ if (delta === null) return notFloat();
3171
+ const base = current ? parseScore(current) : 0;
3172
+ if (base === null) return notFloat();
3173
+ if (!apply) return queued();
3174
+ const target = this.requireHash(database, key, true);
3175
+ if (!target || !("fields" in target)) return target ?? wrongType();
3176
+ const next = scoreText(base + delta);
3177
+ target.fields.set(field, utf8(next));
3178
+ database.put(key, target);
3179
+ return bulk(next);
3180
+ }
3181
+ return unknownCommand(command, args);
3182
+ }
3183
+ requireList(database, key, create) {
3184
+ const value = database.get(key);
3185
+ if (!value) {
3186
+ if (!create) return null;
3187
+ const created = { kind: "list", items: [] };
3188
+ database.put(key, created);
3189
+ return created;
3190
+ }
3191
+ if (value.kind !== "list") return wrongType();
3192
+ return value;
3193
+ }
3194
+ cmdList(session, command, args, apply) {
3195
+ const database = this.database(session.db);
3196
+ const key = text(args[0]);
3197
+ if (command === "lpush" || command === "rpush") {
3198
+ if (args.length < 2) return arityError(command);
3199
+ const existing = this.requireList(database, key, false);
3200
+ if (existing && !("items" in existing)) return existing;
3201
+ if (!apply) return queued();
3202
+ const list = this.requireList(database, key, true);
3203
+ if (!list || !("items" in list)) return list ?? wrongType();
3204
+ const items = args.slice(1);
3205
+ if (command === "lpush") for (const item of items) list.items.unshift(item);
3206
+ else for (const item of items) list.items.push(item);
3207
+ database.put(key, list);
3208
+ const length = list.items.length;
3209
+ this.wake(session.db, key);
3210
+ return int(length);
3211
+ }
3212
+ if (command === "lpop" || command === "rpop") {
3213
+ if (args.length < 1 || args.length > 2) return arityError(command);
3214
+ const list = this.requireList(database, key, false);
3215
+ if (list === null) return args.length === 2 ? nilArray() : nil();
3216
+ if (!("items" in list)) return list;
3217
+ const count = args.length === 2 ? parseI64(args[1] ?? utf8("")) : null;
3218
+ if (args.length === 2 && (count === null || count < 0n)) return notInteger();
3219
+ if (!apply) return queued();
3220
+ const take = count === null ? 1 : Number(count);
3221
+ const popped = [];
3222
+ for (let i = 0; i < take && list.items.length > 0; i++) {
3223
+ const item = command === "lpop" ? list.items.shift() : list.items.pop();
3224
+ if (item) popped.push(item);
3225
+ }
3226
+ database.dropIfEmpty(key);
3227
+ if (database.has(key)) database.put(key, list);
3228
+ if (count === null) return popped[0] ? { t: "bulk", v: popped[0] } : nil();
3229
+ return arr(popped.map((item) => ({ t: "bulk", v: item })));
3230
+ }
3231
+ if (command === "llen") {
3232
+ if (args.length !== 1) return arityError("llen");
3233
+ const list = this.requireList(database, key, false);
3234
+ if (list === null) return int(0);
3235
+ if (!("items" in list)) return list;
3236
+ return int(list.items.length);
3237
+ }
3238
+ if (command === "lrange") {
3239
+ if (args.length !== 3) return arityError("lrange");
3240
+ const list = this.requireList(database, key, false);
3241
+ if (list === null) return arr([]);
3242
+ if (!("items" in list)) return list;
3243
+ const [start, end] = indexes(list.items.length, args[1], args[2]);
3244
+ if (start > end) return arr([]);
3245
+ return arr(list.items.slice(start, end + 1).map((item) => ({ t: "bulk", v: item })));
3246
+ }
3247
+ if (command === "lindex") {
3248
+ if (args.length !== 2) return arityError("lindex");
3249
+ const list = this.requireList(database, key, false);
3250
+ if (list === null) return nil();
3251
+ if (!("items" in list)) return list;
3252
+ const at = listIndex(list.items.length, args[1]);
3253
+ const item = at === null ? void 0 : list.items[at];
3254
+ return item ? { t: "bulk", v: item } : nil();
3255
+ }
3256
+ if (command === "lset") {
3257
+ if (args.length !== 3) return arityError("lset");
3258
+ const list = this.requireList(database, key, false);
3259
+ if (list === null) return err("ERR no such key");
3260
+ if (!("items" in list)) return list;
3261
+ const at = listIndex(list.items.length, args[1]);
3262
+ if (at === null || list.items[at] === void 0) return err("ERR index out of range");
3263
+ if (!apply) return queued();
3264
+ list.items[at] = args[2] ?? utf8("");
3265
+ database.put(key, list);
3266
+ return ok();
3267
+ }
3268
+ if (command === "lrem") {
3269
+ if (args.length !== 3) return arityError("lrem");
3270
+ const list = this.requireList(database, key, false);
3271
+ if (list === null) return int(0);
3272
+ if (!("items" in list)) return list;
3273
+ const count = parseI64(args[1] ?? utf8(""));
3274
+ if (count === null) return notInteger();
3275
+ if (!apply) return queued();
3276
+ const target = args[2] ?? utf8("");
3277
+ const matches = list.items.map((item, index) => ({ item, index })).filter(
3278
+ (item) => item.item.length === target.length && item.item.every((byte, index) => byte === target[index])
3279
+ );
3280
+ const chosen = count === 0n ? matches : count > 0n ? matches.slice(0, Number(count)) : matches.slice(matches.length + Number(count));
3281
+ const remove = new Set(chosen.map((item) => item.index));
3282
+ list.items = list.items.filter((_, index) => !remove.has(index));
3283
+ database.dropIfEmpty(key);
3284
+ if (database.has(key)) database.put(key, list);
3285
+ return int(chosen.length);
3286
+ }
3287
+ if (command === "ltrim") {
3288
+ if (args.length !== 3) return arityError("ltrim");
3289
+ const list = this.requireList(database, key, false);
3290
+ if (list === null) return ok();
3291
+ if (!("items" in list)) return list;
3292
+ if (!apply) return queued();
3293
+ const [start, end] = indexes(list.items.length, args[1], args[2]);
3294
+ list.items = start > end ? [] : list.items.slice(start, end + 1);
3295
+ database.dropIfEmpty(key);
3296
+ if (database.has(key)) database.put(key, list);
3297
+ return ok();
3298
+ }
3299
+ if (command === "linsert") {
3300
+ if (args.length !== 4) return arityError("linsert");
3301
+ const list = this.requireList(database, key, false);
3302
+ if (list === null) return int(0);
3303
+ if (!("items" in list)) return list;
3304
+ const where = upper(args[1]);
3305
+ if (where !== "BEFORE" && where !== "AFTER") return syntaxError();
3306
+ const pivot = args[2] ?? utf8("");
3307
+ const at = list.items.findIndex(
3308
+ (item) => item.length === pivot.length && item.every((byte, index) => byte === pivot[index])
3309
+ );
3310
+ if (at < 0) return int(-1);
3311
+ if (!apply) return queued();
3312
+ list.items.splice(where === "BEFORE" ? at : at + 1, 0, args[3] ?? utf8(""));
3313
+ database.put(key, list);
3314
+ this.wake(session.db, key);
3315
+ return int(list.items.length);
3316
+ }
3317
+ if (command === "lmove" || command === "rpoplpush") {
3318
+ const source = key;
3319
+ const dest = command === "rpoplpush" ? text(args[1]) : text(args[1]);
3320
+ const from = command === "rpoplpush" ? "right" : upper(args[2]);
3321
+ const to = command === "rpoplpush" ? "left" : upper(args[3]);
3322
+ if (command === "lmove" && args.length !== 4) return arityError("lmove");
3323
+ if (command === "rpoplpush" && args.length !== 2) return arityError("rpoplpush");
3324
+ if (from !== "LEFT" && from !== "RIGHT" && command === "lmove") return syntaxError();
3325
+ if (to !== "LEFT" && to !== "RIGHT" && command === "lmove") return syntaxError();
3326
+ const list = this.requireList(database, source, false);
3327
+ if (list === null) return nil();
3328
+ if (!("items" in list)) return list;
3329
+ const destList = this.requireList(database, dest, false);
3330
+ if (destList && !("items" in destList)) return destList;
3331
+ if (!apply) return queued();
3332
+ const item = (command === "rpoplpush" || from === "RIGHT" ? list.items.pop() : list.items.shift()) ?? null;
3333
+ if (!item) return nil();
3334
+ database.dropIfEmpty(source);
3335
+ if (database.has(source)) database.put(source, list);
3336
+ const target = this.requireList(database, dest, true);
3337
+ if (!target || !("items" in target)) return target ?? wrongType();
3338
+ if (command === "rpoplpush" || to === "LEFT") target.items.unshift(item);
3339
+ else target.items.push(item);
3340
+ database.put(dest, target);
3341
+ this.wake(session.db, dest);
3342
+ return { t: "bulk", v: item };
3343
+ }
3344
+ return unknownCommand(command, args);
3345
+ }
3346
+ cmdBlockingList(session, args, apply, side) {
3347
+ const name = side === "left" ? "blpop" : "brpop";
3348
+ if (args.length < 2) return arityError(name);
3349
+ const timeout = parseTimeout(args[args.length - 1]);
3350
+ if (timeout === null) return err("ERR timeout is not a float or out of range");
3351
+ const keys = args.slice(0, -1).map((arg) => text(arg));
3352
+ for (const key of keys) {
3353
+ const value = this.database(session.db).get(key);
3354
+ if (value && value.kind !== "list") return wrongType();
3355
+ }
3356
+ if (!apply) return queued();
3357
+ const immediate = this.takeList(session.db, keys, side);
3358
+ if (immediate) return immediate;
3359
+ if (session.inExec || this.scriptDepth > 0) return nilArray();
3360
+ return this.block({
3361
+ kind: "list",
3362
+ keys,
3363
+ db: session.db,
3364
+ side,
3365
+ count: 1,
3366
+ ...timeout > 0 ? { deadline: this.clock.now() + timeout * 1e3 } : {},
3367
+ session
3368
+ });
3369
+ }
3370
+ takeList(dbIndex, keys, side) {
3371
+ if (this.consumersPaused) return null;
3372
+ const database = this.database(dbIndex);
3373
+ for (const key of keys) {
3374
+ const value = database.get(key);
3375
+ if (!value) continue;
3376
+ if (value.kind !== "list") return wrongType();
3377
+ const item = side === "left" ? value.items.shift() : value.items.pop();
3378
+ if (!item) continue;
3379
+ database.dropIfEmpty(key);
3380
+ if (database.has(key)) database.put(key, value);
3381
+ return arr([bulk(key), { t: "bulk", v: item }]);
3382
+ }
3383
+ return null;
3384
+ }
3385
+ requireSet(database, key, create) {
3386
+ const value = database.get(key);
3387
+ if (!value) {
3388
+ if (!create) return null;
3389
+ const created = { kind: "set", members: /* @__PURE__ */ new Map() };
3390
+ database.put(key, created);
3391
+ return created;
3392
+ }
3393
+ if (value.kind !== "set") return wrongType();
3394
+ return value;
3395
+ }
3396
+ cmdSetFamily(session, command, args, apply) {
3397
+ const database = this.database(session.db);
3398
+ const key = text(args[0]);
3399
+ if (command === "sadd" || command === "srem") {
3400
+ if (args.length < 2) return arityError(command);
3401
+ const existing = this.requireSet(database, key, false);
3402
+ if (existing && !("members" in existing)) return existing;
3403
+ if (!apply) return queued();
3404
+ if (command === "srem" && existing === null) return int(0);
3405
+ const set = this.requireSet(database, key, command === "sadd");
3406
+ if (!set || !("members" in set)) return set ?? int(0);
3407
+ let changed = 0;
3408
+ for (const arg of args.slice(1)) {
3409
+ const member = memberKey(arg);
3410
+ if (command === "sadd" && !set.members.has(member)) {
3411
+ set.members.set(member, arg);
3412
+ changed++;
3413
+ }
3414
+ if (command === "srem" && set.members.delete(member)) changed++;
3415
+ }
3416
+ if (command === "sadd") database.put(key, set);
3417
+ database.dropIfEmpty(key);
3418
+ if (database.has(key)) database.put(key, set);
3419
+ return int(changed);
3420
+ }
3421
+ if (command === "sismember") {
3422
+ if (args.length !== 2) return arityError("sismember");
3423
+ const set = this.requireSet(database, key, false);
3424
+ if (set === null) return int(0);
3425
+ if (!("members" in set)) return set;
3426
+ return int(set.members.has(memberKey(args[1] ?? utf8(""))) ? 1 : 0);
3427
+ }
3428
+ if (command === "smismember") {
3429
+ if (args.length < 2) return arityError("smismember");
3430
+ const set = this.requireSet(database, key, false);
3431
+ if (set && !("members" in set)) return set;
3432
+ return arr(
3433
+ args.slice(1).map((arg) => int(set && "members" in set && set.members.has(memberKey(arg)) ? 1 : 0))
3434
+ );
3435
+ }
3436
+ if (command === "smembers" || command === "scard") {
3437
+ if (args.length !== 1) return arityError(command);
3438
+ const set = this.requireSet(database, key, false);
3439
+ if (set === null) return command === "scard" ? int(0) : arr([]);
3440
+ if (!("members" in set)) return set;
3441
+ if (command === "scard") return int(set.members.size);
3442
+ const members = [...set.members.values()].sort((a, b) => compareBytes(a, b));
3443
+ return arr(members.map((member) => ({ t: "bulk", v: member })));
3444
+ }
3445
+ if (command === "spop" || command === "srandmember") {
3446
+ if (args.length < 1 || args.length > 2) return arityError(command);
3447
+ const set = this.requireSet(database, key, false);
3448
+ if (set === null) return args.length === 2 ? arr([]) : nil();
3449
+ if (!("members" in set)) return set;
3450
+ const count = args.length === 2 ? parseI64(args[1] ?? utf8("")) : null;
3451
+ if (args.length === 2 && count === null) return notInteger();
3452
+ if (!apply && command === "spop") return queued();
3453
+ const members = [...set.members.values()];
3454
+ if (members.length === 0) return count === null ? nil() : arr([]);
3455
+ const picked = [];
3456
+ const pool = members.slice();
3457
+ const times = count === null ? 1 : Math.min(
3458
+ Math.abs(Number(count)),
3459
+ command === "srandmember" && count < 0n ? Number(-count) : pool.length
3460
+ );
3461
+ for (let i = 0; i < (count !== null && count < 0n && command === "srandmember" ? Number(-count) : times); i++) {
3462
+ const index = Math.floor(
3463
+ this.random() * (command === "srandmember" && count !== null && count < 0n ? members.length : pool.length)
3464
+ );
3465
+ const member = command === "srandmember" && count !== null && count < 0n ? members[index] : pool.splice(index, 1)[0];
3466
+ if (member) picked.push(member);
3467
+ }
3468
+ if (command === "spop") {
3469
+ for (const member of picked) set.members.delete(memberKey(member));
3470
+ database.dropIfEmpty(key);
3471
+ if (database.has(key)) database.put(key, set);
3472
+ }
3473
+ if (count === null) return picked[0] ? { t: "bulk", v: picked[0] } : nil();
3474
+ return arr(picked.map((member) => ({ t: "bulk", v: member })));
3475
+ }
3476
+ if (command === "sinter" || command === "sunion" || command === "sdiff") {
3477
+ if (args.length < 1) return arityError(command);
3478
+ const sets = [];
3479
+ for (const arg of args) {
3480
+ const set = this.requireSet(database, text(arg), false);
3481
+ if (set && !("members" in set)) return set;
3482
+ sets.push(set && "members" in set ? set : null);
3483
+ }
3484
+ const [first2, ...rest] = sets;
3485
+ let members = new Map(first2?.members ?? []);
3486
+ if (command === "sinter") {
3487
+ for (const set of rest) {
3488
+ members = new Map([...members].filter(([member]) => set?.members.has(member)));
3489
+ }
3490
+ } else if (command === "sunion") {
3491
+ for (const set of rest)
3492
+ for (const [member, bytes] of set?.members ?? []) members.set(member, bytes);
3493
+ } else {
3494
+ for (const set of rest)
3495
+ for (const member of set?.members.keys() ?? []) members.delete(member);
3496
+ }
3497
+ const ordered = [...members.values()].sort((a, b) => compareBytes(a, b));
3498
+ return arr(ordered.map((member) => ({ t: "bulk", v: member })));
3499
+ }
3500
+ if (command === "smove") {
3501
+ if (args.length !== 3) return arityError("smove");
3502
+ const source = this.requireSet(database, key, false);
3503
+ if (source && !("members" in source)) return source;
3504
+ const destKey = text(args[1]);
3505
+ const dest = this.requireSet(database, destKey, false);
3506
+ if (dest && !("members" in dest)) return dest;
3507
+ const member = memberKey(args[2] ?? utf8(""));
3508
+ if (!source || !("members" in source) || !source.members.has(member)) return int(0);
3509
+ if (!apply) return queued();
3510
+ const bytes = source.members.get(member) ?? utf8("");
3511
+ source.members.delete(member);
3512
+ database.dropIfEmpty(key);
3513
+ if (database.has(key)) database.put(key, source);
3514
+ const target = this.requireSet(database, destKey, true);
3515
+ if (!target || !("members" in target)) return target ?? wrongType();
3516
+ target.members.set(member, bytes);
3517
+ database.put(destKey, target);
3518
+ return int(1);
3519
+ }
3520
+ return unknownCommand(command, args);
3521
+ }
3522
+ requireZset(database, key, create) {
3523
+ const value = database.get(key);
3524
+ if (!value) {
3525
+ if (!create) return null;
3526
+ const created = { kind: "zset", members: /* @__PURE__ */ new Map() };
3527
+ database.put(key, created);
3528
+ return created;
3529
+ }
3530
+ if (value.kind !== "zset") return wrongType();
3531
+ return value;
3532
+ }
3533
+ cmdZset(session, command, args, apply) {
3534
+ const database = this.database(session.db);
3535
+ const key = text(args[0]);
3536
+ if (command === "zadd") return this.cmdZadd(session, args, apply);
3537
+ if (command === "zrem") {
3538
+ if (args.length < 2) return arityError("zrem");
3539
+ const zset = this.requireZset(database, key, false);
3540
+ if (zset === null) return int(0);
3541
+ if (!("members" in zset)) return zset;
3542
+ if (!apply) return queued();
3543
+ let removed = 0;
3544
+ for (const arg of args.slice(1)) if (zset.members.delete(memberKey(arg))) removed++;
3545
+ database.dropIfEmpty(key);
3546
+ if (database.has(key)) database.put(key, zset);
3547
+ return int(removed);
3548
+ }
3549
+ if (command === "zcard") {
3550
+ if (args.length !== 1) return arityError("zcard");
3551
+ const zset = this.requireZset(database, key, false);
3552
+ if (zset === null) return int(0);
3553
+ if (!("members" in zset)) return zset;
3554
+ return int(zset.members.size);
3555
+ }
3556
+ if (command === "zscore" || command === "zmscore") {
3557
+ if (command === "zscore" && args.length !== 2) return arityError("zscore");
3558
+ if (command === "zmscore" && args.length < 2) return arityError("zmscore");
3559
+ const zset = this.requireZset(database, key, false);
3560
+ if (zset && !("members" in zset)) return zset;
3561
+ const scores = args.slice(1).map((arg) => {
3562
+ const member = zset && "members" in zset ? zset.members.get(memberKey(arg)) : void 0;
3563
+ return member ? bulk(scoreText(member.score)) : nil();
3564
+ });
3565
+ return command === "zscore" ? scores[0] ?? nil() : arr(scores);
3566
+ }
3567
+ if (command === "zincrby") {
3568
+ if (args.length !== 3) return arityError("zincrby");
3569
+ const delta = parseScore(args[1]);
3570
+ if (delta === null) return notFloat();
3571
+ if (!apply) return queued();
3572
+ const zset = this.requireZset(database, key, true);
3573
+ if (!zset || !("members" in zset)) return zset ?? wrongType();
3574
+ const member = args[2] ?? utf8("");
3575
+ const current = zset.members.get(memberKey(member));
3576
+ const score = (current?.score ?? 0) + delta;
3577
+ zset.members.set(memberKey(member), { member, score });
3578
+ database.put(key, zset);
3579
+ this.wake(session.db, key);
3580
+ return bulk(scoreText(score));
3581
+ }
3582
+ if (command === "zrank" || command === "zrevrank") {
3583
+ if (args.length !== 2) return arityError(command);
3584
+ const zset = this.requireZset(database, key, false);
3585
+ if (zset === null) return nil();
3586
+ if (!("members" in zset)) return zset;
3587
+ const ordered = database.zsetOrdered(zset);
3588
+ const at = ordered.findIndex(
3589
+ (item) => memberKey(item.member) === memberKey(args[1] ?? utf8(""))
3590
+ );
3591
+ if (at < 0) return nil();
3592
+ return int(command === "zrevrank" ? ordered.length - 1 - at : at);
3593
+ }
3594
+ if (command === "zrange" || command === "zrevrange" || command === "zrangebyscore" || command === "zrevrangebyscore") {
3595
+ return this.cmdZrange(database, command, args);
3596
+ }
3597
+ if (command === "zcount") {
3598
+ if (args.length !== 3) return arityError("zcount");
3599
+ const zset = this.requireZset(database, key, false);
3600
+ if (zset === null) return int(0);
3601
+ if (!("members" in zset)) return zset;
3602
+ const min = parseBound(text(args[1]));
3603
+ const max = parseBound(text(args[2]));
3604
+ if (!min || !max) return notFloat();
3605
+ return int(database.zsetOrdered(zset).filter((item) => inBound(item.score, min, max)).length);
3606
+ }
3607
+ if (command === "zpopmin" || command === "zpopmax") {
3608
+ if (args.length < 1 || args.length > 2) return arityError(command);
3609
+ const zset = this.requireZset(database, key, false);
3610
+ if (zset === null) return arr([]);
3611
+ if (!("members" in zset)) return zset;
3612
+ const count = args.length === 2 ? parseI64(args[1] ?? utf8("")) : 1n;
3613
+ if (count === null || count < 0n) return notInteger();
3614
+ if (!apply) return queued();
3615
+ const ordered = database.zsetOrdered(zset);
3616
+ const picked = command === "zpopmax" ? ordered.slice(-Number(count)).reverse() : ordered.slice(0, Number(count));
3617
+ for (const item of picked) zset.members.delete(memberKey(item.member));
3618
+ database.dropIfEmpty(key);
3619
+ if (database.has(key)) database.put(key, zset);
3620
+ return arr(
3621
+ picked.flatMap((item) => [
3622
+ { t: "bulk", v: item.member },
3623
+ bulk(scoreText(item.score))
3624
+ ])
3625
+ );
3626
+ }
3627
+ if (command === "zremrangebyscore" || command === "zremrangebyrank") {
3628
+ if (args.length !== 3) return arityError(command);
3629
+ const zset = this.requireZset(database, key, false);
3630
+ if (zset === null) return int(0);
3631
+ if (!("members" in zset)) return zset;
3632
+ if (!apply) return queued();
3633
+ const ordered = database.zsetOrdered(zset);
3634
+ let removed = 0;
3635
+ if (command === "zremrangebyrank") {
3636
+ const [start, end] = indexes(ordered.length, args[1], args[2]);
3637
+ ordered.forEach((item, index) => {
3638
+ if (index >= start && index <= end) {
3639
+ zset.members.delete(memberKey(item.member));
3640
+ removed++;
3641
+ }
3642
+ });
3643
+ } else {
3644
+ const min = parseBound(text(args[1]));
3645
+ const max = parseBound(text(args[2]));
3646
+ if (!min || !max) return notFloat();
3647
+ for (const item of ordered) {
3648
+ if (!inBound(item.score, min, max)) continue;
3649
+ zset.members.delete(memberKey(item.member));
3650
+ removed++;
3651
+ }
3652
+ }
3653
+ database.dropIfEmpty(key);
3654
+ if (database.has(key)) database.put(key, zset);
3655
+ return int(removed);
3656
+ }
3657
+ return unknownCommand(command, args);
3658
+ }
3659
+ cmdZadd(session, args, apply) {
3660
+ if (args.length < 3) return arityError("zadd");
3661
+ let index = 1;
3662
+ let nx = false;
3663
+ let xx = false;
3664
+ let gt = false;
3665
+ let lt = false;
3666
+ let ch = false;
3667
+ let incr = false;
3668
+ while (index < args.length) {
3669
+ const option = upper(args[index]);
3670
+ if (!["NX", "XX", "GT", "LT", "CH", "INCR"].includes(option)) break;
3671
+ if (option === "NX") nx = true;
3672
+ if (option === "XX") xx = true;
3673
+ if (option === "GT") gt = true;
3674
+ if (option === "LT") lt = true;
3675
+ if (option === "CH") ch = true;
3676
+ if (option === "INCR") incr = true;
3677
+ index++;
3678
+ }
3679
+ if ((gt || lt) && nx)
3680
+ return err("ERR GT, LT, and/or NX options at the same time are not compatible");
3681
+ if ((args.length - index) % 2 !== 0 || index >= args.length) return syntaxError();
3682
+ if (incr && args.length - index !== 2)
3683
+ return err("ERR INCR option supports a single increment-element pair");
3684
+ const database = this.database(session.db);
3685
+ const key = text(args[0]);
3686
+ const existing = this.requireZset(database, key, false);
3687
+ if (existing && !("members" in existing)) return existing;
3688
+ if (!apply) return queued();
3689
+ const zset = this.requireZset(database, key, true);
3690
+ if (!zset || !("members" in zset)) return zset ?? wrongType();
3691
+ let changed = 0;
3692
+ let added = 0;
3693
+ let updatedScore = 0;
3694
+ for (let i = index; i < args.length; i += 2) {
3695
+ const score = parseScore(args[i]);
3696
+ if (score === null) return notFloat();
3697
+ const member = args[i + 1] ?? utf8("");
3698
+ const current = zset.members.get(memberKey(member));
3699
+ if (nx && current) continue;
3700
+ if ((xx || gt || lt) && !current) continue;
3701
+ if (gt && current && score <= current.score) continue;
3702
+ if (lt && current && score >= current.score) continue;
3703
+ const next = incr ? (current?.score ?? 0) + score : score;
3704
+ if (!current) added++;
3705
+ else if (current.score !== next) updatedScore++;
3706
+ zset.members.set(memberKey(member), { member, score: next });
3707
+ if (incr) {
3708
+ database.put(key, zset);
3709
+ this.wake(session.db, key);
3710
+ return bulk(scoreText(next));
3711
+ }
3712
+ }
3713
+ changed = ch ? added + updatedScore : added;
3714
+ database.put(key, zset);
3715
+ this.wake(session.db, key);
3716
+ return int(changed);
3717
+ }
3718
+ cmdZrange(database, command, args) {
3719
+ if (args.length < 3) return arityError(command);
3720
+ const zset = this.requireZset(database, text(args[0]), false);
3721
+ if (zset === null) return arr([]);
3722
+ if (!("members" in zset)) return zset;
3723
+ let byScore = command === "zrangebyscore" || command === "zrevrangebyscore";
3724
+ let rev = command === "zrevrange" || command === "zrevrangebyscore";
3725
+ let withScores = false;
3726
+ let limit = null;
3727
+ const rest = args.slice(3);
3728
+ for (let i = 0; i < rest.length; i++) {
3729
+ const option = upper(rest[i]);
3730
+ if (option === "BYSCORE") byScore = true;
3731
+ else if (option === "REV") rev = true;
3732
+ else if (option === "WITHSCORES") withScores = true;
3733
+ else if (option === "LIMIT") {
3734
+ const offset = parseI64(rest[++i] ?? utf8(""));
3735
+ const count = parseI64(rest[++i] ?? utf8(""));
3736
+ if (offset === null || count === null) return notInteger();
3737
+ limit = { offset: Number(offset), count: Number(count) };
3738
+ } else return syntaxError();
3739
+ }
3740
+ let ordered = database.zsetOrdered(zset);
3741
+ if (byScore) {
3742
+ const left = parseBound(text(args[1]));
3743
+ const right = parseBound(text(args[2]));
3744
+ if (!left || !right) return notFloat();
3745
+ const min = rev ? right : left;
3746
+ const max = rev ? left : right;
3747
+ ordered = ordered.filter((item) => inBound(item.score, min, max));
3748
+ } else {
3749
+ const [start, end] = indexes(ordered.length, args[1], args[2]);
3750
+ ordered = start > end ? [] : ordered.slice(start, end + 1);
3751
+ }
3752
+ if (rev) ordered.reverse();
3753
+ if (limit) ordered = ordered.slice(limit.offset, limit.offset + limit.count);
3754
+ return arr(
3755
+ ordered.flatMap((item) => {
3756
+ const member = { t: "bulk", v: item.member };
3757
+ return withScores ? [member, bulk(scoreText(item.score))] : [member];
3758
+ })
3759
+ );
3760
+ }
3761
+ cmdBlockingZset(session, args, apply, max) {
3762
+ const name = max ? "bzpopmax" : "bzpopmin";
3763
+ if (args.length < 2) return arityError(name);
3764
+ const timeout = parseTimeout(args[args.length - 1]);
3765
+ if (timeout === null) return err("ERR timeout is not a float or out of range");
3766
+ const keys = args.slice(0, -1).map((arg) => text(arg));
3767
+ for (const key of keys) {
3768
+ const value = this.database(session.db).get(key);
3769
+ if (value && value.kind !== "zset") return wrongType();
3770
+ }
3771
+ if (!apply) return queued();
3772
+ const immediate = this.takeZset(session.db, keys, max);
3773
+ if (immediate) return immediate;
3774
+ if (session.inExec || this.scriptDepth > 0) return nilArray();
3775
+ return this.block({
3776
+ kind: "zset",
3777
+ keys,
3778
+ db: session.db,
3779
+ side: "left",
3780
+ max,
3781
+ count: 1,
3782
+ ...timeout > 0 ? { deadline: this.clock.now() + timeout * 1e3 } : {},
3783
+ session
3784
+ });
3785
+ }
3786
+ takeZset(dbIndex, keys, max) {
3787
+ if (this.consumersPaused) return null;
3788
+ const database = this.database(dbIndex);
3789
+ for (const key of keys) {
3790
+ const value = database.get(key);
3791
+ if (!value) continue;
3792
+ if (value.kind !== "zset") return wrongType();
3793
+ const ordered = database.zsetOrdered(value);
3794
+ const member = max ? ordered[ordered.length - 1] : ordered[0];
3795
+ if (!member) continue;
3796
+ value.members.delete(memberKey(member.member));
3797
+ database.dropIfEmpty(key);
3798
+ if (database.has(key)) database.put(key, value);
3799
+ return arr([bulk(key), { t: "bulk", v: member.member }, bulk(scoreText(member.score))]);
3800
+ }
3801
+ return null;
3802
+ }
3803
+ cmdStream(session, command, args, apply) {
3804
+ if (command === "xadd") return this.cmdXadd(session, args, apply);
3805
+ if (command === "xlen") {
3806
+ if (args.length !== 1) return arityError("xlen");
3807
+ const stream = this.stream(session, text(args[0]), false);
3808
+ if (stream === null) return int(0);
3809
+ if (!(stream instanceof Object) || !("entries" in stream)) return stream;
3810
+ return int(stream.entries.length);
3811
+ }
3812
+ if (command === "xrange" || command === "xrevrange")
3813
+ return this.cmdXrange(session, command, args);
3814
+ if (command === "xdel") return this.cmdXdel(session, args, apply);
3815
+ if (command === "xtrim") return this.cmdXtrim(session, args, apply);
3816
+ if (command === "xgroup") return this.cmdXgroup(session, args, apply);
3817
+ if (command === "xread") return this.cmdXread(session, args, apply, false);
3818
+ if (command === "xreadgroup") return this.cmdXread(session, args, apply, true);
3819
+ if (command === "xack") return this.cmdXack(session, args, apply);
3820
+ if (command === "xpending") return this.cmdXpending(session, args);
3821
+ if (command === "xclaim") return this.cmdXclaim(session, args, apply, false);
3822
+ if (command === "xautoclaim") return this.cmdXclaim(session, args, apply, true);
3823
+ if (command === "xinfo") return this.cmdXinfo(session, args);
3824
+ return unknownCommand(command, args);
3825
+ }
3826
+ stream(session, key, create) {
3827
+ const database = this.database(session.db);
3828
+ const value = database.get(key);
3829
+ if (!value) {
3830
+ if (!create) return null;
3831
+ const created = { kind: "stream", stream: emptyStream() };
3832
+ database.put(key, created);
3833
+ return created.stream;
3834
+ }
3835
+ if (value.kind !== "stream") return wrongType();
3836
+ return value.stream;
3837
+ }
3838
+ cmdXadd(session, args, apply) {
3839
+ if (args.length < 4) return arityError("xadd");
3840
+ let index = 1;
3841
+ let nomk = false;
3842
+ let maxlen = null;
3843
+ const key = text(args[0]);
3844
+ while (index < args.length) {
3845
+ const option = upper(args[index]);
3846
+ if (option === "NOMKSTREAM") {
3847
+ nomk = true;
3848
+ index++;
3849
+ continue;
3850
+ }
3851
+ if (option === "MAXLEN" || option === "MINID") {
3852
+ index++;
3853
+ if (upper(args[index]) === "~" || upper(args[index]) === "=") index++;
3854
+ const parsed = parseI64(args[index] ?? utf8(""));
3855
+ if (parsed === null || parsed < 0n) return syntaxError();
3856
+ if (option === "MAXLEN") maxlen = Number(parsed);
3857
+ index++;
3858
+ continue;
3859
+ }
3860
+ break;
3861
+ }
3862
+ const idArg = text(args[index]);
3863
+ const fields = args.slice(index + 1);
3864
+ if (fields.length < 2 || fields.length % 2 !== 0) return syntaxError();
3865
+ const database = this.database(session.db);
3866
+ if (!database.has(key) && nomk) return nil();
3867
+ const stream = this.stream(session, key, true);
3868
+ if (!stream || !("entries" in stream)) return stream ?? wrongType();
3869
+ if (!apply) return queued();
3870
+ const id = this.nextId(stream, idArg);
3871
+ if (typeof id !== "string") return id;
3872
+ stream.entries.push({ id, ...parseStreamId(id) ?? { ms: 0n, seq: 0n }, fields });
3873
+ if (maxlen !== null && stream.entries.length > maxlen) {
3874
+ stream.entries.splice(0, stream.entries.length - maxlen);
3875
+ }
3876
+ const value = database.get(key);
3877
+ if (value && value.kind === "stream") database.put(key, value);
3878
+ this.wake(session.db, key);
3879
+ return bulk(id);
3880
+ }
3881
+ nextId(stream, requested) {
3882
+ if (requested === "*") {
3883
+ let ms = BigInt(Math.max(0, Math.trunc(this.clock.now())));
3884
+ if (ms > stream.lastMs) {
3885
+ stream.lastMs = ms;
3886
+ stream.lastSeq = 0n;
3887
+ } else {
3888
+ ms = stream.lastMs;
3889
+ stream.lastSeq += 1n;
3890
+ }
3891
+ if (stream.lastMs === 0n && stream.lastSeq === 0n) stream.lastSeq = 1n;
3892
+ return `${stream.lastMs}-${stream.lastSeq}`;
3893
+ }
3894
+ const parsed = parseStreamId(requested);
3895
+ if (!parsed) return err("ERR Invalid stream ID specified as stream command argument");
3896
+ if (parsed.ms === 0n && parsed.seq === 0n) {
3897
+ return err("ERR The ID specified in XADD must be greater than 0-0");
3898
+ }
3899
+ const top = stream.entries[stream.entries.length - 1];
3900
+ if (top && cmpId(requested, top.id) <= 0) {
3901
+ return err("ERR The ID specified in XADD is equal or smaller than the target stream top item");
3902
+ }
3903
+ if (parsed.ms > stream.lastMs || parsed.ms === stream.lastMs && parsed.seq > stream.lastSeq) {
3904
+ stream.lastMs = parsed.ms;
3905
+ stream.lastSeq = parsed.seq;
3906
+ }
3907
+ return `${parsed.ms}-${parsed.seq}`;
3908
+ }
3909
+ cmdXrange(session, command, args) {
3910
+ if (args.length < 3) return arityError(command);
3911
+ const stream = this.stream(session, text(args[0]), false);
3912
+ if (stream === null) return arr([]);
3913
+ if (!("entries" in stream)) return stream;
3914
+ const start = text(args[1]);
3915
+ const end = text(args[2]);
3916
+ let count = Infinity;
3917
+ if (upper(args[3]) === "COUNT") {
3918
+ const parsed = parseI64(args[4] ?? utf8(""));
3919
+ if (parsed === null) return syntaxError();
3920
+ count = Number(parsed);
3921
+ }
3922
+ let entries = stream.entries.filter((entry) => {
3923
+ const afterStart = start === "-" || cmpId(entry.id, start) >= 0;
3924
+ const beforeEnd = end === "+" || cmpId(entry.id, end) <= 0;
3925
+ return afterStart && beforeEnd;
3926
+ });
3927
+ if (command === "xrevrange") entries = entries.slice().reverse();
3928
+ return arr(entries.slice(0, count).map((entry) => entryFields(entry)));
3929
+ }
3930
+ cmdXdel(session, args, apply) {
3931
+ if (args.length < 2) return arityError("xdel");
3932
+ const stream = this.stream(session, text(args[0]), false);
3933
+ if (stream === null) return int(0);
3934
+ if (!("entries" in stream)) return stream;
3935
+ if (!apply) return queued();
3936
+ const ids = new Set(args.slice(1).map((arg) => text(arg)));
3937
+ const before = stream.entries.length;
3938
+ stream.entries = stream.entries.filter((entry) => !ids.has(entry.id));
3939
+ return int(before - stream.entries.length);
3940
+ }
3941
+ cmdXtrim(session, args, apply) {
3942
+ if (args.length < 3) return arityError("xtrim");
3943
+ const stream = this.stream(session, text(args[0]), false);
3944
+ if (stream === null) return int(0);
3945
+ if (!("entries" in stream)) return stream;
3946
+ let index = 1;
3947
+ const strategy = upper(args[index]);
3948
+ if (strategy !== "MAXLEN" && strategy !== "MINID") return syntaxError();
3949
+ index++;
3950
+ if (upper(args[index]) === "~" || upper(args[index]) === "=") index++;
3951
+ if (!apply) return queued();
3952
+ const before = stream.entries.length;
3953
+ if (strategy === "MAXLEN") {
3954
+ const length = parseI64(args[index] ?? utf8(""));
3955
+ if (length === null) return syntaxError();
3956
+ if (stream.entries.length > Number(length)) {
3957
+ stream.entries.splice(0, stream.entries.length - Number(length));
3958
+ }
3959
+ }
3960
+ return int(before - stream.entries.length);
3961
+ }
3962
+ cmdXgroup(session, args, apply) {
3963
+ const sub = upper(args[0]);
3964
+ if (sub === "CREATE") {
3965
+ if (args.length < 4) return arityError("xgroup");
3966
+ const key = text(args[1]);
3967
+ const group = text(args[2]);
3968
+ const id = text(args[3]);
3969
+ const mk = args.some((arg) => upper(arg) === "MKSTREAM");
3970
+ let stream = this.stream(session, key, false);
3971
+ if (stream === null) {
3972
+ if (!mk) {
3973
+ return err(
3974
+ "ERR The XGROUP subcommand requires the key to exist. Note that for CREATE you may want to use the MKSTREAM option to create an empty stream automatically."
3975
+ );
3976
+ }
3977
+ if (!apply) return queued();
3978
+ stream = this.stream(session, key, true);
3979
+ }
3980
+ if (!stream || !("groups" in stream)) return stream ?? wrongType();
3981
+ if (stream.groups.has(group)) return err("BUSYGROUP Consumer Group name already exists");
3982
+ if (!apply) return queued();
3983
+ const last = stream.entries[stream.entries.length - 1];
3984
+ const lastId = id === "$" ? last?.id ?? "0-0" : id === "0" ? "0-0" : id;
3985
+ if (lastId !== "0-0" && !parseStreamId(lastId)) {
3986
+ return err("ERR Invalid stream ID specified as stream command argument");
3987
+ }
3988
+ stream.groups.set(group, { name: group, lastId, consumers: /* @__PURE__ */ new Map(), pel: /* @__PURE__ */ new Map() });
3989
+ return ok();
3990
+ }
3991
+ if (sub === "DESTROY") {
3992
+ if (args.length !== 3) return arityError("xgroup");
3993
+ const stream = this.stream(session, text(args[1]), false);
3994
+ if (!stream || !("groups" in stream)) return int(0);
3995
+ if (!apply) return queued();
3996
+ return int(stream.groups.delete(text(args[2])) ? 1 : 0);
3997
+ }
3998
+ if (sub === "CREATECONSUMER") {
3999
+ if (args.length !== 4) return arityError("xgroup");
4000
+ const stream = this.stream(session, text(args[1]), false);
4001
+ if (!stream || !("groups" in stream))
4002
+ return err("ERR The XGROUP subcommand requires the key to exist");
4003
+ const group = stream.groups.get(text(args[2]));
4004
+ if (!group) return err("NOGROUP No such consumer group");
4005
+ if (!apply) return queued();
4006
+ const name = text(args[3]);
4007
+ const created = !group.consumers.has(name);
4008
+ group.consumers.set(name, { seenAt: this.clock.now() });
4009
+ return int(created ? 1 : 0);
4010
+ }
4011
+ if (sub === "DELCONSUMER") {
4012
+ if (args.length !== 4) return arityError("xgroup");
4013
+ const stream = this.stream(session, text(args[1]), false);
4014
+ if (!stream || !("groups" in stream)) return int(0);
4015
+ const group = stream.groups.get(text(args[2]));
4016
+ if (!group) return int(0);
4017
+ if (!apply) return queued();
4018
+ const name = text(args[3]);
4019
+ let pending = 0;
4020
+ for (const [id, item] of group.pel) {
4021
+ if (item.consumer !== name) continue;
4022
+ group.pel.delete(id);
4023
+ pending++;
4024
+ }
4025
+ group.consumers.delete(name);
4026
+ return int(pending);
4027
+ }
4028
+ if (sub === "SETID") {
4029
+ if (args.length < 4) return arityError("xgroup");
4030
+ const stream = this.stream(session, text(args[1]), false);
4031
+ if (!stream || !("groups" in stream))
4032
+ return err("ERR The XGROUP subcommand requires the key to exist");
4033
+ const group = stream.groups.get(text(args[2]));
4034
+ if (!group) return err("NOGROUP No such consumer group");
4035
+ if (!apply) return queued();
4036
+ const id = text(args[3]);
4037
+ group.lastId = id === "$" ? stream.entries[stream.entries.length - 1]?.id ?? "0-0" : id;
4038
+ return ok();
4039
+ }
4040
+ return err(`ERR unknown subcommand '${text(args[0]).toLowerCase()}'. Try XGROUP HELP.`);
4041
+ }
4042
+ cmdXread(session, args, apply, group) {
4043
+ let index = 0;
4044
+ let groupName = "";
4045
+ let consumer = "";
4046
+ let count = Infinity;
4047
+ let block = null;
4048
+ let noack = false;
4049
+ if (group) {
4050
+ if (upper(args[0]) !== "GROUP") return syntaxError();
4051
+ groupName = text(args[1]);
4052
+ consumer = text(args[2]);
4053
+ index = 3;
4054
+ }
4055
+ while (index < args.length && upper(args[index]) !== "STREAMS") {
4056
+ const option = upper(args[index]);
4057
+ if (option === "COUNT") {
4058
+ const parsed = parseI64(args[++index] ?? utf8(""));
4059
+ if (parsed === null || parsed < 0n) return syntaxError();
4060
+ count = Number(parsed);
4061
+ } else if (option === "BLOCK") {
4062
+ const parsed = parseI64(args[++index] ?? utf8(""));
4063
+ if (parsed === null || parsed < 0n) return syntaxError();
4064
+ block = Number(parsed);
4065
+ } else if (option === "NOACK") noack = true;
4066
+ else return syntaxError();
4067
+ index++;
4068
+ }
4069
+ if (upper(args[index]) !== "STREAMS") return syntaxError();
4070
+ const rest = args.slice(index + 1);
4071
+ if (rest.length < 2 || rest.length % 2 !== 0) return syntaxError();
4072
+ const half = rest.length / 2;
4073
+ const keys = rest.slice(0, half).map((arg) => text(arg));
4074
+ const ids = rest.slice(half).map((arg) => text(arg));
4075
+ if (!apply) return queued();
4076
+ for (const key of keys) {
4077
+ const value = this.database(session.db).get(key);
4078
+ if (value && value.kind !== "stream") return wrongType();
4079
+ }
4080
+ const read = this.readStreams(session, keys, ids, count, group, groupName, consumer, noack);
4081
+ if (read.t === "error") return read;
4082
+ if (!(this.consumersPaused && block !== null) && read.t === "array" && read.v && read.v.length > 0) {
4083
+ return read;
4084
+ }
4085
+ if (block === null || session.inExec) return nilArray();
4086
+ if (this.scriptDepth > 0) return scriptDenied();
4087
+ const streamIds = ids.map((id, idIndex) => {
4088
+ if (id !== "$") return id;
4089
+ const existing = this.stream(session, keys[idIndex] ?? "", false);
4090
+ if (existing && "entries" in existing) return existing.entries.at(-1)?.id ?? "0-0";
4091
+ return "0-0";
4092
+ });
4093
+ return this.block({
4094
+ kind: "stream",
4095
+ keys,
4096
+ db: session.db,
4097
+ side: "left",
4098
+ count,
4099
+ streamIds,
4100
+ ...group ? { group: groupName, consumer, ...noack ? { noack: true } : {} } : {},
4101
+ ...block > 0 ? { deadline: this.clock.now() + block } : {},
4102
+ session
4103
+ });
4104
+ }
4105
+ readStreams(session, keys, ids, count, grouped, groupName, consumer, noack) {
4106
+ const rows = [];
4107
+ for (let i = 0; i < keys.length; i++) {
4108
+ const key = keys[i] ?? "";
4109
+ const id = ids[i] ?? "0-0";
4110
+ const stream = this.stream(session, key, false);
4111
+ if (stream === null) continue;
4112
+ if (!("entries" in stream)) return stream;
4113
+ let chosen = [];
4114
+ if (!grouped) {
4115
+ chosen = stream.entries.filter((entry) => id === "$" ? false : cmpId(entry.id, id) > 0).slice(0, count);
4116
+ } else {
4117
+ const group = stream.groups.get(groupName);
4118
+ if (!group) return err("NOGROUP No such key or consumer group");
4119
+ if (id === ">") {
4120
+ chosen = stream.entries.filter((entry) => cmpId(entry.id, group.lastId) > 0).slice(0, count);
4121
+ for (const entry of chosen) {
4122
+ group.lastId = entry.id;
4123
+ if (!noack) {
4124
+ const pending = group.pel.get(entry.id);
4125
+ group.pel.set(entry.id, {
4126
+ id: entry.id,
4127
+ consumer,
4128
+ deliveries: (pending?.deliveries ?? 0) + 1,
4129
+ deliveredAt: this.clock.now()
4130
+ });
4131
+ }
4132
+ group.consumers.set(consumer, { seenAt: this.clock.now() });
4133
+ }
4134
+ } else {
4135
+ chosen = [...group.pel.values()].filter((item) => item.consumer === consumer && cmpId(item.id, id) > 0).map((item) => stream.entries.find((entry) => entry.id === item.id)).filter((entry) => entry !== void 0).slice(0, count);
4136
+ }
4137
+ }
4138
+ if (chosen.length > 0)
4139
+ rows.push(arr([bulk(key), arr(chosen.map((entry) => entryFields(entry)))]));
4140
+ }
4141
+ return rows.length > 0 ? arr(rows) : nilArray();
4142
+ }
4143
+ cmdXack(session, args, apply) {
4144
+ if (args.length < 3) return arityError("xack");
4145
+ const stream = this.stream(session, text(args[0]), false);
4146
+ if (stream === null) return int(0);
4147
+ if (!("groups" in stream)) return stream;
4148
+ const group = stream.groups.get(text(args[1]));
4149
+ if (!group) return int(0);
4150
+ if (!apply) return queued();
4151
+ let acked = 0;
4152
+ for (const id of args.slice(2).map((arg) => text(arg))) {
4153
+ if (group.pel.delete(id)) acked++;
4154
+ }
4155
+ return int(acked);
4156
+ }
4157
+ cmdXpending(session, args) {
4158
+ if (args.length < 2) return arityError("xpending");
4159
+ const stream = this.stream(session, text(args[0]), false);
4160
+ if (stream === null) return err("ERR no such key");
4161
+ if (!("groups" in stream)) return stream;
4162
+ const group = stream.groups.get(text(args[1]));
4163
+ if (!group) return err("NOGROUP No such consumer group");
4164
+ if (args.length === 2) {
4165
+ if (group.pel.size === 0) return arr([int(0), nil(), nil(), arr([])]);
4166
+ const ids = [...group.pel.keys()].sort(cmpId);
4167
+ const counts = /* @__PURE__ */ new Map();
4168
+ for (const item of group.pel.values())
4169
+ counts.set(item.consumer, (counts.get(item.consumer) ?? 0) + 1);
4170
+ return arr([
4171
+ int(group.pel.size),
4172
+ bulk(ids[0] ?? ""),
4173
+ bulk(ids[ids.length - 1] ?? ""),
4174
+ arr([...counts].map(([name, count2]) => arr([bulk(name), bulk(String(count2))])))
4175
+ ]);
4176
+ }
4177
+ const start = text(args[2]);
4178
+ const end = text(args[3]);
4179
+ const count = parseI64(args[4] ?? utf8(""));
4180
+ if (count === null) return syntaxError();
4181
+ const consumer = args[5] ? text(args[5]) : null;
4182
+ const rows = [...group.pel.values()].filter(
4183
+ (item) => (start === "-" || cmpId(item.id, start) >= 0) && (end === "+" || cmpId(item.id, end) <= 0)
4184
+ ).filter((item) => consumer === null || item.consumer === consumer).sort((a, b) => cmpId(a.id, b.id)).slice(0, Number(count));
4185
+ return arr(
4186
+ rows.map(
4187
+ (item) => arr([
4188
+ bulk(item.id),
4189
+ bulk(item.consumer),
4190
+ int(Math.max(0, this.clock.now() - item.deliveredAt)),
4191
+ int(item.deliveries)
4192
+ ])
4193
+ )
4194
+ );
4195
+ }
4196
+ cmdXclaim(session, args, apply, auto) {
4197
+ const minIdleIndex = auto ? 3 : 3;
4198
+ if (args.length < (auto ? 5 : 5)) return arityError(auto ? "xautoclaim" : "xclaim");
4199
+ const stream = this.stream(session, text(args[0]), false);
4200
+ if (!stream || !("groups" in stream)) return auto ? nilArray() : arr([]);
4201
+ const group = stream.groups.get(text(args[1]));
4202
+ if (!group) return err("NOGROUP No such consumer group");
4203
+ const consumer = text(args[2]);
4204
+ const minIdle = Number(text(args[minIdleIndex]));
4205
+ if (!Number.isFinite(minIdle) || minIdle < 0) return syntaxError();
4206
+ if (!apply) return queued();
4207
+ let count = auto ? 100 : Infinity;
4208
+ let justId = false;
4209
+ const ids = [];
4210
+ if (auto) {
4211
+ const start = text(args[4]);
4212
+ for (let i = 5; i < args.length; i++) {
4213
+ if (upper(args[i]) === "COUNT") {
4214
+ const parsed = parseI64(args[++i] ?? utf8(""));
4215
+ if (parsed === null) return syntaxError();
4216
+ count = Number(parsed);
4217
+ } else if (upper(args[i]) === "JUSTID") justId = true;
4218
+ }
4219
+ const claimed2 = [];
4220
+ const deleted = [];
4221
+ let finished = true;
4222
+ for (const id of [...group.pel.keys()].sort(cmpId)) {
4223
+ if (cmpId(id, start) <= 0) continue;
4224
+ if (claimed2.length >= count) {
4225
+ finished = false;
4226
+ break;
4227
+ }
4228
+ const item = group.pel.get(id);
4229
+ if (!item) continue;
4230
+ const entry = stream.entries.find((candidate) => candidate.id === id);
4231
+ if (!entry) {
4232
+ group.pel.delete(id);
4233
+ deleted.push(id);
4234
+ continue;
4235
+ }
4236
+ if (this.clock.now() - item.deliveredAt < minIdle) continue;
4237
+ item.consumer = consumer;
4238
+ item.deliveries++;
4239
+ item.deliveredAt = this.clock.now();
4240
+ group.consumers.set(consumer, { seenAt: this.clock.now() });
4241
+ claimed2.push(entry);
4242
+ }
4243
+ return arr([
4244
+ bulk(finished ? "0-0" : claimed2[claimed2.length - 1]?.id ?? "0-0"),
4245
+ arr(claimed2.map((entry) => justId ? bulk(entry.id) : entryFields(entry))),
4246
+ arr(deleted.map((id) => bulk(id)))
4247
+ ]);
4248
+ }
4249
+ for (let i = 4; i < args.length; i++) {
4250
+ const option = upper(args[i]);
4251
+ if (option === "IDLE" || option === "TIME" || option === "RETRYCOUNT") i++;
4252
+ else if (option === "FORCE" || option === "JUSTID") {
4253
+ if (option === "JUSTID") justId = true;
4254
+ } else ids.push(text(args[i]));
4255
+ }
4256
+ const claimed = [];
4257
+ for (const id of ids) {
4258
+ const item = group.pel.get(id);
4259
+ const entry = stream.entries.find((candidate) => candidate.id === id);
4260
+ if (!item || !entry) continue;
4261
+ if (this.clock.now() - item.deliveredAt < minIdle) continue;
4262
+ item.consumer = consumer;
4263
+ item.deliveries++;
4264
+ item.deliveredAt = this.clock.now();
4265
+ claimed.push(entry);
4266
+ }
4267
+ group.consumers.set(consumer, { seenAt: this.clock.now() });
4268
+ return arr(claimed.map((entry) => justId ? bulk(entry.id) : entryFields(entry)));
4269
+ }
4270
+ cmdXinfo(session, args) {
4271
+ const sub = upper(args[0]);
4272
+ if (sub === "STREAM") {
4273
+ const stream = this.stream(session, text(args[1]), false);
4274
+ if (!stream || !("entries" in stream)) return err("ERR no such key");
4275
+ const first2 = stream.entries[0];
4276
+ const last = stream.entries[stream.entries.length - 1];
4277
+ return arr([
4278
+ bulk("length"),
4279
+ int(stream.entries.length),
4280
+ bulk("radix-tree-keys"),
4281
+ int(0),
4282
+ bulk("radix-tree-nodes"),
4283
+ int(0),
4284
+ bulk("last-generated-id"),
4285
+ bulk(last?.id ?? "0-0"),
4286
+ bulk("groups"),
4287
+ int(stream.groups.size),
4288
+ bulk("first-entry"),
4289
+ first2 ? entryFields(first2) : nil(),
4290
+ bulk("last-entry"),
4291
+ last ? entryFields(last) : nil()
4292
+ ]);
4293
+ }
4294
+ if (sub === "GROUPS") {
4295
+ const stream = this.stream(session, text(args[1]), false);
4296
+ if (!stream || !("groups" in stream)) return err("ERR no such key");
4297
+ return arr(
4298
+ [...stream.groups.values()].map(
4299
+ (group) => arr([
4300
+ bulk("name"),
4301
+ bulk(group.name),
4302
+ bulk("consumers"),
4303
+ int(group.consumers.size),
4304
+ bulk("pending"),
4305
+ int(group.pel.size),
4306
+ bulk("last-delivered-id"),
4307
+ bulk(group.lastId)
4308
+ ])
4309
+ )
4310
+ );
4311
+ }
4312
+ if (sub === "CONSUMERS") {
4313
+ const stream = this.stream(session, text(args[1]), false);
4314
+ if (!stream || !("groups" in stream)) return err("ERR no such key");
4315
+ const group = stream.groups.get(text(args[2]));
4316
+ if (!group) return err("NOGROUP No such consumer group");
4317
+ return arr(
4318
+ [...group.consumers].map(([name, info]) => {
4319
+ let pending = 0;
4320
+ for (const item of group.pel.values()) if (item.consumer === name) pending++;
4321
+ return arr([
4322
+ bulk("name"),
4323
+ bulk(name),
4324
+ bulk("pending"),
4325
+ int(pending),
4326
+ bulk("idle"),
4327
+ int(Math.max(0, this.clock.now() - info.seenAt))
4328
+ ]);
4329
+ })
4330
+ );
4331
+ }
4332
+ return syntaxError();
4333
+ }
4334
+ cmdSub(session, command, args) {
4335
+ const pattern = command.startsWith("p");
4336
+ const leaving = command.includes("unsubscribe");
4337
+ const names = args.map((arg) => text(arg));
4338
+ if (!leaving && names.length === 0) return arityError(command);
4339
+ const targets = names.length > 0 ? names : [...pattern ? session.patterns : session.channels];
4340
+ if (targets.length === 0) {
4341
+ return push([bulk(command), nil(), int(session.channels.size + session.patterns.size)]);
4342
+ }
4343
+ let last = ok();
4344
+ targets.forEach((name, index) => {
4345
+ if (leaving) {
4346
+ if (pattern) {
4347
+ session.patterns.delete(name);
4348
+ this.patternSubs.get(name)?.delete(session);
4349
+ } else {
4350
+ session.channels.delete(name);
4351
+ this.channelSubs.get(name)?.delete(session);
4352
+ }
4353
+ } else if (pattern) {
4354
+ session.patterns.add(name);
4355
+ const set = this.patternSubs.get(name) ?? /* @__PURE__ */ new Set();
4356
+ set.add(session);
4357
+ this.patternSubs.set(name, set);
4358
+ } else {
4359
+ session.channels.add(name);
4360
+ const set = this.channelSubs.get(name) ?? /* @__PURE__ */ new Set();
4361
+ set.add(session);
4362
+ this.channelSubs.set(name, set);
4363
+ }
4364
+ const reply = push([
4365
+ bulk(command),
4366
+ bulk(name),
4367
+ int(session.channels.size + session.patterns.size)
4368
+ ]);
4369
+ if (index < targets.length - 1) session.onPush(reply);
4370
+ else last = reply;
4371
+ });
4372
+ return last;
4373
+ }
4374
+ cmdPublish(args) {
4375
+ if (args.length !== 2) return arityError("publish");
4376
+ const channel = text(args[0]);
4377
+ const message = args[1] ?? utf8("");
4378
+ let delivered = 0;
4379
+ for (const session of this.channelSubs.get(channel) ?? []) {
4380
+ session.onPush(push([bulk("message"), bulk(channel), { t: "bulk", v: message }]));
4381
+ delivered++;
4382
+ }
4383
+ for (const [pattern, sessions] of this.patternSubs) {
4384
+ if (!globMatch(pattern, channel)) continue;
4385
+ for (const session of sessions) {
4386
+ session.onPush(
4387
+ push([bulk("pmessage"), bulk(pattern), bulk(channel), { t: "bulk", v: message }])
4388
+ );
4389
+ delivered++;
4390
+ }
4391
+ }
4392
+ return int(delivered);
4393
+ }
4394
+ cmdPubsub(args) {
4395
+ const sub = upper(args[0]);
4396
+ if (sub === "NUMSUB") {
4397
+ const rows = [];
4398
+ for (const arg of args.slice(1)) {
4399
+ rows.push(bulk(text(arg)), int(this.channelSubs.get(text(arg))?.size ?? 0));
4400
+ }
4401
+ return arr(rows);
4402
+ }
4403
+ if (sub === "CHANNELS") {
4404
+ const pattern = args[1] ? text(args[1]) : "*";
4405
+ return arr(
4406
+ [...this.channelSubs.keys()].filter((channel) => globMatch(pattern, channel)).map((channel) => bulk(channel))
4407
+ );
4408
+ }
4409
+ if (sub === "NUMPAT") return int(this.patternSubs.size);
4410
+ return arityError("pubsub");
4411
+ }
4412
+ cmdExec(session) {
4413
+ if (!session.multi) return execWithoutMulti();
4414
+ const queuedCommands = session.multi;
4415
+ const failed = session.multiFailed;
4416
+ const watching = session.watching;
4417
+ session.multi = null;
4418
+ session.multiFailed = false;
4419
+ session.watching = [];
4420
+ if (failed) return execAbort();
4421
+ for (const watch of watching) {
4422
+ if (this.database(watch.db).generation(watch.key) !== watch.gen) return nilArray();
4423
+ }
4424
+ const replies = [];
4425
+ session.inExec = true;
4426
+ try {
4427
+ for (const command of queuedCommands) {
4428
+ const reply = this.execCmd(session, command.name, command.args, true);
4429
+ replies.push(reply instanceof Promise ? nilArray() : reply);
4430
+ }
4431
+ } finally {
4432
+ session.inExec = false;
4433
+ }
4434
+ return arr(replies);
4435
+ }
4436
+ cmdDiscard(session) {
4437
+ if (!session.multi) return discardWithoutMulti();
4438
+ session.multi = null;
4439
+ session.multiFailed = false;
4440
+ session.watching = [];
4441
+ return ok();
4442
+ }
4443
+ cmdWatch(session, args) {
4444
+ if (args.length < 1) return arityError("watch");
4445
+ if (session.multi) return err("ERR WATCH inside MULTI is not allowed");
4446
+ for (const arg of args) {
4447
+ const key = text(arg);
4448
+ session.watching.push({ db: session.db, key, gen: this.database(session.db).generation(key) });
4449
+ }
4450
+ return ok();
4451
+ }
4452
+ cmdEval(session, command, args, apply) {
4453
+ if (args.length < 2) return arityError(command);
4454
+ const source = command === "eval" ? text(args[0]) : this.scripts.get(text(args[0]).toLowerCase());
4455
+ if (command === "evalsha" && source === void 0) return noScript();
4456
+ const numkeys = parseI64(args[1] ?? utf8(""));
4457
+ if (numkeys === null) return notInteger();
4458
+ if (numkeys < 0n) return err("ERR Number of keys can't be negative");
4459
+ if (args.length < 2 + Number(numkeys)) {
4460
+ return err("ERR Number of keys can't be greater than number of args");
4461
+ }
4462
+ if (!apply || source === void 0) return queued();
4463
+ const keys = args.slice(2, 2 + Number(numkeys));
4464
+ const argv = args.slice(2 + Number(numkeys));
4465
+ return this.runScript(session, source, keys, argv);
4466
+ }
4467
+ cmdScript(args, apply) {
4468
+ const sub = upper(args[0]);
4469
+ if (sub === "LOAD") {
4470
+ if (args.length !== 2) return arityError("script|load");
4471
+ if (!apply) return queued();
4472
+ const source = text(args[1]);
4473
+ const digest = sha1Hex(utf8(source));
4474
+ this.scripts.set(digest, source);
4475
+ return bulk(digest);
4476
+ }
4477
+ if (sub === "EXISTS") {
4478
+ return arr(args.slice(1).map((arg) => int(this.scripts.has(text(arg).toLowerCase()) ? 1 : 0)));
4479
+ }
4480
+ if (sub === "FLUSH") {
4481
+ if (!apply) return queued();
4482
+ this.scripts.clear();
4483
+ return ok();
4484
+ }
4485
+ if (sub === "KILL") {
4486
+ if (this.faultValue !== "busy" && this.scriptDepth === 0) return notBusy();
4487
+ if (this.faultValue === "busy") this.faultValue = null;
4488
+ return ok();
4489
+ }
4490
+ return arityError("script");
4491
+ }
4492
+ runScript(session, source, keys, argv) {
4493
+ const host = {
4494
+ call: (callArgs) => this.luaDispatch(session, callArgs, false),
4495
+ pcall: (callArgs) => this.luaDispatch(session, callArgs, true)
4496
+ };
4497
+ const env = luaGlobals(host);
4498
+ const keyTable = env.get("redis");
4499
+ const keysTable = new LuaTable();
4500
+ const argvTable = new LuaTable();
4501
+ keys.forEach((key, index) => {
4502
+ keysTable.set(index + 1, latin1(key));
4503
+ });
4504
+ argv.forEach((arg, index) => {
4505
+ argvTable.set(index + 1, latin1(arg));
4506
+ });
4507
+ env.define("KEYS", keysTable);
4508
+ env.define("ARGV", argvTable);
4509
+ if (keyTable instanceof LuaTable) keyTable.set("pcall", host.pcall);
4510
+ this.scriptDepth++;
4511
+ try {
4512
+ const values = runLua(source, env);
4513
+ return luaToReply(values[0] ?? null);
4514
+ } catch (error) {
4515
+ const message = error instanceof Error ? error.message : String(error);
4516
+ return err(`ERR Error running script: ${message}`);
4517
+ } finally {
4518
+ this.scriptDepth--;
4519
+ }
4520
+ }
4521
+ luaDispatch(session, args, trap) {
4522
+ const name = typeof args[0] === "string" ? args[0].toLowerCase() : "";
4523
+ const bytes = args.slice(1).map((arg) => luaToBytes(arg));
4524
+ try {
4525
+ const reply = this.execCmd(session, name, bytes, true);
4526
+ if (reply instanceof Promise)
4527
+ throw new Error("This Redis command is not allowed from scripts");
4528
+ if (reply.t === "error") {
4529
+ if (!trap) throw new Error(reply.v);
4530
+ const table = new LuaTable();
4531
+ table.set("err", reply.v);
4532
+ return [table];
4533
+ }
4534
+ return [replyToLua(reply)];
4535
+ } catch (error) {
4536
+ if (!trap) throw error;
4537
+ const table = new LuaTable();
4538
+ table.set("err", error instanceof Error ? error.message : String(error));
4539
+ return [table];
4540
+ }
4541
+ }
4542
+ block(spec) {
4543
+ return new Promise((resolve, reject) => {
4544
+ const waiter = {
4545
+ kind: spec.kind,
4546
+ keys: spec.keys,
4547
+ db: spec.db,
4548
+ side: spec.side,
4549
+ count: spec.count,
4550
+ settled: false,
4551
+ resolve,
4552
+ reject,
4553
+ ...spec.deadline !== void 0 ? { deadline: spec.deadline } : {},
4554
+ ...spec.group !== void 0 ? { group: spec.group } : {},
4555
+ ...spec.consumer !== void 0 ? { consumer: spec.consumer } : {},
4556
+ ...spec.streamIds !== void 0 ? { streamIds: spec.streamIds } : {},
4557
+ ...spec.noack ? { noack: true } : {},
4558
+ ...spec.max ? { max: true } : {},
4559
+ ...spec.destKey !== void 0 ? { destKey: spec.destKey } : {},
4560
+ ...spec.destSide !== void 0 ? { destSide: spec.destSide } : {}
4561
+ };
4562
+ waiterSession.set(waiter, spec.session);
4563
+ this.waiters.push(waiter);
4564
+ if (spec.deadline !== void 0) {
4565
+ const deadline = spec.deadline;
4566
+ const fire = () => {
4567
+ if (this.clock.now() < deadline) return;
4568
+ this.enqueue(() => this.finishWaiter(waiter, nilArray()));
4569
+ };
4570
+ waiter.unsub = this.clock.subscribe(fire);
4571
+ if (!this.clock.manual) {
4572
+ waiter.timer = setTimeout(fire, Math.max(0, deadline - this.clock.now()));
4573
+ }
4574
+ }
4575
+ });
4576
+ }
4577
+ wake(dbIndex, key) {
4578
+ for (const waiter of [...this.waiters]) {
4579
+ if (waiter.settled || waiter.db !== dbIndex || !waiter.keys.includes(key)) continue;
4580
+ const reply = this.satisfy(waiter);
4581
+ if (reply) this.finishWaiter(waiter, reply);
4582
+ }
4583
+ }
4584
+ wakeAll() {
4585
+ for (const waiter of [...this.waiters]) {
4586
+ const reply = this.satisfy(waiter);
4587
+ if (reply) this.finishWaiter(waiter, reply);
4588
+ }
4589
+ }
4590
+ satisfy(waiter) {
4591
+ const session = waiterSession.get(waiter);
4592
+ if (!session || session.dead) return nilArray();
4593
+ if (this.consumersPaused) return null;
4594
+ if (waiter.kind === "list") return this.takeList(waiter.db, waiter.keys, waiter.side);
4595
+ if (waiter.kind === "zset") return this.takeZset(waiter.db, waiter.keys, waiter.max === true);
4596
+ const reply = this.readStreams(
4597
+ session,
4598
+ waiter.keys,
4599
+ waiter.streamIds ?? waiter.keys.map(() => ">"),
4600
+ waiter.count,
4601
+ waiter.group !== void 0,
4602
+ waiter.group ?? "",
4603
+ waiter.consumer ?? "",
4604
+ waiter.noack === true
4605
+ );
4606
+ if (reply.t === "array" && reply.v && reply.v.length > 0) return reply;
4607
+ if (reply.t === "error") return reply;
4608
+ return null;
4609
+ }
4610
+ finishWaiter(waiter, reply) {
4611
+ if (waiter.settled) return;
4612
+ waiter.settled = true;
4613
+ waiter.unsub?.();
4614
+ if (waiter.timer !== void 0) clearTimeout(waiter.timer);
4615
+ this.waiters = this.waiters.filter((item) => item !== waiter);
4616
+ waiterSession.delete(waiter);
4617
+ waiter.resolve(reply);
4618
+ }
4619
+ failWaiter(waiter, error) {
4620
+ if (waiter.settled) return;
4621
+ waiter.settled = true;
4622
+ waiter.unsub?.();
4623
+ if (waiter.timer !== void 0) clearTimeout(waiter.timer);
4624
+ this.waiters = this.waiters.filter((item) => item !== waiter);
4625
+ waiterSession.delete(waiter);
4626
+ waiter.reject(error);
4627
+ }
4628
+ leavePubsub(session) {
4629
+ for (const channel of session.channels) this.channelSubs.get(channel)?.delete(session);
4630
+ for (const pattern of session.patterns) this.patternSubs.get(pattern)?.delete(session);
4631
+ session.channels.clear();
4632
+ session.patterns.clear();
4633
+ }
4634
+ };
4635
+ var waiterSession = /* @__PURE__ */ new WeakMap();
4636
+ function concatBytes(left, right) {
4637
+ const out = new Uint8Array(left.length + right.length);
4638
+ out.set(left);
4639
+ out.set(right, left.length);
4640
+ return out;
4641
+ }
4642
+ function compareBytes(left, right) {
4643
+ const length = Math.min(left.length, right.length);
4644
+ for (let i = 0; i < length; i++) {
4645
+ const diff = (left[i] ?? 0) - (right[i] ?? 0);
4646
+ if (diff !== 0) return diff;
4647
+ }
4648
+ return left.length - right.length;
4649
+ }
4650
+ function indexes(length, startRaw, endRaw) {
4651
+ let start = Number(text(startRaw));
4652
+ let end = Number(text(endRaw));
4653
+ if (!Number.isInteger(start)) start = 0;
4654
+ if (!Number.isInteger(end)) end = -1;
4655
+ if (start < 0) start = length + start;
4656
+ if (end < 0) end = length + end;
4657
+ if (start < 0) start = 0;
4658
+ if (end >= length) end = length - 1;
4659
+ return [start, end];
4660
+ }
4661
+ function listIndex(length, raw) {
4662
+ const parsed = parseI64(raw ?? utf8(""));
4663
+ if (parsed === null) return null;
4664
+ let index = Number(parsed);
4665
+ if (index < 0) index = length + index;
4666
+ if (index < 0 || index >= length) return null;
4667
+ return index;
4668
+ }
4669
+ function parseBound(value) {
4670
+ if (value === "+inf" || value === "inf") return { value: Infinity, exclusive: false };
4671
+ if (value === "-inf") return { value: -Infinity, exclusive: false };
4672
+ let exclusive = false;
4673
+ let textValue = value;
4674
+ if (textValue.startsWith("(")) {
4675
+ exclusive = true;
4676
+ textValue = textValue.slice(1);
4677
+ }
4678
+ const score = parseScore(utf8(textValue));
4679
+ if (score === null) return null;
4680
+ return { value: score, exclusive };
4681
+ }
4682
+ function inBound(score, min, max) {
4683
+ if (min.exclusive ? score <= min.value : score < min.value) return false;
4684
+ if (max.exclusive ? score >= max.value : score > max.value) return false;
4685
+ return true;
4686
+ }
4687
+ function toArg(value) {
4688
+ if (typeof value === "number") return utf8(String(value));
4689
+ if (typeof value === "string") return utf8(value);
4690
+ return value;
4691
+ }
4692
+ var RedisClient = class {
4693
+ constructor(redis, session) {
4694
+ this.redis = redis;
4695
+ this.session = session;
4696
+ }
4697
+ redis;
4698
+ session;
4699
+ get protocol() {
4700
+ return this.session.protocol;
4701
+ }
4702
+ onPush(listener) {
4703
+ const previous = this.session.onPush;
4704
+ this.session.onPush = (reply) => {
4705
+ previous(reply);
4706
+ listener(reply);
4707
+ };
4708
+ }
4709
+ call(command, ...args) {
4710
+ return this.raw(command, ...args).then((reply) => decodeReply(reply));
4711
+ }
4712
+ raw(command, ...args) {
4713
+ return this.redis.perform(
4714
+ this.session,
4715
+ command,
4716
+ args.map((arg) => toArg(arg))
4717
+ );
4718
+ }
4719
+ pipeline() {
4720
+ return new RedisPipeline(this);
4721
+ }
4722
+ async quit() {
4723
+ const reply = await this.call("QUIT");
4724
+ this.session.dead = true;
4725
+ return String(reply);
4726
+ }
4727
+ };
4728
+ var RedisPipeline = class {
4729
+ constructor(client) {
4730
+ this.client = client;
4731
+ }
4732
+ client;
4733
+ commands = [];
4734
+ call(name, ...args) {
4735
+ this.commands.push({ name, args });
4736
+ return this;
4737
+ }
4738
+ async exec() {
4739
+ const out = [];
4740
+ for (const command of this.commands) {
4741
+ try {
4742
+ out.push(await this.client.call(command.name, ...command.args));
4743
+ } catch (error) {
4744
+ out.push(error);
4745
+ }
4746
+ }
4747
+ return out;
4748
+ }
4749
+ };
4750
+ function createRedis(options) {
4751
+ return new Redis(options ?? {});
4752
+ }
4753
+
4754
+ export {
4755
+ manualClock,
4756
+ wallClock,
4757
+ encodeReply,
4758
+ RespParser,
4759
+ asCommand,
4760
+ RedisConnectionError,
4761
+ RedisReplyError,
4762
+ Redis,
4763
+ RedisClient,
4764
+ RedisPipeline,
4765
+ createRedis
4766
+ };
4767
+ //# sourceMappingURL=chunk-4HDEOTYJ.js.map