proxydi
Version:
A typed hierarchical DI container that resolves circular dependencies via Proxy
121 lines (84 loc) • 4.95 kB
Markdown
# @injectAll and ResolveScope
Injects arrays of dependencies from container hierarchy. This is how ProxyDI works with collections — each instance with the same semantic dependencyId need to be placed to separate child container with its own dependencies.
By default resolves from children containers only.
ResolveScope allows explicit control over where to search (parent, current, children, or any combination).
## How It Works
`@injectAll` creates permanent Proxy (via `makeInjectAllProxy.ts`) that rebuilds array on each access by calling `container.resolveAll()`. This means array always reflects current container hierarchy state.
```typescript
@injectable()
class PluginManager {
@injectAll('plugin') plugins: Plugin[]; // Dynamic array
}
```
`ResolveScope` bitwise enum controls where to search:
- `ResolveScope.Children` - all children recursively (default)
- `ResolveScope.Current` - current container only
- `ResolveScope.Parent` - parent container only
- `ResolveScope.All` - everywhere (parent + current + children)
### Dependency Resolution Graph
```mermaid
graph TB
Grandparent["Grandparent Container<br/>plugin: A"]
Parent["Parent Container"]
Uncle["Uncle Container<br/>plugin: C"]
Current["<b>Current Container</b><br/>plugin: D<br/>@injectAll here"]
Sibling["Sibling Container<br/>plugin: E"]
Child1["Child Container 1<br/>plugin: F"]
Child2["Child Container 2<br/>plugin: G"]
Child1_1["Child Container 1.1<br/>plugin: H"]
Grandparent --> Parent
Grandparent --> Uncle
Parent --> Current
Parent --> Sibling
Current --> Child1
Current --> Child2
Child1 --> Child1_1
style Current fill:#d97706,stroke:#f59e0b,stroke-width:3px,color:#fff
style Grandparent fill:#15803d,stroke:#22c55e,color:#fff
style Parent fill:#6b7280,stroke:#9ca3af,color:#fff
style Child1 fill:#1d4ed8,stroke:#3b82f6,color:#fff
style Child2 fill:#1d4ed8,stroke:#3b82f6,color:#fff
style Child1_1 fill:#1d4ed8,stroke:#3b82f6,color:#fff
style Uncle fill:#b91c1c,stroke:#ef4444,color:#fff
style Sibling fill:#b91c1c,stroke:#ef4444,color:#fff
```
**What gets resolved from Current container:**
- `ResolveScope.Children` (Default) → **[F, G, H]** (all children recursively, including nested)
- `ResolveScope.Current` → **[D]** (Current only)
- `ResolveScope.Parent` → **[A]** (searches up hierarchy until found in Grandparent)
- `ResolveScope.All` → **[A, D, F, G, H]** (parent + current + children)
**Cannot access sibling branches** (Uncle, Sibling) — only direct hierarchy line.
## Code Organization
- `injectAll.decorator.ts` — `@injectAll(dependencyId, scope?)` decorator
- `makeInjectAllProxy.ts` — Proxy that calls `container.resolveAll()` on each access
- `resolveAll.ts` — Wrapper for `container.resolveAll()`
- `types.ts` — `AllInjection` type and `ResolveScope` enum
## Architectural Decisions
### Why array stays dynamic (never baked)?
Unlike `@inject` single dependencies (auto-bake after first use), `@injectAll` arrays remain Proxy permanently.
**Reason:** Container hierarchy may change, mew children containers added, dependencies registered/removed. Array must reflect current state.
**Trade-off:** Array access slower (~100x) but elements are baked through `container.resolve()`.
`bakeInjections()` skips `@injectAll` — only single injections baked.
\\
### Why no auto-baking array itself?
Elements ARE baked through `container.resolve()` which triggers normal injection Proxy baking. Array itself stays dynamic, but element property access is fast after first use.
## Performance Characteristics
| Component | Performance | Reason |
| ---------------------------------- | -------------------- | ---------------------------------------------------- |
| Array access (`plugins.length`) | ~100x slower | Proxy calls `container.resolveAll()` on every access |
| Element access (`plugins[0].name`) | Fast after first use | Elements baked through `container.resolve()` |
| Adding child container | No impact | Lazy resolution on next array access |
**Recommendation:** Cache array reference if accessing multiple times in loop:
```typescript
const plugins = this.plugins; // One resolveAll() call
plugins.forEach((p) => p.activate()); // Fast
```
Avoid:
```typescript
this.plugins.forEach((p) => p.activate()); // resolveAll() on .forEach AND on each iteration
```
## Related Documentation
- [README @injectAll example](../README.md) — user-facing usage
- `injectAll.test.ts` — comprehensive test coverage
- `resolveScope.test.ts` — all scope combinations
- `readme-proposal.test.ts:106-158` — real-world example with Director/Actors