Demo

Cron job

Quark ERP သည် cron ပုံစံနှစ်မျိုး ထောက်ပံ့ပါသည် — process-local @CronJob (in-process timer) နှင့် distributed CronJobService (BullMQ + Redis + Postgres)။ Shared / multi-instance အတွက် BullMQ၊ single-process tick အတွက် @CronJob ကို အသုံးပြုရပါသည်။

အမျိုးအစားနှစ်ခု

အလုပ် မည်သို့ run ရမည်နှင့် ကိုက်သော ပုံစံကို ရွေးချယ်ရပါသည်။

  • Process cron (@CronJob) — @Service resolve ဖြစ်သောအခါ စတင်သော in-process schedule; Node cron package (ဤ process တွင်သာ)
  • BullMQ cron (CronJobService) — Redis queue + Postgres (cron_jobs) metadata; retry၊ repeat limit၊ multi-instance ဘေးကင်းပါသည်

မည်သည့်အချိန်တွင် မည်သည်ကို သုံးမည်

  • BullMQ CronJobService — domain sweep၊ reminder၊ reconcile၊ restart ခံနိုင်ပြီး instance အများနှင့် မှန်ကန်ရမည့် အလုပ်
  • Process @CronJob — single process ပေါ်ရှိ ပေါ့ပါး housekeeping သို့မဟုတ် Redis မလိုသော in-memory tick
  • အလုပ်အသစ်အတွက် @quan-erp-plugins/cron-schedular-backend မသုံးရပါ — တူညီသော API များသည် core တွင် CronJobService အဖြစ် ရှိပါသည်

1. Process cron (@CronJob)

DI-resolved @Service ပေါ်ရှိ method ကို decorate လုပ်ရပါသည်။ Class resolve ဖြစ်သောအခါ core က expression ဖြင့် process-local CronJob စတင်ပါသည်။ DB row / Redis / retry မရှိပါ — service load လုပ်သော process တိုင်း သီးခြား tick လုပ်ပါသည်။

  • expression (လိုအပ်) — cron pattern၊ ဥပမာ 0 * * * *
  • name (optional) — log အတွက် label
  • Class သည် @Service() ဖြစ်ရပါမည် (သို့မဟုတ် DI-resolved) နှင့် module providers တွင် မှတ်ပုံတင်ရပါမည်
