Repository: 7mind/izumi
Pull request #866 (2020-01-29 18:25:12)

Improve documentation, document `Bootloader`
* replace most uses of produce(GCMode.NoGC) with produceGet
* improve GC & weak set examples
* improve `Depending on Locator` chapter, document PlannerInput & Bootloader


Patch:
diff --git a/build.sbt b/build.sbt
index cee7ff416..1c3cda663 100644
--- a/build.sbt
+++ b/build.sbt
@@ -1677,7 +1677,6 @@ lazy val `logstage-core` = project.in(file("logstage/logstage-core"))
       "org.scala-lang" % "scala-reflect" % scalaVersion.value % Provided,
       "org.typelevel" %% "cats-core" % V.cats % Optional,
       "dev.zio" %% "zio" % V.zio % Optional,
-      "io.github.cquiroz" %% "scala-java-time" % V.scala_java_time,
       "org.typelevel" %% "cats-core" % V.cats % Test,
       "org.typelevel" %% "cats-effect" % V.cats_effect % Test,
       "dev.zio" %% "zio" % V.zio % Test
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
index ec12f01f3..05cb4c271 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/definition/DIResource.scala
@@ -19,27 +19,40 @@ import scala.language.implicitConversions
 import scala.reflect.macros.blackbox
 
 /**
-  * `DIResource` is a class that captures the effectful
-  * allocation of a resource, along with its finalizer.
-  *
+  * `DIResource` is a class that captures the effectful allocation of a resource, along with its finalizer.
   * This can be used to wrap expensive resources.
   *
-  * Example with [[DIResource.make]]:
+  * Resources can be created using [[DIResource.make]]:
   *
   * {{{
   *   def open(file: File): DIResource[IO, BufferedReader] =
   *     DIResource.make(IO { new BufferedReader(new FileReader(file)) })(in => IO { in.close() })
   * }}}
   *
-  * Example with inheritance:
+  * Using inheritance from [[DIResource]]:
   *
   * {{{
-  *   final class BufferedReaderResource(file: File) extends DIResource[IO, BufferedReader] {
-  *     val acquire = IO { new BufferedReader(new FileReader(file)) }
-  *     def release(in: BufferedReader) = IO { in.close() }
-  *   }
+  *   final class BufferedReaderResource(file: File)
+  *     extends DIResource[IO, BufferedReader] {
+  *       val acquire = IO { new BufferedReader(new FileReader(file)) }
+  *       def release(in: BufferedReader) = IO { in.close() }
+  *     }
+  * }}}
+  *
+  * Using constructor-based inheritance from [[DIResource.Make]], [[DIResource.LiftF]], etc:
+  *
+  * {{{
+  *   final class BufferedReaderResource(file: File)
+  *     extends DIResource.Make[IO, BufferedReader](
+  *       acquire = IO { new BufferedReader(new FileReader(file)) },
+  *       release = in => IO { in.close() },
+  *     }
   * }}}
   *
+  * Or by converting an existing [[cats.effect.Resource]] or a [[zio.ZManaged]].
+  * Use [[DIResource.fromCats]], [[DIResource.DIResourceCatsSyntax#toCats]] and
+  * [[DIResource.fromZIO]], [[DIResource.DIResourceZIOSyntax#toZIO]] to convert back and forth.
+  *
   * Usage is done via [[DIResource.DIResourceUse#use use]]:
   *
   * {{{
@@ -67,13 +80,10 @@ import scala.reflect.macros.blackbox
   * `DIResource` can be used in non-FP context with [[DIResource.Simple]]
   * it can also mimic Java's initialization-after-construction with [[DIResource.Mutable]]
   *
-  * DIResource is compatible with [[cats.effect.Resource]]. Use [[DIResource.fromCats]]
-  * and [[DIResource.DIResourceCatsSyntax.toCats]] to convert back and forth.
-  *
   * Use DIResource's to specify lifecycles of objects injected into the object graph.
   *
-  * Example:
-  * {{{
+  *  {{{
+  *   import distage.{DIResource, ModuleDef, Injector}
   *   import cats.effect.IO
   *
   *   class DBConnection
@@ -92,10 +102,9 @@ import scala.reflect.macros.blackbox
   *     make[MyApp]
   *   }
   *
-  *   Injector().produceF[IO](module).use {
-  *     objects =>
-  *       objects.get[MyApp].run
-  *   }.unsafeRunSync()
+  *   Injector().produceFGet[IO, MyApp](module)
+  *     .use(_.run())
+  *     .unsafeRunSync()
   * }}}
   *
   * Will produce the following output:
@@ -109,11 +118,12 @@ import scala.reflect.macros.blackbox
   * }}}
   *
   * The lifecycle of the entire object graph is itself expressed with `DIResource`,
-  * you can control it by controlling the scope in `.use` or by manually using
+  * you can control it by controlling the scope of `.use` or by manually invoking
   * [[DIResourceBase.acquire]] and [[DIResourceBase.release]].
   *
-  * @see ModuleDef.fromResource: [[izumi.distage.model.definition.dsl.ModuleDefDSL.MakeDSL.fromResource]]
+  * @see ModuleDef.fromResource: [[izumi.distage.model.definition.dsl.ModuleDefDSL.MakeDSLBase#fromResource]]
   *      [[cats.effect.Resource]]: https://typelevel.org/cats-effect/datatypes/resource.html
+  *      [[zio.ZManaged]]: https://zio.dev/docs/datatypes/datatypes_managed
   **/
 trait DIResource[+F[_], Resource] extends DIResourceBase[F, Resource] {
   def acquire: F[Resource]
@@ -259,13 +269,61 @@ object DIResource {
     def unsafeGet()(implicit F: DIApplicative[F]): F[A] = F.map(resource.acquire)(resource.extract)
   }
 
-  trait Simple[A] extends DIResource[Identity, A]
-
-  trait Mutable[+A] extends DIResource.Self[Identity, A] { this: A => }
+  /**
+    * Class-based proxy over a [[DIResource]] value
+    *
+    * {{{
+    *   class IntRes extends DIResource.Of(DIResource.pure(1000))
+    * }}}
+    *
+    * For binding resource values using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
+    */
+  class Of[+F[_], +A](private[Of] val inner: DIResourceBase[F, A]) extends DIResourceBase[F, A] {
+    override final type InnerResource = inner.InnerResource
+    override final def acquire: F[inner.InnerResource] = inner.acquire
+    override final def release(resource: inner.InnerResource): F[Unit] = inner.release(resource)
+    override final def extract(resource: inner.InnerResource): A = inner.extract(resource)
+  }
 
-  trait MutableNoClose[+A] extends DIResource.SelfNoClose[Identity, A] { this: A => }
+  /**
+    * Class-based proxy over a [[cats.effect.Resource]] value
+    *
+    * {{{
+    *   class IntRes extends DIResource.OfCats(Resource.pure(1000))
+    * }}}
+    *
+    * For binding resource values using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
+    */
+  class OfCats[F[_]: Bracket[?[_], Throwable], A](inner: Resource[F, A]) extends DIResource.Of[F, A](fromCats(inner))
 
-  abstract class NoClose[+F[_]: DIApplicative, A] extends DIResourceBase.NoClose[F, A] with DIResource[F, A]
+  /**
+    * Class-based proxy over a [[zio.ZManaged]] value
+    *
+    * {{{
+    *   class IntRes extends DIResource.OfZIO(Managed.succeed(1000))
+    * }}}
+    *
+    * For binding resource values using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
+    */
+  class OfZIO[-R, +E, +A](inner: ZManaged[R, E, A]) extends DIResource.Of[ZIO[R, E, ?], A](fromZIO(inner))
 
   /**
     * Class-based variant of [[make]]:
@@ -275,7 +333,15 @@ object DIResource {
     *     acquire = IO(1000)
     *   )(release = _ => IO.unit)
     * }}}
-    */
+    *
+    * For binding resources using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
+    **/
   class Make[+F[_], A] private[this](acquire0: () => F[A])(release: A => F[Unit], @unused dummy: Boolean = false) extends DIResource[F, A] {
     def this(acquire: => F[A])(release: A => F[Unit]) = this(() => acquire)(release)
 
@@ -289,6 +355,14 @@ object DIResource {
     * {{{
     *   class IntRes extends DIResource.Make_(IO(1000))(IO.unit)
     * }}}
+    *
+    * For binding resources using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
     */
   class Make_[+F[_], A](acquire: => F[A])(release: => F[Unit]) extends Make[F, A](acquire)(_ => release)
 
@@ -298,6 +372,14 @@ object DIResource {
     * {{{
     *   class IntRes extends DIResource.MakePair(IO(1000 -> IO.unit))
     * }}}
+    *
+    * For binding resources using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
     */
   class MakePair[F[_], A] private[this](acquire0: () => F[(A, F[Unit])], @unused dummy: Boolean = false) extends FromCats[F, A] {
     def this(acquire: => F[(A, F[Unit])]) = this(() => acquire)
@@ -311,6 +393,14 @@ object DIResource {
     * {{{
     *   class IntRes extends DIResource.LiftF(acquire = IO(1000))
     * }}}
+    *
+    * For binding resources using class syntax in [[ModuleDef]]:
+    *
+    * {{{
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
+    * }}}
     */
   class LiftF[+F[_]: DIApplicative, A] private[this](acquire0: () => F[A], @unused dummy: Boolean = false) extends NoClose[F, A] {
     def this(acquire: => F[A]) = this(() => acquire)
@@ -326,45 +416,22 @@ object DIResource {
     *     acquire = IO(new FileOutputStream("abc"))
     *   )
     * }}}
-    */
-  class FromAutoCloseable[+F[_]: DIEffect, +A <: AutoCloseable](acquire: => F[A]) extends DIResource.Of(DIResource.fromAutoCloseableF(acquire))
-
-  /**
-    * Class-based proxy over a [[DIResource]] value
     *
-    * {{{
-    *   class IntRes extends DIResource.Of(DIResource.pure(1000))
-    * }}}
-    */
-  class Of[+F[_], +A](private[Of] val inner: DIResourceBase[F, A]) extends DIResourceBase[F, A] {
-    override final type InnerResource = inner.InnerResource
-    override final def acquire: F[inner.InnerResource] = inner.acquire
-    override final def release(resource: inner.InnerResource): F[Unit] = inner.release(resource)
-    override final def extract(resource: inner.InnerResource): A = inner.extract(resource)
-  }
-
-  /**
-    * Class-based proxy over a [[cats.effect.Resource]] value
+    * For binding resources using class syntax in [[ModuleDef]]:
     *
     * {{{
-    *   class IntRes extends DIResource.OfCats(Resource.pure(1000))
+    *   val module = new ModuleDef {
+    *     make[Int].fromResource[IntRes]
+    *   }
     * }}}
     */
-  class OfCats[F[_]: Bracket[?[_], Throwable], A](inner: Resource[F, A]) extends DIResource.Of[F, A](fromCats(inner))
+  class FromAutoCloseable[+F[_]: DIEffect, +A <: AutoCloseable](acquire: => F[A]) extends DIResource.Of(DIResource.fromAutoCloseableF(acquire))
 
-  /**
-    * Class-based proxy over a [[zio.ZManaged]] value
-    *
-    * {{{
-    *   class IntRes extends DIResource.OfZIO(Managed.succeed(1000))
-    * }}}
-    */
-  class OfZIO[-R, +E, +A](inner: ZManaged[R, E, A]) extends DIResource.Of[ZIO[R, E, ?], A](fromZIO(inner))
+  trait Simple[A] extends DIResource[Identity, A]
 
-  abstract class SelfNoClose[+F[_]: DIApplicative, +A] extends DIResourceBase.NoClose[F, A] { this: A =>
-    override type InnerResource = Unit
-    override final def extract(resource: Unit): A = this
-  }
+  trait Mutable[+A] extends DIResource.Self[Identity, A] { this: A => }
+
+  trait MutableNoClose[+A] extends DIResource.SelfNoClose[Identity, A] { this: A => }
 
   trait Self[+F[_], +A] extends DIResourceBase[F, A] { this: A =>
     def release: F[Unit]
@@ -373,6 +440,13 @@ object DIResource {
     override final def extract(resource: Unit): A = this
   }
 
+  abstract class SelfNoClose[+F[_]: DIApplicative, +A] extends DIResourceBase.NoClose[F, A] { this: A =>
+    override type InnerResource = Unit
+    override final def extract(resource: Unit): A = this
+  }
+
+  abstract class NoClose[+F[_]: DIApplicative, A] extends DIResourceBase.NoClose[F, A] with DIResource[F, A]
+
   trait FromCats[F[_], A] extends DIResourceBase[F, A] {
     override final type InnerResource = (A, F[Unit])
     override final def release(resource: (A, F[Unit])): F[Unit] = resource._2
@@ -633,5 +707,4 @@ object DIResource {
       c.abort(c.enclosingPosition, s"could not find implicit ResourceTag for ${c.universe.weakTypeOf[R]}!\n$tagTrace")
     }
   }
-
 }
diff --git a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala
index cff7fbf47..a96bd5e93 100644
--- a/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala
+++ b/distage/distage-core-api/src/main/scala/izumi/distage/model/plan/GCMode.scala
@@ -7,8 +7,8 @@ sealed trait GCMode {
 }
 
 object GCMode {
-  def apply(key: DIKey, keys: DIKey*): GCMode = {
-    GCRoots(keys.toSet + key)
+  def apply(root: DIKey, roots: DIKey*): GCMode = {
+    GCRoots(roots.toSet + root)
   }
   def apply(roots: Set[DIKey]): GCMode = {
     GCMode.GCRoots(roots)
diff --git a/doc/microsite/src/main/tut/distage/advanced-features.md b/doc/microsite/src/main/tut/distage/advanced-features.md
index 1168aa68b..854c7f169 100644
--- a/doc/microsite/src/main/tut/distage/advanced-features.md
+++ b/doc/microsite/src/main/tut/distage/advanced-features.md
@@ -18,9 +18,13 @@ To use garbage collector, pass GC roots as an argument to `Injector.produce*` me
 ```scala mdoc:reset:to-string
 import distage._
 
-case class A(b: B)
-case class B()
-case class C() {
+class A(b: B) {
+  println("A!")
+}
+class B() {
+  println("B!")
+}
+class C() {
   println("C!")
 }
 
@@ -32,16 +36,17 @@ val module = new ModuleDef {
 
 // declare `A` as a GC root
 
-val roots = GCMode.GCRoots(Set[DIKey](DIKey.get[A]))
+val gc = GCMode(root = DIKey.get[A])
 
 // create an object graph from description in `module`
 // with `A` as a GC root
 
-val objects = Injector().produce(module, roots).unsafeGet()
+val objects = Injector().produce(module, gc).unsafeGet()
 
 // A and B are in the object graph
 
 objects.find[A]
+
 objects.find[B]
 
 // C is missing
@@ -49,8 +54,35 @@ objects.find[B]
 objects.find[C]
 ```
 
-Class `C` was removed because neither `B` nor `A` depended on it. It's not present in the `Locator` and the `"C!"` message was never printed.
-But, if class `B` were to depend on `C` as in `case class B(c: C)`, it would've been retained, because `A` - the GC root, would depend on `B` which in turns depends on `C`.
+Class `C` was removed because neither `B` nor `A` depended on it. It's not present in the `Locator` and the `"C!"` message has never been printed.
+If class `B` had a `C` parameter, like `class B(c: C)`; `C` would have been retained, because `A` - the GC root, would depend on `B`, and `B` would depend on `C`.
+
+```scala mdoc:reset-object:invisible:to-string
+import distage._
+
+class A(b: B) {
+  println("A!")
+}
+class C() {
+  println("C!")
+}
+
+val module = new ModuleDef {
+  make[A]
+  make[B]
+  make[C]
+}
+```
+
+```scala mdoc:to-string
+class B(c: C) {
+  println("B!")
+}
+
+val objects = Injector().produce(module, GCMode(DIKey.get[A])).unsafeGet()
+
+objects.find[C]
+```
 
 ### Circular Dependencies Support
 
@@ -84,11 +116,11 @@ If you want to disable it, use `NoProxies` bootstrap configuration:
 Injector.NoProxies()
 ```
 
-#### Manual Resolution with by-name parameters
+Proxies are not supported on Scala.js.
 
-Most cycles can be resolved manually when identified using By Name parameters.
+#### Manual Resolution with by-name parameters
 
-Circular dependencies in the following example are all resolved via Scala's native By Name, no proxies are generated:
+Most cycles can be resolved without proxies, using By-Name parameters:
 
 ```scala mdoc:reset:to-string
 import distage.{GCMode, ModuleDef, Injector}
@@ -122,18 +154,17 @@ locator.get[B].a eq locator.get[A]
 locator.get[C].c eq locator.get[C]
 ```
 
-The proxy generation via `cglib` is currently enabled by default, because in scenarios with extreme late-binding cycles
-can emerge unexpectedly, out of control of the origin module.
+The proxy generation via `cglib` is enabled by default, because in scenarios with extreme late-binding cycles can emerge unexpectedly,
+out of control of the origin module.
 
-NB: Currently a limitation applies to by-names - ALL dependencies of a class engaged in a by-name circular dependency must
-be by-name, otherwise distage will revert to generating proxies.
+Note: Currently a limitation applies to by-names - ALL dependencies of a class engaged in a by-name circular dependency must
+be by-name, otherwise `distage` will revert to generating proxies.
 
 ### Auto-Sets
 
 AutoSet @scaladoc[Planner](izumi.distage.model.Planner) Hooks can traverse the plan and collect all future objects that match a predicate.
 
 Using Auto-Sets you can e.g. collect all `AutoCloseable` classes and `.close()` them after the application has finished work.
-
 NOTE: please use @ref[Resource bindings](basics.md#resource-bindings-lifecycle) for real lifecycle, this is just an example.
 
 ```scala mdoc:reset:to-string
@@ -151,13 +182,14 @@ class B(val a: A) extends PrintResource("B")
 class C(val b: B) extends PrintResource("C")
 
 val bootstrapModule = new BootstrapModuleDef {
-  many[PlanningHook].add(new AutoSetHook[PrintResource, PrintResource](identity))
+  many[PlanningHook]
+    .add(new AutoSetHook[PrintResource, PrintResource](identity))
 }
 
 val appModule = new ModuleDef {
-  make[C]
-  make[B]
   make[A]
+  make[B]
+  make[C]
 }
 
 val resources = Injector(bootstrapModule)
@@ -181,7 +213,7 @@ See also: same concept in [MacWire](https://github.com/softwaremill/macwire#mult
 ### Weak Sets
 
 @ref[Set bindings](basics.md#set-bindings) can contain *weak* references. References designated as weak will
-be retained *only* if there are other dependencies on them **except** for the set addition.
+be retained *only* if there are *other* dependencies on the referred bindings, *NOT* if there's a dependency only on the entire Set.
 
 Example:
 
@@ -190,11 +222,11 @@ import distage._
 
 sealed trait Elem
 
-final class Strong extends Elem {
+final case class Strong() extends Elem {
   println("Strong constructed")
 }
 
-final class Weak extends Elem {
+final case class Weak() extends Elem {
   println("Weak constructed")
 }
 
@@ -215,7 +247,17 @@ val roots = Set[DIKey](DIKey.get[Set[Elem]])
 
 val objects = Injector().produce(PlannerInput(module, roots)).unsafeGet()
 
-objects.get[Set[Elem]].size == 1
+// Strong is around
+
+objects.find[Strong]
+
+// Weak is not
+
+objects.find[Strong]
+
+// There's only Strong in the Set
+
+objects.get[Set[Elem]]
 ```
 
 The `Weak` class was not required by any dependency of `Set[Elem]`, so it was pruned.
@@ -251,12 +293,18 @@ final class Strong(weak: Weak) extends Elem {
 
 val objects = Injector().produce(PlannerInput(module, roots)).unsafeGet()
 
-objects.get[Set[Elem]].size == 2
+// Weak is around
+
+objects.find[Weak]
+
+// both Strong and Weak are in the Set
+
+objects.get[Set[Elem]]
 ```
 
 ### Inner Classes and Path-Dependent Types
 
-Path-dependent types with a value prefix will instantiate normally:
+Path-dependent types with a value prefix will be instantiated normally:
 
 ```scala mdoc:reset:to-string
 import distage.{GCMode, ModuleDef, Injector}
@@ -275,17 +323,19 @@ Injector()
   .use(_.get[path.A])
 ```
 
-Since version `0.10`, path-dependent types with a type (non-value) prefix are no longer supported, see issue: https://github.com/7mind/izumi/issues/764
+Since version `0.10`, support for path-dependent types with a non-value (type) prefix hasn't reimplemented after a rewrite of the internals, see issue: https://github.com/7mind/izumi/issues/764
 
 ### Depending on Locator
 
-Objects can depend on the Locator (container of the final object graph):
+Objects can depend on the outer object graph that contains them (@scaladoc[Locator](izumi.distage.model.Locator)), by including a @scaladoc[LocatorRef](izumi.distage.model.recursive.LocatorRef) parameter:
 
 ```scala mdoc:reset:to-string
-import distage._
+import distage.{ModuleDef, LocatorRef, Injector, GCMode}
 
-class A(all: LocatorRef) {
-  def c = all.get.get[C]
+class A(
+  objects: LocatorRef
+) {
+  def c = objects.get.get[C]
 }
 class B
 class C
@@ -298,17 +348,52 @@ val module = new ModuleDef {
 
 val objects = Injector().produce(module, GCMode.NoGC).unsafeGet()
 
-assert(objects.get[A].c eq objects.get[C]) 
+// A took C from the object graph
+
+objects.get[A].c
+
+// this C is the same C as in this `objects` value
+
+val thisC = objects.get[C]
+val thatC = objects.get[A].c
+
+assert(thisC == thatC)
 ```
 
 Locator contains metadata about the plan and the bindings from which it was ultimately created:
 
 ```scala mdoc:to-string
-// Plan that created this locator
+import distage.{OrderedPlan, ModuleBase}
+
+// Plan that created this locator (after GC)
 
 val plan: OrderedPlan = objects.plan
 
-// Bindings from which the Plan was built
+// Bindings from which the Plan was built (after GC)
+
+val bindings: ModuleBase = plan.definition
+```
+
+The plan and bindings in Locator are saved in the state they were AFTER @ref[Garbage Collection](#garbage-collection) has been performed.
+Objects can request the original input via a `PlannerInput` parameter:
+
+```scala mdoc:reset:to-string
+import distage.{DIKey, GCMode, ModuleDef, PlannerInput, Injector}
+
+class InjectionInfo(val plannerInput: PlannerInput)
 
-val moduleDef: ModuleBase = plan.definition
+val module = new ModuleDef {
+  make[InjectionInfo]
+}
+
+val input = PlannerInput(module, GCMode(root = DIKey.get[InjectionInfo]))
+
+val injectionInfo = Injector().produce(input).unsafeGet().get[InjectionInfo]
+
+// the PlannerInput in `InjectionInfo` is the same as `input`
+
+assert(injectionInfo.plannerInput == input)
 ```
+
+@scaladoc[Bootloader](izumi.distage.model.recursive.Bootloader) is another summonable parameter that contains the above information in aggregate
+and lets you create another object graph from the same inputs as the current or with alterations.
diff --git a/doc/microsite/src/main/tut/distage/basics.md b/doc/microsite/src/main/tut/distage/basics.md
index ded2ccab2..3f02b97f8 100644
--- a/doc/microsite/src/main/tut/distage/basics.md
+++ b/doc/microsite/src/main/tut/distage/basics.md
@@ -154,14 +154,14 @@ val capsInjector = Injector(Activation(Style -> Style.AllCaps))
 // Check the result:
 
 capsInjector
-  .produce(CombinedModule, GCMode.NoGC)
-  .use(_.get[HelloByeApp].run())
+  .produceGet[HelloByeApp](CombinedModule)
+  .use(_.run())
 
 // Check that result changes with a different configuration:
 
 Injector(Activation(Style -> Style.Normal))
-  .produce(CombinedModule, GCMode.NoGC)
-  .use(_.get[HelloByeApp].run())
+  .produceGet[HelloByeApp](CombinedModule)
+  .use(_.run())
 ```
 
 @scaladoc[distage.StandardAxis](izumi.distage.model.definition.StandardAxis) contains some bundled Axis for back-end development: `Repo.Prod/Dummy`, `Env.Prod/Test` & `ExternalApi.Prod/Mock`  
@@ -233,7 +233,9 @@ val module = new ModuleDef {
 Will produce the following output:
 
 ```scala mdoc:to-string
-val objectGraphResource = Injector().produceF[IO](module, GCMode.NoGC)
+import distage.DIKey
+
+val objectGraphResource = Injector().produceF[IO](module, GCMode(root = DIKey.get[MyApp]))
 
 objectGraphResource
   .use(_.get[MyApp].run)
@@ -264,11 +266,12 @@ val module = new ModuleDef {
   make[Init].fromResource[InitResource]
 }
 
-val closedInit = Injector().produce(module, GCMode.NoGC).use {
-  objects =>
-    val init = objects.get[Init] 
-    println(init.initialized)
-    init
+val closedInit = Injector()
+  .produceGet[Init](module)
+  .use {
+    init =>
+      println(init.initialized)
+      init
 }
 
 println(closedInit.initialized)
@@ -430,11 +433,9 @@ val kvStoreModule = new ModuleDef {
 }
 
 val io = Injector()
-  .produceF[Task](kvStoreModule, GCMode.NoGC)
+  .produceGetF[Task, KVStore[IO]](kvStoreModule)
   .use {
-    objects =>
-      val kv = objects.get[KVStore[IO]]
-      
+    kv =>
       for {
         _    <- kv.put("apple", "pie")
         res1 <- kv.get("apple")
diff --git a/doc/microsite/src/main/tut/distage/distage-framework.md b/doc/microsite/src/main/tut/distage/distage-framework.md
index 568a11557..b458b2da4 100644
--- a/doc/microsite/src/main/tut/distage/distage-framework.md
+++ b/doc/microsite/src/main/tut/distage/distage-framework.md
@@ -149,9 +149,9 @@ val appModule = appModules.merge
 
 // launch
 
-Injector().produce(appModule, GCMode.NoGC).use {
-  _.get[PetStoreController].run()
-}
+Injector()
+  .produceGet[PetStoreController](appModule)
+  .use(_.run())
 ```
 
 ### Compile-time checks
diff --git a/doc/microsite/src/main/tut/logstage/00_logstage.md b/doc/microsite/src/main/tut/logstage/00_logstage.md
index f0c294e96..2581be627 100644
--- a/doc/microsite/src/main/tut/logstage/00_logstage.md
+++ b/doc/microsite/src/main/tut/logstage/00_logstage.md
@@ -16,13 +16,35 @@ Key features:
 6. Method-level logging granularity. Can configure methods `com.example.Service.start` and `com.example.Service.doSomething` independently,
 7. Slf4J adapters: route legacy Slf4J logs into LogStage router
 
+Dependencies
+------------
+
+@@@vars
+```scala
+libraryDependencies ++= Seq(
+  // LogStage core library
+  "io.7mind.izumi" %% "logstage-core" % "$izumi.version$",
+  // Json output
+  "io.7mind.izumi" %% "logstage-rendering-circe" % "$izumi.version$",
+  // Router from Slf4j to LogStage
+  "io.7mind.izumi" %% "logstage-adapter-slf4j" % "$izumi.version$",
+  // Configure LogStage with Typesafe Config
+  "io.7mind.izumi" %% "logstage-config" % "$izumi.version$",
+  // LogStage integration with DIStage
+  "io.7mind.izumi" %% "distage-extension-logstage" % "$izumi.version$",
+  // Router from LogStage to Slf4J
+  "io.7mind.izumi" %% "logstage-sink-slf4j " % "$izumi.version$",
+)
+```
+@@@
+
 Overview
 --------
 
 The following snippet:
 
 ```scala mdoc:reset
-import logstage._
+import logstage.IzLogger
 import scala.util.Random
 
 val logger = IzLogger()
@@ -90,63 +112,16 @@ Syntax Reference
    logger.info(${camelCaseName-> ' '})
    ```
 
-Dependencies
-------------
-
-```scala
-// LogStage core
-libraryDependencies += Izumi.R.logstage_core
-
-// Optional
-libraryDependencies ++= Seq(
-  // Json output
-  Izumi.R.logstage_rendering_circe,
-  // Router from Slf4j to LogStage
-  Izumi.R.logstage_adapter-slf4j,
-  // Configure LogStage with Typesafe Config
-  Izumi.R.logstage_config,
-  // LogStage integration with DIStage 
-  Izumi.R.logstage_di,
-  // Router from LogStage to Slf4J
-  Izumi.R.logstage_sink_slf4j,
-)
-```
-
-or
-
-@@@vars
-```scala
-val izumi_version = "$izumi.version$"
-// LogStage core
-libraryDependencies += "io.7mind.izumi" %% "logstage-core" % izumi_version
-
-// Optional
-libraryDependencies ++= Seq(
-  // Json output
-  "io.7mind.izumi" %% "logstage-rendering-circe" % izumi_version,
-  // Router from Slf4j to LogStage
-  "io.7mind.izumi" %% "logstage-adapter-slf4j" % izumi_version,    
-  // Configure LogStage with Typesafe Config
-  "io.7mind.izumi" %% "logstage-config" % izumi_version,
-  // LogStage integration with DIStage 
-  "io.7mind.izumi" %% "distage-extension-logstage" % izumi_version,
-  // Router from LogStage to Slf4J
-  "io.7mind.izumi" %% "logstage-sink-slf4j " % izumi_version,
-)
-```
-@@@
-
-If you're not using @ref[sbt-izumi-deps](../sbt/00_sbt.md#bills-of-materials) plugin.
 
 Basic setup
 -----------
 
 ```scala mdoc:reset
-import logstage._
-import logstage.circe._
+import logstage.{ConsoleSink, IzLogger, Trace}
+import logstage.circe.LogstageCirceRenderingPolicy
 
-val jsonSink = ConsoleSink.json(prettyPrint = true)
 val textSink = ConsoleSink.text(colored = true)
+val jsonSink = ConsoleSink(LogstageCirceRenderingPolicy(prettyPrint = true))
 
 val sinks = List(jsonSink, textSink)
 
@@ -164,7 +139,7 @@ Log algebras
 `LogIO` and `LogBIO` algebras provide a purely-functional API for one- and two-parameter effect types respectively:
 
 ```scala mdoc:reset
-import logstage._
+import logstage.{IzLogger, LogIO}
 import cats.effect.IO
 
 val logger = IzLogger()
@@ -209,7 +184,7 @@ def load(entity: Entity): cats.effect.IO[Unit] = cats.effect.IO.unit
 ```scala mdoc
 import cats.effect.IO
 import cats.implicits._
-import logstage._
+import logstage.LogIO
 import io.circe.Printer
 import io.circe.syntax._
 
@@ -234,7 +209,7 @@ have to mutate a global singleton `StaticLogRouter`. Replace its `LogRouter`
 with the same one you use elsewhere in your application to use the same configuration for Slf4j. 
 
 ```scala mdoc:reset
-import logstage._
+import logstage.IzLogger
 import izumi.logstage.api.routing.StaticLogRouter
 
 val myLogger = IzLogger()


Test patch:
diff --git a/doc/microsite/src/main/tut/distage/distage-testkit.md b/doc/microsite/src/main/tut/distage/distage-testkit.md
index b9b152d4d..336f152f7 100644
--- a/doc/microsite/src/main/tut/distage/distage-testkit.md
+++ b/doc/microsite/src/main/tut/distage/distage-testkit.md
@@ -66,7 +66,7 @@ import izumi.distage.testkit.services.DISyntaxZIOEnv
 import leaderboard.model.{Score, UserId}
 import leaderboard.repo.{Ladder, Profiles}
 import leaderboard.zioenv.{ladder, rnd}
-import zio.{IO, ZIO}
+import zio.IO
 
 abstract class LeaderboardTest extends DistageBIOSpecScalatest[IO] with DISyntaxZIOEnv {
   override def config = TestConfig(