feat(server): job system (#10134)

This commit is contained in:
forehalo
2025-02-18 05:41:56 +00:00
parent f6a86c10fe
commit cb895d4cb0
26 changed files with 1045 additions and 131 deletions

View File

@@ -0,0 +1,77 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { ModuleScanner } from '../../nestjs';
import { getJobHandlerMetadata } from './def';
interface JobHandler {
name: string;
fn: (payload: any) => any;
}
@Injectable()
export class JobHandlerScanner implements OnModuleInit {
private readonly handlers: Record<string, JobHandler> = {};
constructor(private readonly scanner: ModuleScanner) {}
async onModuleInit() {
this.scan();
}
getHandler(jobName: JobName): JobHandler | undefined {
return this.handlers[jobName];
}
private scan() {
const providers = this.scanner.getAtInjectables();
providers.forEach(wrapper => {
const { instance, name } = wrapper;
if (!instance || wrapper.isAlias) {
return;
}
const methods = this.scanner.getAllMethodNames(instance);
methods.forEach(method => {
const fn = instance[method];
let jobNames = getJobHandlerMetadata(instance[method]);
if (jobNames.length === 0) {
return;
}
const signature = `${name}.${method}`;
if (typeof fn !== 'function') {
throw new Error(`Job handler [${signature}] is not a function.`);
}
if (!wrapper.isDependencyTreeStatic()) {
throw new Error(
`Provider [${name}] could not be RequestScoped or TransientScoped injectable if it contains job handlers.`
);
}
jobNames.forEach(jobName => {
if (this.handlers[jobName]) {
throw new Error(
`Job handler ${jobName} already defined in [${this.handlers[jobName].name}].`
);
}
this.handlers[jobName] = {
name: signature,
fn: (payload: any) => {
// NOTE(@forehalo):
// we might create spies on the job handlers when testing,
// avoid reusing `fn` variable to fail the spies or stubs
return instance[method].bind(instance)(payload);
},
};
});
});
});
}
}