@volter/world-core 3.0.38 → 3.0.39

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.
Files changed (179) hide show
  1. package/app-route.cjs +26 -25
  2. package/app-route.d.cts +1 -0
  3. package/dist/app-route.cjs +26 -25
  4. package/dist/app-route.d.cts +1 -0
  5. package/dist/generated/pack-facts.json +409 -352
  6. package/dist/inject.cjs +474 -95
  7. package/dist/network-policy.cjs +108 -13
  8. package/dist/network-policy.d.cts +8 -3
  9. package/dist/src/actions.js +2 -1
  10. package/dist/src/anthropic-wire.d.ts +2 -0
  11. package/dist/src/anthropic-wire.js +5 -0
  12. package/dist/src/attestation.d.ts +6 -0
  13. package/dist/src/attestation.js +27 -0
  14. package/dist/src/bytes.d.ts +3 -0
  15. package/dist/src/bytes.js +17 -0
  16. package/dist/src/changeset.js +2 -1
  17. package/dist/src/clickhouse/sql.js +73 -14
  18. package/dist/src/derived-core.d.ts +210 -32
  19. package/dist/src/derived-core.js +510 -244
  20. package/dist/src/derived-real.d.ts +1 -1
  21. package/dist/src/derived-real.js +704 -93
  22. package/dist/src/derived.d.ts +18 -2
  23. package/dist/src/derived.js +21 -2
  24. package/dist/src/events.d.ts +28 -45
  25. package/dist/src/events.js +40 -58
  26. package/dist/src/exact-json.d.ts +10 -0
  27. package/dist/src/exact-json.js +98 -0
  28. package/dist/src/executor.d.ts +17 -0
  29. package/dist/src/executor.js +91 -30
  30. package/dist/src/file-response.js +1 -1
  31. package/dist/src/form.d.ts +8 -0
  32. package/dist/src/form.js +76 -0
  33. package/dist/src/git/format.d.ts +16 -0
  34. package/dist/src/git/format.js +110 -0
  35. package/dist/src/git/index.d.ts +1 -0
  36. package/dist/src/git/index.js +1 -0
  37. package/dist/src/git/lfs.d.ts +1 -1
  38. package/dist/src/git/lfs.js +4 -3
  39. package/dist/src/graphql-wire.d.ts +6 -0
  40. package/dist/src/graphql-wire.js +6 -3
  41. package/dist/src/grpc-wire.js +2 -1
  42. package/dist/src/head.d.ts +1 -0
  43. package/dist/src/head.js +15 -4
  44. package/dist/src/held-reads.d.ts +6 -0
  45. package/dist/src/held-reads.js +60 -0
  46. package/dist/src/history.js +5 -4
  47. package/dist/src/host-port.d.ts +5 -0
  48. package/dist/src/host-port.js +14 -0
  49. package/dist/src/index.d.ts +11 -3
  50. package/dist/src/index.js +9 -4
  51. package/dist/src/log.js +12 -7
  52. package/dist/src/machines.d.ts +81 -5
  53. package/dist/src/machines.js +125 -9
  54. package/dist/src/managed-database.d.ts +15 -11
  55. package/dist/src/managed-database.js +38 -37
  56. package/dist/src/multipart.d.ts +21 -3
  57. package/dist/src/multipart.js +112 -38
  58. package/dist/src/observe.d.ts +3 -0
  59. package/dist/src/observe.js +6 -2
  60. package/dist/src/openai-wire.d.ts +16 -0
  61. package/dist/src/openai-wire.js +43 -12
  62. package/dist/src/pack-fetch.d.ts +4 -6
  63. package/dist/src/pack-fetch.js +189 -57
  64. package/dist/src/packRegistry.d.ts +7 -1
  65. package/dist/src/packRegistry.js +20 -3
  66. package/dist/src/private-transport.d.ts +1 -0
  67. package/dist/src/private-transport.js +11 -0
  68. package/dist/src/protobuf.js +10 -4
  69. package/dist/src/rateBudget.d.ts +8 -1
  70. package/dist/src/rateBudget.js +1 -0
  71. package/dist/src/redis/engine.d.ts +14 -5
  72. package/dist/src/redis/engine.js +271 -72
  73. package/dist/src/redis/frames.d.ts +17 -0
  74. package/dist/src/redis/frames.js +123 -0
  75. package/dist/src/redis/index.d.ts +6 -0
  76. package/dist/src/redis/index.js +6 -0
  77. package/dist/src/redis/json.d.ts +2 -0
  78. package/dist/src/redis/json.js +14 -0
  79. package/dist/src/redis/lua-libs.d.ts +4 -0
  80. package/dist/src/redis/lua-libs.js +173 -0
  81. package/dist/src/redis/lua.d.ts +1 -0
  82. package/dist/src/redis/lua.js +36 -0
  83. package/dist/src/redis/member-storage.d.ts +5 -0
  84. package/dist/src/redis/member-storage.js +75 -0
  85. package/dist/src/redis/resp2.d.ts +4 -0
  86. package/dist/src/redis/resp2.js +29 -0
  87. package/dist/src/redis/stream.d.ts +14 -0
  88. package/dist/src/redis/stream.js +335 -0
  89. package/dist/src/remote-execute.d.ts +2 -2
  90. package/dist/src/request-body.d.ts +11 -0
  91. package/dist/src/request-body.js +11 -0
  92. package/dist/src/runtime.d.ts +6 -5
  93. package/dist/src/runtime.js +5 -5
  94. package/dist/src/s3/wire.d.ts +4 -4
  95. package/dist/src/s3/wire.js +5 -3
  96. package/dist/src/scenario.d.ts +22 -0
  97. package/dist/src/scenario.js +35 -31
  98. package/dist/src/serve-http.d.ts +2 -1
  99. package/dist/src/serve-http.js +10 -1
  100. package/dist/src/serve.js +7 -4
  101. package/dist/src/signing.d.ts +23 -5
  102. package/dist/src/signing.js +74 -11
  103. package/dist/src/sigv4.d.ts +3 -1
  104. package/dist/src/sigv4.js +4 -2
  105. package/dist/src/smtp.js +2 -1
  106. package/dist/src/sockets.js +8 -3
  107. package/dist/src/state-system.d.ts +3 -0
  108. package/dist/src/storage.js +5 -2
  109. package/dist/src/twin-fetch.d.ts +3 -1
  110. package/dist/src/twin-fetch.js +3 -2
  111. package/dist/src/vendor-call.d.ts +10 -3
  112. package/dist/src/vendor-call.js +58 -14
  113. package/dist/test-fixtures/attestation.SOURCE.md +1 -0
  114. package/dist/test-fixtures/attestation.json +50 -0
  115. package/generated/pack-facts.json +409 -352
  116. package/inject.cjs +474 -95
  117. package/network-policy.cjs +108 -13
  118. package/network-policy.d.cts +8 -3
  119. package/package.json +11 -4
  120. package/src/actions.ts +2 -1
  121. package/src/anthropic-wire.ts +6 -0
  122. package/src/attestation.ts +23 -0
  123. package/src/bytes.ts +21 -0
  124. package/src/changeset.ts +2 -1
  125. package/src/clickhouse/sql.ts +50 -16
  126. package/src/derived-core.ts +573 -249
  127. package/src/derived-real.ts +552 -42
  128. package/src/derived.ts +23 -4
  129. package/src/events.ts +54 -85
  130. package/src/exact-json.ts +71 -0
  131. package/src/executor.ts +86 -27
  132. package/src/file-response.ts +1 -1
  133. package/src/form.ts +66 -0
  134. package/src/git/format.ts +89 -0
  135. package/src/git/index.ts +1 -0
  136. package/src/git/lfs.ts +5 -4
  137. package/src/graphql-wire.ts +8 -3
  138. package/src/grpc-wire.ts +2 -1
  139. package/src/head.ts +15 -5
  140. package/src/held-reads.ts +54 -0
  141. package/src/history.ts +5 -4
  142. package/src/host-port.ts +9 -0
  143. package/src/index.ts +10 -4
  144. package/src/log.ts +11 -7
  145. package/src/machines.ts +135 -13
  146. package/src/managed-database.ts +48 -40
  147. package/src/multipart.ts +110 -35
  148. package/src/observe.ts +6 -3
  149. package/src/openai-wire.ts +45 -14
  150. package/src/pack-fetch.ts +173 -50
  151. package/src/packRegistry.ts +27 -4
  152. package/src/private-transport.ts +8 -0
  153. package/src/protobuf.ts +8 -4
  154. package/src/rateBudget.ts +6 -1
  155. package/src/redis/engine.ts +168 -63
  156. package/src/redis/frames.ts +53 -0
  157. package/src/redis/index.ts +6 -0
  158. package/src/redis/json.ts +13 -0
  159. package/src/redis/lua-libs.ts +62 -0
  160. package/src/redis/lua.ts +20 -0
  161. package/src/redis/member-storage.ts +58 -0
  162. package/src/redis/resp2.ts +25 -0
  163. package/src/redis/stream.ts +102 -0
  164. package/src/remote-execute.ts +2 -2
  165. package/src/request-body.ts +12 -0
  166. package/src/runtime.ts +6 -5
  167. package/src/s3/wire.ts +8 -5
  168. package/src/scenario.ts +34 -13
  169. package/src/serve-http.ts +11 -2
  170. package/src/serve.ts +4 -3
  171. package/src/signing.ts +71 -17
  172. package/src/sigv4.ts +5 -3
  173. package/src/smtp.ts +3 -2
  174. package/src/sockets.ts +8 -3
  175. package/src/state-system.ts +3 -0
  176. package/src/storage.ts +5 -2
  177. package/src/twin-fetch.ts +4 -3
  178. package/src/vendor-call.ts +59 -12
  179. package/test-fixtures/attestation.json +50 -0
@@ -66,6 +66,15 @@ export class RedisStatus {
66
66
  }
67
67
  }
68
68
  export const OK = new RedisStatus('OK');
69
+ /** The JSON projection of a RESP reply — what Upstash puts in `{"result": …}`. */
70
+ // Redis integers are signed 64-bit on RESP; JSON clients read them as JSON numbers.
71
+ export class RedisInteger {
72
+ integer;
73
+ constructor(integer) {
74
+ this.integer = integer;
75
+ }
76
+ }
77
+ const integerReply = (n) => n >= -9007199254740991n && n <= 9007199254740991n ? Number(n) : new RedisInteger(String(n));
69
78
  /** A command-level failure. `message` is the vendor's literal error string. Maps to HTTP 400. */
70
79
  export class RedisCommandError extends Error {
71
80
  constructor(message) {
@@ -125,6 +134,7 @@ export const SERVED_COMMANDS = {
125
134
  ZADD: [3, -1], ZINCRBY: [3, 3], ZSCORE: [2, 2], ZCARD: [1, 1], ZCOUNT: [3, 3],
126
135
  ZRANK: [2, 3], ZREVRANK: [2, 3], ZREM: [2, -1], ZREMRANGEBYSCORE: [3, 3], ZREMRANGEBYRANK: [3, 3],
127
136
  ZRANGE: [3, -1], ZREVRANGE: [3, -1], ZRANGEBYSCORE: [3, -1], ZREVRANGEBYSCORE: [3, -1],
137
+ LPOS: [2, -1], RPOPLPUSH: [2, 2], ZPOPMIN: [1, 2], XTRIM: [3, -1],
128
138
  XADD: [4, -1], XLEN: [1, 1], XRANGE: [3, -1], XREVRANGE: [3, -1], XDEL: [2, -1],
129
139
  SCRIPT: [1, -1], EVAL: [2, -1], EVAL_RO: [2, -1], EVALSHA: [2, -1], EVALSHA_RO: [2, -1],
130
140
  };
@@ -135,7 +145,7 @@ const WRITE_COMMANDS = new Set([
135
145
  'EXPIRE', 'PEXPIRE', 'EXPIREAT', 'PEXPIREAT', 'PERSIST', 'RENAME', 'RENAMENX',
136
146
  'FLUSHDB', 'FLUSHALL', 'HSET', 'HSETNX', 'HMSET', 'HDEL', 'HINCRBY', 'HINCRBYFLOAT',
137
147
  'SADD', 'SREM', 'SPOP', 'LPUSH', 'RPUSH', 'LPOP', 'RPOP', 'LREM', 'LSET', 'LTRIM',
138
- 'ZADD', 'ZINCRBY', 'ZREM', 'ZREMRANGEBYSCORE', 'ZREMRANGEBYRANK', 'XADD', 'XDEL',
148
+ 'RPOPLPUSH', 'ZPOPMIN', 'XTRIM', 'ZADD', 'ZINCRBY', 'ZREM', 'ZREMRANGEBYSCORE', 'ZREMRANGEBYRANK', 'XADD', 'XDEL',
139
149
  // NB: 'SCRIPT' is deliberately ABSENT — it is classified per SUBCOMMAND below, because
140
150
  // `SCRIPT EXISTS` is a pure read and a read-only twin must serve every read (§9 round 1 found
141
151
  // it answering 405). Likewise `EVAL_RO`/`EVALSHA_RO` are reads by definition.
@@ -238,7 +248,7 @@ function patchImage(image, changed, removed, scope) {
238
248
  name,
239
249
  kind: rec.kind,
240
250
  v: rec.v,
241
- pexpireAt: typeof rec.pexpire_at === 'number' ? rec.pexpire_at : null,
251
+ pexpireAt: typeof rec.pexpire_at === 'string' || typeof rec.pexpire_at === 'number' ? String(rec.pexpire_at) : null,
242
252
  ...(typeof rec.last_id === 'string' && rec.last_id !== '' ? { lastId: rec.last_id } : {}),
243
253
  ...(typeof rec._rev === 'number' ? { _rev: rec._rev } : {}),
244
254
  ...(rec.meta !== undefined && rec.meta !== null ? { meta: rec.meta } : {}),
@@ -285,7 +295,7 @@ export class KeySpace {
285
295
  live(row) {
286
296
  if (!row)
287
297
  return undefined;
288
- return row.pexpireAt !== null && row.pexpireAt < this.nowMs ? undefined : row;
298
+ return row.pexpireAt !== null && BigInt(row.pexpireAt) < BigInt(this.nowMs) ? undefined : row;
289
299
  }
290
300
  all() { return [...this.rows.values()].filter((r) => this.live(r) !== undefined); }
291
301
  // ── the EVAL script cache ────────────────────────────────────────────────────────────────
@@ -440,14 +450,36 @@ export const asZSet = (row) => {
440
450
  export const storeZSet = (pairs) => pairs.map(([m, sc]) => [m, fmtScore(sc)]);
441
451
  export const asStream = (row) => { expectType(row, STREAM); expectShape(row, Array.isArray(row?.v)); return row ? row.v.map(([id, f]) => [id, f.map(([field, value]) => [field, value])]) : []; };
442
452
  // ── numeric parsing (Redis's exact acceptance rules) ──────────────────────────────────────────
443
- export function toInt(raw) {
444
- if (raw !== raw.trim() || !/^[+-]?\d+$/.test(raw))
445
- throw new RedisCommandError(NOT_INT);
446
- const n = Number(raw);
447
- if (!Number.isSafeInteger(n))
448
- throw new RedisCommandError(NOT_INT);
453
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/util.c "Convert a string into a long long."
454
+ export function integerArgument(raw, message = NOT_INT, unsigned = false) {
455
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/util.c "strtoull() is used instead."
456
+ if (unsigned) {
457
+ const canonical = /^(0|[1-9]\d*|-[1-9]\d*)$/.test(raw);
458
+ if (!canonical && !/^[ \t\r\n\v\f]*[+-]?\d+$/.test(raw))
459
+ throw new RedisCommandError(message);
460
+ const n = BigInt(raw.trim());
461
+ if (n > 18446744073709551615n || n < -18446744073709551615n || canonical && n < 0n)
462
+ throw new RedisCommandError(message);
463
+ return BigInt.asUintN(64, n);
464
+ }
465
+ if (!/^(0|[1-9]\d*|-[1-9]\d*)$/.test(raw))
466
+ throw new RedisCommandError(message);
467
+ const n = BigInt(raw);
468
+ if (n < -9223372036854775808n || n > 9223372036854775807n)
469
+ throw new RedisCommandError(message);
449
470
  return n;
450
471
  }
472
+ // Collection positions/counts are bounded by their actual arrays, not by Number.MAX_SAFE_INTEGER.
473
+ export const commandInteger = (raw, message = NOT_INT) => Number(integerArgument(raw, message));
474
+ export const toInt = commandInteger;
475
+ export const unsignedArgument = (raw, message) => integerArgument(raw, message, true);
476
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/object.c "value is out of range, must be positive"
477
+ export function positiveInteger(raw, message = 'ERR value is out of range, must be positive') {
478
+ const n = integerArgument(raw, message);
479
+ if (n < 0)
480
+ throw new RedisCommandError(message);
481
+ return Number(n);
482
+ }
451
483
  export function toFloat(raw) {
452
484
  if (raw !== raw.trim() || raw === '')
453
485
  throw new RedisCommandError(NOT_FLOAT);
@@ -532,20 +564,13 @@ export function globMatch(pattern, subject) {
532
564
  }
533
565
  // ── TTL option parsing, shared by SET and GETEX ────────────────────────────────────────────────
534
566
  function ttlFromToken(token, value, nowMs, cmd) {
535
- const n = toInt(value);
536
- if (token === 'ex') {
537
- if (n <= 0)
538
- throw new RedisCommandError(`ERR invalid expire time in '${cmd}' command`);
539
- return nowMs + n * 1000;
540
- }
541
- if (token === 'px') {
542
- if (n <= 0)
543
- throw new RedisCommandError(`ERR invalid expire time in '${cmd}' command`);
544
- return nowMs + n;
545
- }
546
- if (token === 'exat')
547
- return n * 1000;
548
- return n; // pxat
567
+ const n = integerArgument(value);
568
+ const millis = token === 'ex' || token === 'exat' ? n * 1000n : n;
569
+ const at = token === 'ex' || token === 'px' ? BigInt(Math.trunc(nowMs)) + millis : millis;
570
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/t_string.c "Overflow detected."
571
+ if (n <= 0n || millis > 9223372036854775807n || at > 9223372036854775807n)
572
+ throw new RedisCommandError(`ERR invalid expire time in '${cmd}' command`);
573
+ return String(at);
549
574
  }
550
575
  // ─────────────────────────────────────────────────────────────────────────────────────────────
551
576
  // COMMAND DISPATCH — fully synchronous over the snapshot (see the file header for why).
@@ -564,6 +589,107 @@ export function execOne(space, argv) {
564
589
  return dialect.exec(space, argv);
565
590
  switch (name) {
566
591
  // ── connection / server ──────────────────────────────────────────────────────────────────
592
+ // https://redis.io/docs/latest/commands/rpoplpush/: atomically moves the last element to the destination's head.
593
+ case 'RPOPLPUSH': {
594
+ const sourceRow = space.get(a[0]);
595
+ const source = asList(sourceRow);
596
+ asList(space.get(a[1]));
597
+ const value = source.pop();
598
+ if (value === undefined)
599
+ return null;
600
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/t_list.c "listTypePush(dstobj,value,where);"
601
+ // A rotation rewrites the existing list, including a singleton, without deleting its key or expiry.
602
+ if (a[0] === a[1]) {
603
+ source.unshift(value);
604
+ space.put({ ...sourceRow, v: source }, 'list.move');
605
+ return value;
606
+ }
607
+ if (source.length)
608
+ space.put({ ...space.get(a[0]), v: source }, 'list.move');
609
+ else
610
+ space.remove(a[0], 'list.move');
611
+ const dest = space.get(a[1]);
612
+ const values = asList(dest);
613
+ values.unshift(value);
614
+ space.put({ name: a[1], kind: 'list', v: values, pexpireAt: dest?.pexpireAt ?? null }, 'list.move');
615
+ return value;
616
+ }
617
+ // https://redis.io/docs/latest/commands/lpos/: rank, count and maxlen apply to matching positions.
618
+ case 'LPOS': {
619
+ const values = asList(space.get(a[0]));
620
+ let rank = 1, count, max = values.length;
621
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/t_list.c "COUNT can't be negative"
622
+ for (let i = 2; i < a.length; i += 2) {
623
+ const option = a[i].toUpperCase();
624
+ if (!['RANK', 'COUNT', 'MAXLEN'].includes(option) || i + 1 >= a.length)
625
+ throw new RedisCommandError(SYNTAX);
626
+ let value;
627
+ try {
628
+ value = commandInteger(a[i + 1]);
629
+ }
630
+ catch (error) {
631
+ if (option === 'RANK')
632
+ throw error;
633
+ throw new RedisCommandError(`ERR ${option} can't be negative`);
634
+ }
635
+ if (option === 'RANK') {
636
+ if (a[i + 1] === '-9223372036854775808')
637
+ throw new RedisCommandError('ERR value is out of range, value must between -9223372036854775807 and 9223372036854775807');
638
+ if (value === 0)
639
+ throw new RedisCommandError("ERR RANK can't be zero: use 1 to start from the first match, 2 from the second ... or use negative to start from the end of the list");
640
+ rank = value;
641
+ }
642
+ else {
643
+ if (value < 0)
644
+ throw new RedisCommandError(`ERR ${option} can't be negative`);
645
+ if (option === 'COUNT')
646
+ count = value;
647
+ else
648
+ max = value || values.length;
649
+ }
650
+ }
651
+ const matches = Array.from({ length: values.length }, (_, i) => i);
652
+ if (rank < 0)
653
+ matches.reverse();
654
+ const hits = matches.slice(0, max).filter((i) => values[i] === a[1]).slice(Math.abs(rank) - 1);
655
+ return count === undefined ? hits[0] ?? null : hits.slice(0, count === 0 ? undefined : count);
656
+ }
657
+ // https://redis.io/docs/latest/commands/zpopmin/: lowest score first; ties by member.
658
+ case 'ZPOPMIN': {
659
+ const row = space.get(a[0]);
660
+ const pairs = sortZSet(asZSet(row));
661
+ let count = 1;
662
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/object.c "value is out of range, must be positive"
663
+ try {
664
+ if (a[1] !== undefined)
665
+ count = commandInteger(a[1]);
666
+ }
667
+ catch {
668
+ throw new RedisCommandError('ERR value is out of range, must be positive');
669
+ }
670
+ if (count < 0)
671
+ throw new RedisCommandError('ERR value is out of range, must be positive');
672
+ const popped = pairs.splice(0, count);
673
+ if (popped.length) {
674
+ if (pairs.length)
675
+ space.put({ ...row, v: storeZSet(sortZSet(pairs)) }, 'zset.pop');
676
+ else
677
+ space.remove(a[0], 'zset.pop');
678
+ }
679
+ return popped.flatMap(([m, score]) => [m, fmtScore(score)]);
680
+ }
681
+ // https://redis.io/docs/latest/commands/xtrim/: exact trimming also satisfies the approximate upper bound.
682
+ case 'XTRIM': {
683
+ const row = space.get(a[0]);
684
+ const entries = asStream(row);
685
+ const spec = trimSpec(a, 1);
686
+ if (spec.next !== a.length)
687
+ throw new RedisCommandError(SYNTAX);
688
+ const kept = trimEntries(entries, spec);
689
+ if (row && kept.length !== entries.length)
690
+ space.put({ ...row, v: kept }, 'stream.trim');
691
+ return entries.length - kept.length;
692
+ }
567
693
  case 'PING': return a.length === 0 ? new RedisStatus('PONG') : a[0];
568
694
  case 'ECHO': return a[0];
569
695
  case 'DBSIZE': return visibleKeys(space).length;
@@ -585,9 +711,8 @@ export function execOne(space, argv) {
585
711
  return keys.length === 0 ? null : keys[0];
586
712
  }
587
713
  case 'SCAN': {
588
- const cursor = toInt(a[0]);
589
- if (cursor < 0)
590
- throw new RedisCommandError('ERR invalid cursor');
714
+ // SCAN uses Redis's unsigned cursor parser, unlike signed COUNT.
715
+ const cursor = Number(unsignedArgument(a[0], 'ERR invalid cursor'));
591
716
  let match = null;
592
717
  let count = 10;
593
718
  let typeFilter = null;
@@ -669,7 +794,7 @@ export function execOne(space, argv) {
669
794
  case 'EXPIREAT':
670
795
  case 'PEXPIREAT': {
671
796
  const row = space.get(a[0]);
672
- const n = toInt(a[1]);
797
+ const n = integerArgument(a[1]);
673
798
  // Redis 7.0's options (redis.io/docs/latest/commands/expire/, "Options"): NX sets only a key with no expiry, XX
674
799
  // only one with an expiry, GT only a later deadline, LT only an earlier one, a key with no expiry counting as
675
800
  // one that never expires; NX with any other, and GT with LT, are refused
@@ -679,26 +804,27 @@ export function execOne(space, argv) {
679
804
  throw new RedisCommandError('ERR NX and XX, GT or LT options at the same time are not compatible');
680
805
  if (opts.has('GT') && opts.has('LT'))
681
806
  throw new RedisCommandError('ERR GT and LT options at the same time are not compatible');
807
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/expire.c "overflow by either unit conversion or basetime addition."
808
+ const millis = name === 'EXPIRE' || name === 'EXPIREAT' ? n * 1000n : n;
809
+ const deadline = name === 'EXPIRE' || name === 'PEXPIRE' ? BigInt(Math.trunc(nowMs)) + millis : millis;
810
+ if (millis < -9223372036854775808n || millis > 9223372036854775807n || deadline > 9223372036854775807n)
811
+ throw new RedisCommandError(`ERR invalid expire time in '${name.toLowerCase()}' command`);
682
812
  if (!row)
683
813
  return 0;
684
- const at = name === 'EXPIRE' ? nowMs + n * 1000
685
- : name === 'PEXPIRE' ? nowMs + n
686
- : name === 'EXPIREAT' ? n * 1000
687
- : n;
688
814
  if (opts.has('NX') && row.pexpireAt !== null)
689
815
  return 0;
690
816
  if (opts.has('XX') && row.pexpireAt === null)
691
817
  return 0;
692
- if (opts.has('GT') && (row.pexpireAt === null || at <= row.pexpireAt))
818
+ if (opts.has('GT') && (row.pexpireAt === null || deadline <= BigInt(row.pexpireAt)))
693
819
  return 0;
694
- if (opts.has('LT') && row.pexpireAt !== null && at >= row.pexpireAt)
820
+ if (opts.has('LT') && row.pexpireAt !== null && deadline >= BigInt(row.pexpireAt))
695
821
  return 0;
696
822
  // A deadline already in the past DELETES the key, exactly as Redis does.
697
- if (at <= nowMs) {
823
+ if (deadline <= BigInt(nowMs)) {
698
824
  space.remove(a[0], 'key.expire_now');
699
825
  return 1;
700
826
  }
701
- space.put({ ...row, pexpireAt: at }, 'key.expire');
827
+ space.put({ ...row, pexpireAt: String(deadline) }, 'key.expire');
702
828
  return 1;
703
829
  }
704
830
  case 'TTL':
@@ -708,11 +834,11 @@ export function execOne(space, argv) {
708
834
  return -2;
709
835
  if (row.pexpireAt === null)
710
836
  return -1;
711
- const remainingMs = row.pexpireAt - nowMs;
837
+ const remainingMs = BigInt(row.pexpireAt) - BigInt(nowMs);
712
838
  // Redis computes `(ttl+500)/1000` in integer arithmetic — round HALF-UP, not ceil. With ceil,
713
839
  // a key set `EX 10` and read 600ms later still reported 10 where the vendor says 9 (§9 round
714
840
  // 1); every earlier verify happened to land on an exact second boundary, where they agree.
715
- return name === 'TTL' ? Math.round(remainingMs / 1000) : remainingMs;
841
+ return integerReply(name === 'TTL' ? (remainingMs + 500n) / 1000n : remainingMs);
716
842
  }
717
843
  case 'PERSIST': {
718
844
  const row = space.get(a[0]);
@@ -721,6 +847,7 @@ export function execOne(space, argv) {
721
847
  space.put({ ...row, pexpireAt: null }, 'key.persist');
722
848
  return 1;
723
849
  }
850
+ // source: https://redis.io/docs/latest/commands/expire/ "the associated time to live is transferred to the new key name"
724
851
  case 'RENAME':
725
852
  case 'RENAMENX': {
726
853
  const row = space.get(a[0]);
@@ -746,6 +873,7 @@ export function execOne(space, argv) {
746
873
  space.remove(a[0], 'string.getdel');
747
874
  return v === undefined ? null : v;
748
875
  }
876
+ // source: https://redis.io/docs/latest/commands/getset/ "Any previous time to live associated with the key is discarded"
749
877
  case 'GETSET': {
750
878
  const old = asString(space.get(a[0]));
751
879
  space.put({ name: a[0], kind: 'string', v: a[1], pexpireAt: null }, 'string.getset');
@@ -824,6 +952,7 @@ export function execOne(space, argv) {
824
952
  return get ? (old ?? null) : null;
825
953
  if (xx && !existing)
826
954
  return get ? (old ?? null) : null;
955
+ // source: https://redis.io/docs/latest/commands/set/ "Retain the time to live associated with the key"
827
956
  space.put({ name: key, kind: 'string', v: value, pexpireAt: keepTtl ? (existing?.pexpireAt ?? null) : pexpireAt }, 'string.set');
828
957
  return get ? (old ?? null) : OK;
829
958
  }
@@ -835,10 +964,8 @@ export function execOne(space, argv) {
835
964
  }
836
965
  case 'SETEX':
837
966
  case 'PSETEX': {
838
- const n = toInt(a[1]);
839
- if (n <= 0)
840
- throw new RedisCommandError(`ERR invalid expire time in '${name.toLowerCase()}' command`);
841
- space.put({ name: a[0], kind: 'string', v: a[2], pexpireAt: nowMs + (name === 'SETEX' ? n * 1000 : n) }, 'string.setex');
967
+ const pexpireAt = ttlFromToken(name === 'SETEX' ? 'ex' : 'px', a[1], nowMs, name.toLowerCase());
968
+ space.put({ name: a[0], kind: 'string', v: a[2], pexpireAt }, 'string.setex');
842
969
  return OK;
843
970
  }
844
971
  case 'MGET': return a.map((k) => { const r = space.get(k); return r && r.kind === 'string' ? r.v : null; });
@@ -866,17 +993,17 @@ export function execOne(space, argv) {
866
993
  case 'DECR':
867
994
  case 'INCRBY':
868
995
  case 'DECRBY': {
869
- const by = name === 'INCR' ? 1 : name === 'DECR' ? -1 : toInt(a[1]) * (name === 'DECRBY' ? -1 : 1);
996
+ const by = name === 'INCR' ? 1n : name === 'DECR' ? -1n : integerArgument(a[1]) * (name === 'DECRBY' ? -1n : 1n);
870
997
  const row = space.get(a[0]);
871
998
  const current = asString(row);
872
- const next = (current === undefined ? 0 : toInt(current)) + by;
873
- if (!Number.isSafeInteger(next))
999
+ const next = (current === undefined ? 0n : integerArgument(current)) + by;
1000
+ if (next < -9223372036854775808n || next > 9223372036854775807n)
874
1001
  throw new RedisCommandError('ERR increment or decrement would overflow');
875
1002
  // INCR PRESERVES the key's TTL (real Redis semantics). This is exactly what makes
876
1003
  // @upstash/ratelimit's fixed-window script correct: the PEXPIRE is armed on the first
877
1004
  // increment and every later INCRBY must leave it alone, or the window would never close.
878
1005
  space.put({ name: a[0], kind: 'string', v: String(next), pexpireAt: row?.pexpireAt ?? null }, 'string.incr');
879
- return next;
1006
+ return integerReply(next);
880
1007
  }
881
1008
  case 'INCRBYFLOAT': {
882
1009
  const row = space.get(a[0]);
@@ -947,9 +1074,10 @@ export function execOne(space, argv) {
947
1074
  const pairs = asHash(row);
948
1075
  const idx = pairs.findIndex(([f]) => f === a[1]);
949
1076
  const current = idx < 0 ? undefined : pairs[idx][1];
950
- const base = current === undefined ? 0 : (isFloat ? toFloat(current) : toInt(current));
951
- const next = isFloat ? guardFloatResult(base + toFloat(a[2])) : base + toInt(a[2]);
952
- if (!isFloat && !Number.isSafeInteger(next))
1077
+ const by = isFloat ? toFloat(a[2]) : integerArgument(a[2]);
1078
+ const base = current === undefined ? (isFloat ? 0 : 0n) : isFloat ? toFloat(current) : integerArgument(current, 'ERR hash value is not an integer');
1079
+ const next = isFloat ? guardFloatResult(base + by) : base + by;
1080
+ if (!isFloat && (next < -9223372036854775808n || next > 9223372036854775807n))
953
1081
  throw new RedisCommandError('ERR increment or decrement would overflow');
954
1082
  const text = isFloat ? fmtScore(next) : String(next);
955
1083
  if (idx < 0)
@@ -957,7 +1085,7 @@ export function execOne(space, argv) {
957
1085
  else
958
1086
  pairs[idx] = [a[1], text];
959
1087
  space.put({ name: a[0], kind: 'hash', v: pairs, pexpireAt: row?.pexpireAt ?? null }, 'hash.incrby');
960
- return isFloat ? text : next;
1088
+ return isFloat ? text : integerReply(next);
961
1089
  }
962
1090
  // ── sets ─────────────────────────────────────────────────────────────────────────────────
963
1091
  case 'SADD': {
@@ -1031,11 +1159,9 @@ export function execOne(space, argv) {
1031
1159
  }
1032
1160
  case 'LPOP':
1033
1161
  case 'RPOP': {
1162
+ const count = a.length >= 2 ? positiveInteger(a[1]) : null;
1034
1163
  const row = space.get(a[0]);
1035
1164
  const items = asList(row);
1036
- const count = a.length >= 2 ? toInt(a[1]) : null;
1037
- if (count !== null && count < 0)
1038
- throw new RedisCommandError('ERR value is out of range, must be positive');
1039
1165
  if (items.length === 0)
1040
1166
  return null;
1041
1167
  const n = count === null ? 1 : count;
@@ -1073,9 +1199,9 @@ export function execOne(space, argv) {
1073
1199
  case 'LTRIM': {
1074
1200
  const row = space.get(a[0]);
1075
1201
  const items = asList(row);
1202
+ const [start, stop] = normalizeRange(toInt(a[1]), toInt(a[2]), items.length);
1076
1203
  if (!row)
1077
1204
  return OK;
1078
- const [start, stop] = normalizeRange(toInt(a[1]), toInt(a[2]), items.length);
1079
1205
  const kept = start > stop ? [] : items.slice(start, stop + 1);
1080
1206
  if (kept.length === 0)
1081
1207
  space.remove(a[0], 'list.trim_empty');
@@ -1254,6 +1380,12 @@ export function execOne(space, argv) {
1254
1380
  case 'EVAL_RO':
1255
1381
  case 'EVALSHA':
1256
1382
  case 'EVALSHA_RO': {
1383
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/eval.c "Get the number of arguments that are keys"
1384
+ const numKeys = toInt(a[1]);
1385
+ if (numKeys < 0)
1386
+ throw new RedisCommandError("ERR Number of keys can't be negative");
1387
+ if (numKeys > a.length - 2)
1388
+ throw new RedisCommandError("ERR Number of keys can't be greater than number of args");
1257
1389
  const bySha = name.startsWith('EVALSHA');
1258
1390
  let script;
1259
1391
  if (bySha) {
@@ -1275,11 +1407,6 @@ export function execOne(space, argv) {
1275
1407
  if (!space.ctx.readOnly)
1276
1408
  storeScript(space, script);
1277
1409
  }
1278
- const numKeys = toInt(a[1]);
1279
- if (numKeys < 0)
1280
- throw new RedisCommandError("ERR Number of keys can't be negative");
1281
- if (numKeys > a.length - 2)
1282
- throw new RedisCommandError("ERR Number of keys can't be greater than number of args");
1283
1410
  return evalScript(space, script, a.slice(2, 2 + numKeys), a.slice(2 + numKeys), name.endsWith('_RO'));
1284
1411
  }
1285
1412
  default:
@@ -1452,7 +1579,7 @@ export function zrange(space, name, a) {
1452
1579
  if (_rev)
1453
1580
  selected = selected.reverse();
1454
1581
  if (limit)
1455
- selected = limit.count < 0 ? selected.slice(limit.offset) : selected.slice(limit.offset, limit.offset + limit.count);
1582
+ selected = limit.offset < 0 ? [] : limit.count < 0 ? selected.slice(limit.offset) : selected.slice(limit.offset, limit.offset + limit.count);
1456
1583
  }
1457
1584
  else {
1458
1585
  const ordered = _rev ? [...sorted].reverse() : sorted;
@@ -1463,8 +1590,8 @@ export function zrange(space, name, a) {
1463
1590
  }
1464
1591
  // ── streams ────────────────────────────────────────────────────────────────────────────────────
1465
1592
  export function compareStreamIds(left, right) {
1466
- const [lms = 0, lseq = 0] = left.split('-').map(Number);
1467
- const [rms = 0, rseq = 0] = right.split('-').map(Number);
1593
+ const [lms = 0n, lseq = 0n] = left.split('-').map(BigInt);
1594
+ const [rms = 0n, rseq = 0n] = right.split('-').map(BigInt);
1468
1595
  if (lms !== rms)
1469
1596
  return lms < rms ? -1 : 1;
1470
1597
  return lseq === rseq ? 0 : lseq < rseq ? -1 : 1;
@@ -1473,59 +1600,129 @@ export function parseStreamBound(raw, side) {
1473
1600
  if (raw === '-')
1474
1601
  return '0-0';
1475
1602
  if (raw === '+')
1476
- return `${Number.MAX_SAFE_INTEGER}-${Number.MAX_SAFE_INTEGER}`;
1603
+ return '18446744073709551615-18446744073709551615';
1477
1604
  const bare = raw.startsWith('(') ? raw.slice(1) : raw;
1478
1605
  if (!/^\d+(-\d+)?$/.test(bare))
1479
1606
  throw new RedisCommandError('ERR Invalid stream ID specified as stream command argument');
1480
- return bare.includes('-') ? bare : `${bare}-${side === 'min' ? 0 : Number.MAX_SAFE_INTEGER}`;
1607
+ return bare.includes('-') ? bare : `${bare}-${side === 'min' ? '0' : '18446744073709551615'}`;
1608
+ }
1609
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/util.c "strtoull() is used instead."
1610
+ // Stream components are uint64; Redis's fallback also accepts a leading plus or zeroes.
1611
+ function streamArgument(raw, wildcard = false) {
1612
+ const at = raw.indexOf('-');
1613
+ const ms = at < 0 ? raw : raw.slice(0, at), seq = at < 0 ? '0' : raw.slice(at + 1);
1614
+ const part = (v) => String(unsignedArgument(v, 'ERR Invalid stream ID specified as stream command argument'));
1615
+ return `${part(ms)}-${wildcard && seq === '*' ? '*' : part(seq)}`;
1616
+ }
1617
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/t_stream.c "The LIMIT argument must be >= 0."
1618
+ // MAXLEN/MINID use a strict unsigned threshold; LIMIT is allowed only with approximate trimming.
1619
+ function trimSpec(a, start) {
1620
+ let i = start;
1621
+ const strategy = a[i++].toUpperCase();
1622
+ if (!['MAXLEN', 'MINID'].includes(strategy))
1623
+ throw new RedisCommandError(SYNTAX);
1624
+ const approximate = a[i] === '~';
1625
+ if (['~', '='].includes(a[i] ?? ''))
1626
+ i++;
1627
+ if (i >= a.length)
1628
+ throw new RedisCommandError(SYNTAX);
1629
+ let threshold = a[i++];
1630
+ if (strategy === 'MAXLEN') {
1631
+ if (commandInteger(threshold) < 0)
1632
+ throw new RedisCommandError('ERR The MAXLEN argument must be >= 0.');
1633
+ }
1634
+ else
1635
+ threshold = streamArgument(threshold);
1636
+ let limit = Infinity;
1637
+ if (a[i]?.toUpperCase() === 'LIMIT') {
1638
+ if (i + 1 >= a.length)
1639
+ throw new RedisCommandError(SYNTAX);
1640
+ i++;
1641
+ limit = commandInteger(a[i++]);
1642
+ if (limit < 0)
1643
+ throw new RedisCommandError('ERR The LIMIT argument must be >= 0.');
1644
+ // source: https://raw.githubusercontent.com/redis/redis/c9d29f6a918c335bc1778d9f68e521c1bbb36a0f/src/t_stream.c "syntax error, LIMIT cannot be used without the special ~ option"
1645
+ if (!approximate)
1646
+ throw new RedisCommandError('ERR syntax error, LIMIT cannot be used without the special ~ option');
1647
+ if (limit === 0)
1648
+ limit = Infinity;
1649
+ }
1650
+ return { strategy, threshold, limit, next: i };
1651
+ }
1652
+ function trimEntries(entries, spec) {
1653
+ const remove = spec.strategy === 'MAXLEN' ? Math.max(0, entries.length - Number(spec.threshold)) : entries.filter(([id]) => compareStreamIds(id, parseStreamBound(spec.threshold, 'min')) < 0).length;
1654
+ return entries.slice(Math.min(remove, spec.limit));
1481
1655
  }
1482
1656
  function xadd(space, a, nowMs) {
1483
1657
  let i = 1;
1484
1658
  let noMkStream = false;
1659
+ let trim;
1485
1660
  for (; i < a.length; i++) {
1486
1661
  const tok = a[i].toLowerCase();
1487
1662
  if (tok === 'nomkstream') {
1488
1663
  noMkStream = true;
1489
1664
  continue;
1490
1665
  }
1491
- // Accepting a trim option and silently NOT trimming would be a fake success, so it is refused.
1492
- if (tok === 'maxlen' || tok === 'minid')
1493
- throw new RedisCommandError(space.ctx.dialect.unmodeled(`XADD ${tok.toUpperCase()} trimming`));
1666
+ if (tok === 'maxlen' || tok === 'minid') {
1667
+ if (trim)
1668
+ throw new RedisCommandError('ERR syntax error, MAXLEN and MINID options at the same time are not compatible');
1669
+ trim = trimSpec(a, i);
1670
+ i = trim.next - 1;
1671
+ continue;
1672
+ }
1494
1673
  break;
1495
1674
  }
1496
1675
  const row = space.get(a[0]);
1497
1676
  if (!row && noMkStream)
1498
1677
  return null;
1499
1678
  const entries = asStream(row);
1500
- const idSpec = a[i];
1679
+ if (row?.lastId === '18446744073709551615-18446744073709551615')
1680
+ throw new RedisCommandError('ERR The stream has exhausted the last possible ID, unable to add more items');
1681
+ const rawId = a[i];
1682
+ if (rawId === undefined)
1683
+ throw new RedisCommandError(wrongArity('xadd'));
1501
1684
  const fields = a.slice(i + 1);
1502
1685
  if (fields.length === 0 || fields.length % 2 !== 0)
1503
1686
  throw new RedisCommandError(wrongArity('xadd'));
1687
+ const idSpec = rawId === '*' ? rawId : streamArgument(rawId, true);
1504
1688
  let id;
1505
1689
  if (idSpec === '*') {
1506
1690
  // The id's ms part is the REQUEST CLOCK (`occurredAt`), never `Date.now()`. Two entries added
1507
1691
  // in the same pinned millisecond get sequence 0, 1, 2 … exactly as Redis does — which is what
1508
1692
  // makes a stream verify reproducible.
1509
- const [lastMs = 0, lastSeq = 0] = (row?.lastId ?? '0-0').split('-').map(Number);
1510
- id = nowMs > lastMs ? `${nowMs}-0` : `${lastMs}-${lastSeq + 1}`;
1693
+ const [lastMs = 0n, lastSeq = 0n] = (row?.lastId ?? '0-0').split('-').map(BigInt);
1694
+ id = BigInt(nowMs) > lastMs ? `${nowMs}-0` : `${lastMs}-${lastSeq + 1n}`;
1695
+ }
1696
+ else if (/^(0|[1-9]\d*)-\*$/.test(idSpec) && BigInt(idSpec.split('-')[0]) <= 18446744073709551615n) {
1697
+ const ms = BigInt(idSpec.split('-')[0]);
1698
+ const [lastMs = 0n, lastSeq = 0n] = (row?.lastId ?? '0-0').split('-').map(BigInt);
1699
+ if (ms < lastMs)
1700
+ throw new RedisCommandError('ERR The ID specified in XADD is equal or smaller than the target stream top item');
1701
+ id = `${ms}-${ms === lastMs ? lastSeq + 1n : 0n}`;
1511
1702
  }
1512
1703
  else {
1513
1704
  id = idSpec.includes('-') ? idSpec : `${idSpec}-0`;
1514
- if (!/^\d+-\d+$/.test(id))
1705
+ if (!/^(0|[1-9]\d*)-(0|[1-9]\d*)$/.test(id) || id.split('-').some((v) => BigInt(v) > 18446744073709551615n))
1515
1706
  throw new RedisCommandError('ERR Invalid stream ID specified as stream command argument');
1516
1707
  // Compare against the RETAINED last-id, not merely the last surviving entry: Redis keeps
1517
1708
  // `last_id` past an XDEL, so an id that was used and then deleted stays refused forever. §9
1518
1709
  // round 1 found the entries-only comparison re-accepting `5-5` after `XDEL st 5-5`.
1710
+ if (id === '0-0')
1711
+ throw new RedisCommandError('ERR The ID specified in XADD must be greater than 0-0');
1519
1712
  const top = row?.lastId ?? (entries.length > 0 ? entries[entries.length - 1][0] : '0-0');
1520
1713
  if (compareStreamIds(id, top) <= 0) {
1521
1714
  throw new RedisCommandError('ERR The ID specified in XADD is equal or smaller than the target stream top item');
1522
1715
  }
1523
1716
  }
1717
+ if (id === '0-0')
1718
+ throw new RedisCommandError('ERR The ID specified in XADD must be greater than 0-0');
1719
+ if (id.split('-').some((v) => BigInt(v) > 18446744073709551615n))
1720
+ throw new RedisCommandError('ERR The stream has exhausted the last possible ID, unable to add more items');
1524
1721
  const pairs = [];
1525
1722
  for (let j = 0; j < fields.length; j += 2)
1526
1723
  pairs.push([fields[j], fields[j + 1]]);
1527
1724
  entries.push([id, pairs]);
1528
- space.put({ name: a[0], kind: STREAM, v: entries, pexpireAt: row?.pexpireAt ?? null, lastId: id }, 'stream.add');
1725
+ space.put({ name: a[0], kind: STREAM, v: trim ? trimEntries(entries, trim) : entries, pexpireAt: row?.pexpireAt ?? null, lastId: id }, 'stream.add');
1529
1726
  return id;
1530
1727
  }
1531
1728
  // ── the script cache ───────────────────────────────────────────────────────────────────────────
@@ -1552,6 +1749,8 @@ function redisToLua(v) {
1552
1749
  return false; // nil bulk reply → Lua `false`
1553
1750
  if (typeof v === 'number' || typeof v === 'string')
1554
1751
  return v;
1752
+ if (v instanceof RedisInteger)
1753
+ return Number(v.integer);
1555
1754
  if (v instanceof RedisStatus) {
1556
1755
  const t = new LuaTable();
1557
1756
  t.set('ok', v.value); // status reply → Lua table {ok=…}
@@ -1659,5 +1858,5 @@ export async function execRedisRun(commands, ctx) {
1659
1858
  }
1660
1859
  const wrote = space.dirty;
1661
1860
  await (space.ctx.dialect.storage?.flush(space) ?? flushKeySpace(space));
1662
- return { items, wrote };
1861
+ return { items, wrote, instant: nowMs };
1663
1862
  }
@@ -0,0 +1,17 @@
1
+ import { type RedisValue } from './engine.js';
2
+ export declare const binaryText: (b: Uint8Array) => string;
3
+ export declare const binaryBytes: (s: string) => Uint8Array;
4
+ export type Frame = {
5
+ value: any;
6
+ end: number;
7
+ error?: boolean;
8
+ type: string;
9
+ };
10
+ export declare function readResp(s: string, start?: number): Frame | undefined;
11
+ export declare function respReply(value: RedisValue | {
12
+ error: string;
13
+ } | Record<string, any>): string;
14
+ export declare const commandFrame: (command: string[]) => Uint8Array;
15
+ export declare function inlineCommand(line: string): string[];
16
+ /** Carry RESP integer/status tags across an internal JSON request without losing int64 bytes. */
17
+ export declare function wireValue(value: any): any;