- Quick Start
- Project Structure & Naming
- Task Triggers
- Task Event
- Calling a Task from Another Task
- Killing or Canceling Task
- Task Options
- Project Status
- To do
@xlsft/worker is a simple but powerful worker framework. Supported on node
, deno
and bun
.
It automatically loads and schedules all *.task.[ts,js,mts,mjs]
files from the tasks
directory.
Tasks can run on a cron schedule or be triggered by events.
Install @xlsft/worker
package
pnpm add jsr:@xlsft/worker # or...
yarn add jsr:@xlsft/worker # or...
npx jsr add @xlsft/worker # or...
bunx jsr add @xlsft/worker
Warning
Do not use deno add jsr:@xlsft/worker
! Due to JSR limitations with dynamic imports, it fails when registering tasks!
Create tasks
folder and entry file
import { defineWorker } from "@xlsft/worker";
defineWorker()
You can pass custom logger or disable logging whatsoever
import { defineWorker } from "@xlsft/worker";
const log = useLogger() // Some logger
defineWorker({ log })
import { defineWorker } from "@xlsft/worker";
defineWorker({ log: false }) // Disables logging
Also you can change default tasks
folder, be aware that it needs to be path from process.cwd()
import { defineWorker } from "@xlsft/worker";
defineWorker({ dir: 'src/tasks' })
Then run your script with following commands
deno run -A main.ts # or...
bun run main.ts # or...
node main.js
All tasks must be placed in the tasks
directory (or directory of your choice).
File names follow the pattern:
<nn(optional)>.<name>.task.[ts,js,mts,mjs] // 01.some_job.task.ts || some_job.task.js || 03.job.task.mjs
Where:
nn(optional)
— numeric prefix for ordering in directoryname
— task name.task.[ts,js,mts,mjs]
— required suffix
Example:
tasks/
│── 01.cron.task.ts
│── 02.event.task.ts
│── other.task.ts
A task trigger can be:
- Cron based — using
TaskCronScheduleSchema
-like type orcron syntax
- Event based — for manual or cross-task triggering with
EventEmitter
- Date based - for triggering task only once at some date
Examples:
- Cron based
cron syntax
:
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Running every minute');
}, '* * * * *');
- Cron based
TaskCronScheduleSchema
:
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Running every 30 minutes');
}, { minute: { every: 30 } });
- Event based:
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Triggered by event.emit("event")');
}, 'event');
- Event based from generated trigger (from task name):
// name.task.ts
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Triggered by event.emit("name")');
});
// Works with cron too
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Triggered by event.emit("name") and every 30 minutes');
}, { minute: { every: 30 } });
- Date based:
// name.task.ts
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Triggered at Tue Jan 01 2030 00:00:00');
}, new Date(2030, 0, 1));
%%{init: {'flowchart': {'nodeSpacing': 12,'rankSpacing':24}}}%%
flowchart TD
Event[Event Emitter] --> TW
Cron[Cron-like Scheduler] --> TW[Task Workers]
Date[Date Scheduler] --> TW
subgraph TW[ ]
direction BT
TW1[Task Worker 1]
TW2[Task Worker 2]
TW3[Task Worker 3]
TW4[Task Worker ...n]
end
For every task event: TaskEvent
class is passed for logging or some other purposes
// event.task.ts
import { defineTask } from "@xlsft/worker"
export default defineTask((event) => {
console.log(event);
// TaskEvent {
// worker: [Function: worker],
// created: 2025-08-14T16:34:31.990Z,
// state: { status: "running" },
// data: { name: "event", trigger: "event", cron: false },
// emit: [Function: emit]
// }
});
Ensure you doesn`t modify event class, because event
is readonly
import { defineTask } from "@xlsft/worker"
export default defineTask((event) => {
console.log(event);
event.state.status = 'canceled'; // TS error
}, { minute: { every: 1 }});
You can call (trigger) another task by emitting an event:
// 01.parent.task.ts
import { defineTask } from "@xlsft/worker";
export default defineTask((event) => {
console.log('Parent job running...');
event.emit('children');
}, { minute: { every: 1 } });
// 02.children.task.ts
import { defineTask, events } from "tasks";
export default defineTask((event) => {
console.log('Children job running');
}, 'children');
%%{init: {'flowchart': {'nodeSpacing': 12,'rankSpacing':24}}}%%
flowchart LR
Parent[Parent Task] --> Bus[Event Bus]
Bus --> Child[Child Task]
You can kill or cancel task using event.kill()
or event.cancel()
The difference between canceling and killing a job is that canceling stops only the current run and allows you to start a new one, whereas killing stops the job entirely and it cannot be restarted
// first_job.task.ts
import { defineTask } from "@xlsft/worker"
export default defineTask((event) => {
console.log('Job running');
event.cancel();
console.log('Unreachable');
}, 'first_job');
// second_job.task.ts
import { defineTask } from "@xlsft/worker"
export default defineTask((event) => {
console.log('Start first job');
event.emit('first_job'); // Works!
}, { minute: { every: 1 }});
// first_job.task.ts
import { defineTask } from "@xlsft/worker"
export default defineTask((event) => {
console.log('Job running');
event.kill();
console.log('Unreachable');
}, 'first_job');
// second_job.task.ts
import { defineTask } from "@xlsft/worker"
export default defineTask((event) => {
console.log('Start first job');
event.emit('first_job'); // Doesn`t work :(
}, { minute: { every: 1 }});
Now you can set 3 different modifications to your worker
retry
– number of additional attempts the worker will make if the task failstimes
– number of times the task should run in a single executiondelay
– time in milliseconds to wait between task executions or retries
export default defineTask((event) => {
console.log('Task starts, but fails');
throw new Error('Error');
}, 'event', { times: 2, delay: 2000, retry: 5 });
In this example:
- The task will run 2 times (times: 2), but, if it fails, job will retry with 2 times again
- It will wait 2 seconds between each run or retry (delay: 2000)
- If it fails, it will retry up to 5 times (retry: 5)
Testing in production — API and internal structure are stable, but needs testing for edge-cases
- Pass logger into event object with custom prefix
- Throw an error if try to emit cron string
- Add an coroutines to tasks (at least try to)
- Add coroutines docs
- Add log at start of the task
- Check for infinity in retry function
- Retry function in coroutines
- Emit working on cron triggers by name
- Depends option in task
- Add dependencies docs