refactor: migrate from node-sqlite3-wasm to better-sqlite3

Replace all db.run(sql, [params]) calls with db.prepare(sql).run(...params)
across all source modules and test files. Also remove the node-sqlite3-wasm
import from tests/db.test.ts and update the type annotation accordingly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-06 21:17:44 +02:00
co-authored by Claude Sonnet 4.6
parent 25d07a6bc4
commit e760853781
16 changed files with 154 additions and 751 deletions
@@ -44,10 +44,9 @@ router.get('/:id', (req: Request, res: Response): void => {
}
// Update last_seen
db.run(
`UPDATE displays SET last_seen = datetime('now') WHERE id = ?`,
[displayId]
);
db.prepare(
`UPDATE displays SET last_seen = datetime('now') WHERE id = ?`
).run(displayId);
const json = buildDisplayJson(displayId);
if (!json) {
@@ -43,11 +43,10 @@ router.post('/', (req, res): void => {
const { name, description, layout_id, api_token, refresh_sec } = req.body;
if (!name) { res.status(400).json({ error: 'name is required' }); return; }
const result = db.run(
const result = db.prepare(
`INSERT INTO displays (name, description, layout_id, api_token, refresh_sec)
VALUES (?, ?, ?, ?, ?)`,
[name, description ?? null, layout_id ?? null, api_token ?? null, refresh_sec ?? 300]
);
VALUES (?, ?, ?, ?, ?)`
).run(name, description ?? null, layout_id ?? null, api_token ?? null, refresh_sec ?? 300);
const id = result.lastInsertRowid as number;
res.status(201).json(db.prepare('SELECT * FROM displays WHERE id = ?').get(id));
} catch (err) {
@@ -78,7 +77,7 @@ router.put('/:id', (req, res): void => {
}
values.push(id);
db.run(`UPDATE displays SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE displays SET ${fields.join(', ')} WHERE id = ?`).run(...values);
const display = db.prepare('SELECT * FROM displays WHERE id = ?').get(id);
if (!display) { res.status(404).json({ error: 'Display not found' }); return; }
res.json(display);
@@ -91,7 +90,7 @@ router.put('/:id', (req, res): void => {
router.delete('/:id', (req, res): void => {
try {
const db = getDb();
const result = db.run('DELETE FROM displays WHERE id = ?', [parseInt(req.params.id)]);
const result = db.prepare('DELETE FROM displays WHERE id = ?').run(parseInt(req.params.id));
if ((result.changes as number) === 0) { res.status(404).json({ error: 'Display not found' }); return; }
res.status(204).send();
} catch (err) {
+7 -10
View File
@@ -1,16 +1,13 @@
import { Database } from 'node-sqlite3-wasm';
import Database from 'better-sqlite3';
import { migrate001 } from './migrations/001-initial';
let dbInstance: Database | null = null;
let dbInstance: Database.Database | null = null;
export function initDb(path: string): Database {
// node-sqlite3-wasm: pass undefined for in-memory DB
const db = new Database(path === ':memory:' ? undefined : path);
export function initDb(path: string): Database.Database {
const db = new Database(path === ':memory:' ? ':memory:' : path);
// Enable WAL mode (file-based databases only, :memory: uses 'memory' mode)
db.exec('PRAGMA journal_mode = WAL');
// Enable foreign key constraints
db.exec('PRAGMA foreign_keys = ON');
db.pragma('journal_mode = WAL');
db.pragma('foreign_keys = ON');
// Run migrations
migrate001(db);
@@ -19,7 +16,7 @@ export function initDb(path: string): Database {
return db;
}
export function getDb(): Database {
export function getDb(): Database.Database {
if (!dbInstance) {
throw new Error('Database not initialized. Call initDb() first.');
}
@@ -1,4 +1,4 @@
import { Database } from 'node-sqlite3-wasm';
import Database from 'better-sqlite3';
export function migrate001(db: Database): void {
db.exec(`
+3 -4
View File
@@ -19,10 +19,9 @@ async function main(): Promise<void> {
if (userCount.count === 0) {
const hash = bcrypt.hashSync('admin', 10);
db.run(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`,
['admin', hash]
);
db.prepare(
`INSERT INTO users (username, password_hash) VALUES (?, ?)`
).run('admin', hash);
console.log('Created default admin user (admin/admin)');
}
@@ -54,10 +54,9 @@ export function updateDatapointValue(id: number, rawValue: string): Datapoint |
const transformed = applyTransform(rawValue, config);
db.run(
`UPDATE datapoints SET value = ?, status = 'ok', updated_at = datetime('now') WHERE id = ?`,
[transformed, id]
);
db.prepare(
`UPDATE datapoints SET value = ?, status = 'ok', updated_at = datetime('now') WHERE id = ?`
).run(transformed, id);
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint | undefined;
}
@@ -87,7 +86,7 @@ export function updateDatapoint(id: number, data: DatapointUpdate): Datapoint |
if (fields.length === 0) return getDatapointById(id);
values.push(id);
db.run(`UPDATE datapoints SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE datapoints SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getDatapointById(id);
}
@@ -97,11 +96,10 @@ export function createDatapointFromMqtt(
topicPath: string
): Datapoint {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO datapoints (name, label, source_type, source_id, topic_id, enabled)
VALUES (?, ?, 'mqtt', ?, ?, 1)`,
[topicPath, topicPath, brokerId, topicId]
);
VALUES (?, ?, 'mqtt', ?, ?, 1)`
).run(topicPath, topicPath, brokerId, topicId);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint;
}
@@ -112,11 +110,10 @@ export function createDatapointFromPg(
queryName: string
): Datapoint {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO datapoints (name, label, source_type, source_id, query_id, enabled)
VALUES (?, ?, 'postgresql', ?, ?, 1)`,
[queryName, queryName, sourceId, queryId]
);
VALUES (?, ?, 'postgresql', ?, ?, 1)`
).run(queryName, queryName, sourceId, queryId);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM datapoints WHERE id = ?').get(id) as Datapoint;
}
@@ -92,17 +92,16 @@ export function getLayoutById(id: number): LayoutWithElements | undefined {
export function createLayout(data: CreateLayoutData): Layout {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO layouts (name, type, width, height, grid_cols, grid_rows)
VALUES (?, ?, ?, ?, ?, ?)`,
[
data.name,
data.type ?? 'free',
data.width ?? 800,
data.height ?? 480,
data.grid_cols ?? null,
data.grid_rows ?? null,
]
VALUES (?, ?, ?, ?, ?, ?)`
).run(
data.name,
data.type ?? 'free',
data.width ?? 800,
data.height ?? 480,
data.grid_cols ?? null,
data.grid_rows ?? null,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout;
@@ -125,41 +124,40 @@ export function updateLayout(id: number, data: UpdateLayoutData): Layout | undef
}
values.push(id);
db.run(`UPDATE layouts SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE layouts SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return db.prepare('SELECT * FROM layouts WHERE id = ?').get(id) as Layout | undefined;
}
export function deleteLayout(id: number): boolean {
const db = getDb();
const result = db.run('DELETE FROM layouts WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM layouts WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
export function addElement(layoutId: number, data: CreateElementData): LayoutElement {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO layout_elements
(layout_id, datapoint_id, type, x, y, width, height, font_size, font_weight,
text_align, icon, style, static_text, grid_col, grid_row, sort_order)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
layoutId,
data.datapoint_id ?? null,
data.type,
data.x ?? 0,
data.y ?? 0,
data.width ?? null,
data.height ?? null,
data.font_size ?? 24,
data.font_weight ?? 'normal',
data.text_align ?? 'left',
data.icon ?? null,
data.style ?? 'plain',
data.static_text ?? null,
data.grid_col ?? null,
data.grid_row ?? null,
data.sort_order ?? 0,
]
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
).run(
layoutId,
data.datapoint_id ?? null,
data.type,
data.x ?? 0,
data.y ?? 0,
data.width ?? null,
data.height ?? null,
data.font_size ?? 24,
data.font_weight ?? 'normal',
data.text_align ?? 'left',
data.icon ?? null,
data.style ?? 'plain',
data.static_text ?? null,
data.grid_col ?? null,
data.grid_row ?? null,
data.sort_order ?? 0,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement;
@@ -200,12 +198,12 @@ export function updateElement(id: number, data: UpdateElementData): LayoutElemen
}
values.push(id);
db.run(`UPDATE layout_elements SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE layout_elements SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return db.prepare('SELECT * FROM layout_elements WHERE id = ?').get(id) as LayoutElement | undefined;
}
export function deleteElement(id: number): boolean {
const db = getDb();
const result = db.run('DELETE FROM layout_elements WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM layout_elements WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
@@ -55,18 +55,17 @@ export function getBrokerById(id: number): MqttBroker | undefined {
export function createBroker(data: CreateBrokerData): MqttBroker {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO mqtt_brokers (name, host, port, username, password, use_tls, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?)`,
[
data.name,
data.host,
data.port ?? 1883,
data.username ?? null,
data.password ?? null,
data.use_tls ?? 0,
data.enabled ?? 1,
]
VALUES (?, ?, ?, ?, ?, ?, ?)`
).run(
data.name,
data.host,
data.port ?? 1883,
data.username ?? null,
data.password ?? null,
data.use_tls ?? 0,
data.enabled ?? 1,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM mqtt_brokers WHERE id = ?').get(id) as MqttBroker;
@@ -89,14 +88,14 @@ export function updateBroker(id: number, data: UpdateBrokerData): MqttBroker | u
fields.push("updated_at = datetime('now')");
values.push(id);
db.run(`UPDATE mqtt_brokers SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE mqtt_brokers SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getBrokerById(id);
}
export function deleteBroker(id: number): boolean {
const db = getDb();
disconnectBroker(id);
const result = db.run('DELETE FROM mqtt_brokers WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM mqtt_brokers WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
@@ -107,10 +106,9 @@ function buildConnectUrl(broker: MqttBroker): string {
function setStatus(id: number, status: string): void {
const db = getDb();
db.run(
`UPDATE mqtt_brokers SET status = ?, updated_at = datetime('now') WHERE id = ?`,
[status, id]
);
db.prepare(
`UPDATE mqtt_brokers SET status = ?, updated_at = datetime('now') WHERE id = ?`
).run(status, id);
}
export function connectBroker(id: number): Promise<mqtt.MqttClient> {
@@ -172,7 +170,7 @@ export function connectBroker(id: number): Promise<mqtt.MqttClient> {
.get(id, t) as MqttTopic | undefined;
if (!topicRecord) continue;
db.run('UPDATE mqtt_topics SET last_payload = ? WHERE id = ?', [p, topicRecord.id]);
db.prepare('UPDATE mqtt_topics SET last_payload = ? WHERE id = ?').run(p, topicRecord.id);
const extracted = extractValue(p, topicRecord.json_path);
if (extracted === null) continue;
@@ -40,7 +40,7 @@ export function updateTopic(id: number, data: UpdateTopicData): MqttTopic | unde
}
values.push(id);
db.run(`UPDATE mqtt_topics SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE mqtt_topics SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return db.prepare('SELECT * FROM mqtt_topics WHERE id = ?').get(id) as MqttTopic | undefined;
}
@@ -51,11 +51,10 @@ function ensureTopicExists(brokerId: number, topic: string): void {
.get(brokerId, topic);
if (!existing) {
db.run(
db.prepare(
`INSERT INTO mqtt_topics (broker_id, topic, selected, ignored)
VALUES (?, ?, 0, 0)`,
[brokerId, topic]
);
VALUES (?, ?, 0, 0)`
).run(brokerId, topic);
}
}
@@ -105,10 +104,9 @@ export function startDiscovery(brokerId: number, prefix?: string): Promise<MqttT
discoveredTopics.add(t);
ensureTopicExists(brokerId, t);
}
db.run(
'UPDATE mqtt_topics SET last_payload = ? WHERE broker_id = ? AND topic = ?',
[p, brokerId, t]
);
db.prepare(
'UPDATE mqtt_topics SET last_payload = ? WHERE broker_id = ? AND topic = ?'
).run(p, brokerId, t);
}
payloadCache.clear();
};
@@ -179,11 +177,10 @@ export function addManualTopic(brokerId: number, topic: string): MqttTopic {
if (existing) return existing;
const result = db.run(
const result = db.prepare(
`INSERT INTO mqtt_topics (broker_id, topic, selected, ignored)
VALUES (?, ?, 0, 0)`,
[brokerId, topic]
);
VALUES (?, ?, 0, 0)`
).run(brokerId, topic);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM mqtt_topics WHERE id = ?').get(id) as MqttTopic;
}
@@ -39,16 +39,15 @@ export function getQueryById(id: number): PgQuery | undefined {
export function createQuery(data: CreateQueryData): PgQuery {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO pg_queries (source_id, name, query, interval_sec, result_column)
VALUES (?, ?, ?, ?, ?)`,
[
data.source_id,
data.name,
data.query,
data.interval_sec ?? 60,
data.result_column,
]
VALUES (?, ?, ?, ?, ?)`
).run(
data.source_id,
data.name,
data.query,
data.interval_sec ?? 60,
data.result_column,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM pg_queries WHERE id = ?').get(id) as PgQuery;
@@ -67,14 +66,14 @@ export function updateQuery(id: number, data: UpdateQueryData): PgQuery | undefi
if (fields.length === 0) return getQueryById(id);
values.push(id);
db.run(`UPDATE pg_queries SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE pg_queries SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getQueryById(id);
}
export function deleteQuery(id: number): boolean {
const db = getDb();
stopQueryTimer(id);
const result = db.run('DELETE FROM pg_queries WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM pg_queries WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
@@ -99,10 +98,9 @@ async function executeQuery(queryId: number): Promise<void> {
const valueStr = String(rawValue);
const db = getDb();
db.run(
`UPDATE pg_queries SET last_result = ?, last_run_at = datetime('now') WHERE id = ?`,
[valueStr, queryId]
);
db.prepare(
`UPDATE pg_queries SET last_result = ?, last_run_at = datetime('now') WHERE id = ?`
).run(valueStr, queryId);
const dp = getDatapointByQueryId(queryId);
if (dp) {
@@ -42,19 +42,18 @@ export function getSourceById(id: number): PgSource | undefined {
export function createSource(data: CreateSourceData): PgSource {
const db = getDb();
const result = db.run(
const result = db.prepare(
`INSERT INTO pg_sources (name, host, port, database_name, username, password, use_tls, enabled)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`,
[
data.name,
data.host,
data.port ?? 5432,
data.database_name,
data.username,
data.password,
data.use_tls ?? 0,
data.enabled ?? 1,
]
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
).run(
data.name,
data.host,
data.port ?? 5432,
data.database_name,
data.username,
data.password,
data.use_tls ?? 0,
data.enabled ?? 1,
);
const id = result.lastInsertRowid as number;
return db.prepare('SELECT * FROM pg_sources WHERE id = ?').get(id) as PgSource;
@@ -77,20 +76,20 @@ export function updateSource(id: number, data: UpdateSourceData): PgSource | und
if (fields.length === 0) return getSourceById(id);
values.push(id);
db.run(`UPDATE pg_sources SET ${fields.join(', ')} WHERE id = ?`, values);
db.prepare(`UPDATE pg_sources SET ${fields.join(', ')} WHERE id = ?`).run(...values);
return getSourceById(id);
}
export function deleteSource(id: number): boolean {
const db = getDb();
disconnectSource(id);
const result = db.run('DELETE FROM pg_sources WHERE id = ?', [id]);
const result = db.prepare('DELETE FROM pg_sources WHERE id = ?').run(id);
return (result.changes as number) > 0;
}
function setStatus(id: number, status: string): void {
const db = getDb();
db.run('UPDATE pg_sources SET status = ? WHERE id = ?', [status, id]);
db.prepare('UPDATE pg_sources SET status = ? WHERE id = ?').run(status, id);
}
export async function connectSource(id: number): Promise<Pool> {