opinionated-machine
Version:
Very opinionated DI framework for fastify, built on top of awilix
230 lines • 8.23 kB
JavaScript
import { asClass, asFunction } from 'awilix';
import { isEnqueuedJobWorkersEnabled, isJobQueueEnabled, isMessageQueueConsumerEnabled, isPeriodicJobEnabled, resolveJobQueuesEnabled, } from './diConfigUtils.js';
export function asSingletonClass(Type, opts) {
return asClass(Type, {
...opts,
lifetime: 'SINGLETON',
});
}
/**
* Register a class with an additional config parameter passed to the constructor.
* Uses asFunction wrapper internally to pass the config as a second parameter.
* Requires PROXY injection mode.
*
* @example
* ```typescript
* myService: asClassWithConfig(MyService, { enableFeature: true }),
* ```
*/
export function asClassWithConfig(Type, config, opts) {
const Ctor = Type;
// biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation with cradle proxy
return asFunction((cradle) => new Ctor(cradle, config), {
...opts,
lifetime: opts?.lifetime ?? 'SINGLETON',
});
}
export function asSingletonFunction(fn, opts) {
return asFunction(fn, {
...opts,
lifetime: 'SINGLETON',
});
}
export function asServiceClass(Type, opts) {
return asClass(Type, {
public: true,
...opts,
lifetime: 'SINGLETON',
});
}
export function asUseCaseClass(Type, opts) {
return asClass(Type, {
public: true,
...opts,
lifetime: 'SINGLETON',
});
}
export function asRepositoryClass(Type, opts) {
return asClass(Type, {
public: false,
...opts,
lifetime: 'SINGLETON',
});
}
export function asControllerClass(Type, opts) {
return asClass(Type, {
public: false,
...opts,
lifetime: 'SINGLETON',
});
}
/**
* Register an SSE controller class with the DI container.
*
* SSE controllers handle Server-Sent Events connections and require
* graceful shutdown to close all active connections.
*
* When `diOptions.isTestMode` is true, connection spying is enabled
* allowing tests to await connections via `controller.connectionSpy`.
*
* @example
* ```typescript
* // Without test mode
* notificationsSSEController: asSSEControllerClass(NotificationsSSEController),
*
* // With test mode (enables connection spy)
* notificationsSSEController: asSSEControllerClass(NotificationsSSEController, { diOptions }),
*
* // With rooms enabled (resolves sseRoomBroadcaster from DI)
* dashboardController: asSSEControllerClass(DashboardSSEController, { diOptions, rooms: true }),
* ```
*/
export function asSSEControllerClass(Type, sseOptions, opts) {
const Ctor = Type;
const enableConnectionSpy = sseOptions?.diOptions.isTestMode ?? false;
const enableRooms = sseOptions?.rooms ?? false;
return asFunction(
// biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation with cradle proxy
(cradle) => {
const sseConfig = {
...(enableConnectionSpy && { enableConnectionSpy: true }),
...(enableRooms && { roomBroadcaster: cradle.sseRoomBroadcaster }),
};
return new Ctor(cradle, Object.keys(sseConfig).length > 0 ? sseConfig : undefined);
}, {
public: false,
isSSEController: true,
asyncDispose: 'closeAllConnections',
asyncDisposePriority: 5, // Close SSE connections early in shutdown
...opts,
lifetime: 'SINGLETON',
});
}
/**
* Register a dual-mode controller class with the DI container.
*
* Dual-mode controllers handle both SSE streaming and JSON responses on the
* same route path, automatically branching based on the `Accept` header.
* They require graceful shutdown to close all active SSE connections.
*
* When `diOptions.isTestMode` is true, connection spying is enabled
* allowing tests to await connections via `controller.connectionSpy`.
*
* @example
* ```typescript
* // Without test mode
* chatController: asDualModeControllerClass(ChatController),
*
* // With test mode (enables connection spy)
* chatController: asDualModeControllerClass(ChatController, { diOptions }),
*
* // With rooms enabled (resolves sseRoomBroadcaster from DI)
* dashboardController: asDualModeControllerClass(DashboardController, { diOptions, rooms: true }),
* ```
*/
export function asDualModeControllerClass(Type, dualModeOptions, opts) {
const enableConnectionSpy = dualModeOptions?.diOptions.isTestMode ?? false;
const enableRooms = dualModeOptions?.rooms ?? false;
return asFunction(
// biome-ignore lint/suspicious/noExplicitAny: Dynamic constructor invocation with cradle proxy
(cradle) => {
const Ctor = Type;
const sseConfig = {
...(enableConnectionSpy && { enableConnectionSpy: true }),
...(enableRooms && { roomBroadcaster: cradle.sseRoomBroadcaster }),
};
return new Ctor(cradle, Object.keys(sseConfig).length > 0 ? sseConfig : undefined);
}, {
public: false,
isDualModeController: true,
asyncDispose: 'closeAllConnections',
asyncDisposePriority: 5, // Close connections early in shutdown
...opts,
lifetime: 'SINGLETON',
});
}
export function asMessageQueueHandlerClass(Type, mqOptions, opts) {
return asClass(Type, {
// these follow message-queue-toolkit conventions
asyncInit: 'start',
asyncDispose: 'close',
asyncDisposePriority: 10,
enabled: isMessageQueueConsumerEnabled(mqOptions.diOptions.messageQueueConsumersEnabled, mqOptions.queueName),
lifetime: 'SINGLETON',
public: false,
...opts,
});
}
export function asEnqueuedJobWorkerClass(Type, workerOptions, opts) {
return asClass(Type, {
// these follow background-jobs-common conventions
asyncInit: 'start',
asyncDispose: 'dispose',
asyncDisposePriority: 15,
public: false,
enabled: isEnqueuedJobWorkersEnabled(workerOptions.diOptions.enqueuedJobWorkersEnabled, workerOptions.queueName),
lifetime: 'SINGLETON',
...opts,
});
}
/**
* Helper function to register a pg-boss job processor class with the DI container.
* Handles asyncInit/asyncDispose lifecycle and enabled check based on diOptions.
*
* @example
* ```typescript
* enrichUserPresenceJob: asPgBossProcessorClass(EnrichUserPresenceJob, {
* diOptions,
* queueName: EnrichUserPresenceJob.QUEUE_ID,
* }),
* ```
*/
export function asPgBossProcessorClass(Type, processorOptions, opts) {
return asClass(Type, {
asyncInit: 'start',
asyncInitPriority: 20, // Initialize after pgBoss (priority 10)
asyncDispose: 'stop',
asyncDisposePriority: 10,
public: false,
enabled: isEnqueuedJobWorkersEnabled(processorOptions.diOptions.enqueuedJobWorkersEnabled, processorOptions.queueName),
lifetime: 'SINGLETON',
...opts,
});
}
export function asPeriodicJobClass(Type, workerOptions, opts) {
return asClass(Type, {
// this follows background-jobs-common conventions
eagerInject: 'register',
asyncDispose: 'dispose',
public: false,
enabled: isPeriodicJobEnabled(workerOptions.diOptions.periodicJobsEnabled, workerOptions.jobName),
lifetime: 'SINGLETON',
...opts,
});
}
export function asJobQueueClass(Type, queueOptions, opts) {
return asClass(Type, {
// these follow background-jobs-common conventions
asyncInit: 'start',
asyncDispose: 'dispose',
asyncDisposePriority: 20,
public: true,
enabled: isJobQueueEnabled(queueOptions.diOptions.jobQueuesEnabled, queueOptions.queueName),
lifetime: 'SINGLETON',
...opts,
});
}
export function asEnqueuedJobQueueManagerFunction(fn, diOptions, opts) {
return asFunction(fn, {
// these follow background-jobs-common conventions
asyncInit: (manager) => manager.start(resolveJobQueuesEnabled(diOptions)),
asyncDispose: 'dispose',
asyncInitPriority: 20,
asyncDisposePriority: 20,
public: true,
enabled: isJobQueueEnabled(diOptions.jobQueuesEnabled),
lifetime: 'SINGLETON',
...opts,
});
}
//# sourceMappingURL=resolverFunctions.js.map