Skip to main content

inversify-di

Inversify dependency injection patterns for the server. Use when creating or modifying DI container bindings, adding injectable services, writing tests with dependency overrides, or managing TYPES symbols.

Source facts

Repository
recca0120/code-quest
Last source activity
May 13, 2026 at 16:19
Detected SKILL.md language
English
Stars
11
Forks
2

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
inversify-di
description
Inversify dependency injection patterns for the server. Use when creating or modifying DI container bindings, adding injectable services, writing tests with dependency overrides, or managing TYPES symbols.
# Inversify DI ## TYPES Registry **File:** `apps/server/src/types.ts` ```typescript export const TYPES = { RunnerFactory: Symbol.for('RunnerFactory'), SessionStore: Symbol.for('SessionStore'), RawEventStore: Symbol.for('RawEventStore'), ChatHandler: Symbol.for('ChatHandler'), Database: Symbol.for('Database'), UsageTracker: Symbol.for('UsageTracker'), SettingsStore: Symbol.for('SettingsStore'), ChannelManager: Symbol.for('ChannelManager'), } as const; ``` ## Container Setup **File:** `apps/server/src/container.ts` — `createContainer(options)` All bindings are inline (no ContainerModule pattern): | Symbol | Binding | Scope | |--------|---------|-------| | RunnerFactory | `.toConstantValue(factory)` | — | | Database | `.toConstantValue(db)` | — | | SessionStore | `.toConstantValue(store)` | — | | RawEventStore | `.toConstantValue(store)` | — | | SettingsStore | `.toConstantValue(store)` | — | | ChannelManager | `.toConstantValue(mgr)` | — | | UsageTracker | `.to(UsageTracker).inSingletonScope()` | Singleton | | ChatHandler | `.to(ChatHandler).inSingletonScope()` | Singleton | Only `ChatHandler` and `UsageTracker` use `@injectable()` + `@inject()`. ## Injectable Services ```typescript // apps/server/src/socket/chat-handler.ts @injectable() export class ChatHandler implements HandlerContext { constructor( @inject(TYPES.RunnerFactory) public runnerFactory: RunnerFactory, @inject(TYPES.RawEventStore) public rawEventStore: RawEventStore, @inject(TYPES.SessionStore) public sessionStore: SessionStore, @inject(TYPES.UsageTracker) public usageTracker: UsageTracker, @inject(TYPES.ChannelManager) public channelManager: ChannelManager, @inject(TYPES.SettingsStore) @optional() settingsStore?: SettingsStore, ) {} } ``` ## HandlerContext Interface **File:** `apps/server/src/socket/handler-context.ts` Aggregates all injected services. Socket handlers access dependencies through this interface. ## Composite Store Pattern `buildStores()` in container.ts creates stores based on `StoreConfig`, wrapping multiple backends in `CompositeRawStore` / `CompositeSessionStore` when needed. ## Testing **File:** `apps/server/src/test/create-test-container.ts` ```typescript export function createTestContainer(overrides): Container { const container = createContainer({ ...overrides, storeConfig: { sqlite: true } }); const db = container.get<DrizzleDatabase>(TYPES.Database); migrate(db, { migrationsFolder }); container.rebindSync<SettingsStore>(TYPES.SettingsStore) .toConstantValue(new InMemorySettingsStore()); return container; } ``` Key test methods: - `container.get<T>(TYPES.X)` — retrieve service - `container.rebindSync<T>(TYPES.X).toConstantValue(mock)` — replace binding ## Adding a New Service 1. Add symbol to `TYPES` in `apps/server/src/types.ts` 2. If constructor-injectable: add `@injectable()` + `@inject()` decorators 3. If constant-bound: instantiate and `.bind(TYPES.X).toConstantValue(instance)` 4. Add to test container if tests need it --- ## Inversify v8 Breaking Changes > Reference: https://inversify.io/docs/guides/migrating-from-v7/ ### API Renames | v7 | v8 | |----|----| | `container.loadSync(module)` | `container.load(module)` | | `container.load(module)` | `container.loadAsync(module)` | | `container.rebindSync<T>(id)` | `container.rebind<T>(id)` (now sync by default) | | `container.isBoundNamed(id, name)` | `container.isBound({ serviceIdentifier: id, named: name })` | | `container.isBoundTagged(id, key, val)` | `container.isBound({ serviceIdentifier: id, tag: { key, value: val } })` | ### Removed APIs - **`rebindSync`** is gone — `rebind` is now synchronous by default in v8; update all call sites. - **`toProvider()`** is removed — replace with `toFactory()` using an async inner function if you need a promise-returning factory. ### Module System - v8 is **ESM-only**. CommonJS (`require`) is no longer supported. ### Service Identifiers - **Plain functions are no longer valid service identifiers.** Use `string` or `Symbol` (e.g., `Symbol.for('MyService')`) instead. Abstract classes are still valid. ### Migration Tip for This Project The test container currently calls `container.rebindSync` (v7). When upgrading to v8, rename all `rebindSync` calls to `rebind`.
View on GitHub