What it does
- Decorate a private (or public) method on a @Service class
- The method’s return value is registered as a singleton in the plugin DI container
- Other services/controllers can then @Inject that instance by type (or the returned class)
- Ideal for wrapping libraries you do not own (BullMQ, ioredis, AWS SDK, etc.)
Rules
- Host class must be @Service() and listed in the module providers
- Factory runs during DI setup — keep it side-effect light (create client / queue; defer heavy work)
- Return one concrete instance per @Bean method
- Constructor injection is not supported anywhere — consumers inject with property @Inject
BullMQ Queue example
Common pattern: expose a Queue for custom background work. For recurring schedules prefer CronJobService instead.
TSXfactory
1import { Queue } from "bullmq";
2import { Bean, Service } from "@quan-erp/shared-backend-core";
3
4@Service()
5export class QueueRegistryService {
6 @Bean()
7 private registerQueue() {
8 return new Queue("my-queue", { connection: redisOptions });
9 }
10}TSXinject consumer
1import { Queue } from "bullmq";
2import { Inject, Service } from "@quan-erp/shared-backend-core";
3
4@Service()
5export class JobPublisherService {
6 @Inject(Queue) // or the concrete token your factory returns
7 private queue: Queue;
8
9 async enqueue(payload: unknown) {
10 await this.queue.add("task", payload);
11 }
12}When to use
- Yes — Queue / Worker / Redis / HTTP SDK clients that need a single shared instance
- Yes — adapters that need env/config at construction time
- No — normal plugin services (use @Service + @Inject)
- No — cron schedules (use CronJobService; see Cron job)
Practices
- Keep factory methods small and named clearly (registerX / createX)
- Read connection options from PluginEnv / base env — never hardcode secrets
- On uninstall, disconnect clients in @OnUninstall if the library needs cleanup
- Register the host @Service in @Module({ providers }) so the factory actually runs