SENDLINE open source
open source / mongo4s / runtime backends

Runtime backends

Each runtime module provides given Effect[F] (sequencing, failure, and a finalizer that sees how the action ended) and given RsBridge[F, S] (Reactive-Streams Publisher → F/S):

Module Effect Stream Notes
mongo4s-cats any F with cats.effect.kernel.Async fs2.Stream via fs2.interop.reactivestreams
mongo4s-zio zio.Task zio.stream.ZStream via zio-interop-reactivestreams
mongo4s-kyo A < (Async & Abort[Throwable]) kyo.Stream via kyo-reactive-streams
mongo4s-rapid rapid.Task rapid.Stream

Beyond the members a runtime has to supply, Effect carries defaults it may override with its own: monotonic and sleep — the pause between transaction retries. All four backends sleep on the runtime's scheduler rather than a blocked thread; cats and zio also read the runtime's clock for monotonic, while kyo and rapid keep the default, System.nanoTime, which a test clock of theirs does not move.

The four agree on what an error is. A function passed to map or flatMap that throws, and a suspend whose body throws, fail the effect, so attempt sees it. On zio that means a throw is a failure rather than a defect, and a defect — a ZIO.die of your own — is never recovered by attempt or handleErrorWith: it rolls back a transaction on its way out, since a finalizer sees it as Errored, and leaves withTransaction a defect still.

MongoClient.fromClient/fromSettings/fromConnectionString return a bare F[MongoClient[F, S]] — you own calling .close. Every runtime module also ships MongoClientResource with the same three constructor names, wrapped in that runtime's own resource-safety idiom rather than one type copy-pasted across all four:

scala
// cats — cats.effect.Resource
import mongo4s.cats.MongoClientResource

MongoClientResource.fromConnectionString[IO]("mongodb://localhost:27017").use { client =>
  // logic
}
scala
// zio — ZIO.acquireRelease, released when the enclosing ZIO.scoped block exits
import mongo4s.zio.MongoClientResource

ZIO.scoped:
  for
    client <- MongoClientResource.fromConnectionString("mongodb://localhost:27017")
    ...
  yield ()
scala
// kyo — Scope.acquireRelease, released by Scope.run
import mongo4s.kyo.MongoClientResource

Scope.run:
  for
    client <- MongoClientResource.fromConnectionString("mongodb://localhost:27017")
    ...
  yield ()
scala
// rapid has no Resource/Scope type — Task.guarantee is its only finalizer primitive, attached to an
// already-known computation — so this is bracket-shaped (a `use` callback) instead of a composable value
import mongo4s.rapid.MongoClientResource

MongoClientResource.fromConnectionString("mongodb://localhost:27017") { client =>
  // logic
}

Bridge configuration

RsBridgeConfig controls how a driver Publisher becomes your F and S. A given of your own overrides the default wherever an RsBridge is summoned:

scala
import mongo4s.RsBridgeConfig

given RsBridgeConfig = RsBridgeConfig.default
  .withBufferSize(512)        // outstanding demand for streaming reads
  .withTimeout(5.seconds)     // per non-streaming operation; unset by default
  .withStrictSingleResult     // fail on a second result instead of taking the first

bufferSize bounds memory against a fast cursor. On cats and kyo it does not apply to all, which asks for everything by definition; zio and rapid read all through the same buffered source as a stream, so it applies there. On cats it is also how many documents a stream gathers before it emits the first: fs2 hands a Publisher's elements on in chunks of that size, so with the default of 256, find().stream.take(1) reads 256 documents — or the whole result, if smaller — before it answers. A stream read for its first few elements, or processed one document at a time as they arrive, wants a smaller bufferSize; change streams already use a buffer of one. timeout is a backstop for a cursor that stops signalling entirely — the driver has its own timeouts, and streams are deliberately excluded, since a change stream sitting idle is working rather than stuck. strictSingleResult is off by default, matching the driver.

Operations that expect at most one document read two, not the whole cursor — enough to notice a second result under strictSingleResult, and no more. find(...).first and AggregateQuery.first also push a limit/$limit to the server, so on those the server sends one document and strictSingleResult has nothing to catch; it is distinct and the other single-result publishers that it guards.

built from README.md at v6.0.0 edit this page on GitHub