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

io.github.nafg.tcn.TriggeredChangeNotificationService.scala Maven / Gradle / Ivy

package io.github.nafg.tcn

import org.postgresql.PGConnection
import zio.stream.ZStream
import zio.{Hub, ZIO, ZLayer, durationInt}

trait TriggeredChangeNotificationService  {
  def stream(
    tableName: String,
    keyColumnName: String = "id"
  ): ZStream[Any, Nothing, (TriggeredChangeNotification.Trigger, String)]
}
object TriggeredChangeNotificationService {

  private def stream: ZStream[ConnectionFactory, Nothing, TriggeredChangeNotification] =
    ZStream
      .serviceWithStream[ConnectionFactory] { connectionFactory =>
        ZStream.scoped[Any](connectionFactory.newConnection)
      }
      .tap { conn =>
        ZIO.scoped[Any](
          ZIO
            .fromAutoCloseable(ZIO.attemptBlocking(conn.createStatement()))
            .tap(s => ZIO.attemptBlocking(s.execute("LISTEN tcn")))
        )
      }
      .map(_.unwrap(classOf[PGConnection]))
      .flatMap { conn =>
        ZStream.logInfo("Beginning to listen") *>
          ZStream
            .fromIterableZIO(ZIO.attemptBlocking(Option(conn.getNotifications(1000)).map(_.toSeq).getOrElse(Nil)))
            .forever
            .flatMap { notification =>
              TriggeredChangeNotification.parser.parseString(notification.getParameter) match {
                case Right((tableName, operation, keyValuePairs)) =>
                  ZStream.succeed(TriggeredChangeNotification(tableName, operation, keyValuePairs))
                case Left(error)                                  =>
                  ZStream.logError(s"Failed to parse notification:\n${error.pretty}") *> ZStream.empty
              }
            }
      }
      .catchAllCause(ZStream.logErrorCause(_) *> ZStream.empty)
      .forever
      .timeoutTo(10.minutes)(stream)

  val layer: ZLayer[ConnectionFactory, Nothing, TriggeredChangeNotificationService] =
    ZLayer {
      for {
        hub <- Hub.sliding[TriggeredChangeNotification](1024)
        _   <- stream.runForeach(hub.publish(_)).fork
      } yield new TriggeredChangeNotificationService {
        override def stream(tableName: String, keyColumnName: String) =
          ZStream
            .fromHub(hub)
            .collect { case TriggeredChangeNotification(`tableName`, trigger, keyValuePairs) =>
              keyValuePairs
                .get(keyColumnName)
                .map(trigger -> _)
            }
            .collectSome
      }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy