All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.chrisdavenport.rediculous.RedisCommands.scala Maven / Gradle / Ivy

The newest version!
package io.chrisdavenport.rediculous

import cats._
import cats.implicits._
import cats.data.{NonEmptyList => NEL}
import RedisProtocol._
import _root_.io.chrisdavenport.rediculous.implicits._
import scala.collection.immutable.Nil
import RedisCtx.syntax.all._
import scodec.bits.ByteVector

object RedisCommands {

  def objectrefcount[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("OBJECT", "refcount", key.encode))

  def objectidletime[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("OBJECT", "idletime", key.encode))

  def objectencoding[F[_]: RedisCtx](key: String): F[String] =
    RedisCtx[F].keyed(key, NEL.of("OBJECT", "encoding", key.encode))

  def linsertbefore[F[_]: RedisCtx](key: String, pivot: String, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("LINSERT", key.encode, "BEFORE", pivot.encode, value.encode))

  def linsertafter[F[_]: RedisCtx](key: String, pivot: String, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("LINSERT", key.encode, "AFTER", pivot.encode, value.encode))

  def getType[F[_]: RedisCtx](key: String): F[RedisType] = 
    RedisCtx[F].keyed(key, NEL.of("TYPE", key.encode))

  // TODO slow log commands

  def zrange[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("ZRANGE", key.encode, start.encode, stop.encode))

  def zrangewithscores[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[List[(String, Double)]] = 
    RedisCtx[F].keyed(key, NEL.of("ZRANGE", key.encode, start.encode, stop.encode, "WITHSCORES"))

  def zrevrange[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("ZREVRANGE", key.encode, start.encode, stop.encode))

  def zrevrangewithscores[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[List[(String, Double)]] =
    RedisCtx[F].keyed(key, NEL.of("ZREVRANGE", key.encode, start.encode, stop.encode, "WITHSCORES"))

  def zrangebyscore[F[_]: RedisCtx](key: String, min: Double, max: Double): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("ZRANGEBYSCORE", key.encode, min.encode, max.encode))

  def zrangebyscorewithscores[F[_]: RedisCtx](key: String, min: Double, max: Double): F[List[(String, Double)]] = 
    RedisCtx[F].keyed(key, NEL.of("ZRANGEBYSCORE", key.encode, min.encode, max.encode, "WITHSCORES"))

  def zrangebyscorelimit[F[_]: RedisCtx](key: String, min: Double, max: Double, offset: Long, count: Long): F[List[String]] =
    RedisCtx[F].keyed(key, NEL.of("ZRANGEBYSCORE", key.encode, min.encode, max.encode, "LIMIT", offset.encode, count.encode))

  def zrangebyscorelimitwithscores[F[_]: RedisCtx](key: String, min: Double, max: Double, offset: Long, count: Long): F[List[(String, Double)]] =
    RedisCtx[F].keyed(key, NEL.of("ZRANGEBYSCORE", key.encode, min.encode, max.encode, "WITHSCORES", "LIMIT", offset.encode, count.encode))

  def zrevrangebyscore[F[_]: RedisCtx](key: String, min: Double, max: Double): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("ZREVRANGEBYSCORE", key.encode, min.encode, max.encode))

  def zrevrangebyscorewithscores[F[_]: RedisCtx](key: String, min: Double, max: Double): F[List[(String, Double)]] =
    RedisCtx[F].keyed(key, NEL.of("ZREVRANGEBYSCORE", key.encode, min.encode, max.encode, "WITHSCORES"))

