Skip to content

Commit 5d340ab

Browse files
committed
feat: enhance task driver manager and plugin options
- Added `driver` property to `TaskDriverManager` class for better driver management. - Introduced `initializeDefaultDriver` option in `TasksPluginOptions` to control default driver initialization. - Updated documentation to reflect changes in driver setup and options. - Improved SQLiteDriver with statement caching for better performance.
1 parent 0f47819 commit 5d340ab

File tree

10 files changed

+120
-51
lines changed

10 files changed

+120
-51
lines changed

apps/website/docs/api-reference/tasks/classes/task-driver-manager.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ const taskId = await manager.createTask({
4141

4242
```ts title="Signature"
4343
class TaskDriverManager {
44+
public driver: TaskDriver | null = null;
4445
setDriver(driver: TaskDriver) => void;
4546
createTask(task: TaskData) => Promise<string>;
4647
deleteTask(identifier: string) => Promise<void>;
@@ -50,6 +51,11 @@ class TaskDriverManager {
5051

5152
<div className="members-wrapper">
5253

54+
### driver
55+
56+
<MemberInfo kind="property" type={`<a href='/docs/next/api-reference/tasks/interfaces/task-driver#taskdriver'>TaskDriver</a> | null`} />
57+
58+
5359
### setDriver
5460

5561
<MemberInfo kind="method" type={`(driver: <a href='/docs/next/api-reference/tasks/interfaces/task-driver#taskdriver'>TaskDriver</a>) => void`} />

apps/website/docs/api-reference/tasks/classes/tasks-plugin.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
1313

1414
## TasksPlugin
1515

16-
<GenerationInfo sourceFile="packages/tasks/src/plugin.ts" sourceLine="49" packageName="@commandkit/tasks" />
16+
<GenerationInfo sourceFile="packages/tasks/src/plugin.ts" sourceLine="54" packageName="@commandkit/tasks" />
1717

1818
CommandKit plugin that provides task management capabilities.
1919

apps/website/docs/api-reference/tasks/interfaces/tasks-plugin-options.mdx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
1313

1414
## TasksPluginOptions
1515

16-
<GenerationInfo sourceFile="packages/tasks/src/plugin.ts" sourceLine="25" packageName="@commandkit/tasks" />
16+
<GenerationInfo sourceFile="packages/tasks/src/plugin.ts" sourceLine="24" packageName="@commandkit/tasks" />
1717

1818
Configuration options for the tasks plugin.
1919

@@ -22,6 +22,20 @@ Future versions may support customizing the tasks directory path and HMR behavio
2222

2323
```ts title="Signature"
2424
interface TasksPluginOptions {
25-
25+
initializeDefaultDriver?: boolean;
2626
}
2727
```
28+
29+
<div className="members-wrapper">
30+
31+
### initializeDefaultDriver
32+
33+
<MemberInfo kind="property" type={`boolean`} default={`true`} />
34+
35+
Whether to initialize the default driver.
36+
37+
If true, the plugin will initialize the default driver.
38+
If false, the plugin will not initialize the default driver.
39+
40+
41+
</div>

apps/website/docs/guide/17-tasks/01-getting-started.mdx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ export default defineConfig({
2828
});
2929
```
3030

31-
## Setting Up a Driver
31+
## Setting Up a Driver (optional)
3232

33-
Configure a driver before the tasks plugin loads:
33+
By default, the plugin will initialize the sqlite driver. You can set up a different driver by calling `setDriver` function from the `@commandkit/tasks` package.
34+
If you want to disable the default driver initialization behavior, you can pass `initializeDefaultDriver: false` to the `tasks()` options in your commandkit config.
3435

3536
```ts
3637
import { setDriver } from '@commandkit/tasks';

apps/website/docs/guide/17-tasks/02-task-drivers.mdx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,10 @@ class CustomDriver implements TaskDriver {
173173
async create(task: TaskData): Promise<string> {
174174
// Implement your scheduling logic
175175
const id = await this.scheduler.schedule(task);
176+
177+
// invoke the runner function to execute the task (normally, this would be invoked by the scheduler)
178+
await this.runner?.(task);
179+
176180
return id;
177181
}
178182

packages/tasks/README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ export default defineConfig({
2929
});
3030
```
3131

32-
### 2. Set up a driver
32+
### 2. Set up a driver (optional)
33+
34+
By default, the plugin will initialize the sqlite driver. You can set up a different driver by calling `setDriver` function from the `@commandkit/tasks` package. If you want to disable the default driver initialization behavior, you can pass `initializeDefaultDriver: false` to the `tasks()` options in your commandkit config.
3335

3436
```ts
3537
import { setDriver } from '@commandkit/tasks';

packages/tasks/src/driver-manager.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { PartialTaskData, TaskData } from './types';
2424
* ```
2525
*/
2626
export class TaskDriverManager {
27-
private driver: TaskDriver | null = null;
27+
public driver: TaskDriver | null = null;
2828

2929
/**
3030
* Sets the active task driver.

packages/tasks/src/drivers/sqlite.ts

Lines changed: 60 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { TaskDriver, TaskRunner } from '../driver';
22
import { TaskData } from '../types';
3-
import { DatabaseSync } from 'node:sqlite';
3+
import { DatabaseSync, StatementSync } from 'node:sqlite';
44
import cronParser from 'cron-parser';
55

66
/**
@@ -21,6 +21,14 @@ export class SQLiteDriver implements TaskDriver {
2121
private runner: TaskRunner | null = null;
2222
private db: DatabaseSync;
2323
private interval: NodeJS.Timeout | null = null;
24+
private statements!: {
25+
count: StatementSync;
26+
select: StatementSync;
27+
insert: StatementSync;
28+
delete: StatementSync;
29+
updateNextRun: StatementSync;
30+
updateCompleted: StatementSync;
31+
};
2432

2533
/**
2634
* Create a new SQLiteDriver instance.
@@ -43,7 +51,7 @@ export class SQLiteDriver implements TaskDriver {
4351
* Initialize the jobs table and start the polling loop.
4452
*/
4553
private init() {
46-
this.db.exec(`CREATE TABLE IF NOT EXISTS jobs (
54+
this.db.exec(/* sql */ `CREATE TABLE IF NOT EXISTS jobs (
4755
id INTEGER PRIMARY KEY AUTOINCREMENT,
4856
name TEXT NOT NULL,
4957
data TEXT,
@@ -55,6 +63,26 @@ export class SQLiteDriver implements TaskDriver {
5563
created_at INTEGER NOT NULL,
5664
last_run INTEGER
5765
)`);
66+
67+
this.statements = {
68+
count: this.db.prepare(
69+
/* sql */ `SELECT COUNT(*) as count FROM jobs WHERE status = 'pending' AND next_run <= ?`,
70+
),
71+
select: this.db.prepare(
72+
/* sql */ `SELECT * FROM jobs WHERE status = 'pending' AND next_run <= ? ORDER BY next_run ASC LIMIT ? OFFSET ?`,
73+
),
74+
insert: this.db.prepare(
75+
/* sql */ `INSERT INTO jobs (name, data, schedule_type, schedule_value, timezone, next_run, status, created_at) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`,
76+
),
77+
delete: this.db.prepare(/* sql */ `DELETE FROM jobs WHERE id = ?`),
78+
updateNextRun: this.db.prepare(
79+
/* sql */ `UPDATE jobs SET next_run = ?, last_run = ? WHERE id = ?`,
80+
),
81+
updateCompleted: this.db.prepare(
82+
/* sql */ `UPDATE jobs SET status = 'completed', last_run = ? WHERE id = ?`,
83+
),
84+
};
85+
5886
this.startPolling();
5987
}
6088

@@ -82,10 +110,7 @@ export class SQLiteDriver implements TaskDriver {
82110
nextRun = typeof schedule === 'number' ? schedule : schedule.getTime();
83111
}
84112

85-
const stmt = this.db.prepare(
86-
`INSERT INTO jobs (name, data, schedule_type, schedule_value, timezone, next_run, status, created_at) VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)`,
87-
);
88-
const result = stmt.run(
113+
const result = this.statements.insert.run(
89114
name,
90115
JSON.stringify(data ?? {}),
91116
scheduleType,
@@ -111,8 +136,7 @@ export class SQLiteDriver implements TaskDriver {
111136
* @param identifier Job ID
112137
*/
113138
async delete(identifier: string): Promise<void> {
114-
const stmt = this.db.prepare(`DELETE FROM jobs WHERE id = ?`);
115-
stmt.run(identifier);
139+
this.statements.delete.run(identifier);
116140
}
117141

118142
/**
@@ -129,7 +153,7 @@ export class SQLiteDriver implements TaskDriver {
129153
*/
130154
private startPolling() {
131155
if (this.interval) clearInterval(this.interval);
132-
this.interval = setInterval(() => this.pollJobs(), 1000);
156+
this.interval = setInterval(() => this.pollJobs(), 1000).unref();
133157
// Run immediately on startup
134158
this.pollJobs();
135159
}
@@ -140,24 +164,30 @@ export class SQLiteDriver implements TaskDriver {
140164
private pollJobs() {
141165
if (!this.runner) return;
142166
const now = Date.now();
143-
const stmt = this.db.prepare(
144-
`SELECT * FROM jobs WHERE status = 'pending' AND next_run <= ?`,
145-
);
146-
const rows = stmt.all(now) as Array<{
147-
id: number;
148-
name: string;
149-
data: string;
150-
schedule_type: string;
151-
schedule_value: string;
152-
timezone: string | null;
153-
next_run: number;
154-
status: string;
155-
created_at: number;
156-
last_run: number | null;
157-
}>;
158-
159-
for (const job of rows) {
160-
this.executeJob(job);
167+
const chunkSize = 10;
168+
169+
const countResult = this.statements.count.get(now) as { count: number };
170+
const totalJobs = countResult.count;
171+
172+
if (totalJobs === 0) return;
173+
174+
for (let offset = 0; offset < totalJobs; offset += chunkSize) {
175+
const rows = this.statements.select.all(now, chunkSize, offset) as Array<{
176+
id: number;
177+
name: string;
178+
data: string;
179+
schedule_type: string;
180+
schedule_value: string;
181+
timezone: string | null;
182+
next_run: number;
183+
status: string;
184+
created_at: number;
185+
last_run: number | null;
186+
}>;
187+
188+
for (const job of rows) {
189+
this.executeJob(job);
190+
}
161191
}
162192
}
163193

@@ -202,21 +232,12 @@ export class SQLiteDriver implements TaskDriver {
202232
nextRun = null;
203233
}
204234
if (nextRun) {
205-
const stmt = this.db.prepare(
206-
`UPDATE jobs SET next_run = ?, last_run = ? WHERE id = ?`,
207-
);
208-
stmt.run(nextRun, now, job.id);
235+
this.statements.updateNextRun.run(nextRun, now, job.id);
209236
} else {
210-
const stmt = this.db.prepare(
211-
`UPDATE jobs SET status = 'completed', last_run = ? WHERE id = ?`,
212-
);
213-
stmt.run(now, job.id);
237+
this.statements.updateCompleted.run(now, job.id);
214238
}
215239
} else {
216-
const stmt = this.db.prepare(
217-
`UPDATE jobs SET status = 'completed', last_run = ? WHERE id = ?`,
218-
);
219-
stmt.run(now, job.id);
240+
this.statements.updateCompleted.run(now, job.id);
220241
}
221242
}
222243
}

packages/tasks/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,10 @@ import { TasksPlugin, TasksPluginOptions } from './plugin';
2727
* @returns A configured tasks plugin instance
2828
*/
2929
export function tasks(options?: TasksPluginOptions) {
30-
return new TasksPlugin(options ?? {});
30+
return new TasksPlugin({
31+
initializeDefaultDriver: true,
32+
...options,
33+
});
3134
}
3235

3336
export * from './plugin';

packages/tasks/src/plugin.ts

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
import {
2-
CommandKit,
32
CommandKitPluginRuntime,
43
Logger,
54
RuntimePlugin,
@@ -23,9 +22,15 @@ import { existsSync } from 'node:fs';
2322
* Future versions may support customizing the tasks directory path and HMR behavior.
2423
*/
2524
export interface TasksPluginOptions {
26-
// Future options may include:
27-
// tasksPath?: string;
28-
// enableHMR?: boolean;
25+
/**
26+
* Whether to initialize the default driver.
27+
*
28+
* If true, the plugin will initialize the default driver.
29+
* If false, the plugin will not initialize the default driver.
30+
*
31+
* @default true
32+
*/
33+
initializeDefaultDriver?: boolean;
2934
}
3035

3136
/**
@@ -64,6 +69,19 @@ export class TasksPlugin extends RuntimePlugin<TasksPluginOptions> {
6469
* @param ctx - The CommandKit plugin runtime context
6570
*/
6671
public async activate(ctx: CommandKitPluginRuntime): Promise<void> {
72+
if (this.options.initializeDefaultDriver && !taskDriverManager.driver) {
73+
try {
74+
const { SQLiteDriver } =
75+
require('./drivers/sqlite') as typeof import('./drivers/sqlite');
76+
77+
taskDriverManager.setDriver(new SQLiteDriver());
78+
} catch (e: any) {
79+
Logger.error(
80+
`Failed to initialize the default driver for tasks plugin: ${e?.stack || e}`,
81+
);
82+
}
83+
}
84+
6785
taskDriverManager.setTaskRunner(async (task) => {
6886
try {
6987
const taskInstance = this.tasks.get(task.name);

0 commit comments

Comments
 (0)