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

dev.cheleb.ziotapir.laminar.package.scala Maven / Gradle / Ivy

There is a newer version: 0.8.3
Show newest version
package dev.cheleb.ziotapir.laminar

import zio.*
import zio.json.*
import zio.json.JsonCodec
import zio.stream.*

import scala.annotation.targetName

import com.raquo.laminar.api.L.*
import dev.cheleb.ziojwt.WithToken
import dev.cheleb.ziotapir.*
import sttp.capabilities.zio.ZioStreams
import sttp.model.Uri
import sttp.tapir.Endpoint

/** Extension methods for ZIO JS.
  *
  * Extension are of two kinds:
  *   - targeting Endpoints: allowing to call an endpoint with a payload
  *   - targeting ZIO: allowing to run a ZIO in JS
  *
  * Also, there are two kinds of ZIO, and associated 2 extensions:
  *   - SameOriginBackendClient: for requests to the same origin
  *   - DifferentOriginBackendClient: for requests to a different origin
  *
  * This object contains:
  *   - convenience methods for calling endpoints.
  *   - extension methods for ZIO that are specific to the Laminar JS
  *     environment.
  */

/** Extension to ZIO[SameOriginBackendClient, E, A] that allows us to run in JS.
  */
extension [E <: Throwable, A](zio: ZIO[SameOriginBackendClient, E, A])

  /** Run the underlying request to the default backend.
    */
  private def exec: Unit =
    Unsafe.unsafe { implicit unsafe =>
      Runtime.default.unsafe.fork(
        zio.provide(SameOriginBackendClientLive.configuredLayer)
      )
    }

  /** Run the ZIO in JS.
    *
    * @return
    */
  def runJs: Unit =
    zio
      .tapError(th => Console.printLineError(th.getMessage()))
      .exec

  /** Run the ZIO in JS, and emit the error to an EventBus.
    * @param errorBus
    *   the event bus to emit the error to
    */
  def runJs(errorBus: EventBus[E]): Unit =
    zio
      .tapError(e => ZIO.attempt(errorBus.emit(e)))
      .exec

  /** Emit the result of the ZIO to an EventBus.
    *
    * Underlying request to the default backend.
    * @param bus
    */
  def emitTo(bus: EventBus[A]): Unit =
    zio
      .tapError(th => Console.printLineError(th.getMessage()))
      .tap(a => ZIO.attempt(bus.emit(a)))
      .exec

  /** Emit the result and error of the ZIO to an EventBus.
    *
    * Underlying request to the default backend.
    *
    * @param bus
    * @param error
    */
  def emitTo(
      bus: EventBus[A],
      error: EventBus[E]
  ): Unit =
    zio
      .tapError(e => ZIO.attempt(error.emit(e)))
      .tap(a => ZIO.attempt(bus.emit(a)))
      .exec

  /** Emit the result of the ZIO to an EventBus of Either.
    *
    * Underlying request to the default backend.
    *
    * @param bus
    *   event bus to emit the result to
    */
  def emitToEither(
      bus: EventBus[Either[E, A]]
  ): Unit =
    zio
      .tapError(e => ZIO.attempt(bus.emit(Left(e))))
      .tap(a => ZIO.attempt(bus.emit(Right(a))))
      .exec

  /** Emit the result of the ZIO to an EventBus, and return the EventStream.
    */
  def toEventStream: EventStream[A] = {
    val eventBus = EventBus[A]()
    emitTo(eventBus)
    eventBus.events
  }

/** Extension to ZIO[DifferentOriginBackendClient, E, A] that allows us to run
  * in JS.
  */
extension [E <: Throwable, A](zio: ZIO[DifferentOriginBackendClient, E, A])
  /** Run the underlying request to the default backend.
    */
  @targetName("dexec")
  private def exec: Unit =
    Unsafe.unsafe { implicit unsafe =>
      Runtime.default.unsafe.fork(
        zio.provide(DifferentOriginBackendClientLive.configuredLayer)
      )
    }

  /** Run the ZIO in JS.
    *
    * @return
    */
  @targetName("drunJs")
  def runJs: Unit =
    zio
      .tapError(th => Console.printLineError(th.getMessage()))
      .exec

  /** Run the ZIO in JS, and emit the error to an EventBus.
    * @param errorBus
    *   the event bus to emit the error to
    */
  @targetName("drunJs")
  def runJs(errorBus: EventBus[E]): Unit =
    zio
      .tapError(e => ZIO.attempt(errorBus.emit(e)))
      .exec

  /** Emit the result of the ZIO to an EventBus.
    *
    * Underlying request to the default backend.
    * @param bus
    */
  @targetName("demitTo")
  def emitTo(bus: EventBus[A]): Unit =
    zio
      .tapError(th => Console.printLineError(th.getMessage()))
      .tap(a => ZIO.attempt(bus.emit(a)))
      .exec

  /** Emit the result and error of the ZIO to an EventBus.
    *
    * Underlying request to the default backend.
    *
    * @param bus
    * @param error
    */
  @targetName("demitTo")
  def emitTo(
      bus: EventBus[A],
      error: EventBus[E]
  ): Unit =
    zio
      .tapError(e => ZIO.attempt(error.emit(e)))
      .tap(a => ZIO.attempt(bus.emit(a)))
      .exec

  /** Emit the result of the ZIO to an EventBus of Either.
    *
    * Underlying request to the default backend.
    *
    * @param bus
    *   event bus to emit the result to
    */
  @targetName("demitToEither")
  def emitToEither(
      bus: EventBus[Either[E, A]]
  ): Unit =
    zio
      .tapError(e => ZIO.attempt(bus.emit(Left(e))))
      .tap(a => ZIO.attempt(bus.emit(Right(a))))
      .exec

  /** Emit the result of the ZIO to an EventBus, and return the EventStream.
    */
  @targetName("dtoEventStream")
  def toEventStream: EventStream[A] = {
    val eventBus = EventBus[A]()
    emitTo(eventBus)
    eventBus.events
  }