  def zrevrangebyscorelimit[F[_]: RedisCtx](key: String, min: Double, max: Double, offset: Long, count: Long): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("ZREVRANGEBYSCORE", key.encode, min.encode, max.encode, "LIMIT", offset.encode, count.encode))

  def zrevrangebyscorelimitwithscores[F[_]: RedisCtx](key: String, min: Double, max: Double, offset: Long, count: Long): F[List[(String, Double)]] = 
    RedisCtx[F].keyed(key, NEL.of("ZREVRANGEBYSCORE", key.encode, min.encode, max.encode, "WITHSCORES", "LIMIT", offset.encode, count.encode))

  // TODO Sort
  // TODO aggregate

  def eval[F[_]: RedisCtx, A: RedisResult](script: String, keys: List[String], args: List[String]): F[A] = {
    val cmd = NEL("EVAL", script :: keys.length.encode :: keys ::: args)
    keys match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }
    
  def evalsha[F[_]: RedisCtx, A: RedisResult](script: String, keys: List[String], args: List[String]): F[A] = {
    val cmd = NEL("EVALSHA", script :: keys.length.encode :: keys ::: args)
    keys match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }

  def bitcount[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("BITCOUNT", key.encode))

  def bitcountrange[F[_]: RedisCtx](key: String, start: Long, end: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("BITCOUNT", key.encode, start.encode, end.encode))


  private def bitop[F[_]: RedisCtx](operation: String, keys: List[String]): F[Long]= {
    val cmd = NEL("BITOP", operation :: keys)
    keys match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }
  
  def bitopand[F[_]: RedisCtx](destkey: String, srckeys: List[String]): F[Long] = 
    bitop("AND", destkey :: srckeys)

  def bitopor[F[_]: RedisCtx](destkey: String, srckeys: List[String]): F[Long] = 
    bitop("OR", destkey :: srckeys)

  def bitopxor[F[_]: RedisCtx](destkey: String, srckeys: List[String]): F[Long] = 
    bitop("XOR", destkey :: srckeys)

  def bitopnot[F[_]: RedisCtx](destkey: String, srckey: String): F[Long] = 
    bitop("NOT", destkey :: srckey :: Nil)

  // TODO Migrate

  sealed trait Condition
  object Condition {
    case object Nx extends Condition
    case object Xx extends Condition
    implicit val arg: RedisArg[Condition] = RedisArg[String].contramap[Condition]{
      case Nx => "NX"
      case Xx => "XX"
    }
  }

  final case class SetOpts(
    setSeconds: Option[Long],
    setMilliseconds: Option[Long],
    setCondition: Option[Condition],
    keepTTL: Boolean
  )
  object SetOpts{
    val default = SetOpts(None, None, None, false)
  }

  def set[F[_]: RedisCtx](key: String, value: String, setOpts: SetOpts = SetOpts.default): F[Option[Status]] = {
    val ex = setOpts.setSeconds.toList.flatMap(l => List("EX", l.encode))
    val px = setOpts.setMilliseconds.toList.flatMap(l => List("PX", l.encode))
    val condition = setOpts.setCondition.toList.map(_.encode)
    val keepTTL = Alternative[List].guard(setOpts.keepTTL).as("KEEPTTL")
    RedisCtx[F].keyed(key, NEL("SET", key.encode :: value.encode :: ex ::: px ::: condition ::: keepTTL))
  }



  def setBV[F[_]: RedisCtx](key: ByteVector, value: ByteVector, setOpts: SetOpts = SetOpts.default): F[Option[Status]] = {
    val ex = setOpts.setSeconds.toList.flatMap(l => List("EX", l.encode)).map(toBV)
    val px = setOpts.setMilliseconds.toList.flatMap(l => List("PX", l.encode)).map(toBV)
    val condition = setOpts.setCondition.toList.map(_.encode).map(toBV)
    val keepTTL = Alternative[List].guard(setOpts.keepTTL).as("KEEPTTL").map(toBV)
    RedisCtx[F].keyedBV(key, NEL(toBV("SET"), key :: value :: ex ::: px ::: condition ::: keepTTL))
  }

  final case class ZAddOpts(
    condition: Option[Condition],
    change: Boolean,
    increment: Boolean
  )
  object ZAddOpts {
    val default = ZAddOpts(None, false, false)
  }

  def zadd[F[_]: RedisCtx](key: String, scoreMember: List[(Double, String)], options: ZAddOpts = ZAddOpts.default): F[Long] = {
    val scores = scoreMember.flatMap{ case (x, y) => List(x.encode, y.encode)}
    val condition = options.condition.toList.map(_.encode)
    val change = Alternative[List].guard(options.change).as("CH")
    val increment = Alternative[List].guard(options.increment).as("INCR")
    RedisCtx[F].keyed(key, NEL("ZADD", key :: condition ::: change ::: increment ::: scores))
  }

  sealed trait ReplyMode
  object ReplyMode {
    case object On extends ReplyMode
    case object Off extends ReplyMode
    case object Skip extends ReplyMode

    implicit val arg: RedisArg[ReplyMode] = RedisArg[String].contramap[ReplyMode]{
      case On => "ON"
      case Off => "OFF"
      case Skip => "SKIP"
    }
  }

  def clientreply[F[_]: RedisCtx](mode: ReplyMode): F[Boolean] = 
    RedisCtx[F].unkeyed(NEL.of("CLIENT REPLY", mode.encode))

  def srandmember[F[_]: RedisCtx](key: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("SRANDMEMBER", key.encode))

  def srandmemberMulti[F[_]: RedisCtx](key: String, count: Long): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("SRANDMEMBER", key.encode, count.encode))

  def spop[F[_]: RedisCtx](key: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("SPOP", key.encode))

  def spopMulti[F[_]: RedisCtx](key: String,  count: Long): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("SPOP", key.encode, count.encode))

  def info[F[_]: RedisCtx]: F[String] = 
    RedisCtx[F].unkeyed(NEL.of("INFO"))

  def infosection[F[_]: RedisCtx](section: String): F[String] = 
    RedisCtx[F].unkeyed(NEL.of("INFO", section.encode))

  def exists[F[_]: RedisCtx](key: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("EXISTS", key.encode))
  
  // TODO Scan
  // TODO LEX

  sealed trait Trimming
  object Trimming {
    case object Approximate extends Trimming
    case object Exact extends Trimming
    implicit val arg: RedisArg[Trimming] = RedisArg[String].contramap[Trimming]{
      case Approximate => "~"
      case Exact => "="
    }
  }

  final case class XAddOpts(
    id: Option[String],
    maxLength: Option[Long],
    trimming: Option[Trimming],
    noMkStream: Boolean,
    minId: Option[String],
    limit: Option[Long]
  )
  object XAddOpts {
    val default = XAddOpts(None, None, None, false, None, None)
  }

  def xadd[F[_]: RedisCtx](stream: String, body: List[(String, String)], xaddOpts: XAddOpts = XAddOpts.default): F[String] = {
    val maxLen = xaddOpts.maxLength.toList.flatMap{ l => List("MAXLEN".some, xaddOpts.trimming.map(_.encode), l.encode.some).flattenOption }
    val minId = xaddOpts.minId.toList.flatMap{ l => List("MINID".some, xaddOpts.trimming.map(_.encode), l.encode.some).flattenOption }
    val limit = xaddOpts.limit.toList.flatMap(l=> if (xaddOpts.trimming.contains(Trimming.Approximate)) List("LIMIT", l.encode) else List.empty)
    val noMkStream = Alternative[List].guard(xaddOpts.noMkStream).as("NOMKSTREAM")
    val id = List(xaddOpts.id.getOrElse("*"))
    val bodyEnd = body.foldLeft(List.empty[String]){ case (s, (k,v)) => s ::: List(k.encode, v.encode) }

    RedisCtx[F].unkeyed(NEL("XADD", stream :: maxLen ::: minId ::: limit ::: noMkStream ::: id ::: bodyEnd))
  }

  final case class XReadOpts(
    blockMillisecond: Option[Long],
    count: Option[Long],
    noAck: Boolean
  )
  object XReadOpts {
    val default = XReadOpts(None, None, false)
  }

  sealed trait StreamOffset {
    def stream: String
    def offset: String
  }

  object StreamOffset {
    case class All(stream: String) extends StreamOffset {
      override def offset: String = "0"
    }
    case class Latest(stream: String) extends StreamOffset {
      override def offset: String = "$"
    }
    /** Only applicable when using with xreadgroup */
    case class LastConsumed(stream: String) extends StreamOffset {
      override def offset: String = ">"
    }
    case class From(stream: String, offset: String) extends StreamOffset 
  }

  final case class StreamsRecord(
    recordId: String,
    keyValues: List[(String, String)]
  )

  object StreamsRecord {
    implicit val result : RedisResult[StreamsRecord] = new RedisResult[StreamsRecord] {
      def decode(resp: Resp): Either[Resp,StreamsRecord] = {
        def two[A](l: List[A], acc: List[(A, A)] = List.empty): List[(A, A)] = l match {
          case first :: second :: rest => two(rest, (first, second):: acc)
          case _ => acc.reverse
        }
        resp match {
          case  Resp.Array(Some(Resp.BulkString(Some(recordIdBV)) :: Resp.Array(Some(rawKeyValues)) :: Nil)) => 
            for {
              recordId <- recordIdBV.decodeUtf8.leftMap(_ => resp)
              keyValuesList <- rawKeyValues.traverse(RedisResult[String].decode).map(two(_))
            } yield StreamsRecord(recordId, keyValuesList)
          case otherwise => Left(otherwise)
        }
      }
    }
  }

  final case class XReadResponse(
    stream: String,
    records: List[StreamsRecord]
  )
  object XReadResponse{
    implicit val result: RedisResult[XReadResponse] = new RedisResult[XReadResponse] {
      def decode(resp: Resp): Either[Resp,XReadResponse] = {
        resp match {
          case Resp.Array(Some(Resp.BulkString(Some(streamBV)) :: Resp.Array(Some(list)) :: Nil)) => 
            streamBV.decodeUtf8.leftMap(_ => resp).flatMap{ stream => 
              list.traverse(RedisResult[StreamsRecord].decode).map(l => 
                XReadResponse(stream, l)
              )
            }
          case otherwise => Left(otherwise)
        }
      }
    }
  }

  def xread[F[_]: RedisCtx](streams: Set[StreamOffset], xreadOpts: XReadOpts = XReadOpts.default): F[Option[List[XReadResponse]]] = {//F[Option[List[List[(String, List[List[(String, List[(String, String)])]])]]]] = {
    val block = xreadOpts.blockMillisecond.toList.flatMap(l => List("BLOCK", l.encode))
    val count = xreadOpts.count.toList.flatMap(l => List("COUNT", l.encode))
    val noAck = Alternative[List].guard(xreadOpts.noAck).as("NOACK")
    val (streamKeys, streamOffsets) = streams
      .foldLeft((List.empty[String], List.empty[String])){ 
        case ((keys, offsets), streamOffset) => 
          (streamOffset.stream :: keys, streamOffset.offset :: offsets)
      }
    val streamPairs = "STREAMS" :: streamKeys ::: streamOffsets

    RedisCtx[F].unkeyed(NEL("XREAD", block ::: count ::: noAck ::: streamPairs))
  }

  case class Consumer(group: String, name: String)

  def xreadgroup[F[_]: RedisCtx](consumer: Consumer, streams: Set[StreamOffset], xreadOpts: XReadOpts = XReadOpts.default): F[Option[List[XReadResponse]]] = {
    val group = List("GROUP", consumer.group, consumer.name)
    val block = xreadOpts.blockMillisecond.toList.flatMap(l => List("BLOCK", l.encode))
    val count = xreadOpts.count.toList.flatMap(l => List("COUNT", l.encode))
    val noAck = Alternative[List].guard(xreadOpts.noAck).as("NOACK")
    val (streamKeys, streamOffsets) = streams
      .foldLeft((List.empty[String], List.empty[String])){ 
        case ((keys, offsets), streamOffset) => 
          (streamOffset.stream :: keys, streamOffset.offset :: offsets)
      }
    val streamPairs = "STREAMS" :: streamKeys ::: streamOffsets

    RedisCtx[F].unkeyed(NEL("XREADGROUP", group ::: block ::: count ::: noAck ::: streamPairs))
  }

  def xrange[F[_]: RedisCtx](stream: String, startOpt: Option[String] = None, endOpt: Option[String] = None, countOpt: Option[Int] = None): F[Option[List[StreamsRecord]]] = {
    val start = List(startOpt.getOrElse("-"))
    val end = List(endOpt.getOrElse("+"))
    val count = countOpt.toList.flatMap(l => List("COUNT", l.encode))

    RedisCtx[F].unkeyed(NEL("XRANGE", stream :: start ::: end ::: count))
  }

  def xrevrange[F[_]: RedisCtx](stream: String, endOpt: Option[String] = None, startOpt: Option[String] = None, countOpt: Option[Int] = None): F[Option[List[StreamsRecord]]] = {
    val end = List(endOpt.getOrElse("+"))
    val start = List(startOpt.getOrElse("-"))
    val count = countOpt.toList.flatMap(l => List("COUNT", l.encode))

    RedisCtx[F].unkeyed(NEL("XREVRANGE", stream :: end ::: start ::: count))
  }

  def xgroupcreate[F[_]: RedisCtx](stream: String, groupName: String, startId: String, mkStream: Boolean = false): F[Status] = {
    val mkStreamFragment = Alternative[List].guard(mkStream).as("MKSTREAM")
    RedisCtx[F].unkeyed(NEL("XGROUP", "CREATE" :: stream :: groupName :: startId :: mkStreamFragment))
  }

  @deprecated("use xgroupcreate(stream: String, groupName: String, startId: String, mkStream: Boolean) instead", "0.3.3")
  def xgroupcreate[F[_]](stream: String, groupName: String, startId: String, ctx: RedisCtx[F]): F[Status] = 
    xgroupcreate(stream, groupName, startId, false)(ctx)

  def xgroupsetid[F[_]: RedisCtx](stream: String, groupName: String, messageId: String): F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("XGROUP", "SETID", stream, groupName, messageId))

  def xgroupdelconsumer[F[_]: RedisCtx](stream: String, groupName: String, consumer: String): F[Long] = 
    RedisCtx[F].unkeyed(NEL.of("XGROUP", "DELCONSUMER", stream, groupName, consumer))

  def xgroupdestroy[F[_]: RedisCtx](stream: String, groupName: String): F[Boolean] =
    RedisCtx[F].unkeyed(NEL.of("XGROUP", "DESTROY", stream, groupName))

  def xack[F[_]: RedisCtx](stream: String, groupName: String, messageIds: List[String]): F[Long] = 
    RedisCtx[F].unkeyed(NEL("XACK", stream :: groupName :: messageIds))

  def xlen[F[_]: RedisCtx](stream: String): F[Long] = 
    RedisCtx[F].unkeyed(NEL.of("XLEN", stream))
  
  sealed trait XTrimStrategy
  object XTrimStrategy {
    case class MaxLen(maxLen: Int) extends XTrimStrategy
    case class MinId(minId: String) extends XTrimStrategy
  }
  
  def xtrim[F[_]: RedisCtx](stream: String, strategy: XTrimStrategy, trimmingOpt: Option[Trimming] = None, limitOpt: Option[Int] = None): F[Int] = {
    val strategyFragment = strategy match {
      case XTrimStrategy.MaxLen(maxLen) => List("MAXLEN".some, trimmingOpt.map(_.encode), maxLen.encode.some).flatten
      case XTrimStrategy.MinId(minId) => List("MINID".some, trimmingOpt.map(_.encode), minId.encode.some).flatten
    }

    val limit = limitOpt.toList.flatMap(l => if (trimmingOpt.contains(Trimming.Approximate)) List("LIMIT", l.encode) else List.empty)

    RedisCtx[F].unkeyed(NEL("XTRIM", stream :: strategyFragment ::: limit))
  }

  final case class XPendingSummary(
    totalPending: Long,
    smallestId: String,
    greatestId: String,
    consumerPendings: List[(String, Int)]
  )
  object XPendingSummary {
    implicit val result: RedisResult[XPendingSummary] = new RedisResult[XPendingSummary] {
      def decode(resp: Resp): Either[Resp,XPendingSummary] = 
        resp match {
          case Resp.Array(Some(Resp.Integer(totalPending) :: Resp.BulkString(Some(smallestIdBV)) :: Resp.BulkString(Some(greatestIdBV)) :: Resp.Array(Some(list)) :: Nil)) => 
            for {
              smallestId <- smallestIdBV.decodeUtf8.leftMap(_ => resp)
              greatestId <- greatestIdBV.decodeUtf8.leftMap(_ => resp)
              consumerPendings <- list.traverse{ 
                                    case Resp.Array(Some(Resp.BulkString(Some(consumerNameBV)) :: Resp.BulkString(Some(pendingCountBV)) :: Nil)) =>  
                                      (consumerNameBV.decodeUtf8, pendingCountBV.decodeUtf8.map(_.toInt))
                                        .tupled
                                        .leftMap(_ => resp)
                                    case _ => Left(resp)
                                  }
            } yield XPendingSummary(totalPending, smallestId, greatestId, consumerPendings)
            
          case otherwise => Left(otherwise)
        }
    }
  }

  def xpendingsummary[F[_]: RedisCtx](stream: String, groupName: String): F[XPendingSummary] = 
    RedisCtx[F].unkeyed(NEL.of("XPENDING", stream, groupName))

  // TOOD xpendingdetail

  final case class XClaimArgs(
    minIdleTime: Long,
    idle: Option[Long] = None,
    time: Option[Long] = None,
    retrycount: Option[Long] = None,
    force: Boolean = false,
  )

  private def xclaimraw[F[_]: RedisCtx, A: RedisResult](stream: String, consumer: Consumer, args: XClaimArgs, justId: Boolean, messageIds: List[String]): F[A] = {
    val consumerFragment = List(consumer.group, consumer.name)
    val minIdleTime = List(args.minIdleTime.encode)
    val idle = args.idle.toList.flatMap(l => List("IDLE", l.encode))
    val time = args.time.toList.flatMap(l => List("TIME", l.encode))
    val retrycount = args.retrycount.toList.flatMap(l => List("RETRYCOUNT", l.encode))
    val force = Alternative[List].guard(args.force).as("FORCE")
    val justIdFragment = Alternative[List].guard(justId).as("JUSTID")
    val argFragment = idle ::: time ::: retrycount ::: force ::: justIdFragment
    RedisCtx[F].unkeyed(NEL("XCLAIM", stream :: consumerFragment ::: minIdleTime ::: messageIds ::: argFragment))
  }

  def auth[F[_]: RedisCtx](username: String, password: String): F[Status] = {
    RedisCtx[F].unkeyed(NEL("AUTH", username :: password :: Nil))
  }

  def auth[F[_]: RedisCtx](password: String): F[Status] = {
    RedisCtx[F].unkeyed(NEL("AUTH", password :: Nil))
  }

  def xclaimsummary[F[_]: RedisCtx](stream: String, consumer: Consumer, args: XClaimArgs, messageIds: List[String]): F[List[String]] = 
    xclaimraw(stream, consumer, args, true, messageIds)

  def xclaimdetail[F[_]: RedisCtx](stream: String, consumer: Consumer, args: XClaimArgs, messageIds: List[String]): F[List[StreamsRecord]] =
    xclaimraw(stream, consumer, args, false, messageIds)

  final case class XAutoClaimArgs(
    consumer: Consumer,
    minIdleTime: Long,
    startId: String,
    count: Option[Long] = None,
  )

  final case class XAutoClaimSummary(
    startId: String,
    claimedMsgIds: List[String],
    deletedIds: List[String]
  )
  object XAutoClaimSummary {
    implicit val result: RedisResult[XAutoClaimSummary] = new RedisResult[XAutoClaimSummary] {
      def decode(resp: Resp): Either[Resp,XAutoClaimSummary] = 
        resp match {
          case Resp.Array(Some(startId :: claimedMsgIds :: deletedIds :: Nil)) => 
            (RedisResult[String].decode(startId), RedisResult[List[String]].decode(claimedMsgIds), RedisResult[List[String]].decode(deletedIds))
              .tupled
              .map((XAutoClaimSummary.apply _).tupled)
          case otherwise => Left(otherwise)
        }
    }
  }
  final case class XAutoClaimDetail(
    startId: String,
    claimedMsgs: List[StreamsRecord],
    deletedIds: List[String]
  )
  object XAutoClaimDetail {
    implicit val result: RedisResult[XAutoClaimDetail] = new RedisResult[XAutoClaimDetail] {
      def decode(resp: Resp): Either[Resp,XAutoClaimDetail] = 
        resp match {
          case Resp.Array(Some(startId :: claimedMsgs :: deletedIds :: Nil)) => 
            (RedisResult[String].decode(startId), RedisResult[List[StreamsRecord]].decode(claimedMsgs), RedisResult[List[String]].decode(deletedIds))
              .tupled
              .map((XAutoClaimDetail.apply _).tupled)
          case otherwise => Left(otherwise)
        }
    }
  }
  
  private def xautoclaimraw[F[_]: RedisCtx, A: RedisResult](stream: String, args: XAutoClaimArgs, justId: Boolean): F[A] = {
    val consumer = List(args.consumer.group, args.consumer.name)
    val minIdleTime = List(args.minIdleTime.encode)
    val startId = List(args.startId)
    val count = args.count.toList.flatMap(l => List("COUNT", l.encode))
    val justIdFragment = Alternative[List].guard(justId).as("JUSTID")
    val argFragment = consumer ::: minIdleTime ::: startId ::: count ::: justIdFragment 
    RedisCtx[F].unkeyed(NEL("XAUTOCLAIM", stream :: argFragment))
  }

  def xautoclaimsummary[F[_]: RedisCtx](stream: String, args: XAutoClaimArgs): F[XAutoClaimSummary] = 
    xautoclaimraw(stream, args, true)

  def xautoclaimdetail[F[_]: RedisCtx](stream: String, args: XAutoClaimArgs): F[XAutoClaimDetail] = 
    xautoclaimraw(stream, args, false)

  final case class StreamInfo(
    length: Long,
    radixTreeKeys: Long,
    radixTreeNodes: Long,
    lastGeneratedId: String,
    maxDeletedEntryId: String,
    entriesAdded: Long,
    groups: Long,
    firstEntry: Option[StreamsRecord],
    lastEntry: Option[StreamsRecord],
    recordedFirstEntryId: String,
  )
  object StreamInfo {
    val empty: StreamInfo = StreamInfo(0, 0, 0, "", "", 0, 0, None, None, "")

    implicit val result: RedisResult[StreamInfo] = new RedisResult[StreamInfo] {
      def decode(resp: Resp): Either[Resp, StreamInfo] = {
        RedisResult[List[(String, Resp)]]
          .decode(resp)
          .flatMap{ kvs => 
            kvs.foldLeftM[Either[Resp, *], StreamInfo](empty){
              case (info, ("length", Resp.Integer(v))) => info.copy(length = v).asRight
              case (info, ("radix-tree-keys", Resp.Integer(v))) => info.copy(radixTreeKeys = v).asRight
              case (info, ("radix-tree-nodes", Resp.Integer(v))) => info.copy(radixTreeNodes = v).asRight
              case (info, ("last-generated-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(lastGeneratedId = v))
              case (info, ("max-deleted-entry-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(maxDeletedEntryId = v))
              case (info, ("entries-added", Resp.Integer(v))) => info.copy(entriesAdded = v).asRight
              case (info, ("groups", Resp.Integer(v))) => info.copy(groups = v).asRight
              case (info, ("first-entry", r)) => RedisResult[Option[StreamsRecord]].decode(r).map(v => info.copy(firstEntry = v))
              case (info, ("last-entry", r)) => RedisResult[Option[StreamsRecord]].decode(r).map(v => info.copy(lastEntry = v))
              case (info, ("recorded-first-entry-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(recordedFirstEntryId = v))
              case (info, (_,_)) => info.asRight
            }
          }
      }
    }
  }
  
  final case class StreamInfoFull(
    length: Long,
    radixTreeKeys: Long,
    radixTreeNodes: Long,
    lastGeneratedId: String,
    maxDeletedEntryId: String,
    entriesAdded: Long,
    entries: List[StreamsRecord],
    groups: List[StreamInfoFull.ConsumerGroupInfo],
    recordedFirstEntryId: String,
  )

  object StreamInfoFull {
    final case class GroupPel(entryId: String, consumerName: String, deliveryTimeMs: Long, deliveryCount: Long)
    final case class ConsumerPel(entryId: String, deliveryTimeMs: Long, deliveryCount: Long)
    final case class Consumer(name: String, seenTimeMs: Long, pelCount: Long, pending: List[ConsumerPel])
    object Consumer {
      val empty: Consumer = Consumer("", 0, 0, Nil)
      implicit val result: RedisResult[Consumer] = new RedisResult[Consumer] {
        def decode(resp: Resp): Either[Resp,Consumer] = 
          RedisResult[List[(String, Resp)]]
            .decode(resp)
            .flatMap{ kvs => 
              kvs.foldLeftM[Either[Resp, *], Consumer](empty){
                case (st, ("name", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => st.copy(name = v))
                case (st, ("seen-time", Resp.Integer(v))) => st.copy(seenTimeMs = v).asRight
                case (st, ("pel-count", Resp.Integer(v))) => st.copy(pelCount = v).asRight
                case (st, ("pending", r)) => 
                  RedisResult[List[(String, Long, Long)]]
                    .decode(r)
                    .map(v => st.copy(pending = v.map((ConsumerPel.apply _).tupled)))
                case (st, (_, _)) => st.asRight
              }
            }
      }
    }

    final case class ConsumerGroupInfo(
      name: String,
      lastDeliveredId: String,
      entriesRead: Long,
      lag: Long,
      pelCount: Long,
      pending: List[GroupPel],
      consumers: List[Consumer]
    )
    object ConsumerGroupInfo {
      val empty: ConsumerGroupInfo = ConsumerGroupInfo("", "", 0, 0, 0, List.empty, List.empty)
      implicit val result: RedisResult[ConsumerGroupInfo] = new RedisResult[ConsumerGroupInfo] {
        def decode(resp: Resp): Either[Resp, ConsumerGroupInfo] = {
          RedisResult[List[(String, Resp)]]
            .decode(resp)
            .flatMap{ kvs => 
              kvs.foldLeftM[Either[Resp, *], ConsumerGroupInfo](empty){
                case (info, ("name", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(name = v))
                case (info, ("last-delivered-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(lastDeliveredId = v))
                case (info, ("entries-read", Resp.Integer(v))) => info.copy(entriesRead = v).asRight
                case (info, ("lag", Resp.Integer(v))) => info.copy(lag = v).asRight
                case (info, ("pel-count", Resp.Integer(v))) => info.copy(pelCount = v).asRight
                case (info, ("pending", r)) => 
                  RedisResult[List[(String, String, Long, Long)]]
                    .decode(r)
                    .map(v => info.copy(pending = v.map((GroupPel.apply _).tupled)))
                case (info, ("consumers", r)) => 
                  RedisResult[List[Consumer]]
                    .decode(r)
                    .map(v => info.copy(consumers = v))
                case (info, (_, _)) => info.asRight
              }
            }
        }
      }
    }

    val empty: StreamInfoFull = StreamInfoFull(0, 0, 0, "", "", 0, List.empty, List.empty, "")
    implicit val result: RedisResult[StreamInfoFull] = new RedisResult[StreamInfoFull] {
      def decode(resp: Resp): Either[Resp, StreamInfoFull] = {
        RedisResult[List[(String, Resp)]]
          .decode(resp)
          .flatMap{ kvs => 
            kvs.foldLeftM[Either[Resp, *], StreamInfoFull](empty){
              case (info, ("length", Resp.Integer(v))) => info.copy(length = v).asRight
              case (info, ("radix-tree-keys", Resp.Integer(v))) => info.copy(radixTreeKeys = v).asRight
              case (info, ("radix-tree-nodes", Resp.Integer(v))) => info.copy(radixTreeNodes = v).asRight
              case (info, ("last-generated-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(lastGeneratedId = v))
              case (info, ("max-deleted-entry-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(maxDeletedEntryId = v))
              case (info, ("entries-added", Resp.Integer(v))) => info.copy(entriesAdded = v).asRight
              case (info, ("entries", r)) => RedisResult[List[StreamsRecord]].decode(r).map(v => info.copy(entries = v))
              case (info, ("groups", r)) => RedisResult[List[ConsumerGroupInfo]].decode(r).map(v => info.copy(groups = v))
              case (info, ("recorded-first-entry-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(recordedFirstEntryId = v))
              case (info, (_, _)) => info.asRight
            }
          }
      }
    }
  }

  private def xinfostreamraw[F[_]: RedisCtx, A: RedisResult](stream: String, fullOpt: Boolean, countOpt: Option[Int]): F[A] = {
    val full = Alternative[List].guard(fullOpt).as("FULL")
    val count = countOpt.toList.flatMap(c => List("COUNT", c.encode))
    RedisCtx[F].unkeyed(NEL("XINFO", "STREAM" :: stream :: full ::: count))
  }

  def xinfostream[F[_]: RedisCtx](stream: String): F[StreamInfo] = 
    xinfostreamraw(stream, false, None)

  def xinfostreamfull[F[_]: RedisCtx](stream: String, countOpt: Option[Int] = None): F[StreamInfoFull] = 
    xinfostreamraw(stream, true, countOpt)

  final case class StreamConsumersInfo(
    name: String,
    pending: Long,
    idleMs: Long
  )
  object StreamConsumersInfo {
    val empty: StreamConsumersInfo = StreamConsumersInfo("", 0, 0)
    implicit val result: RedisResult[StreamConsumersInfo] = new RedisResult[StreamConsumersInfo] {
      def decode(resp: Resp): Either[Resp,StreamConsumersInfo] = 
        RedisResult[List[(String, Resp)]]
          .decode(resp)
          .flatMap{ kvs => 
            kvs.foldLeftM[Either[Resp, *], StreamConsumersInfo](empty){
              case (info, ("name", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(name = v))
              case (info, ("pending", Resp.Integer(v))) => info.copy(pending = v).asRight
              case (info, ("idle", Resp.Integer(v))) => info.copy(idleMs = v).asRight
              case (info, (_, _)) => info.asRight
            }
          }
    }
  }
  
  def xinfoconsumer[F[_]: RedisCtx](stream: String, groupName: String): F[List[StreamConsumersInfo]] = 
    RedisCtx[F].unkeyed(NEL.of("XINFO", "CONSUMERS", stream, groupName))

  final case class StreamGroupsInfo(
    name: String,
    consumers: Long,
    pending: Long,
    lastDeliveredId: String,
    entriesRead: Long,
    lag: Long
  )

  object StreamGroupsInfo {
    val empty: StreamGroupsInfo = StreamGroupsInfo("", 0, 0, "", 0, 0)
    implicit val result: RedisResult[StreamGroupsInfo] = new RedisResult[StreamGroupsInfo] {
      def decode(resp: Resp): Either[Resp,StreamGroupsInfo] = 
        RedisResult[List[(String, Resp)]]
          .decode(resp)
          .flatMap{ kvs => 
            kvs.foldLeftM[Either[Resp, *], StreamGroupsInfo](empty){
              case (info, ("name", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(name = v))
              case (info, ("consumers", Resp.Integer(v))) => info.copy(consumers = v).asRight
              case (info, ("pending", Resp.Integer(v))) => info.copy(pending = v).asRight
              case (info, ("last-delivered-id", Resp.BulkString(Some(bv)))) => bv.decodeUtf8.leftMap(_ => resp).map(v => info.copy(lastDeliveredId = v))
              case (info, ("entries-read", Resp.Integer(v))) => info.copy(entriesRead = v).asRight
              case (info, ("lag", Resp.Integer(v))) => info.copy(lag = v).asRight
              case (info, (_, _)) => info.asRight
            }
          }
    }
  }

  def xinfogroup[F[_]: RedisCtx](stream: String): F[List[StreamGroupsInfo]] = 
    RedisCtx[F].unkeyed(NEL.of("XINFO", "GROUPS", stream))

  def xdel[F[_]: RedisCtx](stream: String, messageIds: List[String]): F[Long] = 
    RedisCtx[F].unkeyed(NEL("XDEL", stream :: messageIds))

  // Simple String Commands

  def ping[F[_]: RedisCtx]: F[Status] =
    RedisCtx[F].unkeyed[Status](NEL.of("PING"))

  def ttl[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("TTL", key.encode))

  def setnx[F[_]: RedisCtx](key: String, value: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("SETNX", key.encode, value.encode))

  def pttl[F[_]: RedisCtx](key: String): F[Long] =
    RedisCtx[F].keyed(key, NEL.of("PTTL", key.encode))

  def commandcount[F[_]: RedisCtx]: F[Long] = 
    RedisCtx[F].unkeyed(NEL.of("COMMAND", "COUNT"))

  def clientsetname[F[_]: RedisCtx](connectionName: String): F[String] = 
    RedisCtx[F].unkeyed(NEL.of("CLIENT", "SETNAME", connectionName.encode))

  def zrank[F[_]: RedisCtx](key: String, member: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("ZRANK", key.encode, member.encode))

  def zremrangebyscore[F[_]: RedisCtx](key: String, min: Double, max: Double): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("ZREMRANGEBYSCORE", key.encode, min.encode, max.encode))

  def hkeys[F[_]: RedisCtx](key: String): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("HKEYS", key.encode))

  def slaveof[F[_]: RedisCtx](host: String, port: Int): F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("SLAVEOF", host.encode, port.encode))

  def rpushx[F[_]: RedisCtx](key: String, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("RPUSHX", key.encode, value.encode))

  def debugobject[F[_]: RedisCtx](key: String): F[String] = 
    RedisCtx[F].keyed(key, NEL.of("DEBUG", "OBJECT", key.encode))

  def bgsave[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("BGSAVE"))

  def hlen[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("HLEN", key.encode))

  def rpoplpush[F[_]: RedisCtx](source: String, destination: String): F[Option[String]] = 
    RedisCtx[F].keyed(source, NEL.of("RPOPLPUSH", source.encode, destination.encode))

  def brpop[F[_]: RedisCtx](key: List[String], timeout: Long): F[Option[(String, String)]] = {
    val cmd =  NEL.of("BRPOP", (key.map(_.encode) ++ List(timeout.encode)):_*)
    key match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }

  def bgrewriteaof[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("BGREWRITEAOF"))

  def zincrby[F[_]: RedisCtx](key: String, increment: Long, member: String): F[Double] = 
    RedisCtx[F].keyed(key, NEL.of("ZINCRBY", key.encode, increment.encode, member.encode))

  def hgetall[F[_]: RedisCtx](key: String): F[List[(String, String)]] = 
    RedisCtx[F].keyed(key, NEL.of("HGETALL", key.encode))

  def hmset[F[_]: RedisCtx](key: String, fieldValue: List[(String, String)]): F[Status] = 
    RedisCtx[F].keyed(key, NEL("HMSET", key.encode :: fieldValue.flatMap{ case (x, y) => List(x.encode, y.encode)}))

  def sinter[F[_]: RedisCtx](key: List[String]): F[List[String]] = {
    val cmd = NEL("SINTER", key.map(_.encode))
    key match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }

  def pfadd[F[_]: RedisCtx](key: String, value: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("PFADD", key.encode :: value.map(_.encode)))

  def zremrangebyrank[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("ZREMRANGEBYRANK", key.encode, start.encode, stop.encode))

  def flushdb[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("FLUSHDB"))

  def sadd[F[_]: RedisCtx](key: String, member: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("SADD", key.encode :: member.map(_.encode)))

  def lindex[F[_]: RedisCtx](key: String, index: Int): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("LINDEX", key.encode, index.encode))

  def lpush[F[_]: RedisCtx](key: String, value: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("LPUSH", key.encode :: value.map(_.encode)))

  def hstrlen[F[_]: RedisCtx](key: String, field: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("HSTRLEN", key.encode, field.encode))

  def smove[F[_]: RedisCtx](source: String, destination: String, member: String): F[Boolean] = 
    RedisCtx[F].keyed(source, NEL.of("SMOVE", source.encode, destination.encode, member.encode))

  def zscore[F[_]: RedisCtx](key: String, member: String): F[Option[Double]] = 
    RedisCtx[F].keyed(key, NEL.of("ZSCORE", key.encode, member.encode))

  def configresetstat[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("CONFIG", "RESETSTAT"))

  def pfcount[F[_]: RedisCtx](key: List[String]): F[Long] = {
    val cmd = NEL("PFCOUNT", key.map(_.encode))
    key match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }

  def hdel[F[_]: RedisCtx](key: String, field: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("HDEL", key.encode :: field.map(_.encode)))

  def incrbyfloat[F[_]: RedisCtx](key: String, increment: Double): F[Double] = 
    RedisCtx[F].keyed(key, NEL.of("INCRBYFLOAT", key.encode, increment.encode))

  def setbit[F[_]: RedisCtx](key: String, offset: Long, value: String): F[Long] =
    RedisCtx[F].keyed(key, NEL.of("SETBIT", key.encode, offset.encode, value.encode))

  def flushall[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("FLUSHALL"))

  def incrby[F[_]: RedisCtx](key: String, increment: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("INCRBY", key.encode, increment.encode))

  def time[F[_]: RedisCtx]: F[(Long, Long)] = 
    RedisCtx[F].unkeyed(NEL.of("TIME"))

  def smembers[F[_]: RedisCtx](key: String): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("SMEMBERS", key.encode))

  def zlexcount[F[_]: RedisCtx](key: String, min: String, max: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("ZLEXCOUNT", key.encode, min.encode, max.encode))

  def sunion[F[_]: RedisCtx](key: List[String]): F[List[String]] = {
    val cmd = NEL("SUNION", key.map(_.encode))
    key match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case k :: _ => RedisCtx[F].keyed(k, cmd)
    }
  }

  def sinterstore[F[_]: RedisCtx](destination: String, key: List[String]): F[Long] = 
    RedisCtx[F].keyed(destination, NEL("SINTERSTORE", destination.encode :: key.map(_.encode)))

  def hvals[F[_]: RedisCtx](key: String): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("HVALS", key.encode))

  def configset[F[_]: RedisCtx](parameter: String, value: String): F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("CONFIG", "SET", parameter.encode, value.encode))

  def scriptflush[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("SCRIPT", "FLUSH"))

  def dbsize[F[_]: RedisCtx]: F[Long] = 
    RedisCtx[F].unkeyed(NEL.of("DBSIZE"))

  def wait[F[_]: RedisCtx](numslaves: Long, timeout: Long): F[Long] = 
    RedisCtx[F].unkeyed(NEL.of("WAIT", numslaves.encode, timeout.encode))

  def lpop[F[_]: RedisCtx](key: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("LPOP", key.encode))

  def clientpause[F[_]: RedisCtx](timeout: Long): F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("CLIENT", "PAUSE", timeout.encode))

  def expire[F[_]: RedisCtx](key: String, seconds: Long): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("EXPIRE", key.encode, seconds.encode))

  private[rediculous] def mget[F[_]: RedisCtx](key: String): F[List[Option[String]]] = {
    val cmd = NEL("MGET", key.encode :: Nil)
    RedisCtx[F].keyed(key, cmd)
  }

  def mget[F[_]: RedisCtx](key: String, keys: String*): F[List[Option[String]]] = {
    RedisCtx[F].keyed(key, NEL("MGET", (key :: keys.toList).map(_.encode)))
  }

  def bitpos[F[_]: RedisCtx](key: String, bit: Long, start: Long, end: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("BITPOS", key.encode, bit.encode, start.encode, end.encode))

  def lastsave[F[_]: RedisCtx]: F[Long] = 
    RedisCtx[F].unkeyed(NEL.of("LASTSAVE"))

  def pexpire[F[_]: RedisCtx](key: String, milliseconds: Long): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("PEXPIRE", key.encode, milliseconds.encode))

  def clientlist[F[_]: RedisCtx]: F[List[String]] = 
    RedisCtx[F].unkeyed(NEL.of("CLIENT", "LIST"))

  def renamenx[F[_]: RedisCtx](key: String, newkey: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("RENAMENX", key.encode, newkey.encode))

  def pfmerge[F[_]: RedisCtx](destkey: String, sourcekey: List[String]): F[String] = 
    RedisCtx[F].keyed(destkey, NEL("PFMERGE", destkey.encode :: sourcekey.map(_.encode)))

  def lrem[F[_]: RedisCtx](key: String, count: Long, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("LREM", key.encode, count.encode, value.encode))

  def sdiff[F[_]: RedisCtx](key: List[String]): F[List[String]] = {
    val cmd = NEL("SDIFF", key.map(_.encode))
    key match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case key :: _ => RedisCtx[F].keyed(key, cmd)
    }
  }

  def get[F[_]: RedisCtx](key: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("GET", key.encode))

  def getBV[F[_]: RedisCtx](key: ByteVector): F[Option[ByteVector]] = 
    RedisCtx[F].keyedBV(key, NEL.of(toBV("GET"), key))

  def getrange[F[_]: RedisCtx](key: String, start: Long, end: Long): F[String] = 
    RedisCtx[F].keyed(key, NEL.of("GETRANGE", key.encode, start.encode, end.encode))

  def sdiffstore[F[_]: RedisCtx](destination: String, key: List[String]): F[Long] = 
    RedisCtx[F].keyed(destination, NEL("SDIFFSTORE", destination.encode :: key.map(_.encode)))

  def zcount[F[_]: RedisCtx](key: String, min: Double, max: Double): F[Long] =
    RedisCtx[F].keyed(key, NEL.of("ZCOUNT", key.encode, min.encode, max.encode))

  def scriptload[F[_]: RedisCtx](script: String): F[String] =
    RedisCtx[F].unkeyed(NEL.of("SCRIPT", "LOAD", script.encode))

  def getset[F[_]: RedisCtx](key: String, value: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("GETSET", key.encode, value.encode))

  def dump[F[_]: RedisCtx](key: String): F[String] = 
    RedisCtx[F].keyed(key, NEL.of("DUMP", key.encode))

  def keys[F[_]: RedisCtx](pattern: String): F[List[String]] = 
    RedisCtx[F].unkeyed(NEL.of("KEYS", pattern.encode))

  def configget[F[_]: RedisCtx](parameter: String): F[List[(String, String)]] = 
    RedisCtx[F].unkeyed(NEL.of("CONFIG", "GET", parameter.encode))

  def rpush[F[_]: RedisCtx](key: String, value: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("RPUSH", key.encode :: value.map(_.encode)))

  def randomkey[F[_]: RedisCtx]: F[Option[String]] = 
    RedisCtx[F].unkeyed(NEL.of("RANDOMKEY"))

  def hsetnx[F[_]: RedisCtx](key: String, field: String, value: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("HSETNX", key.encode, field.encode, value.encode))

  private[rediculous] def mset[F[_]: RedisCtx](keyvalue: (String, String)): F[Status] =
    RedisCtx[F].keyed(keyvalue._1, NEL("MSET", List(keyvalue._1.encode, keyvalue._2.encode)))

  def mset[F[_]: RedisCtx](keyValue: (String, String), keyValues: (String, String)*): F[List[Status]] = {
    val command = NEL("MSET", (keyValue :: keyValues.toList).flatMap(t => List(t._1.encode, t._2.encode)))
    RedisCtx[F].keyed(keyValue._1, command)
  }

  def setex[F[_]: RedisCtx](key: String, seconds: Long, value: String): F[Status] = 
    RedisCtx[F].keyed(key, NEL.of("SETEX", key.encode, seconds.encode, value.encode))

  def psetex[F[_]: RedisCtx](key: String, milliseconds: Long, value: String): F[Status] = 
    RedisCtx[F].keyed(key, NEL.of("PSETEX", key.encode, milliseconds.encode, value.encode))

  def scan[F[_]: RedisCtx](cursor: Long, patternOpt: Option[String] = None, countOpt: Option[Long] = None, typeOpt: Option[RedisType] = None): F[(Long, List[String])] = {
    val pattern = patternOpt.toList.flatMap(l => List("MATCH", l.encode))
    val count = countOpt.toList.flatMap(l => List("COUNT", l.encode))
    val `type` = typeOpt.toList.flatMap(l => List("TYPE", l.encode))
    RedisCtx[F].unkeyed(NEL("SCAN", cursor.encode :: pattern ::: count ::: `type`))
  }

  def scard[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("SCARD", key.encode))

  def scriptexists[F[_]: RedisCtx](script: List[String]): F[List[Boolean]] = 
    RedisCtx[F].unkeyed(NEL("SCRIPT", "EXISTS" :: script.map(_.encode)))

  def sunionstore[F[_]: RedisCtx](destination: String, key: List[String] ): F[Long] = 
    RedisCtx[F].keyed(destination, NEL("SUNIONSTORE", destination.encode :: key.map(_.encode)))

  def persist[F[_]: RedisCtx](key: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("PERSIST", key.encode))

  def strlen[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("STRLEN", key.encode))

  def lpushx[F[_]: RedisCtx](key: String, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("LPUSHX", key.encode, value.encode))

  def hset[F[_]: RedisCtx](key: String, field: String, value: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("HSET", key.encode, field.encode, value.encode))

  def brpoplpush[F[_]: RedisCtx](source: String, destination: String, timeout: Long): F[Option[String]] = 
    RedisCtx[F].keyed(source, NEL.of("BRPOPLPUSH", source.encode, destination.encode, timeout.encode))

  def zrevrank[F[_]: RedisCtx](key: String, member: String): F[Option[Long]] = 
    RedisCtx[F].keyed(key, NEL.of("ZREVRANK", key.encode, member.encode))

  def scriptkill[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("SCRIPT", "KILL"))

  def setrange[F[_]: RedisCtx](key: String, offset: Long, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("SETRANGE", key.encode, offset.encode, value.encode))

  def del[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL("DEL", key.encode :: Nil))

  def hincrbyfloat[F[_]: RedisCtx](key: String,field: String,increment: Double): F[Double] = 
    RedisCtx[F].keyed(key, NEL.of("HINCRBYFLOAT", key.encode, field.encode, increment.encode))


  def hincrby[F[_]: RedisCtx](key: String, field: String, increment: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("HINCRBY", key.encode, field.encode, increment.encode))

  def zremrangebylex[F[_]: RedisCtx](key: String, min: String, max: String): F[Long]  =
    RedisCtx[F].keyed(key, NEL.of("ZREMRANGEBYLEX", key.encode, min.encode, max.encode))

  def rpop[F[_]: RedisCtx](key: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("RPOP", key.encode))

  def rename[F[_]: RedisCtx](key: String, newkey: String): F[Status] = 
    RedisCtx[F].keyed(key, NEL.of("RENAME", key.encode, newkey.encode))

  def zrem[F[_]: RedisCtx](key: String, member: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("ZREM", key.encode :: member.map(_.encode)))

  def hexists[F[_]: RedisCtx](key: String, field: String): F[Boolean] =
    RedisCtx[F].keyed(key, NEL.of("HEXISTS", key.encode, field.encode))

  def clientgetname[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("CLIENT", "GETNAME"))

  def configerewrite[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("CONFIG", "REWRITE"))

  def decr[F[_]: RedisCtx](key: String): F[Long] =
    RedisCtx[F].keyed(key, NEL.of("DECR", key.encode))

  def hmget[F[_]: RedisCtx](key: String, field: List[String]): F[List[Option[String]]] = 
    RedisCtx[F].keyed(key, NEL("HMGET", key.encode :: field.map(_.encode)))

  def lrange[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[List[String]] = 
    RedisCtx[F].keyed(key, NEL.of("LRANGE", key.encode, start.encode, stop.encode))

  def decrby[F[_]: RedisCtx](key: String, decrement: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("DECRBY", key.encode, decrement.encode))

  def llen[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("LLEN", key.encode))

  def append[F[_]: RedisCtx](key: String, value: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("APPEND", key.encode, value.encode))

  def incr[F[_]: RedisCtx](key: String): F[Long] =
    RedisCtx[F].keyed(key, NEL.of("INCR", key.encode))

  def hget[F[_]: RedisCtx](key: String, field: String): F[Option[String]] = 
    RedisCtx[F].keyed(key, NEL.of("HGET", key.encode, field.encode))

  def pexpireat[F[_]: RedisCtx](key: String, milliseconds: Long): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("PEXPIREAT", key.encode, milliseconds.encode))

  def ltrim[F[_]: RedisCtx](key: String, start: Long, stop: Long): F[Status] = 
    RedisCtx[F].keyed(key, NEL.of("LTRIM", key.encode, start.encode, stop.encode))

  def zcard[F[_]: RedisCtx](key: String): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("ZCARD", key.encode))

  def lset[F[_]: RedisCtx](key: String, index: Long, value: String): F[Status] = 
    RedisCtx[F].keyed(key, NEL.of("LSET", key.encode, index.encode, value.encode))

  def expireat[F[_]: RedisCtx](key: String, timestamp: Long): F[Boolean] =
    RedisCtx[F].keyed(key, NEL.of("EXPIREAT", key.encode, timestamp.encode))

  def save[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("SAVE"))

  def move[F[_]: RedisCtx](key: String, db: Long): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("MOVE", key.encode, db.encode))

  def getbit[F[_]: RedisCtx](key: String, offset: Long): F[Long] = 
    RedisCtx[F].keyed(key, NEL.of("GETBIT", key.encode, offset.encode))

  def msetnx[F[_]: RedisCtx](keyvalue: List[(String, String)]): F[Boolean] = {
    val cmd = NEL("MSETNX", keyvalue.flatMap{case (x, y) => List(x.encode, y.encode)})
    keyvalue match {
      case Nil => RedisCtx[F].unkeyed(cmd)
      case (key, _) :: _ => RedisCtx[F].keyed(key, cmd)
    }
  }

  def commandinfo[F[_]: RedisCtx](commandName: List[String]): F[List[String]] = 
    RedisCtx[F].unkeyed(NEL("COMMAND", "INFO" :: commandName.map(_.encode)))

  def quit[F[_]: RedisCtx]: F[Status] = 
    RedisCtx[F].unkeyed(NEL.of("QUIT"))

  def blpop[F[_]: RedisCtx](key: List[String], timeout: Long): F[Option[(String, String)]] = {
    val cmd = NEL("BLPOP", key.map(_.encode) ++ List(timeout.encode))
    key.headOption match {
      case Some(value) => RedisCtx[F].keyed(value, cmd)
      case None => RedisCtx[F].unkeyed(cmd)
    }
  }
  
  def srem[F[_]: RedisCtx](key: String, member: List[String]): F[Long] = 
    RedisCtx[F].keyed(key, NEL("SREM", key.encode :: member.map(_.encode)))

  def echo[F[_]: RedisCtx](message: String): F[String] = 
    RedisCtx[F].unkeyed(NEL.of("ECHO", message.encode))

  def sismember[F[_]: RedisCtx](key: String, member: String): F[Boolean] = 
    RedisCtx[F].keyed(key, NEL.of("SISMEMBER", key.encode, member.encode))

  def publish[F[_]: RedisCtx](channel: String, message: String): F[Int] = 
    RedisCtx[F].unkeyed[Int](cats.data.NonEmptyList.of("PUBLISH", channel, message))

  private def toBV(s: String): ByteVector = ByteVector.encodeUtf8(s).fold(throw _, identity(_))
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy