Skip to content

External & Monorepo Types

ioc-arise resolves DI tokens for constructor and factory dependencies typed with interfaces from external npm packages or sibling monorepo packages — not just interfaces defined in the analyzed source tree.

The convention is identical to local interfaces: the TypeScript symbol name (e.g., ITombstoneRepository) becomes the DI token string. You are responsible for registering a concrete implementation under that token (e.g., in another module).

Import the interface directly from the external package and use it as a constructor or factory parameter type.

services/OrderService.ts
import type { IPaymentGateway } from '@payments/sdk';
import type { IOrderService } from './IOrderService';
export class OrderService implements IOrderService {
constructor(
private gateway: IPaymentGateway,
) {}
}
Terminal window
npx @notjustcoders/ioc-arise generate
container.register('IOrderService', {
useClass: OrderService,
dependencies: ['IPaymentGateway'],
lifecycle: Lifecycle.Singleton,
});

IPaymentGateway is included as a dependency token even though its definition lives in @payments/sdk. Register the concrete implementation under 'IPaymentGateway' elsewhere in your container setup.


This is the most common monorepo pattern. A local file re-exports a type from a sibling package, and consuming files import from that local stub.

Project layout:

src/
├── ports/
│ └── ITombstoneRepository.ts ← re-export stub
├── IWordEntryRepository.ts ← locally defined interface
└── PermanentlyDeleteWordUseCase.ts

Re-export stub:

src/ports/ITombstoneRepository.ts
export type { ITombstoneRepository } from '@word-tracker/sync';

Factory using both a local and an external interface:

src/PermanentlyDeleteWordUseCase.ts
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) => {
const entry = deps.wordEntryRepository.findById(wordId);
deps.tombstoneRepository.markDeleted(wordId);
};
}
Terminal window
npx @notjustcoders/ioc-arise generate

Before this feature (broken):

// tombstoneRepository was silently dropped
container.register('createPermanentlyDeleteWordUseCase', {
useFactory: (wordEntryRepository) =>
createPermanentlyDeleteWordUseCase({ wordEntryRepository, tombstoneRepository }),
dependencies: ['IWordEntryRepository'],
});

After this feature (correct):

container.register('createPermanentlyDeleteWordUseCase', {
useFactory: (wordEntryRepository, tombstoneRepository) =>
createPermanentlyDeleteWordUseCase({ wordEntryRepository, tombstoneRepository }),
dependencies: ['IWordEntryRepository', 'ITombstoneRepository'],
lifecycle: Lifecycle.Singleton,
});

Register a concrete implementation under the external token in your container setup, then resolve normally:

// Register the concrete sync implementation from the sibling package
import { SyncTombstoneRepository } from '@word-tracker/sync';
container.register('ITombstoneRepository', {
useClass: SyncTombstoneRepository,
lifecycle: Lifecycle.Singleton,
});
// Resolve the factory — all dependencies are satisfied
const deleteWord = container.resolve('createPermanentlyDeleteWordUseCase');
await deleteWord('word-123');