
Built a compile-time DI container for TypeScript (no reflect-metadata), and benchmarked it against tsyringe / inversify / typed-inject
Most TS DI either leans on reflect-metadata (Inversify, tsyringe, Nest) or drops decorators entirely for hand-wiring (typed-inject, brandi). I wanted decorators + auto-discovery without runtime reflection, so I built diadem: you decorate classes, run a build step that analyzes constructors with the TS compiler API, and it emits the wiring. Nothing reflects at runtime.
There are two output modes. A data manifest for dev/tests (mockable, inspectable), and --emit=compiled for prod, which writes straight-line new X(dep) wiring plus a typed accessor so resolving an unregistered token is a tsc error, not a runtime throw. It emits plain .ts, so no custom transformer or ts-patch (unlike @wessberg/di).
I didn't want to just claim it's fast, so there's a benchmark in the repo (same 11-service graph in every framework, npm run report). On the metrics that actually matter in prod:
- Bundle (gzipped): diadem 6.2 KB vs tsyringe 11 KB vs inversify 22 KB (typed-inject is smaller at 2.1 KB).
- Cold start (Δ over bare Node): diadem +4 ms, typed-inject +8, tsyringe +23, inversify +56.
- Scaling to 300 services (build time): diadem 0.11 ms, typed-inject 0.25, inversify 2.58.
Honest about the limits: typed-inject ships a smaller bundle, raw resolve speed is a noise-floor tie among everything that avoids reflection, token resolution is name-based (not the full type-level graph check typed-inject does), and it's early (v0.1.0, experimentalDecorators).
Repo (MIT): https://github.com/astralstriker/diadem · npm i @devcraft-ts/diadem
Curious what people think of the build-time approach, and whether the name-based resolution vs full type-checking tradeoff would stop you from using it. Happy to take the benchmark apart if anyone wants to poke holes in the methodology.