What we haven't built yet (and why)
TinyDI ships with exactly two lifetimes (Singleton, Transient), no child containers, no async resolution API, and no plugin system. None of these are accidental gaps — each one is a deliberate scope cut, made explicit in the core library's own source comments and expanded on in the FAQ and the project ROADMAP.md. This post is about the actual reasoning, not a vague "maybe later".
The general rule: minimalism wins when it conflicts with future-proofing
Wherever staying minimal today and staying open to a specific future extension pulled in different directions, minimalism won — that was the explicit priority throughout the core library work. It is an easy priority to state and a harder one to hold to: "this would be useful" is true of almost any feature you could add to a DI container, which is exactly why it cannot be the bar.
What makes the cuts below defensible, rather than just "we ran out of time," is that each of the four extensions was checked against the current design and found to still be reachable without a breaking change. An irreversible scope cut is a much riskier bet than one you can still walk back later.
Scoped lifetime
export enum ServiceLifetime {
Singleton,
Transient,
// Scoped is a natural future extension (per-request/per-operation
// instances), but it is not implemented here — see the project ROADMAP.
}
A per-request or per-operation lifetime is the most commonly requested extension in reflection-based containers, and it's the one users ask about most (see the FAQ). It did not make it into this version because it changes the shape of resolution itself: resolveFactory's singleton caching would need to become scope-aware instead of being a single boolean per registration. Mostly additive, but not free.
Child containers
This one is nearly free, and it is free specifically because of the factory signature decision described in the reflect-metadata post. Since a Factory<T> already receives the container explicitly rather than closing over module-level state, nothing internally assumes there is only ever one container instance in play. A parent/child relationship — a child falling back to its parent's registrations when it has none of its own — is additive on top of what exists today, not a redesign.
Async resolution
Factory<T> is synchronous by design: (container: Container) => T, not => Promise<T>. This is the one extension of the four that genuinely is not free. Singleton caching today stores T | undefined plus a boolean flag; supporting async factories properly means also caching the in-flight Promise<T>, so two concurrent resolve() calls for the same not-yet-built singleton await the same promise instead of racing to build two instances. That is a real internal change to resolveFactory, not just an added method.
The workaround today
You can resolve a promise-returning value as the service itself — Token<Promise<T>> — and await it at the call site. It works, but it does not get you the "await once, cache once" behavior a real async API would provide.
Plugins
A plugin system is the most "free" of the four: a plugin would just be a function that calls registerInstance/registerFactory on a container it's handed, using only public API that already exists. Fully additive, no internal change required — it just hasn't been built because nothing in the current scope needs it yet.
Why write this down at all
A roadmap that just lists feature names ("Scoped", "Child Containers", "Async", "Plugins") reads as a wish list. Stating for each one whether it's additive or requires a redesign is a more honest signal to anyone deciding whether to depend on TinyDI today: three of these four extensions can land as 2.x-style additions without breaking existing code; one of them (async) is an open question that would need real design work before it ships.
That distinction matters more than the feature list itself, and it generalizes past this one library: any project keeping a "not yet" list should be able to say, for each item, whether it is additive or would need a redesign. If you cannot say which, you do not actually know what that scope cut is costing you.