TSX@CronJob
1import { CronJob, Module, Service } from "@quan-erp/shared-backend-core"; 2import metadata from "../../module.metadata.json" with { type: "json" }; 3 4@Service() 5export class LocalHousekeepingService { 6 @CronJob({ expression: "0 * * * *", name: "HourlyLocalCleanup" }) 7 async cleanup() { 8 // runs on this Node process only 9 } 10} 11 12@Module({ 13 name: metadata.name, 14 providers: [LocalHousekeepingService], 15 controllers: [], 16 entities: [], 17}) 18export class MyPluginModule {}

2. BullMQ cron (CronJobService)

Job များကို Redis ပေါ်ရှိ BullMQ မှ enqueue လုပ်ပါသည်။ Schedule metadata ကို Postgres (cron_jobs) တွင် သိမ်းပါသည်။ Boot တွင် active schedule များကို Redis သို့ ပြန် load လုပ်ပါသည်။ Callback များသည် in-memory သာဖြစ်သဖြင့် boot တိုင်း listener ပြန်တွဲရပါမည်။

CronJobService ကို inject လုပ်ပါ

Builtin core service ကို ContainerRegistryManager.BUILTIN_PLUGIN ဖြင့် inject လုပ်ရပါသည်။

TSXinject
1import { 2 ContainerRegistryManager, 3 CronJobService, 4 Inject, 5 Service, 6} from "@quan-erp/shared-backend-core"; 7 8@Service() 9export class MaturedSweepService { 10 @Inject(CronJobService, ContainerRegistryManager.BUILTIN_PLUGIN) 11 private cronJobService: CronJobService; 12}

BullMQ job မှတ်ပုံတင်ခြင်း

Unique (pluginName, jobName) pair ဖြင့် register ခေါ်ရပါသည်။ pluginName အဖြစ် metadata.name ကို အသုံးပြုရပါသည်။ register က DB row ကို upsert လုပ်ပြီး Redis scheduler ကို (ပြန်)ဖန်တီးပါသည်။

TSXregister
1import { 2 ContainerRegistryManager, 3 CronJobService, 4 Inject, 5 Service, 6} from "@quan-erp/shared-backend-core"; 7import metadata from "../../module.metadata.json" with { type: "json" }; 8 9@Service() 10export class MaturedSweepService { 11 @Inject(CronJobService, ContainerRegistryManager.BUILTIN_PLUGIN) 12 private cronJobService: CronJobService; 13 14 async registerJob() { 15 await this.cronJobService.register({ 16 cronExpression: "0 2 * * *", 17 pluginName: metadata.name, 18 jobName: "matured-daily-sweep", 19 status: "active", 20 startDate: new Date(), 21 data: { reason: "daily-sweep" }, 22 retry: { attempt: 3, delay: 5_000, type: "exponential" }, 23 callback: async (job) => { 24 await this.runSweep(job.data); 25 }, 26 }); 27 } 28 29 private async runSweep(data: unknown) { /* … */ } 30}

Restart ပြီးနောက် listener များ ပြန်တွဲပါ

Boot တွင် schedule များကို Postgres မှ Redis သို့ ပြန် load လုပ်ပါသည်။ Callback များကို မသိမ်းပါ — @OnAllModuleLoaded တွင် အမြဲ ပြန်မှတ်ပုံတင်ရပါသည် (သို့မဟုတ် register ဖြင့် callback ပြန်ပေးရပါသည်)။

TSXlistener
1import { 2 ContainerRegistryManager, 3 CronJobService, 4 Inject, 5 OnAllModuleLoaded, 6 Service, 7} from "@quan-erp/shared-backend-core"; 8import metadata from "../../module.metadata.json" with { type: "json" }; 9 10@Service() 11export class MaturedSweepService { 12 @Inject(CronJobService, ContainerRegistryManager.BUILTIN_PLUGIN) 13 private cronJobService: CronJobService; 14 15 @OnAllModuleLoaded() 16 async onAllModuleLoaded() { 17 this.cronJobService.addEventListener( 18 metadata.name, 19 "matured-daily-sweep", 20 async (job) => { await this.runSweep(job.data); }, 21 ); 22 } 23 24 private async runSweep(data: unknown) { /* … */ } 25}

Stop / remove (BullMQ)

  • stop(pluginName, jobName) — scheduler ကို ခဏရပ်ပြီး inactive အဖြစ် မှတ်ပါသည်
  • remove(pluginName, jobName) — Redis scheduler၊ DB row နှင့် listener များကို ဖျက်ပါသည်
  • stopAllByPluginName / removeAllByPluginName — uninstall အတွက် bulk helper များ

Query helpers

TSXquery
1import { 2 ContainerRegistryManager, 3 CronJobService, 4 Inject, 5 Service, 6} from "@quan-erp/shared-backend-core"; 7import metadata from "../../module.metadata.json" with { type: "json" }; 8 9@Service() 10export class MaturedSweepService { 11 @Inject(CronJobService, ContainerRegistryManager.BUILTIN_PLUGIN) 12 private cronJobService: CronJobService; 13 14 async listJobs() { 15 const job = await this.cronJobService.getByPluginNameWithJobName( 16 metadata.name, 17 "matured-daily-sweep", 18 ); 19 20 const jobs = await this.cronJobService.getByPluginName({ 21 pluginName: metadata.name, 22 skip: 0, 23 limit: 50, 24 status: "active", 25 }); 26 27 return { job, jobs }; 28 } 29}

CreateCronJobDTO

  • cronExpression (လိုအပ်) — ဥပမာ 0 2 * * *
  • pluginName / jobName (လိုအပ်) — key သည် `${pluginName}/${jobName}`
  • status — active | inactive
  • data — BullMQ job.data ပေါ်ရှိ payload
  • startDate — အစောဆုံး စတင်ချိန် (initial delay)
  • repeat.limit — အများဆုံး အကြိမ်ရေ
  • retry.attempt / delay / type (fixed | exponential)
  • callback — register အချိန်တွင် optional in-memory listener

အကြံပြု BullMQ pattern

  1. 01

    Inject

    ContainerRegistryManager.BUILTIN_PLUGIN ဖြင့် CronJobService။

  2. 02

    Boot တွင် မှတ်ပုံတင်ခြင်း

    @OnAllModuleLoaded တွင် register({ pluginName: metadata.name, jobName, cronExpression, … })။

  3. 03

    Boot တိုင်း listener တွဲပါ

    register ပေါ်တွင် callback နှင့်/သို့မဟုတ် addEventListener — schedule သည် restart ကို ခံနိုင်သော်လည်း handler မခံနိုင်ပါ။

  4. 04

    Cleanup

    Uninstall / disable တွင် orphan Redis scheduler မကျန်အောင် stop သို့မဟုတ် remove လုပ်ရပါသည်။

TSXend-to-end
1import { 2 ContainerRegistryManager, 3 CronJobService, 4 Inject, 5 OnAllModuleLoaded, 6 Service, 7} from "@quan-erp/shared-backend-core"; 8import metadata from "../../module.metadata.json" with { type: "json" }; 9 10const JOB_NAME = "inventory-nightly-reconcile"; 11 12@Service() 13export class InventoryReconcileCronService { 14 @Inject(CronJobService, ContainerRegistryManager.BUILTIN_PLUGIN) 15 private cronJobService: CronJobService; 16 17 @OnAllModuleLoaded() 18 async onAllModuleLoaded() { 19 await this.cronJobService.register({ 20 cronExpression: "0 3 * * *", 21 pluginName: metadata.name, 22 jobName: JOB_NAME, 23 startDate: new Date(), 24 callback: async () => { await this.reconcile(); }, 25 }); 26 } 27 28 private async reconcile() { /* domain work */ } 29}