UNPKG

opinionated-machine

Version:

Very opinionated DI framework for fastify, built on top of awilix

230 lines 8.23 kB
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