• tyleo 21 minutes ago

    I don’t think I’ll ever understand the affinity for DI libraries. I write a lot of tested code but it’s always just plain old interfaces and constructor arguments for me.

    Other parts of our org use a DI framework and I feel like it causes a new class of dependency ordering bugs or missing dependencies. These just don’t exist when everything is passed in the ctor.

    • mininao 20 minutes ago

      Does anyone have resources or ideas to share on the merits of dependency injection in js/ts ? To me it almost always feels clunky and antithetical to the spcriptey nature of JS/TS.

      • pshirshov 2 minutes ago

        We described our reasoning behind flavor of DI (dependency graph solvers) in several talks which are listed here https://github.com/7mind/izumi?tab=readme-ov-file#docs

        Most of the benefits do not depend on any particular language/runtime/stack.

        • tantalor 11 minutes ago

          Why would the choice of programming language matter at all with respect to DI?

        • pshirshov 2 hours ago

          A bit surprised (and delighted) to see this on the front page.

          Essentially, this is a greatly simplified port of distage (my library implementing phased DI for Scala).

          Most of the job was done by Claude, the primary point was to showcase phased DI for Typescript, which has many annoyances and limitations, especially when it comes to reflection.

          My contributions here were

          (a) the approach itself: first we turn functions and constructors into runtime-inspectable entities called Functoids, then we trace binding dependencies from requested roots, do conflict resolution and build a DAG of operations, then we produce instances by traversing the graph in topological order.

          (b) a bit unconventional approach to Typescript reflection, which is manual but comes with compile-time validation.

          There are many benefits of phased approach to DI, one of the most important benefits is that you can have "configurable apps" (think use-flags for your applications) which are sound, free of logical conflicts and validated early (in case of Scala we even do it at compile time).

          Also this approach is extremely easy to comprehend and reproduce (even Claude can do it with some guidance and interventions; I've done ports to several other languages, some with LLM assistance, some manually). While most DIs (especially single-phased ones) are hard to comprehend, maintain and port to other languages/runtimes, for this approach you need to have just one concept implemented - Functoid. The DAG-forming logic fits in 200-300 lines of code and would look the same in any language.

          • traspler 2 hours ago

            What's the "Non-invasive" metric? How is it less invasive than TSyringe or just as non-invasive as Awilix?

            • pshirshov an hour ago

              > What's the "Non-invasive" metric?

              You can use it with code you can't modify (decorators are just convenience helpers, you can do same through bindings DSL with bit less type safety).

              TSyringe depends on reflect-metadata and, if my understanding is correct, forces you to use its decorators.

              The comparison table is completely subjective and made with just several glances at the readmes of the mentioned libraries. The point was to showcase phased DI for Typescript.

          • nkohari 16 minutes ago

            I feel like DI frameworks for JavaScript/TypeScript are always too complex, and rely too heavily on decorators to make up for the lack of RTTI. You'd be surprised how far you can get with using string identifiers for dependencies:

            https://github.com/nkohari/forge

            (For context: many years ago, I wrote Ninject, one of the more popular DI frameworks for .NET)

            • pshirshov a minute ago

              In this particular case decorators are completely optional, you don't have to use them. You can provide metadata at binding DSL level.