opinionated-machine
Version:
Very opinionated DI framework for fastify, built on top of awilix
106 lines (105 loc) • 4.32 kB
TypeScript
import type { Resolver } from 'awilix';
import type { DependencyInjectionOptions } from './DIContext.js';
export type MandatoryNameAndRegistrationPair<T> = {
[U in keyof T]: Resolver<T[U]>;
};
/**
* Infers the module's dependency types from the return type of `resolveDependencies()`.
*
* This eliminates the need to manually define a `ModuleDependencies` type that duplicates
* information already present in the resolver return value.
*
* @example
* ```typescript
* export class MyModule extends AbstractModule {
* resolveDependencies(diOptions: DependencyInjectionOptions) {
* return {
* myService: asServiceClass(MyService),
* myRepo: asRepositoryClass(MyRepository),
* }
* }
* }
*
* // Inferred as { myService: MyService; myRepo: MyRepository }
* export type MyModuleDependencies = InferModuleDependencies<MyModule>
* ```
*/
export type InferModuleDependencies<M extends AbstractModule> = ReturnType<M['resolveDependencies']> extends infer R ? {
[K in keyof R]: R[K] extends Resolver<infer T> ? T : never;
} : never;
/**
* Infers only the **public** dependency types from the return type of `resolveDependencies()`,
* omitting non-public dependencies entirely.
*
* When a module is used as a secondary module, only resolvers marked with `public: true`
* (i.e. those created via `asServiceClass`, `asUseCaseClass`, `asJobQueueClass`, or
* `asEnqueuedJobQueueManagerFunction`) are exposed. Non-public resolvers are filtered out.
*
* @example
* ```typescript
* export class MyModule extends AbstractModule {
* resolveDependencies(diOptions: DependencyInjectionOptions) {
* return {
* myService: asServiceClass(MyService), // public → MyService
* myRepo: asRepositoryClass(MyRepository), // private → omitted
* }
* }
* }
*
* // Inferred as { myService: MyService }
* export type MyModulePublicDependencies = InferPublicModuleDependencies<MyModule>
* ```
*/
export type InferPublicModuleDependencies<M extends AbstractModule> = ReturnType<M['resolveDependencies']> extends infer R ? {
[K in keyof R as R[K] extends {
readonly __publicResolver: true;
} ? K : never]: R[K] extends Resolver<infer T> ? T : never;
} : never;
/**
* Augmentation target for accumulating public dependencies across modules.
*
* Each module augments this interface via `declare module` to register its
* public dependencies. The augmentations are project-wide — they apply
* everywhere as long as the augmenting file is part of the TypeScript
* compilation, with no explicit import chain required.
*
* @example
* ```typescript
* // In your module file:
* declare module 'opinionated-machine' {
* interface PublicDependencies extends InferPublicModuleDependencies<MyModule> {}
* }
*
* // In any consumer file:
* import type { PublicDependencies } from 'opinionated-machine'
* // PublicDependencies contains all augmented public deps
* ```
*/
export interface PublicDependencies {
}
export declare abstract class AbstractModule<ModuleDependencies = unknown, ExternalDependencies = never> {
abstract resolveDependencies(diOptions: DependencyInjectionOptions, externalDependencies: ExternalDependencies): MandatoryNameAndRegistrationPair<ModuleDependencies>;
/**
* Override to register REST and SSE controllers.
* Returns empty object by default - no changes needed for modules without controllers.
*
* Controllers registered here are automatically added to the DI container.
* SSE controllers (created with asSSEControllerClass) are automatically detected
* and registered for SSE route handling.
*
* @param diOptions - DI options (use for test mode detection with asSSEControllerClass)
*
* @example
* ```typescript
* public resolveControllers(diOptions: DependencyInjectionOptions) {
* return {
* // REST controller
* usersController: asControllerClass(UsersController),
* // SSE controller (automatically detected via isSSEController flag)
* notificationsSSEController: asSSEControllerClass(NotificationsSSEController, { diOptions }),
* }
* }
* ```
*/
resolveControllers(_diOptions: DependencyInjectionOptions): MandatoryNameAndRegistrationPair<unknown>;
}