4 ms·
Chibi Izumi: Phased dependency injection for TypeScript
- pshirshov 1y agoA 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 1y agoWhat's the "Non-invasive" metric? How is it less invasive than TSyringe or just as non-invasive as Awilix?
- pshirshov 1y 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.
- tyleo 1y agoI 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.
- pshirshov 1y ago> I’ll ever understand the affinity for DI libraries DI addresses/can address/affects more than 10 different aspects of application lifecycle. We've described our reasoning in several talks linked at https://github.com/7mind/izumi?tab=readme-ov-file#docs https://github.com/7mind/izumi?tab=readme-ov-file#docs > I feel like it causes a new class of dependency ordering bugs or missing dependencies That's precisely where phased approach shines.
- tyleo 1y agoI looked at the link you shared but it’s just a page of additional links. Can you provide specific examples here instead? What I think is most useful in cases like this is before-and-after code snippets showing how the library adds value.
- pshirshov 1y agoThere are docs and examples here: https://izumi.7mind.io/distage/ https://izumi.7mind.io/distage/ Not in before-after form though, but there was that "I'll eat my hat" discussion exactly in this form: https://www.reddit.com/r/hascalator/comments/aigfux/comment/eeq43xj/ https://www.reddit.com/r/hascalator/comments/aigfux/comment/...?
- ricardobeat 1y agoI assume the top comment was made in relation to TypeScript, not DI in general. A lot of this content explains how it improves on problems compared to Spring, and statements like 'there was no good way to create configurable apps' or avoiding use of Java reflections, which do not seem relevant to TypeScript/JS.
- mininao 1y agoDoes 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.
- tantalor 1y agoWhy would the choice of programming language matter at all with respect to DI?
- pshirshov 1y agoIt shouldn't. DI as a set of patterns and approaches can be implemented anywhere. The language and the runtime can help you or be a hindrance, but the principles are generic.
- deleted 1y ago[deleted]
- pshirshov 1y agoWe 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 https://github.com/7mind/izumi?tab=readme-ov-file#docs Most of the benefits do not depend on any particular language/runtime/stack.
- deleted 1y ago[deleted]
- nkohari 1y agoI 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 https://github.com/nkohari/forge (For context: many years ago, I wrote Ninject, one of the more popular DI frameworks for .NET)
- pshirshov 1y agoIn this particular case decorators are completely optional, you don't have to use them. You can provide metadata at binding DSL level.
- ambicapter 1y ago> The port was done by guiding Claude with partial manual reviews. > At this point the project is not battle-tested. Expect dragons, landmines and varying mileage. Exciting.
- pshirshov 1y agoIndeed it is. The mere fact that with Claude I got reasonably good prototype in less than 4 hours while manual port to C# took me a week until working prototype is definitely exciting.
- AlexErrant 1y agoWhat does "phased" mean here? My googlefu is failing to turn up meaningful results for "phased dependency injection", and the readme is unhelpful (and probably AI-generated: "distage follows distage's architecture", lol.) The only 2 occurrences of "phase" are comments: `// Plan phase: analyze dependencies, detect errors` and `// Produce phase: create instances`. I'm mostly familiar with DI in C#, and SimpleInjector in particular. Does "phased" mean "we iterate the dep graph to detect lifecycle/circular dep errors"? Similar to how `.verify()` works in SimpleInjector? https://docs.simpleinjector.org/en/latest/howto.html#verify-configuration https://docs.simpleinjector.org/en/latest/howto.html#verify-...
- pshirshov 1y agoGenerative progamming. The problem is solved in phases (or stages) - at first a DAG (effectively a script in a simple DSL) is produced, then it's interpreted (and there may be different ways to interpret, e.g. you may produce a graphviz file or compute some complexity metrics or validate or whatever).