Describe the solution you'd like
Integrate cron as decorator
@Cron("* * * * *")
someFunction() {
}
Hello @IARKI and @shprota
I haven't time to work on it, but If something want to take this feature and develop a new package, you are welcome :)
I saw this nest module (hosted on a non official nest respository) https://github.com/miaowing/nest-schedule
It could be interesting to take this project a example no?
If you have any question, tell me (create separated repository, create package on official repo, etc...)
See you
Romain
See: https://github.com/TypedProject/tsed/wiki
cron in packages directory.name field by @tsed/cron change also the description, keywords, etc... Add contributors field with your nameindex.ts and create a module file. Example: for typeorm TypeORMModule.tsNote: Decorators can be generated with the tsed cli. Just run the cli inside your packages/mypackage.
cd packages/cron
tsed g decorator
import {PlatformApplication, OnDestroy, OnInit} from "@tsed/common";
import {Module, Inject, Constant} from "@tsed/di";
import {MyService} from "./services/MyService";
@Module({
imports: [MyService] // prebuild MyService. Use this field to build services before the module.
})
export class MyModule implements OnInit, OnDestroy {
@Inject()
app: PlatformApplication;
@Constant("myConfig", {})
settings: any;
@Inject()
myService: MyService;
async $onInit(): Promise<any> {
console.log("Init")
}
$onDestroy(): Promise<any> | void {
console.log("Destroy");
}
}
Is there any documentation on interacting with the Store? Creating a simple cron package is trivial, although the only issue I'm having in creating a working solution is just storing (or invoking) the method to be scheduled.
Hello @bpofficial
The most nearest example to register handler and bind handler to another module is the @tsed/socketio package.
https://github.com/TypedProject/tsed/blob/production/packages/socketio/src/SocketIOModule.ts
In you case you need to create a new type of provider:
replace registerSocketService by registerCronProvider
This new registry allow you to create decorator to register a class (like injectable):
import {StoreMerge, applyDecorators} from "@tsed/core";
export interface CronOptions {
}
export function Cron(options: CronOptions) {
return applyDecorators(
StoreMerge("cron", { options }), (target: Type<any>) => {
registerCronProvider(target);
});
}
Now you can create another decorator to register a method, your handler :) (ex: https://github.com/TypedProject/tsed/blob/production/packages/socketio/src/decorators/input.ts)
export interface ScheduleOP {
}
export function Schedule(options: ScheduleOptions): MethodDecorator {
return (target: any, propertyKey: string, descriptor: PropertyDescriptor) => {
Store.from(target).merge("cron", {
handlers: {
[propertyKey]: {
options,
propertyKey
}
}
});
};
}
Now the store have all needed metadata. If you use the decorators as following:
@Cron({test: "kzjfelzfkl"})
class MyCronService {
@Schedule({ opt: '1'})
schedule1(){
}
}
I'll have in the cron store the following object:
{
"options": {"test": "kzjfelzfkl"},
"handlers": {
"schedule1": {
"options": { "opt": "1"},
"propertyKey": "schedule1"
}
}
}
The final step is to bind classes with the cron modules. In the CronModule, add $afterListen to bind all classes:
https://github.com/TypedProject/tsed/blob/production/packages/socketio/src/SocketIOModule.ts#L31
Create a method to retrieve all Cron provider (this is why we had the registerCronProvider ;) ):
https://github.com/TypedProject/tsed/blob/production/packages/socketio/src/SocketIOModule.ts#L54
Create CronService isn't necessary. You can stay in the module to bind your classes and handlers
class CronModule {
@Inject()
injector: InjectorService;
// add methods from SocketIOModule to retrieve all cron providers
$afterListen {
this.getCronProviders().forEach(provider => {
this.addCronProvider(provider);
})聽
}
addCronProvider(provider: Provider) {
const {handlers, options =聽{}} = provider.store.get("cron");
const instance = this.injector.get(provider.token)
Object.values(handlers).forEach((item) => {
const handler = instance[item.propertyKey].bind(instance)
const handlerOptions = item.options ||聽{}
// attach handler to the cron module :)
})
}
}
I think with that you'll able to create nice PR ;)
See you
Romain
Awesome, thank you for your descriptiveness. I鈥檒l shortly make an attempt at this and tests before sending a PR
Implementing a cron-alike recurring task is really pretty trivial but I suggest to take into account that recurring/timed operations in the server code is pretty much an antipattern. A backend application using such operations will most certainly have problems with scaling - will need to implement some sort of leader election to make only one instance run periodic tasks or use an external scheduler like Celery.
Agreed :)
To solve this problem, we can add an option to disable the CRON tasks on a non-leader instance. By giving a environment variable we can determine the leader, I think.
@Configuration({
cron: {
enabled: !!node.env.CRON_LEADER
}
})
and in the module get this options to disabled or enabled the cron tasks:
class CronModule {
@Inject()
injector: InjectorService;
@Constant('cron.enabled', false)
cronEnabled: boolean;
$afterListen {
if (!this.cronEnabled){
return;
}
this.getCronProviders().forEach(provider => {
this.addCronProvider(provider);
})
}
For a dynamic environment the leader may change in the runtime - the current leader instance will crash or restart for example.
This basically means that a separate implementation of the leader election and dynamic checking is required, meaning that another dependency should be created.
Also, for dynamic leader election, we will need to check if the current instance is the leader on each planned cron execution tick, not before initializing the cron providers.
@shprota You鈥檝e right :)
Hello,
Somebody want to work on it?
@bpofficial, @IARKI
Hello,
Somebody want to work on it?
@bpofficial, @IARKI
I definitely would like to contribute to tsed project, but at the moment, I don't have that much time. :(
Hey there 馃憢 thanks @Romakita, this thread helped me a lot to understand how to properly create a module and decorators. Coming from nestjs, first thing I looked for was how to create a module like this and It would be really nice to have something on the docs...
I am currently working on getting BullMQ jobs working with tsed and the info here made me think about creating a proper package and decorators for it 馃槃
Although Bull is a full queue system, it has a repeatable cron jobs feature that could be helpful to fix the issues pointed by @shprota
I just implemented a quick demo following your directions and was able to create a bull worker using decorators. I plan to continue working on it this week during my free time and will create a draft PR to get some early feedback on the decorators and architecture.
Hello @mwebler
Cool, I look forward to your PR ;). if you have any question, tell me :)
It would be really nice to have something on the docs...
ok I'll create a complete section on init. Currently the doc is on the wiki: https://github.com/TypedProject/tsed/wiki
I agree, isn't the right place ;)
See you
Romain
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Most helpful comment
Hello @mwebler
Cool, I look forward to your PR ;). if you have any question, tell me :)
ok I'll create a complete section on init. Currently the doc is on the wiki: https://github.com/TypedProject/tsed/wiki
I agree, isn't the right place ;)
See you
Romain