Concept
The composition root
One file that wires everything. Grepable. Replaceable.
src/composition.ts is the only file in the project that says
new Foo() for a service. Every other file receives its dependencies.
The shape
export async function compose(host, hud) {
const container = new Container();
container.register('stores', () => new RootStore());
container.register('ticker', () => new GsapTicker());
container.register('network', () => new MockNetworkManager(...));
// ...
await scene.init(host);
const fsm = new FSM({ stores, ticker, network, reels, hud });
fsm.register(new IdlePhase());
fsm.register(new SpinPhase());
// ...
return { start: () => fsm.transition('idle'), dispose };
}
All new calls happen here. Phase handlers, presenters, and stores receive
what they need via constructor arguments or the FSM context — they never reach for a global.
Anti-patterns this kills
- "Let me just import the singleton here." — No singleton exists.
- "I'll
new MyService()inside this helper." — The helper takes it as a parameter. - "The service locator will find it." — The locator is only populated here; elsewhere it's read-only.