/** Extension that allows us to turn an unsecure endpoint to a function from a
  * payload to a ZIO.
  */
extension [I, E <: Throwable, O](
    endpoint: Endpoint[Unit, I, E, O, Any]
)
  /** Call the endpoint with a payload, and get a ZIO back.
    * @param payload
    * @return
    */
  def apply(payload: I): RIO[SameOriginBackendClient, O] =
    ZIO
      .service[SameOriginBackendClient]
      .flatMap(_.requestZIO(endpoint)(payload))

  /** Call the endpoint with a payload on a different backend than the origin,
    * and get a ZIO back.
    */
  @targetName("dapply")
  def on(baseUri: Uri)(payload: I): RIO[DifferentOriginBackendClient, O] =
    ZIO
      .service[DifferentOriginBackendClient]
      .flatMap(_.requestZIO(baseUri, endpoint)(payload))

/** Extension that allows us to turn a secured endpoint to a function from a
  * payload to a ZIO.
  */
extension [I, E <: Throwable, O](endpoint: Endpoint[String, I, E, O, Any])
  /** Call the secured endpoint with a payload, and get a ZIO back.
    * @param payload
    * @return
    */
  @targetName("securedApply")
  def apply[UserToken <: WithToken](
      payload: I
  )(using session: Session[UserToken]): RIO[SameOriginBackendClient, O] =
    ZIO
      .service[SameOriginBackendClient]
      .flatMap(_.securedRequestZIO(endpoint)(payload))

  /** Call the secured endpoint with a payload on a different backend than the
    * origin, and get a ZIO back.
    */
  def on[UserToken <: WithToken](baseUri: Uri)(
      payload: I
  )(using session: Session[UserToken]): RIO[DifferentOriginBackendClient, O] =
    ZIO
      .service[DifferentOriginBackendClient]
      .flatMap(_.securedRequestZIO(baseUri, endpoint)(payload))

/** Extension that allows us to turn a stream endpoint to a function from a
  * payload to a ZIO.
  */
extension [I, O](
    endpoint: Endpoint[Unit, I, Throwable, Stream[Throwable, O], ZioStreams]
)

  /** Call the endpoint with a payload on a different backend than the origin,
    * and get a ZIO back.
    */
  @targetName("streamOn")
  def on(
      baseUri: Uri
  )(payload: I): RIO[DifferentOriginBackendClient, Stream[Throwable, O]] =
    ZIO
      .service[DifferentOriginBackendClient]
      .flatMap(_.streamRequestZIO(baseUri, endpoint)(payload))

    /** Call the endpoint with a payload, and get a ZIO back.
      */
  @targetName("streamApply")
  def apply(payload: I): RIO[SameOriginBackendClient, Stream[Throwable, O]] =
    ZIO
      .service[SameOriginBackendClient]
      .flatMap(_.streamRequestZIO(endpoint)(payload))

/** Extension that allows us to turn a stream endpoint to a function from a
  * payload to a ZIO.
  */
extension [I, O](
    endpoint: Endpoint[String, I, Throwable, Stream[Throwable, O], ZioStreams]
)

  /** Call the secured stream endpoint with a payload on a different backend
    * than the origin, and get a ZIO back.
    */
  @targetName("securedStreamOn")
  def on[UserToken <: WithToken](
      baseUri: Uri
  )(payload: I)(using
      session: Session[UserToken]
  ): RIO[DifferentOriginBackendClient, Stream[Throwable, O]] =
    ZIO
      .service[DifferentOriginBackendClient]
      .flatMap(_.securedStreamRequestZIO(baseUri, endpoint)(payload))

  /** Call the secured stream endpoint with a payload, and get a ZIO back.
    */
  @targetName("securedStreamApply")
  def apply[UserToken <: WithToken](payload: I)(using
      session: Session[UserToken]
  ): RIO[SameOriginBackendClient, Stream[Throwable, O]] =
    ZIO
      .service[SameOriginBackendClient]
      .flatMap(_.securedStreamRequestZIO(endpoint)(payload))

/** Extension methods for ZIO[SameOriginBackendClient, Throwable, ZStream], that
  * parse JSONL.
  */
extension (
    zio: ZIO[
      SameOriginBackendClient,
      Throwable,
      ZStream[Any, Throwable, Byte]
    ]
)
  /** Parse a JSONL stream.
    */
  def jsonl[I: JsonCodec, O](f: I => Task[O]) =
    zio
      .flatMap(stream =>
        stream
          .via(ZPipeline.utf8Decode)
          .via(ZPipeline.splitLines)
          .via(ZPipeline.map(_.fromJson[I].toOption.get))
          .runForeach(f)
      )
      .runJs

/** Extension methods for ZIO[DifferentOriginBackendClient, Throwable, ZStream],
  * that parse JSONL.
  */
extension (
    zio: ZIO[
      DifferentOriginBackendClient,
      Throwable,
      ZStream[Any, Throwable, Byte]
    ]
)
  /** Parse a JSONL stream.
    */
  @targetName("djsonl")
  def jsonl[I: JsonCodec](f: I => Task[Unit]) =
    zio
      .flatMap(stream =>
        stream
          .via(ZPipeline.utf8Decode)
          .via(ZPipeline.splitLines)
          .via(ZPipeline.map(_.fromJson[I].toOption.get))
          .runForeach(f)
      )
      .runJs




© 2015 - 2025 Weber Informatics LLC | Privacy Policy