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

japgolly.webapputil.indexeddb.Txn.scala Maven / Gradle / Ivy

There is a newer version: 2.0.0-RC12
Show newest version
package japgolly.webapputil.indexeddb

import cats.Monad
import japgolly.webapputil.indexeddb.TxnMode._

/** Embedded language for safely working with(in) an IndexedDB transaction.
  *
  * This is necessary because whilst all the transaction methods are async, any other type of asynchronicity is not
  * supported and will result in IndexedDB automatically committing and closing the transaction, in which case,
  * further interaction with the transaction will result in a runtime error.
  *
  * Therefore, returning [[AsyncCallback]] from within transactions is dangerous because it allows composition of
  * both kinds of asynchronicity. To avoid this, we use this embedded language and don't publicly expose its
  * interpretation/translation to [[AsyncCallback]]. From the call-site's point of view, a `Txn[A]` is completely
  * opaque.
  *
  * This also has a nice side-effect of ensuring that transaction completion is always awaited because we do it in the
  * transaction functions right after interpretation. Otherwise, the call-sites would always need to remember to do it
  * if live transaction access were exposed.
  *
  * @tparam A The return type.
  */
final case class Txn[+M <: TxnMode, +A](step: TxnStep[M, A]) { self =>
  import TxnStep._

  def map[B](f: A => B): Txn[M, B] =
    Txn(Map(step, f))

  def void: Txn[M, Unit] =
    map(_ => ())
}

object Txn {

  @inline implicit final class InvariantOps[M <: TxnMode, A](private val self: Txn[M, A]) extends AnyVal {
    import TxnStep._

    def flatMap[N <: TxnMode, B](f: A => Txn[N, B])(implicit m: TxnMode.Merge[M, N]): Txn[m.Mode, B] = {
      val step = FlatMap[m.Mode, A, B](m.substM(self.step), a => m.substN(f(a).step))
      Txn(step)
    }

    @inline def unless(cond: Boolean)(implicit ev: TxnStep[RO, Option[Nothing]] => Txn[M, Option[Nothing]]): Txn[M, Option[A]] =
      when(!cond)

    @inline def unless_(cond: Boolean)(implicit ev: TxnStep[RO, Unit] => Txn[M, Unit]): Txn[M, Unit] =
      when_(!cond)

    def when(cond: Boolean)(implicit ev: TxnStep[RO, Option[Nothing]] => Txn[M, Option[Nothing]]): Txn[M, Option[A]] =
      if (cond) self.map(Some(_)) else ev(none)

    def when_(cond: Boolean)(implicit ev: TxnStep[RO, Unit] => Txn[M, Unit]): Txn[M, Unit] =
      if (cond) self.void else ev(unit)

    def >>[N <: TxnMode, B](f: Txn[N, B])(implicit m: TxnMode.Merge[M, N]): Txn[m.Mode, B] = {
      val next = m.substN(f.step)
      val step = FlatMap[m.Mode, A, B](m.substM(self.step), _ => next)
      Txn(step)
    }
  }

  type CatsInstance[M <: TxnMode] = Monad[Txn[M, *]]

  def catsInstance[M <: TxnMode](dsl: TxnDsl[M]): CatsInstance[M] =
    new CatsInstance[M] {
      override def pure[A](a: A) = dsl.pure(a)
      override def map[A, B](fa: Txn[M, A])(f: A => B) = fa map f
      override def flatMap[A, B](fa: Txn[M, A])(f: A => Txn[M, B]) = fa flatMap f
      override def tailRecM[A, B](a: A)(f: A => Txn[M, Either[A, B]]) = dsl.tailRec(a)(f)
    }

  implicit def catsInstanceRO: CatsInstance[RO] = catsInstance(TxnDslRO)
  implicit def catsInstanceRW: CatsInstance[RW] = catsInstance(TxnDslRW)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy