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

colibri.Observable.scala Maven / Gradle / Ivy

The newest version!
package colibri

import cats._
import cats.implicits._
import colibri.effect.RunEffect
import cats.effect.{Async, IO, Resource, Sync, SyncIO}

import java.util.concurrent.Flow
import scala.scalajs.js
import scala.scalajs.js.timers
import scala.collection.Factory
import scala.concurrent.{ExecutionContext, Future}
import scala.concurrent.duration.FiniteDuration
import scala.util.Try

trait Observable[+A] {
  def unsafeSubscribe(sink: Observer[A]): Cancelable
}
object Observable    {
  import org.scalajs.macrotaskexecutor.MacrotaskExecutor
  private val MicrotaskExecutor = scala.scalajs.concurrent.QueueExecutionContext.promises()

  implicit object source extends Source[Observable] {
    @inline def unsafeSubscribe[A](source: Observable[A])(sink: Observer[A]): Cancelable = source.unsafeSubscribe(sink)
  }

  implicit object liftSource extends LiftSource[Observable] {
    @inline def lift[H[_]: Source, A](source: H[A]): Observable[A] = Observable.lift[H, A](source)
  }

  implicit object catsInstances
      extends MonadError[Observable, Throwable]
      with FunctorFilter[Observable]
      with Alternative[Observable]
      with CoflatMap[Observable] {
    @inline override def unit: Observable[Unit]                                                                        = Observable.unit
    @inline override def pure[A](a: A): Observable[A]                                                                  = Observable.pure(a)
    @inline override def map[A, B](fa: Observable[A])(f: A => B): Observable[B]                                        = fa.map(f)
    @inline override def handleErrorWith[A](fa: Observable[A])(f: Throwable => Observable[A]): Observable[A]           =
      fa.attempt.flatMap(_.fold(f, Observable.pure))
    @inline override def raiseError[A](e: Throwable): Observable[A]                                                    = Observable.raiseError(e)
    @inline override def recover[A](fa: Observable[A])(pf: PartialFunction[Throwable, A]): Observable[A]               = fa.recover(pf)
    @inline override def flatMap[A, B](fa: Observable[A])(f: A => Observable[B]): Observable[B]                        = fa.flatMap(f)
    @inline override def flatten[A](ffa: Observable[Observable[A]]): Observable[A]                                     = ffa.flatten
    @inline override def tailRecM[A, B](a: A)(f: A => Observable[Either[A, B]]): Observable[B]                         = Observable.tailRecM[A, B](a)(f)
    @inline override def ensure[A](fa: Observable[A])(error: => Throwable)(predicate: A => Boolean): Observable[A]     =
      fa.mapEither(a => if (predicate(a)) Right(a) else Left(error))
    @inline override def ensureOr[A](fa: Observable[A])(error: A => Throwable)(predicate: A => Boolean): Observable[A] =
      fa.mapEither(a => if (predicate(a)) Right(a) else Left(error(a)))
    @inline override def rethrow[A, EE <: Throwable](fa: Observable[Either[EE, A]]): Observable[A]                     = fa.flattenEither
    @inline override def adaptError[A](fa: Observable[A])(pf: PartialFunction[Throwable, Throwable]): Observable[A]    = fa.adaptError(pf)

    @inline def coflatMap[A, B](fa: Observable[A])(f: Observable[A] => B): Observable[B] = Observable.eval(f(fa))

    @inline override def functor                                                                   = this
    @inline override def mapFilter[A, B](fa: Observable[A])(f: A => Option[B]): Observable[B]      = fa.mapFilter(f)
    @inline override def collect[A, B](fa: Observable[A])(f: PartialFunction[A, B]): Observable[B] = fa.collect(f)
    @inline override def filter[A](fa: Observable[A])(f: A => Boolean): Observable[A]              = fa.filter(f)
    @inline override def empty[T]                                                                  = Observable.empty
    @inline override def combineK[T](a: Observable[T], b: Observable[T])                           = Observable.concat(a, b)
  }

  implicit object catsParallelCombine extends Parallel[Observable] {
    override type F[A] = CombineObservable.Type[A]

    override def monad: Monad[Observable]                         = implicitly[Monad[Observable]]
    override def applicative: Applicative[CombineObservable.Type] = implicitly[Applicative[CombineObservable.Type]]

    override val sequential = new (CombineObservable.Type ~> Observable) {
      def apply[A](fa: CombineObservable.Type[A]): Observable[A] = CombineObservable.unwrap(fa)
    }
    override val parallel   = new (Observable ~> CombineObservable.Type) {
      def apply[A](fa: Observable[A]): CombineObservable.Type[A] = CombineObservable.wrap(fa)
    }
  }

  implicit object catsInstancesSubject extends Invariant[Subject] {
    @inline def imap[A, B](fa: Subject[A])(f: A => B)(g: B => A): Subject[B] = fa.imapProSubject(g)(f)
  }

  implicit object catsInstancesProSubject extends arrow.Profunctor[ProSubject] {
    def dimap[A, B, C, D](fab: ProSubject[A, B])(f: C => A)(g: B => D): ProSubject[C, D] = fab.imapProSubject(f)(g)
  }

  trait ForSemantic[+A] {
    def map[B](f: A => B): Observable[B]
    def flatMap[B](f: A => Observable[B]): Observable[B]
  }

  object Empty extends Observable[Nothing] {
    @inline def unsafeSubscribe(sink: Observer[Nothing]): Cancelable = Cancelable.empty
  }

  @inline def empty: Observable[Nothing] = Empty
  val unit: Observable[Unit]             = Observable.pure(())

  def pure[T](value: T): Observable[T] = new Observable[T] {
    def unsafeSubscribe(sink: Observer[T]): Cancelable = {
      sink.unsafeOnNext(value)
      Cancelable.empty
    }
  }

  @inline def apply[T](value: T): Observable[T] = pure(value)

  @inline def apply[T](value: T, value2: T, values: T*): Observable[T] = Observable.fromIterable(value +: value2 +: values)

  def eval[T](value: => T): Observable[T] = new Observable[T] {
    def unsafeSubscribe(sink: Observer[T]): Cancelable = {
      sink.unsafeOnNext(value)
      Cancelable.empty
    }
  }

  def evalCancelable(value: => Cancelable): Observable[Unit] = new Observable[Unit] {
    def unsafeSubscribe(sink: Observer[Unit]): Cancelable = value
  }

  def evalObservable[T](value: => Observable[T]): Observable[T] = new Observable[T] {
    def unsafeSubscribe(sink: Observer[T]): Cancelable = value.unsafeSubscribe(sink)
  }

  def raiseError[T](error: Throwable): Observable[T] = new Observable[T] {
    def unsafeSubscribe(sink: Observer[T]): Cancelable = {
      sink.unsafeOnError(error)
      Cancelable.empty
    }
  }

  def lift[H[_]: Source, A](source: H[A]): Observable[A] = source match {
    case source: Observable[A @unchecked] => source
    case _                                =>
      new Observable[A] {
        def unsafeSubscribe(sink: Observer[A]): Cancelable = Source[H].unsafeSubscribe(source)(sink)
      }
  }

  def fromPublisher[A](publisher: Flow.Publisher[A]): Observable[A] = Observable.create { observer =>
    var subscription: Flow.Subscription = null
    var isComplete                      = false
    val subscriber                      = new Flow.Subscriber[A] {
      def onNext(a: A)                      = observer.unsafeOnNext(a)
      def onError(t: Throwable)             = observer.unsafeOnError(t)
      def onComplete()                      = isComplete = true
      def onSubscribe(s: Flow.Subscription) = {
        subscription = s
        s.request(Long.MaxValue)
      }
    }

    publisher.subscribe(subscriber)

    Cancelable.withIsEmpty(isComplete)(() => {
      if (subscription != null) {
        subscription.cancel()
        subscription = null
      }
    })
  }

