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:
// cats — cats.effect.Resource
import mongo4s.cats.MongoClientResource
MongoClientResource.fromConnectionString[IO]("mongodb://localhost:27017").use { client =>
// logic
}// 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 ()// kyo — Scope.acquireRelease, released by Scope.run
import mongo4s.kyo.MongoClientResource
Scope.run:
for
client <- MongoClientResource.fromConnectionString("mongodb://localhost:27017")
...
yield ()// 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:
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 firstbufferSize 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.