Factory Functions
Step 1: Write Your Code
Section titled “Step 1: Write Your Code”Separate Parameters Pattern:
/** * @factory */function createUserUseCase(userRepo: IUserRepository) { return (userId: string): string => { return userRepo.getUser(userId); };}Context Object Pattern:
/** * @factory */function createTodoUseCase( context: { userRepo: IUserRepository, todoRepo: ITodoRepository }) { return (userId: string, title: string): void => { const user = context.userRepo.getUser(userId); context.todoRepo.saveTodo(title); };}Step 2: Generate Container
Section titled “Step 2: Generate Container”npx @notjustcoders/ioc-arise generateStep 3: Generated Code
Section titled “Step 3: Generated Code”container.gen.d.ts:
export interface ContainerRegistry { 'createUserUseCase': ReturnType<typeof createUserUseCase>; 'createTodoUseCase': ReturnType<typeof createTodoUseCase>;}container.gen.ts:
import { Container, Lifecycle } from '@notjustcoders/di-container';import type { ContainerRegistry } from './container.gen.d';import { createUserUseCase } from './factories/userUseCaseFactory';import { createTodoUseCase } from './factories/todoUseCaseFactory';
export const container = new Container<ContainerRegistry>();
container.register('createUserUseCase', { useFactory: createUserUseCase, dependencies: ['IUserRepository'], lifecycle: Lifecycle.Singleton});
container.register('createTodoUseCase', { useFactory: (userRepo, todoRepo) => createTodoUseCase({ userRepo, todoRepo }), dependencies: ['IUserRepository', 'ITodoRepository'], lifecycle: Lifecycle.Singleton});Step 4: Use It
Section titled “Step 4: Use It”import { container } from './container.gen';
const createUser = container.resolve('createUserUseCase');const createTodo = container.resolve('createTodoUseCase');
createUser('user-1');createTodo('user-1', 'Buy groceries');External Package Dependencies
Section titled “External Package Dependencies”Factory functions can depend on interfaces originating in external npm packages or sibling monorepo packages. The tool handles two shapes automatically.
Shape 1 — Direct external import:
import type { IPaymentGateway } from '@payments/sdk';import type { IOrderRepository } from './IOrderRepository';
/** * @factory */export function createOrderUseCase(deps: { orderRepository: IOrderRepository; paymentGateway: IPaymentGateway;}) { return (orderId: string) => { // ... };}Generated registration:
container.register('createOrderUseCase', { useFactory: (orderRepository, paymentGateway) => createOrderUseCase({ orderRepository, paymentGateway }), dependencies: ['IOrderRepository', 'IPaymentGateway'], lifecycle: Lifecycle.Singleton});Shape 2 — Local re-export stub (common monorepo pattern):
// src/ports/ITombstoneRepository.ts — re-export stubexport type { ITombstoneRepository } from '@word-tracker/sync';
import type { IWordEntryRepository } from './IWordEntryRepository'; // localimport type { ITombstoneRepository } from './ports/ITombstoneRepository'; // re-export stub
/** * @factory */export function createPermanentlyDeleteWordUseCase(deps: { wordEntryRepository: IWordEntryRepository; tombstoneRepository: ITombstoneRepository;}) { return (wordId: string) => { // ... };}Generated registration:
container.register('createPermanentlyDeleteWordUseCase', { useFactory: (wordEntryRepository, tombstoneRepository) => createPermanentlyDeleteWordUseCase({ wordEntryRepository, tombstoneRepository }), dependencies: ['IWordEntryRepository', 'ITombstoneRepository'], lifecycle: Lifecycle.Singleton});The DI token is always the TypeScript symbol name (ITombstoneRepository, IPaymentGateway, etc.), matching the convention used for local interfaces. You are responsible for registering a concrete implementation under that token — typically in another module of the same container.