Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for ZStream (or any other stream library) #268

Open
carlos-verdes opened this issue Jan 12, 2024 · 3 comments
Open

Support for ZStream (or any other stream library) #268

carlos-verdes opened this issue Jan 12, 2024 · 3 comments

Comments

@carlos-verdes
Copy link

I created an abstraction on top of scala.xml to serve content using ZIO.ZStream, so I can stream results from server instead of loading all content in memory and then flush to the client.

It would be really good to have a feature like that on your library.

@sake92
Copy link
Owner

sake92 commented Jan 12, 2024

Hi @carlos-verdes !

Could you explain a bit more what you are requesting here?
An example maybe?

Hepek is using https://com-lihaoyi.github.io/scalatags undercover, which renders Frags as a String, in memory.

@carlos-verdes
Copy link
Author

carlos-verdes commented Jan 13, 2024

This is more or less what I have done with scala.xml

// my query returns a stream of Pet
val myPets: ZStream[R, Throwable, Pet] = ???
def petToNode(pet: Pet): scala.xml.Node = ???

// new type of document Node called StreamNode
case class StreamNode(nodes: Stream[Throwable, Node]) extends Node:
  def child: collection.Seq[xml.Node] = Seq.empty
  def label: String = "Stream"

// Inject the pet stream inside a dom tree 
val myTemplate: scala.xml.Node =
  <table>
    {{StreamNode(myPets.map(petToNode))}}
  </table>

// traverse the dom tree and create 1 stream entry per element
// when we reach our custom StreamNode we just need to map to string as it's already a stream
tree
extension (node: scala.xml.Node)
  def toZStream: Stream[Throwable, String] =
    node match
      case elem: Elem =>
        val attributes = if elem.attributes != null then elem.attributes.toString else ""
        val pre: Stream[Throwable, String] = ZStream("<" + elem.label + attributes + ">")
        elem.child.foldLeft(pre)((accum, node) => accum ++ node.toZStream) ++
            ZStream("</" + elem.label + ">")
      case Group(nodes) =>
        val emptyStream: Stream[Throwable, String] = ZStream.empty
        nodes.foldLeft(emptyStream)((accum, newNode) => accum ++ newNode.toZStream)
      case StreamNode(nodes) =>
        nodes.map(_.toString)
      case other => ZStream(other.toString)

// then in my app I can build a body from stream and serve streamed results (protecting my backedn from OOM errors)
def app = Http.collectHttp[Request]:

    // sources
    case Method.GET -> Root / "myPets" =>
      Handler
      .fromZIO:
        for env <- ZIO.environment[R]
        yield
          Handler.fromBody(Body.fromStream(myTemplate.toZStream))
      .flatten
      .toHttp 

Previous code will generate the next stream:
ZStream("<table>") ++ myPetsStream.map(_.toString) ++ ZStream("</table>")

@carlos-verdes
Copy link
Author

I'm opening these issues as suggestions but also I'll try to participate with my own code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants