Skip to content

Factory Functions

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);
};
}
Terminal window
npx @notjustcoders/ioc-arise generate

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
});
import { container } from './container.gen';
const createUser = container.resolve('createUserUseCase');
const createTodo = container.resolve('createTodoUseCase');
createUser('user-1');
createTodo('user-1', 'Buy groceries');

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:

use-cases/createOrderUseCase.ts
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/PermanentlyDeleteWordUseCase.ts
// src/ports/ITombstoneRepository.ts — re-export stub
export type { ITombstoneRepository } from '@word-tracker/sync';
import type { IWordEntryRepository } from './IWordEntryRepository'; // local
import 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.