  @inline def create[A](produce: Observer[A] => Cancelable): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = produce(sink)
  }

  def createEmpty(subscription: () => Cancelable): Observable[Nothing] = create(_ => subscription())

  def fromIterable[T](values: Iterable[T]): Observable[T] = new Observable[T] {
    def unsafeSubscribe(sink: Observer[T]): Cancelable = {
      values.foreach(sink.unsafeOnNext)
      Cancelable.empty
    }
  }

  def fromEval[A](evalA: Eval[A]): Observable[A] = eval(evalA.value)

  def fromTry[A](value: Try[A]): Observable[A] = fromEither(value.toEither)

  def fromEither[A](value: Either[Throwable, A]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = {
      value match {
        case Right(a)    => sink.unsafeOnNext(a)
        case Left(error) => sink.unsafeOnError(error)
      }
      Cancelable.empty
    }
  }

  def fromEffect[F[_]: RunEffect, A](effect: F[A]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable =
      RunEffect[F].unsafeRunSyncOrAsyncCancelable[A](effect)(_.fold(sink.unsafeOnError, sink.unsafeOnNext))
  }

  def fromFuture[A](future: => Future[A]): Observable[A] = fromEffect(IO.fromFuture(IO(future)))

  def fromResource[F[_]: RunEffect: Sync, A](resource: Resource[F, A]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = {
      val cancelable = Cancelable.variable()

      val cancelRun = RunEffect[F].unsafeRunSyncOrAsyncCancelable(resource.allocated) {
        case Right((value, finalizer)) =>
          sink.unsafeOnNext(value)

          cancelable.unsafeAddExisting(Cancelable { () =>
            // async and forget the finalizer, we do not need to cancel it.
            // just pass the error to the sink.
            val _ = RunEffect[F].unsafeRunSyncOrAsyncCancelable(finalizer) {
              case Right(())   => ()
              case Left(error) => sink.unsafeOnError(error)
            }
          })
          cancelable.unsafeFreeze()

        case Left(error) =>
          sink.unsafeOnError(error)
      }

      Cancelable.composite(cancelRun, cancelable)
    }
  }

  def like[H[_]: ObservableLike, A](observableLike: H[A]): Observable[A] = ObservableLike[H].toObservable(observableLike)

  def concatEffect[F[_]: RunEffect, T](effects: F[T]*): Observable[T] = fromIterable(effects).mapEffect(identity)

  def concatFuture[T](value1: => Future[T]): Observable[T]                                                                   = concatEffect(IO.fromFuture(IO(value1)))
  def concatFuture[T](value1: => Future[T], value2: => Future[T]): Observable[T]                                             =
    concatEffect(IO.fromFuture(IO(value1)), IO.fromFuture(IO(value2)))
  def concatFuture[T](value1: => Future[T], value2: => Future[T], value3: => Future[T]): Observable[T]                       =
    concatEffect(IO.fromFuture(IO(value1)), IO.fromFuture(IO(value2)), IO.fromFuture(IO(value3)))
  def concatFuture[T](value1: => Future[T], value2: => Future[T], value3: => Future[T], value4: => Future[T]): Observable[T] =
    concatEffect(IO.fromFuture(IO(value1)), IO.fromFuture(IO(value2)), IO.fromFuture(IO(value3)), IO.fromFuture(IO(value4)))
  def concatFuture[T](
      value1: => Future[T],
      value2: => Future[T],
      value3: => Future[T],
      value4: => Future[T],
      value5: => Future[T],
  ): Observable[T] = concatEffect(
    IO.fromFuture(IO(value1)),
    IO.fromFuture(IO(value2)),
    IO.fromFuture(IO(value3)),
    IO.fromFuture(IO(value4)),
    IO.fromFuture(IO(value5)),
  )

  def concatEffect[F[_]: RunEffect, T](effect: F[T], source: Observable[T]): Observable[T] = new Observable[T] {
    def unsafeSubscribe(sink: Observer[T]): Cancelable = {
      val consecutive = Cancelable.consecutive()
      consecutive.unsafeAdd(() =>
        RunEffect[F].unsafeRunSyncOrAsyncCancelable[T](effect) { either =>
          either.fold(sink.unsafeOnError, sink.unsafeOnNext)
          consecutive.switch()
        },
      )
      consecutive.unsafeAdd(() => source.unsafeSubscribe(sink))
      consecutive.unsafeFreeze()
      consecutive
    }
  }
  def concatFuture[T](value: => Future[T], source: Observable[T]): Observable[T]           =
    concatEffect(IO.fromFuture(IO.pure(value)), source)

  @inline def merge[A](sources: Observable[A]*): Observable[A] = mergeIterable(sources)

  def mergeIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = {
      val subscriptions = sources.map { source =>
        source.unsafeSubscribe(sink)
      }

      Cancelable.compositeFromIterable(subscriptions)
    }
  }

  @inline def switch[A](sources: Observable[A]*): Observable[A] = switchIterable(sources)

  def switchIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = {
      val variable = Cancelable.variable()
      sources.foreach { source =>
        variable.unsafeAdd(() => source.unsafeSubscribe(sink))
      }
      variable.unsafeFreeze()

      variable
    }
  }

  @inline def concat[A](sources: Observable[A]*): Observable[A] = concatIterable(sources)

  def concatIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = {
      val consecutive = Cancelable.consecutive()

      var innerCancelCheck      = false
      var innerCheckIsScheduled = false

      sources.foreach { source =>
        consecutive.unsafeAdd { () =>
          var cancelable: Cancelable = null
          cancelable = source.unsafeSubscribe(
            Observer.create[A](
              { a =>
                sink.unsafeOnNext(a)
                if (!innerCheckIsScheduled) {
                  innerCheckIsScheduled = true
                  innerCancelCheck = false
                  MicrotaskExecutor.execute { () =>
                    innerCheckIsScheduled = false
                    if (!innerCancelCheck && cancelable.isEmpty()) consecutive.switch()
                  }
                }
              },
              sink.unsafeOnError,
            ),
          )

          if (cancelable.isEmpty()) {
            innerCancelCheck = true
            consecutive.switch()
          }
          cancelable
        }
      }
      consecutive.unsafeFreeze()

      consecutive
    }
  }

  @inline def race[A](sources: Observable[A]*): Observable[A] = raceIterable(sources)

  def raceIterable[A](sources: Iterable[Observable[A]]): Observable[A] = new Observable[A] {
    def unsafeSubscribe(sink: Observer[A]): Cancelable = {
      val cancelables = new js.Array[Cancelable]

      var winnerIsSelected                       = false
      def cancelAllButIndex(notIndex: Int): Unit = if (!winnerIsSelected) {
        winnerIsSelected = true
        cancelables.view.zipWithIndex.foreach { case (cancelable, idx) =>
          if (idx != notIndex) cancelable.unsafeCancel()
        }
      }

      sources.view.zipWithIndex.foreach { case (source, idx) =>
        if (!winnerIsSelected) {
          cancelables(idx) = source.unsafeSubscribe(
            Observer.combine(Observer.foreach(_ => cancelAllButIndex(idx)), sink),
          )
        }
      }

      Cancelable.compositeFromIterable(cancelables)
    }
  }

  @inline def interval(delay: FiniteDuration): Observable[Long] = intervalMillis(delay.toMillis.toInt)

  def intervalMillis(delay: Int): Observable[Long] = new Observable[Long] {
    def unsafeSubscribe(sink: Observer[Long]): Cancelable = {
      var isCancel      = false
      var counter: Long = 0

      def send(): Unit = {
        val current = counter
        counter += 1
        sink.unsafeOnNext(current)
      }

      send()

      val intervalId = timers.setInterval(delay.toDouble) { if (!isCancel) send() }

      Cancelable { () =>
        isCancel = true
        timers.clearInterval(intervalId)
      }
    }
  }

  def tailRecM[A, B](a: A)(f: A => Observable[Either[A, B]]): Observable[B] = new Observable[B] {
    def unsafeSubscribe(sink: Observer[B]): Cancelable = {
      val subjectRecurse = Subject.publish[Observable[Either[A, B]]]()
      val consecutive    = Cancelable.consecutive()

      var openSubscriptions     = 0
      var innerCheckIsScheduled = false

      var subscription: Cancelable = null
      subscription = subjectRecurse
        .unsafeSubscribe(
          Observer.create[Observable[Either[A, B]]](
            { source =>
              openSubscriptions += 1
              consecutive.unsafeAdd { () =>
                openSubscriptions -= 1
                var cancelable: Cancelable = null
                cancelable = source.unsafeSubscribe(
                  Observer.create[Either[A, B]](
                    { either =>
                      either match {
                        case Right(b) => sink.unsafeOnNext(b)
                        case Left(a)  => subjectRecurse.unsafeOnNext(f(a))
                      }

                      if (!innerCheckIsScheduled) {
                        innerCheckIsScheduled = true
                        MicrotaskExecutor.execute { () =>
                          innerCheckIsScheduled = false
                          if (cancelable.isEmpty()) {
                            if (openSubscriptions == 0) consecutive.unsafeFreeze()
                            else consecutive.switch()
                          }
                        }
                      }
                    },
                    sink.unsafeOnError,
                  ),
                )

                cancelable
              }
            },
            sink.unsafeOnError,
          ),
        )

      subjectRecurse.unsafeOnNext(f(a))

      Cancelable.composite(consecutive, Cancelable.withIsEmptyWrap(consecutive.isEmpty())(subscription))
    }

  }

  @inline implicit class Operations[A](private val source: Observable[A]) extends AnyVal {

    def liftSource[H[_]: LiftSource]: H[A] = LiftSource[H].lift(source)

    def failed: Observable[Throwable] = new Observable[Throwable] {
      def unsafeSubscribe(sink: Observer[Throwable]): Cancelable = source.unsafeSubscribe(sink.failed)
    }

    def dropFailed: Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = source.unsafeSubscribe(sink.dropOnError)
    }

    def debugLog: Observable[A]                 = via(Observer.debugLog)
    def debugLog(prefix: String): Observable[A] = via(Observer.debugLog(prefix))

    def via(sink: Observer[A]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink2: Observer[A]): Cancelable = source.unsafeSubscribe(Observer.combine(sink, sink2))
    }

    def to(sink: Observer[A]): Observable[Unit] = via(sink).void

    def subscribing[B](f: Observable[B]): Observable[A] = tapSubscribe(() => f.unsafeSubscribe())

    def map[B](f: A => B): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contramap(f))
    }

    def parMapEffect[B, F[_]: RunEffect](f: A => F[B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = {
        val tasks = js.Array[Either[Throwable, B]]()

        val taskCancelables = Cancelable.builder()

        val subscription = source.unsafeSubscribe(
          Observer.create(
            { input =>
              val index = tasks.length
              tasks.push(null)

              val effect = f(input)

              taskCancelables.unsafeAdd(() =>
                RunEffect[F].unsafeRunSyncOrAsyncCancelable(effect) { either =>
                  tasks(index) = either
                  val finishedTasks = tasks.takeWhileInPlace(_ != null)
                  finishedTasks.foreach {
                    case Right(value) => sink.unsafeOnNext(value)
                    case Left(error)  => sink.unsafeOnError(error)
                  }
                },
              )
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(
          subscription,
          Cancelable.checkIsEmpty(subscription.isEmpty())(taskCancelables.unsafeFreeze),
          taskCancelables,
        )
      }
    }

    def parMapFuture[B](f: A => Future[B]): Observable[B] = parMapEffect(a => IO.fromFuture(IO(f(a))))

    def discard: Observable[Nothing]                                = Observable.empty.subscribing(source)
    def void: Observable[Unit]                                      = map(_ => ())
    def as[B](value: B): Observable[B]                              = map(_ => value)
    def asEval[B](value: => B): Observable[B]                       = map(_ => value)
    def asEffect[F[_]: RunEffect, B](value: => F[B]): Observable[B] = mapEffect(_ => value)
    def asFuture[B](value: => Future[B]): Observable[B]             = mapFuture(_ => value)

    def mapFilter[B](f: A => Option[B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contramapFilter(f))
    }

    def mapIterable[B](f: A => Iterable[B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contramapIterable(f))
    }

    def collect[B](f: PartialFunction[A, B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contracollect(f))
    }

    def filter(f: A => Boolean): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = source.unsafeSubscribe(sink.contrafilter(f))
    }

    def scan0[B](seed: => B)(f: (B, A) => B): Observable[B] = scan(seed)(f).prependEval(seed)

    def scan[B](seed: => B)(f: (B, A) => B): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contrascan(seed)(f))
    }

    def scanReduce(f: (A, A) => A): Observable[A] =
      scan[Option[A]](None)((previous, current) => Some(previous.fold(current)(f(_, current)))).flattenOption

    def switchScan[B](seed: B)(f: (B, A) => Observable[B]): Observable[B] = new Observable[B] {
      override def unsafeSubscribe(sink: Observer[B]): Cancelable = {
        var current = seed
        val cancel  = Cancelable.variable()

        def recurse(nested: Observable[B], theA: A): Cancelable = nested.unsafeSubscribe(
          Observer.create[B](
            { value =>
              current = value
              val result = f(current, theA)
              sink.unsafeOnNext(value)
              cancel.unsafeAdd(() => recurse(result, theA))
            },
            sink.unsafeOnError,
          ),
        )

        val subscription = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              val next = f(current, value)
              cancel.unsafeAdd(() => recurse(next, value))
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(
          subscription,
          Cancelable.checkIsEmpty(subscription.isEmpty())(cancel.unsafeFreeze),
          cancel,
        )
      }
    }

    def mapEither[B](f: A => Either[Throwable, B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(sink.contramapEither(f))
    }

    def attempt: Observable[Either[Throwable, A]] = new Observable[Either[Throwable, A]] {
      def unsafeSubscribe(sink: Observer[Either[Throwable, A]]): Cancelable =
        source.unsafeSubscribe(Observer.createFromEither(sink.unsafeOnNext))
    }

    def recoverMap(f: Throwable => A): Observable[A] = recover { case t => f(t) }

    def recover(f: PartialFunction[Throwable, A]): Observable[A] = recoverOption(f andThen (Some(_)))

    def recoverOption(f: PartialFunction[Throwable, Option[A]]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        source.unsafeSubscribe(sink.doOnError { error =>
          f.lift(error) match {
            case Some(v) => v.foreach(sink.unsafeOnNext(_))
            case None    => sink.unsafeOnError(error)
          }
        })
      }
    }

    def adaptError(f: PartialFunction[Throwable, Throwable]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable =
        source.unsafeSubscribe(sink.doOnError { error =>
          sink.unsafeOnError(f.applyOrElse[Throwable, Throwable](error, t => t))
        })
    }

    def tapSubscribeEffect[F[_]: RunEffect](f: F[Cancelable]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        val variable = Cancelable.variable()

        val cancelable = RunEffect[F].unsafeRunSyncOrAsyncCancelable[Cancelable](f) {
          case Right(value) =>
            variable.unsafeAddExisting(value)
            variable.unsafeFreeze()
          case Left(error)  => sink.unsafeOnError(error)
        }

        Cancelable.composite(
          cancelable,
          variable,
          source.unsafeSubscribe(sink),
        )
      }
    }

    def tapEffect[F[_]: RunEffect: Functor](f: A => F[Unit]): Observable[A] =
      mapEffect(a => f(a).as(a))

    def tapFailedEffect[F[_]: RunEffect: Applicative](f: Throwable => F[Unit]): Observable[A] =
      attempt.tapEffect(_.swap.traverseTap(f).void).flattenEither

    def tapSubscribe(f: () => Cancelable): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        val cancelable = f()
        Cancelable.composite(
          source.unsafeSubscribe(sink),
          cancelable,
        )
      }
    }

    def tapCancel(f: () => Unit): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        Cancelable.composite(
          source.unsafeSubscribe(sink),
          Cancelable.ignoreIsEmpty { () =>
            f()
          },
        )
      }
    }

    def tap(f: A => Unit): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        source.unsafeSubscribe(sink.doOnNext { value =>
          f(value)
          sink.unsafeOnNext(value)
        })
      }
    }

    def tapFailed(f: Throwable => Unit): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        source.unsafeSubscribe(sink.doOnError { error =>
          f(error)
          sink.unsafeOnError(error)
        })
      }
    }

    def merge(sources: Observable[A]*): Observable[A] = Observable.mergeIterable(source +: sources)

    def switch(sources: Observable[A]*): Observable[A] = Observable.switchIterable(source +: sources)

    def concat(sources: Observable[A]*): Observable[A] = Observable.concatIterable(source +: sources)

    @inline def mergeMap[B](f: A => Observable[B]): Observable[B]               = mapObservableWithCancelable(f)(Cancelable.builder)
    @inline def mergeMapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = mergeMap(a => Observable.fromEffect(f(a)))
    @inline def mergeMapFuture[B](f: A => Future[B]): Observable[B]             = mergeMap(a => Observable.fromFuture(f(a)))

    @inline def switchMap[B](f: A => Observable[B]): Observable[B]               = mapObservableWithCancelable(f)(Cancelable.variable)
    @inline def switchMapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = switchMap(a => Observable.fromEffect(f(a)))
    @inline def switchMapFuture[B](f: A => Future[B]): Observable[B]             = switchMap(a => Observable.fromFuture(f(a)))

    private def mapObservableWithCancelable[B](f: A => Observable[B])(newCancelableSetter: () => Cancelable.Setter): Observable[B] =
      new Observable[B] {
        def unsafeSubscribe(sink: Observer[B]): Cancelable = {
          val setter = newCancelableSetter()

          val subscription = source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                val sourceB = f(value)
                setter.unsafeAdd(() => sourceB.unsafeSubscribe(sink))
              },
              sink.unsafeOnError,
            ),
          )

          Cancelable.composite(
            subscription,
            Cancelable.checkIsEmpty(subscription.isEmpty())(setter.unsafeFreeze),
            setter,
          )
        }
      }

    @inline def mapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = concatMapEffect(f)
    def concatMapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B]   = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = {
        val consecutive = Cancelable.consecutive()

        var isRunOpen = false

        val subscription = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              val effect = f(value)
              consecutive.unsafeAdd { () =>
                isRunOpen = true
                RunEffect[F].unsafeRunSyncOrAsyncCancelable[B](effect) { either =>
                  isRunOpen = false
                  either.fold(sink.unsafeOnError, sink.unsafeOnNext)
                  consecutive.switch()
                }
              }
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(
          subscription,
          Cancelable.withIsEmptyWrap(subscription.isEmpty() && !isRunOpen)(consecutive),
        )
      }
    }

    @inline def mapFuture[B](f: A => Future[B]): Observable[B]       = concatMapFuture(f)
    @inline def concatMapFuture[B](f: A => Future[B]): Observable[B] = mapEffect(v => IO.fromFuture(IO(f(v))))

    @inline def mapCancelable(f: A => Cancelable): Observable[Unit] =
      source.switchMap(a => Observable.evalCancelable(f(a)))

    @inline def mapResource[F[_]: RunEffect: Sync, B](f: A => Resource[F, B]): Observable[B] =
      mapResourceWithCancelable(f)(Cancelable.builder)

    @inline def switchMapResource[F[_]: RunEffect: Sync, B](f: A => Resource[F, B]): Observable[B] =
      mapResourceWithCancelable(f)(Cancelable.variable)

    private def mapResourceWithCancelable[F[_]: RunEffect: Sync, B](
        f: A => Resource[F, B],
    )(newCancelableSetter: () => Cancelable.Setter): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = {
        val consecutive      = Cancelable.consecutive()
        val cancelableSetter = newCancelableSetter()

        val subscription = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              val resource = f(value)
              consecutive.unsafeAdd(() =>
                RunEffect[F].unsafeRunSyncOrAsyncCancelable(resource.allocated) { either =>
                  either match {
                    case Right((value, finalizer)) =>
                      sink.unsafeOnNext(value)

                      cancelableSetter.unsafeAddExisting(Cancelable { () =>
                        // async and forget the finalizer, we do not need to cancel it.
                        // just pass the error to the sink.
                        val _ = RunEffect[F].unsafeRunSyncOrAsyncCancelable(finalizer) {
                          case Right(())   => ()
                          case Left(error) => sink.unsafeOnError(error)
                        }
                      })

                    case Left(error) =>
                      sink.unsafeOnError(error)
                  }

                  consecutive.switch()
                },
              )
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(subscription, consecutive, cancelableSetter)
      }
    }

    def singleMapEffect[F[_]: RunEffect, B](f: A => F[B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = {
        val single = Cancelable.singleOrDrop()

        var isRunOpen = false

        val subscription = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              val effect = f(value)
              single.unsafeAdd { () =>
                isRunOpen = true
                RunEffect[F].unsafeRunSyncOrAsyncCancelable[B](effect) { either =>
                  isRunOpen = false
                  either.fold(sink.unsafeOnError, sink.unsafeOnNext)
                  single.done()
                }
              }
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(
          subscription,
          Cancelable.withIsEmptyWrap(subscription.isEmpty() && !isRunOpen)(single),
        )
      }
    }

    @inline def singleMapFuture[B](f: A => Future[B]): Observable[B] =
      singleMapEffect(v => IO.fromFuture(IO(f(v))))

    @inline def flatMap[B](f: A => Observable[B]): Observable[B] = concatMap(f)

    def concatMap[B](f: A => Observable[B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = {
        val consecutive = Cancelable.consecutive()

        var innerCancelCheck      = false
        var innerCheckIsScheduled = false

        val subscription = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              val source = f(value)

              consecutive.unsafeAdd { () =>
                var cancelable: Cancelable = null
                cancelable = source.unsafeSubscribe(
                  Observer.create[B](
                    { b =>
                      sink.unsafeOnNext(b)
                      if (!innerCheckIsScheduled) {
                        innerCheckIsScheduled = true
                        innerCancelCheck = false
                        MicrotaskExecutor.execute { () =>
                          innerCheckIsScheduled = false
                          if (!innerCancelCheck && cancelable.isEmpty()) consecutive.switch()
                        }
                      }
                    },
                    sink.unsafeOnError,
                  ),
                )

                if (cancelable.isEmpty()) {
                  innerCancelCheck = true
                  consecutive.switch()
                }
                cancelable
              }
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(
          subscription,
          Cancelable.checkIsEmpty(subscription.isEmpty())(consecutive.unsafeFreeze),
          consecutive,
        )
      }
    }

    def forMerge: ForSemantic[A] = new ForSemantic[A] {
      def map[B](f: A => B): Observable[B]                 = source.map(f)
      def flatMap[B](f: A => Observable[B]): Observable[B] = source.mergeMap(f)
    }

    def forSwitch: ForSemantic[A] = new ForSemantic[A] {
      def map[B](f: A => B): Observable[B]                 = source.map(f)
      def flatMap[B](f: A => Observable[B]): Observable[B] = source.switchMap(f)
    }

    def forConcat: ForSemantic[A] = new ForSemantic[A] {
      def map[B](f: A => B): Observable[B]                 = source.map(f)
      def flatMap[B](f: A => Observable[B]): Observable[B] = source.concatMap(f)
    }

    def dropSyncAll: Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var isSync = true

        val cancelable = source.unsafeSubscribe(
          Observer.create[A](
            value => if (!isSync) sink.unsafeOnNext(value),
            sink.unsafeOnError,
          ),
        )

        isSync = false

        cancelable
      }
    }

    def dropUntilSyncLatest: Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var isSync        = true
        var lastSyncValue = Option.empty[A]

        val cancelable = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              if (isSync) {
                lastSyncValue = Some(value)
              } else {
                sink.unsafeOnNext(value)
              }
            },
            sink.unsafeOnError,
          ),
        )

        isSync = false
        lastSyncValue.foreach(sink.unsafeOnNext)
        cancelable
      }
    }

    def dropSyncGlitches: Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var isCancel       = false
        var runIsScheduled = false
        var lastValue      = Option.empty[A]

        val cancelable = source.unsafeSubscribe(
          Observer.create[A](
            { value =>
              lastValue = Some(value)
              if (!runIsScheduled) {
                runIsScheduled = true
                MicrotaskExecutor.execute { () =>
                  runIsScheduled = false
                  if (!isCancel) lastValue.foreach(sink.unsafeOnNext)
                  lastValue = None
                }
              }
            },
            sink.unsafeOnError,
          ),
        )

        Cancelable.composite(
          Cancelable.withIsEmpty(!runIsScheduled) { () =>
            isCancel = true
          },
          cancelable,
        )
      }
    }

    @inline def zip[B](sourceB: Observable[B]): Observable[(A, B)]                                                             =
      zipMap(sourceB)(_ -> _)
    @inline def zip[B, C](sourceB: Observable[B], sourceC: Observable[C]): Observable[(A, B, C)]                               =
      zipMap(sourceB, sourceC)((a, b, c) => (a, b, c))
    @inline def zip[B, C, D](sourceB: Observable[B], sourceC: Observable[C], sourceD: Observable[D]): Observable[(A, B, C, D)] =
      zipMap(sourceB, sourceC, sourceD)((a, b, c, d) => (a, b, c, d))
    @inline def zip[B, C, D, E](
        sourceB: Observable[B],
        sourceC: Observable[C],
        sourceD: Observable[D],
        sourceE: Observable[E],
    ): Observable[(A, B, C, D, E)] =
      zipMap(sourceB, sourceC, sourceD, sourceE)((a, b, c, d, e) => (a, b, c, d, e))
    @inline def zip[B, C, D, E, F](
        sourceB: Observable[B],
        sourceC: Observable[C],
        sourceD: Observable[D],
        sourceE: Observable[E],
        sourceF: Observable[F],
    ): Observable[(A, B, C, D, E, F)] =
      zipMap(sourceB, sourceC, sourceD, sourceE, sourceF)((a, b, c, d, e, f) => (a, b, c, d, e, f))

    def zipMap[B, R](sourceB: Observable[B])(f: (A, B) => R): Observable[R] = new Observable[R] {
      def unsafeSubscribe(sink: Observer[R]): Cancelable = {
        val seqA = new js.Array[A]
        val seqB = new js.Array[B]

        def send(): Unit = if (seqA.nonEmpty && seqB.nonEmpty) {
          val a = seqA.splice(0, deleteCount = 1)(0)
          val b = seqB.splice(0, deleteCount = 1)(0)
          sink.unsafeOnNext(f(a, b))
        }

        Cancelable.composite(
          source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                seqA.push(value)
                send()
              },
              sink.unsafeOnError,
            ),
          ),
          sourceB.unsafeSubscribe(
            Observer.create[B](
              { value =>
                seqB.push(value)
                send()
              },
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    def zipMap[B, C, R](sourceB: Observable[B], sourceC: Observable[C])(f: (A, B, C) => R): Observable[R]                               =
      zipMap(sourceB.zip(sourceC))((a, tail) => f(a, tail._1, tail._2))
    def zipMap[B, C, D, R](sourceB: Observable[B], sourceC: Observable[C], sourceD: Observable[D])(f: (A, B, C, D) => R): Observable[R] =
      zipMap(sourceB.zip(sourceC, sourceD))((a, tail) => f(a, tail._1, tail._2, tail._3))
    def zipMap[B, C, D, E, R](sourceB: Observable[B], sourceC: Observable[C], sourceD: Observable[D], sourceE: Observable[E])(
        f: (A, B, C, D, E) => R,
    ): Observable[R] =
      zipMap(sourceB.zip(sourceC, sourceD, sourceE))((a, tail) => f(a, tail._1, tail._2, tail._3, tail._4))
    def zipMap[B, C, D, E, F, R](
        sourceB: Observable[B],
        sourceC: Observable[C],
        sourceD: Observable[D],
        sourceE: Observable[E],
        sourceF: Observable[F],
    )(f: (A, B, C, D, E, F) => R): Observable[R] =
      zipMap(sourceB.zip(sourceC, sourceD, sourceE, sourceF))((a, tail) => f(a, tail._1, tail._2, tail._3, tail._4, tail._5))

    @inline def combineLatest_(sourceB: Observable[Unit]): Observable[A] = combineLatestMap(sourceB)((a, _) => a)

    @inline def combineLatest[B](sourceB: Observable[B]): Observable[(A, B)]                                                             =
      combineLatestMap(sourceB)(_ -> _)
    @inline def combineLatest[B, C](sourceB: Observable[B], sourceC: Observable[C]): Observable[(A, B, C)]                               =
      combineLatestMap(sourceB, sourceC)((a, b, c) => (a, b, c))
    @inline def combineLatest[B, C, D](sourceB: Observable[B], sourceC: Observable[C], sourceD: Observable[D]): Observable[(A, B, C, D)] =
      combineLatestMap(sourceB, sourceC, sourceD)((a, b, c, d) => (a, b, c, d))
    @inline def combineLatest[B, C, D, E](
        sourceB: Observable[B],
        sourceC: Observable[C],
        sourceD: Observable[D],
        sourceE: Observable[E],
    ): Observable[(A, B, C, D, E)] =
      combineLatestMap(sourceB, sourceC, sourceD, sourceE)((a, b, c, d, e) => (a, b, c, d, e))
    @inline def combineLatest[B, C, D, E, F](
        sourceB: Observable[B],
        sourceC: Observable[C],
        sourceD: Observable[D],
        sourceE: Observable[E],
        sourceF: Observable[F],
    ): Observable[(A, B, C, D, E, F)] =
      combineLatestMap(sourceB, sourceC, sourceD, sourceE, sourceF)((a, b, c, d, e, f) => (a, b, c, d, e, f))

    def combineLatestMap[B, R](sourceB: Observable[B])(f: (A, B) => R): Observable[R] = new Observable[R] {
      def unsafeSubscribe(sink: Observer[R]): Cancelable = {
        var latestA: Option[A] = None
        var latestB: Option[B] = None

        def send(): Unit = for {
          a <- latestA
          b <- latestB
        } sink.unsafeOnNext(f(a, b))

        Cancelable.composite(
          source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                latestA = Some(value)
                send()
              },
              sink.unsafeOnError,
            ),
          ),
          sourceB.unsafeSubscribe(
            Observer.create[B](
              { value =>
                latestB = Some(value)
                send()
              },
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    def combineLatestMap[B, C, R](sourceB: Observable[B], sourceC: Observable[C])(f: (A, B, C) => R): Observable[R] =
      combineLatestMap(sourceB.combineLatest(sourceC))((a, tail) => f(a, tail._1, tail._2))
    def combineLatestMap[B, C, D, R](sourceB: Observable[B], sourceC: Observable[C], sourceD: Observable[D])(
        f: (A, B, C, D) => R,
    ): Observable[R] =
      combineLatestMap(sourceB.combineLatest(sourceC, sourceD))((a, tail) => f(a, tail._1, tail._2, tail._3))
    def combineLatestMap[B, C, D, E, R](sourceB: Observable[B], sourceC: Observable[C], sourceD: Observable[D], sourceE: Observable[E])(
        f: (A, B, C, D, E) => R,
    ): Observable[R] =
      combineLatestMap(sourceB.combineLatest(sourceC, sourceD, sourceE))((a, tail) => f(a, tail._1, tail._2, tail._3, tail._4))
    def combineLatestMap[B, C, D, E, F, R](
        sourceB: Observable[B],
        sourceC: Observable[C],
        sourceD: Observable[D],
        sourceE: Observable[E],
        sourceF: Observable[F],
    )(f: (A, B, C, D, E, F) => R): Observable[R] =
      combineLatestMap(sourceB.combineLatest(sourceC, sourceD, sourceE, sourceF))((a, tail) =>
        f(a, tail._1, tail._2, tail._3, tail._4, tail._5),
      )

    def asLatest[B](latest: Observable[B]): Observable[B] = withLatestMap(latest)((_, b) => b)

    def withLatest[B](latest: Observable[B]): Observable[(A, B)]                                                              =
      withLatestMap(latest)(_ -> _)
    def withLatest[B, C](latestB: Observable[B], latestC: Observable[C]): Observable[(A, B, C)]                               =
      withLatestMap(latestB, latestC)((a, b, c) => (a, b, c))
    def withLatest[B, C, D](latestB: Observable[B], latestC: Observable[C], latestD: Observable[D]): Observable[(A, B, C, D)] =
      withLatestMap(latestB, latestC, latestD)((a, b, c, d) => (a, b, c, d))
    def withLatest[B, C, D, E](
        latestB: Observable[B],
        latestC: Observable[C],
        latestD: Observable[D],
        latestE: Observable[E],
    ): Observable[(A, B, C, D, E)] =
      withLatestMap(latestB, latestC, latestD, latestE)((a, b, c, d, e) => (a, b, c, d, e))
    def withLatest[B, C, D, E, F](
        latestB: Observable[B],
        latestC: Observable[C],
        latestD: Observable[D],
        latestE: Observable[E],
        latestF: Observable[F],
    ): Observable[(A, B, C, D, E, F)] =
      withLatestMap(latestB, latestC, latestD, latestE, latestF)((a, b, c, d, e, f) => (a, b, c, d, e, f))

    def withLatestMap[B, R](latest: Observable[B])(f: (A, B) => R): Observable[R] = new Observable[R] {
      def unsafeSubscribe(sink: Observer[R]): Cancelable = {
        var latestValue: Option[B] = None

        Cancelable.composite(
          latest.unsafeSubscribe(
            Observer.createUnrecovered[B](
              value => latestValue = Some(value),
              sink.unsafeOnError,
            ),
          ),
          source.unsafeSubscribe(
            Observer.create[A](
              value => latestValue.foreach(latestValue => sink.unsafeOnNext(f(value, latestValue))),
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    def withLatestMap[B, C, R](latestB: Observable[B], latestC: Observable[C])(f: (A, B, C) => R): Observable[R] =
      withLatestMap(latestB.combineLatest(latestC))((a, tail) => f(a, tail._1, tail._2))
    def withLatestMap[B, C, D, R](latestB: Observable[B], latestC: Observable[C], latestD: Observable[D])(
        f: (A, B, C, D) => R,
    ): Observable[R] =
      withLatestMap(latestB.combineLatest(latestC, latestD))((a, tail) => f(a, tail._1, tail._2, tail._3))
    def withLatestMap[B, C, D, E, R](latestB: Observable[B], latestC: Observable[C], latestD: Observable[D], latestE: Observable[E])(
        f: (A, B, C, D, E) => R,
    ): Observable[R] =
      withLatestMap(latestB.combineLatest(latestC, latestD, latestE))((a, tail) => f(a, tail._1, tail._2, tail._3, tail._4))
    def withLatestMap[B, C, D, E, F, R](
        latestB: Observable[B],
        latestC: Observable[C],
        latestD: Observable[D],
        latestE: Observable[E],
        latestF: Observable[F],
    )(f: (A, B, C, D, E, F) => R): Observable[R] =
      withLatestMap(latestB.combineLatest(latestC, latestD, latestE, latestF))((a, tail) =>
        f(a, tail._1, tail._2, tail._3, tail._4, tail._5),
      )
    def withLatestMap[B, C, D, E, F, G, R](
        latestB: Observable[B],
        latestC: Observable[C],
        latestD: Observable[D],
        latestE: Observable[E],
        latestF: Observable[F],
        latestG: Observable[G],
    )(f: (A, B, C, D, E, F, G) => R): Observable[R] =
      withLatestMap(latestB.combineLatest(latestC, latestD, latestE, latestF, latestG))((a, tail) =>
        f(a, tail._1, tail._2, tail._3, tail._4, tail._5, tail._6),
      )

    def zipWithIndex[R]: Observable[(A, Int)] = new Observable[(A, Int)] {
      def unsafeSubscribe(sink: Observer[(A, Int)]): Cancelable = {
        var counter = 0

        source.unsafeSubscribe(
          Observer.createUnrecovered(
            { value =>
              val index = counter
              counter += 1
              sink.unsafeOnNext((value, index))
            },
            sink.unsafeOnError,
          ),
        )
      }
    }

    @inline def throttle(duration: FiniteDuration): Observable[A] = throttleMillis(duration.toMillis.toInt)

    def throttleMillis(duration: Int): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var lastTimeout: js.UndefOr[timers.SetTimeoutHandle] = js.undefined
        var isSilent                                         = false

        Cancelable.composite(
          Cancelable.ignoreIsEmpty { () =>
            lastTimeout.foreach(timers.clearTimeout)
          },
          source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                if (!isSilent) {
                  isSilent = true
                  sink.unsafeOnNext(value)
                  lastTimeout = timers.setTimeout(duration.toDouble) {
                    isSilent = false
                  }
                }
              },
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    @inline def debounce(duration: FiniteDuration): Observable[A] = debounceMillis(duration.toMillis.toInt)

    def debounceMillis(duration: Int): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var lastTimeout: js.UndefOr[timers.SetTimeoutHandle] = js.undefined
        var isCancel                                         = false
        var openRuns                                         = 0

        Cancelable.composite(
          Cancelable.withIsEmpty(openRuns == 0) { () =>
            isCancel = true
            lastTimeout.foreach(timers.clearTimeout)
          },
          source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                lastTimeout.foreach { id =>
                  timers.clearTimeout(id)
                }
                openRuns += 1
                lastTimeout = timers.setTimeout(duration.toDouble) {
                  openRuns -= 1
                  if (!isCancel) sink.unsafeOnNext(value)
                }
              },
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    @inline def sampleWith(trigger: Observable[Unit]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var lastValue: Option[A] = None

        def send(): Unit = {
          lastValue.foreach(sink.unsafeOnNext)
          lastValue = None
        }

        Cancelable.composite(
          source.unsafeSubscribe(
            Observer.createUnrecovered(
              value => lastValue = Some(value),
              sink.unsafeOnError,
            ),
          ),
          trigger.unsafeSubscribe(
            Observer.createUnrecovered(
              _ => send(),
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    @inline def sample(duration: FiniteDuration): Observable[A] = sampleMillis(duration.toMillis.toInt)

    def sampleMillis(duration: Int): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var isCancel             = false
        var lastValue: Option[A] = None

        def send(): Unit = {
          lastValue.foreach(sink.unsafeOnNext)
          lastValue = None
        }

        val intervalId = timers.setInterval(duration.toDouble) { if (!isCancel) send() }

        Cancelable.composite(
          Cancelable { () =>
            isCancel = true
            timers.clearInterval(intervalId)
          },
          source.unsafeSubscribe(
            Observer.createUnrecovered(
              value => lastValue = Some(value),
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    @inline def bufferTimed[Col[a] <: Iterable[a]](duration: FiniteDuration)(implicit factory: Factory[A, Col[A]]): Observable[Col[A]] =
      bufferTimedMillis(
      duration.toMillis.toInt,
    )

    def bufferTimedMillis[Col[a] <: Iterable[a]](duration: Int)(implicit factory: Factory[A, Col[A]]): Observable[Col[A]] =
      new Observable[Col[A]] {
      def unsafeSubscribe(sink: Observer[Col[A]]): Cancelable = {
        var isCancel = false
        var builder  = factory.newBuilder

        def send(): Unit = {
          sink.unsafeOnNext(builder.result())
          builder = factory.newBuilder
        }

        val intervalId = timers.setInterval(duration.toDouble) { if (!isCancel) send() }

        Cancelable.composite(
          Cancelable { () =>
            isCancel = true
            timers.clearInterval(intervalId)
          },
          source.unsafeSubscribe(
            Observer.createUnrecovered(
              value => builder += value,
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    def evalOn(ec: ExecutionContext): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var isCancel = false
        var openRuns = 0

        Cancelable.composite(
          Cancelable.withIsEmpty(openRuns == 0) { () =>
            isCancel = true
          },
          source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                openRuns += 1
                ec.execute { () =>
                  openRuns -= 1
                  if (!isCancel) sink.unsafeOnNext(value)
                }
              },
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    def asyncMicro: Observable[A]    = evalOn(MicrotaskExecutor)
    def asyncMacro: Observable[A]    = evalOn(MacrotaskExecutor)
    @inline def async: Observable[A] = asyncMacro

    @inline def delay(duration: FiniteDuration): Observable[A] = delayMillis(duration.toMillis.toInt)

    def delayMillis(duration: Int): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var lastTimeout: js.UndefOr[timers.SetTimeoutHandle] = js.undefined
        var isCancel                                         = false
        var openRuns                                         = 0

        // TODO: we only actually cancel the last timeout. The check isCancel
        // makes sure that unsafeCancelled subscription is really respected.
        Cancelable.composite(
          Cancelable.withIsEmpty(openRuns == 0) { () =>
            isCancel = true
            lastTimeout.foreach(timers.clearTimeout)
          },
          source.unsafeSubscribe(
            Observer.create[A](
              { value =>
                openRuns += 1
                lastTimeout = timers.setTimeout(duration.toDouble) {
                  openRuns -= 1
                  if (!isCancel) sink.unsafeOnNext(value)
                }
              },
              sink.unsafeOnError,
            ),
          ),
        )
      }
    }

    def distinctBy[B](f: A => B)(implicit equality: Eq[B]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var lastValue: Option[B] = None

        source.unsafeSubscribe(
          Observer.createUnrecovered(
            { value =>
              val valueB     = f(value)
              val shouldSend = lastValue.forall(lastValue => !equality.eqv(lastValue, valueB))
              if (shouldSend) {
                lastValue = Some(valueB)
                sink.unsafeOnNext(value)
              }
            },
            sink.unsafeOnError,
          ),
        )
      }
    }

    @inline def distinct(implicit equality: Eq[A]): Observable[A] = distinctBy(identity)

    @inline def distinctByOnEquals[B](f: A => B): Observable[A] = distinctBy(f)(Eq.fromUniversalEquals)
    @inline def distinctOnEquals: Observable[A]                 = distinct(Eq.fromUniversalEquals)

    @inline def transformSource[B](transform: Observable[A] => Observable[B]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = transform(source).unsafeSubscribe(sink)
    }

    @inline def transformSink[B](transform: Observer[B] => Observer[A]): Observable[B] = new Observable[B] {
      def unsafeSubscribe(sink: Observer[B]): Cancelable = source.unsafeSubscribe(transform(sink))
    }

    @inline def publish: Connectable[Observable[A]]           = multicast(Subject.publish[A]())
    @inline def replayLatest: Connectable[Observable[A]]      = multicast(Subject.replayLatest[A]())
    @inline def replayAll: Connectable[Observable[A]]         = multicast(Subject.replayAll[A]())
    @inline def behavior(seed: A): Connectable[Observable[A]] = multicast(Subject.behavior(seed))

    @inline def publishShare: Observable[A]           = publish.refCount
    @inline def replayLatestShare: Observable[A]      = replayLatest.refCount
    @inline def replayAllShare: Observable[A]         = replayAll.refCount
    @inline def behaviorShare(seed: A): Observable[A] = behavior(seed).refCount

    @inline def publishSelector[B](f: Observable[A] => Observable[B]): Observable[B]            = transformSource(s => f(s.publish.refCount))
    @inline def replayLatestSelector[B](f: Observable[A] => Observable[B]): Observable[B]       =
      transformSource(s => f(s.replayLatest.refCount))
    @inline def replayAllSelector[B](f: Observable[A] => Observable[B]): Observable[B]          =
      transformSource(s => f(s.replayAll.refCount))
    @inline def behaviorSelector[B](value: A)(f: Observable[A] => Observable[B]): Observable[B] =
      transformSource(s => f(s.behavior(value).refCount))

    def multicast(pipe: Subject[A]): Connectable[Observable[A]] = Connectable(
      new Observable[A] {
        def unsafeSubscribe(sink: Observer[A]): Cancelable = Source[Observable].unsafeSubscribe(pipe)(sink)
      },
      () => source.unsafeSubscribe(pipe),
    )

    def fold[B](seed: B)(f: (B, A) => B): Observable[B]         = scan(seed)(f).last
    def foldF[F[_]: Async, B](seed: B)(f: (B, A) => B): F[B]    = scan(seed)(f).lastF[F]
    def foldIO[B](seed: B)(f: (B, A) => B): IO[B]               = scan(seed)(f).lastIO
    def unsafeFoldFuture[B](seed: B)(f: (B, A) => B): Future[B] = scan(seed)(f).unsafeLastFuture()

    def foldAllF[F[_]: Async, Col[a] <: Iterable[a]](implicit factory: Factory[A, Col[A]]): F[Col[A]]     = foldF(factory.newBuilder) {
      (buff, next) =>
      buff += next
      buff
    }.map(_.result())
    def foldAllIO[Col[a] <: Iterable[a]](implicit factory: Factory[A, Col[A]]): IO[Col[A]]                = foldAllF[IO, Col]
    def foldAll[Col[a] <: Iterable[a]](implicit factory: Factory[A, Col[A]]): Observable[Col[A]]          = Observable.fromEffect(foldAllIO[Col])
    def unsafeFoldToFuture[Col[a] <: Iterable[a]]()(implicit factory: Factory[A, Col[A]]): Future[Col[A]] =
      foldAllIO[Col].unsafeToFuture()(cats.effect.unsafe.IORuntime.global)

    @inline def prependEffect[F[_]: RunEffect](value: F[A]): Observable[A] = concatEffect[F, A](value, source)
    @inline def prependFuture(value: => Future[A]): Observable[A]          = concatFuture[A](value, source)

    def prepend(value: A): Observable[A]        = prependEval(value)
    def prependEval(value: => A): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        sink.unsafeOnNext(value)
        source.unsafeSubscribe(sink)
      }
    }

    @inline def appendEffect[F[_]: RunEffect](value: F[A]): Observable[A] = concat(Observable.fromEffect(value))
    @inline def appendFuture(value: => Future[A]): Observable[A]          = concat(Observable.fromFuture(value))

    @inline def append(value: A): Observable[A]        = concat(Observable(value))
    @inline def appendEval(value: => A): Observable[A] = concat(Observable.eval(value))

    def startWith(values: Iterable[A]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        values.foreach(sink.unsafeOnNext)
        source.unsafeSubscribe(sink)
      }
    }

    def endWith(values: Iterable[A]): Observable[A] = concat(Observable.fromIterable(values))

    def syncLatestF[F[_]: Sync]: F[Option[A]] = Sync[F].defer {
      var lastValue = Option.empty[F[A]]

      val cancelable = source.unsafeSubscribe(
        Observer.create[A](value => lastValue = Some(Sync[F].pure(value)), error => lastValue = Some(Sync[F].raiseError(error))),
      )
      cancelable.unsafeCancel()

      lastValue.sequence
    }

    @inline def syncLatestIO: IO[Option[A]] = syncLatestF[IO]

    @inline def syncLatestSyncIO: SyncIO[Option[A]] = syncLatestF[SyncIO]

    @inline def syncLatest: Observable[A] = Observable.fromEffect(syncLatestSyncIO).flattenOption

    @inline def unsafeSyncLatest(): Option[A] = syncLatestSyncIO.unsafeRunSync()

    def syncAllF[F[_]: Sync]: F[Seq[A]] = Sync[F].defer {
      val values = collection.mutable.ArrayBuffer[F[A]]()

      val cancelable = source.unsafeSubscribe(
        Observer.create[A](value => values.addOne(Sync[F].pure(value)), error => values.addOne(Sync[F].raiseError(error))),
      )
      cancelable.unsafeCancel()

      values.toSeq.sequence
    }

    @inline def syncAllIO: IO[Seq[A]] = syncAllF[IO]

    @inline def syncAllSyncIO: SyncIO[Seq[A]] = syncAllF[SyncIO]

    @inline def syncAll: Observable[A] = Observable.fromEffect(syncAllSyncIO).flattenIterable

    @inline def unsafeSyncAll(): Seq[A] = syncAllSyncIO.unsafeRunSync()

    @inline def collectFirst[B](f: PartialFunction[A, B]): Observable[B]      = Observable.fromEffect(collectFirstIO(f))
    @inline def collectFirstIO[B](f: PartialFunction[A, B]): IO[B]            = collectFirstF[IO, B](f)
    @inline def collectFirstF[F[_]: Async, B](f: PartialFunction[A, B]): F[B] = mapFilterFirstF(f.lift)

    @inline def mapFilterFirst[B](f: A => Option[B]): Observable[B]      = Observable.fromEffect(mapFilterFirstIO(f))
    @inline def mapFilterFirstIO[B](f: A => Option[B]): IO[B]            = mapFilterFirstF[IO, B](f)
    @inline def mapFilterFirstF[F[_]: Async, B](f: A => Option[B]): F[B] = mapFilter(f).headF

    @inline def collectWhile[B](f: PartialFunction[A, B]): Observable[B] = mapFilterWhile(f.lift)
    @inline def mapFilterWhile[B](f: A => Option[B]): Observable[B]      = map(f).takeWhile(_.isDefined).flattenOption

    def headF[F[_]: Async]: F[A] = Async[F].async[A] { callback =>
      Async[F].delay {
        val cancelable = Cancelable.variable()
        var isDone     = false

        def dispatch(value: Either[Throwable, A]) = if (!isDone) {
          isDone = true
          cancelable.unsafeCancel()
          callback(value)
        }

        cancelable.unsafeAdd(() =>
          source.unsafeSubscribe(Observer.create[A](value => dispatch(Right(value)), error => dispatch(Left(error)))),
        )

        cancelable.unsafeFreeze()

        Some(Async[F].delay(cancelable.unsafeCancel()))
      }
    }

    @inline def headIO: IO[A] = headF[IO]

    @inline def unsafeHeadFuture(): Future[A] = headIO.unsafeToFuture()(cats.effect.unsafe.IORuntime.global)

    @inline def head: Observable[A] = take(1)

    def lastF[F[_]: Async]: F[A] = Async[F].async[A] { callback =>
      Async[F].delay {
        var lastValue: Either[Throwable, A] = null
        val cancelable                      = Cancelable.variable()

        var innerCancelCheck      = false
        var innerCheckIsScheduled = false

        def dispatch(value: Either[Throwable, A]) = {
          lastValue = value
          if (!innerCheckIsScheduled) {
            innerCheckIsScheduled = true
            innerCancelCheck = false
            MicrotaskExecutor.execute { () =>
              innerCheckIsScheduled = false
              if (!innerCancelCheck && cancelable.isEmpty()) callback(lastValue)
            }
          }
        }

        cancelable.unsafeAdd(() =>
          source.unsafeSubscribe(Observer.create[A](value => dispatch(Right(value)), error => dispatch(Left(error)))),
        )

        cancelable.unsafeFreeze()

        if (cancelable.isEmpty() && lastValue != null) {
          innerCancelCheck = true
          callback(lastValue)
        }

        Some(Async[F].delay(cancelable.unsafeCancel()))
      }
    }

    @inline def lastIO: IO[A] = lastF[IO]

    @inline def unsafeLastFuture(): Future[A] = lastIO.unsafeToFuture()(cats.effect.unsafe.IORuntime.global)

    @inline def last: Observable[A] = Observable.fromEffect(lastIO)

    @inline def tail: Observable[A] = drop(1)

    def take(num: Int): Observable[A] = {
      if (num <= 0) Observable.empty
      else
        new Observable[A] {
          def unsafeSubscribe(sink: Observer[A]): Cancelable = {
            var counter      = 0
            val subscription = Cancelable.variable()
            subscription.unsafeAdd(() =>
              source.unsafeSubscribe(sink.contrafilter { _ =>
                if (num > counter) {
                  counter += 1
                  true
                } else {
                  subscription.unsafeCancel()
                  false
                }
              }),
            )
            subscription.unsafeFreeze()

            subscription
          }
        }
    }

    def takeWhileInclusive(predicate: A => Boolean): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var finishedTake = false
        val subscription = Cancelable.variable()
        subscription.unsafeAdd(() =>
          source.unsafeSubscribe(sink.contrafilter { v =>
            if (finishedTake) false
            else {
              if (!predicate(v)) {
                finishedTake = true
                subscription.unsafeCancel()
              }
              true
            }
          }),
        )
        subscription.unsafeFreeze()

        subscription
      }
    }

    def takeWhile(predicate: A => Boolean): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var finishedTake = false
        val subscription = Cancelable.variable()
        subscription.unsafeAdd(() =>
          source.unsafeSubscribe(sink.contrafilter { v =>
            if (finishedTake) false
            else if (predicate(v)) true
            else {
              finishedTake = true
              subscription.unsafeCancel()
              false
            }
          }),
        )
        subscription.unsafeFreeze()

        subscription
      }
    }

    def takeUntil(until: Observable[Unit]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var finishedTake = false
        val subscription = Cancelable.builder()

        subscription.unsafeAdd(() =>
          until.unsafeSubscribe(
            Observer.createUnrecovered[Unit](
              { _ =>
                finishedTake = true
                subscription.unsafeCancel()
              },
              sink.unsafeOnError(_),
            ),
          ),
        )

        if (!finishedTake) subscription.unsafeAdd(() => source.unsafeSubscribe(sink.contrafilter(_ => !finishedTake)))

        subscription.unsafeFreeze()

        subscription
      }
    }

    def replaceWith(obs: Observable[A]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        val replacedSubscription = Cancelable.variable()

        val subscription = obs
          .tap(_ => replacedSubscription.unsafeCancel())
          .unsafeSubscribe(sink)

        replacedSubscription.unsafeAdd(() => source.unsafeSubscribe(sink))

        Cancelable.composite(
          replacedSubscription,
          subscription,
        )
      }
    }

    def drop(num: Int): Observable[A] = {
      if (num <= 0) source
      else
        new Observable[A] {
          def unsafeSubscribe(sink: Observer[A]): Cancelable = {
            var counter = 0
            source.unsafeSubscribe(sink.contrafilter { _ =>
              if (num > counter) {
                counter += 1
                false
              } else true
            })
          }
        }
    }

    def dropWhile(predicate: A => Boolean): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var finishedDrop = false
        source.unsafeSubscribe(sink.contrafilter { v =>
          if (finishedDrop) true
          else if (predicate(v)) false
          else {
            finishedDrop = true
            true
          }
        })
      }
    }

    def dropUntil(until: Observable[Unit]): Observable[A] = new Observable[A] {
      def unsafeSubscribe(sink: Observer[A]): Cancelable = {
        var finishedDrop = false

        val untilCancelable = Cancelable.variable()
        untilCancelable.unsafeAdd(() =>
          until.unsafeSubscribe(
            Observer.createUnrecovered[Unit](
              { _ =>
                finishedDrop = true
                untilCancelable.unsafeCancel()
              },
              sink.unsafeOnError(_),
            ),
          ),
        )

        untilCancelable.unsafeFreeze()

        val subscription = source.unsafeSubscribe(sink.contrafilter(_ => finishedDrop))

        Cancelable.composite(subscription, untilCancelable)
      }
    }

    @inline def subscribeF[F[_]: Sync]: F[Cancelable] = Sync[F].delay(unsafeSubscribe())
    @inline def subscribeIO: IO[Cancelable]           = subscribeF[IO]
    @inline def subscribeSyncIO: SyncIO[Cancelable]   = subscribeF[SyncIO]

    @inline def unsafeSubscribe(): Cancelable           = source.unsafeSubscribe(Observer.empty)
    @inline def unsafeForeach(f: A => Unit): Cancelable = source.unsafeSubscribe(Observer.create(f))
  }

  @inline implicit class ThrowableOperations(private val source: Observable[Throwable]) extends AnyVal {
    @inline def mergeFailed: Observable[Throwable] = source.recoverMap(identity)
  }

  @inline implicit class BooleanOperations(private val source: Observable[Boolean]) extends AnyVal {
    @inline def toggle[A](ifTrue: => A, ifFalse: A): Observable[A] = source.map {
      case true  => ifTrue
      case false => ifFalse
    }

    @inline def toggle[A: Monoid](ifTrue: => A): Observable[A] = toggle(ifTrue, Monoid[A].empty)

    @inline def negated: Observable[Boolean] = source.map(x => !x)
  }

  @inline implicit class IterableOperations[A](private val source: Observable[Iterable[A]]) extends AnyVal {
    @inline def flattenIterable: Observable[A] = source.mapIterable(identity)
  }

  @inline implicit class EitherOperations[A](private val source: Observable[Either[Throwable, A]]) extends AnyVal {
    @inline def flattenEither: Observable[A] = source.mapEither(identity)
  }

  @inline implicit class OptionOperations[A](private val source: Observable[Option[A]]) extends AnyVal {
    @inline def flattenOption: Observable[A] = source.mapFilter(identity)
  }

  @inline implicit class ObservableLikeOperations[F[_]: ObservableLike, A](val source: Observable[F[A]]) {
    @inline def flatten: Observable[A]       = source.flatMap(o => ObservableLike[F].toObservable(o))
    @inline def flattenConcat: Observable[A] = source.concatMap(o => ObservableLike[F].toObservable(o))
    @inline def flattenMerge: Observable[A]  = source.mergeMap(o => ObservableLike[F].toObservable(o))
    @inline def flattenSwitch: Observable[A] = source.switchMap(o => ObservableLike[F].toObservable(o))
  }

  @inline implicit class ProSubjectOperations[I, O](val handler: ProSubject[I, O]) extends AnyVal {
    @inline def transformSubjectSource[O2](g: Observable[O] => Observable[O2]): ProSubject[I, O2]                                   =
      ProSubject.from[I, O2](handler, g(handler))
    @inline def transformSubjectSink[I2](f: Observer[I] => Observer[I2]): ProSubject[I2, O]                                         = ProSubject.from[I2, O](f(handler), handler)
    @inline def transformProSubject[I2, O2](f: Observer[I] => Observer[I2])(g: Observable[O] => Observable[O2]): ProSubject[I2, O2] =
      ProSubject.from[I2, O2](f(handler), g(handler))
    @inline def imapProSubject[I2, O2](f: I2 => I)(g: O => O2): ProSubject[I2, O2]                                                  = transformProSubject(_.contramap(f))(_.map(g))
  }

  @inline implicit class SubjectOperations[A](val handler: Subject[A]) extends AnyVal {
    @inline def transformSubject[A2](f: Observer[A] => Observer[A2])(g: Observable[A] => Observable[A2]): Subject[A2] =
      handler.transformProSubject(f)(g)
    @inline def imapSubject[A2](f: A2 => A)(g: A => A2): Subject[A2]                                                  = handler.transformSubject(_.contramap(f))(_.map(g))
  }

  @inline implicit class ListSubjectOperations[A](val handler: Subject[Seq[A]]) extends AnyVal {
    def sequence: Observable[Seq[Subject[A]]] = new Observable[Seq[Subject[A]]] {
      def unsafeSubscribe(sink: Observer[Seq[Subject[A]]]): Cancelable = {
        handler.unsafeSubscribe(
          Observer.create(
            { sequence =>
              sink.unsafeOnNext(sequence.zipWithIndex.map { case (a, idx) =>
                new Observer[A] with Observable[A] {
                  def unsafeSubscribe(sink: Observer[A]): Cancelable = {
                    sink.unsafeOnNext(a)
                    Cancelable.empty
                  }

                  def unsafeOnNext(value: A): Unit = {
                    handler.unsafeOnNext(sequence.updated(idx, value))
                  }

                  def unsafeOnError(error: Throwable): Unit = {
                    sink.unsafeOnError(error)
                  }
                }
              })
            },
            sink.unsafeOnError,
          ),
        )
      }
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy