[taskboard] add dispatch control plane
This commit is contained in:
94
lib/db.ts
94
lib/db.ts
@@ -7,6 +7,41 @@ const DB_PATH = process.env.DB_PATH || path.join(process.cwd(), "data", "tasks.d
|
||||
fs.mkdirSync(path.dirname(DB_PATH), { recursive: true });
|
||||
|
||||
let database: sqlite3.Database | null = null;
|
||||
let databaseReady: Promise<void> | null = null;
|
||||
|
||||
function getColumnNames(db: sqlite3.Database, tableName: string) {
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
db.all<{ name: string }>(`PRAGMA table_info(${tableName})`, [], (error, rows) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve(rows.map((row) => row.name));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async function ensureColumn(
|
||||
db: sqlite3.Database,
|
||||
tableName: string,
|
||||
columnName: string,
|
||||
definition: string,
|
||||
) {
|
||||
const columnNames = await getColumnNames(db, tableName);
|
||||
if (columnNames.includes(columnName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
db.run(`ALTER TABLE ${tableName} ADD COLUMN ${columnName} ${definition}`, (error) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function getDatabase() {
|
||||
if (database) {
|
||||
@@ -41,12 +76,63 @@ function getDatabase() {
|
||||
timestamp TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)
|
||||
`);
|
||||
database?.run(`
|
||||
CREATE TABLE IF NOT EXISTS task_events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
task_id INTEGER NOT NULL,
|
||||
assignee TEXT NOT NULL DEFAULT '',
|
||||
family TEXT,
|
||||
host TEXT NOT NULL DEFAULT '',
|
||||
event_type TEXT NOT NULL,
|
||||
state TEXT,
|
||||
summary TEXT NOT NULL,
|
||||
detail TEXT NOT NULL DEFAULT '',
|
||||
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||
)
|
||||
`);
|
||||
database?.run(`
|
||||
CREATE INDEX IF NOT EXISTS idx_task_events_task_time
|
||||
ON task_events(task_id, created_at DESC)
|
||||
`);
|
||||
database?.run(`
|
||||
CREATE INDEX IF NOT EXISTS idx_task_events_assignee_time
|
||||
ON task_events(assignee, created_at DESC)
|
||||
`);
|
||||
});
|
||||
|
||||
databaseReady = (async () => {
|
||||
if (!database) {
|
||||
return;
|
||||
}
|
||||
|
||||
await ensureColumn(database, "tasks", "family", "TEXT");
|
||||
await ensureColumn(database, "tasks", "target_host", "TEXT NOT NULL DEFAULT ''");
|
||||
await ensureColumn(database, "tasks", "target_channel", "TEXT NOT NULL DEFAULT ''");
|
||||
await ensureColumn(database, "tasks", "dispatch_method", "TEXT NOT NULL DEFAULT 'manual'");
|
||||
await ensureColumn(database, "tasks", "dispatch_state", "TEXT NOT NULL DEFAULT 'planned'");
|
||||
await ensureColumn(database, "tasks", "template_key", "TEXT");
|
||||
await ensureColumn(database, "tasks", "repo_slug", "TEXT");
|
||||
await ensureColumn(database, "tasks", "base_branch", "TEXT");
|
||||
await ensureColumn(database, "tasks", "preferred_agent", "TEXT");
|
||||
await ensureColumn(database, "tasks", "reasoning_effort", "TEXT");
|
||||
await ensureColumn(database, "tasks", "model_hint", "TEXT");
|
||||
await ensureColumn(database, "tasks", "last_dispatch_at", "TEXT");
|
||||
await ensureColumn(database, "tasks", "acknowledged_at", "TEXT");
|
||||
await ensureColumn(database, "tasks", "last_error", "TEXT");
|
||||
})();
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
export function all<T>(sql: string, params: unknown[] = []) {
|
||||
async function ensureReady() {
|
||||
getDatabase();
|
||||
if (databaseReady) {
|
||||
await databaseReady;
|
||||
}
|
||||
}
|
||||
|
||||
export async function all<T>(sql: string, params: unknown[] = []) {
|
||||
await ensureReady();
|
||||
const db = getDatabase();
|
||||
return new Promise<T[]>((resolve, reject) => {
|
||||
db.all(sql, params, (error, rows) => {
|
||||
@@ -59,7 +145,8 @@ export function all<T>(sql: string, params: unknown[] = []) {
|
||||
});
|
||||
}
|
||||
|
||||
export function get<T>(sql: string, params: unknown[] = []) {
|
||||
export async function get<T>(sql: string, params: unknown[] = []) {
|
||||
await ensureReady();
|
||||
const db = getDatabase();
|
||||
return new Promise<T | undefined>((resolve, reject) => {
|
||||
db.get(sql, params, (error, row) => {
|
||||
@@ -72,7 +159,8 @@ export function get<T>(sql: string, params: unknown[] = []) {
|
||||
});
|
||||
}
|
||||
|
||||
export function run(sql: string, params: unknown[] = []) {
|
||||
export async function run(sql: string, params: unknown[] = []) {
|
||||
await ensureReady();
|
||||
const db = getDatabase();
|
||||
return new Promise<{ lastID: number; changes: number }>((resolve, reject) => {
|
||||
db.run(sql, params, function onRun(error) {
|
||||
|
||||
Reference in New Issue
Block a user