Use node:Sqlite

This commit is contained in:
2026-07-27 09:19:17 +00:00
parent 17e646c2e3
commit 1723b1cca1
5 changed files with 87 additions and 12 deletions
+11 -1
View File
@@ -16,7 +16,8 @@
"debug": "^4.4.3", "debug": "^4.4.3",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"hono": "^4.12.26", "hono": "^4.12.26",
"kysely": "^0.29.2", "kysely": "^0.29.4",
"kysely-node-native-sqlite": "^1.0.1",
"ws": "^8.21.0", "ws": "^8.21.0",
"zod": "^4.4.3" "zod": "^4.4.3"
}, },
@@ -8314,6 +8315,15 @@
"node": ">=22.0.0" "node": ">=22.0.0"
} }
}, },
"node_modules/kysely-node-native-sqlite": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/kysely-node-native-sqlite/-/kysely-node-native-sqlite-1.0.1.tgz",
"integrity": "sha512-FHty4bi885waQMMMw2P9F+mqZUdozd1OBWQI72CffsQzbZhcOgsz3jpZRTUM1xAtG1wkz/VQAj3JavQvCdhL2w==",
"license": "ISC",
"peerDependencies": {
"kysely": ">=0.27.0"
}
},
"node_modules/language-subtag-registry": { "node_modules/language-subtag-registry": {
"version": "0.3.23", "version": "0.3.23",
"resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+2 -1
View File
@@ -51,7 +51,8 @@
"debug": "^4.4.3", "debug": "^4.4.3",
"dotenv": "^17.4.2", "dotenv": "^17.4.2",
"hono": "^4.12.26", "hono": "^4.12.26",
"kysely": "^0.29.2", "kysely": "^0.29.4",
"kysely-node-native-sqlite": "^1.0.1",
"ws": "^8.21.0", "ws": "^8.21.0",
"zod": "^4.4.3" "zod": "^4.4.3"
}, },
+59
View File
@@ -0,0 +1,59 @@
import { Config } from './services/config.ts';
import { Database, MigrationService } from './services/storage/index.ts';
import { Logger } from './utils/logger.ts';
/** Application composition root. */
export class App {
/**
* Construct infrastructure and validate all routes before returning a runnable app.
*/
static async create(): Promise<App> {
const config = Config.fromEnv();
const debug = new Logger('sync-server-v2');
debug('config loaded: %O', config);
// Persistence must be ready before routes accept traffic.
const database = new Database({ path: config.database.path, debug });
const migrations = new MigrationService(database, debug);
await migrations.migrateToLatest();
return new App(database);
}
private stopPromise: Promise<void> | undefined;
constructor(private readonly database: Database) {}
async start(): Promise<void> {}
/** Stop transports before releasing the database they may still use. */
async stop(): Promise<void> {
this.stopPromise ??= this.database.destroy();
await this.stopPromise;
}
}
const app = await App.create();
let shuttingDown = false;
const shutdown = (signal: NodeJS.Signals): void => {
if (shuttingDown) {
return;
}
shuttingDown = true;
void app.stop().catch((error) => {
console.error('Graceful shutdown failed after ' + signal, error);
process.exitCode = 1;
});
};
process.once('SIGINT', (): void => shutdown('SIGINT'));
process.once('SIGTERM', (): void => shutdown('SIGTERM'));
try {
await app.start();
} catch (error: unknown) {
await app.stop();
throw error;
}
+13 -9
View File
@@ -1,5 +1,6 @@
import DatabaseConstructor from 'better-sqlite3'; import { CompiledQuery, Kysely } from 'kysely';
import { Kysely, SqliteDialect } from 'kysely'; import { NodeNativeSqliteDialect } from 'kysely-node-native-sqlite';
import type { DatabaseTables } from './tables.ts'; import type { DatabaseTables } from './tables.ts';
import type { Logger } from '../../utils/logger.ts'; import type { Logger } from '../../utils/logger.ts';
@@ -13,13 +14,13 @@ export type DatabaseOptions = {
}; };
/** /**
* Thin wrapper around better-sqlite3 and Kysely. * Thin wrapper around Kysely and NodeNativeSqliteDialect (which uses node:sqlite).
* *
* Owns connection setup, pragma configuration, and graceful teardown. * Owns connection setup, pragma configuration, and graceful teardown.
*/ */
export class Database { export class Database {
private readonly debug: Logger; private readonly debug: Logger;
private readonly sqlite: DatabaseConstructor.Database; private readonly dialect: NodeNativeSqliteDialect;
private readonly kysely: Kysely<DatabaseTables>; private readonly kysely: Kysely<DatabaseTables>;
/** /**
@@ -32,13 +33,15 @@ export class Database {
this.debug = options.debug.extend('database'); this.debug = options.debug.extend('database');
// Create the SQLite database. // Create the SQLite database.
this.sqlite = new DatabaseConstructor(options.path); this.dialect = new NodeNativeSqliteDialect(options.path);
this.configurePragmas();
// Create the Kysely database. // Create the Kysely database.
this.kysely = new Kysely<DatabaseTables>({ this.kysely = new Kysely<DatabaseTables>({
dialect: new SqliteDialect({ database: this.sqlite }), dialect: this.dialect,
}); });
// Configure the SQLite pragmas.
this.configurePragmas();
} }
/** /**
@@ -65,7 +68,8 @@ export class Database {
*/ */
private configurePragmas(): void { private configurePragmas(): void {
this.debug('configuring SQLite pragmas'); this.debug('configuring SQLite pragmas');
this.sqlite.pragma('journal_mode = WAL');
this.sqlite.pragma('foreign_keys = ON'); this.kysely.executeQuery(CompiledQuery.raw('PRAGMA journal_mode = WAL'));
this.kysely.executeQuery(CompiledQuery.raw('PRAGMA foreign_keys = ON'));
} }
} }
+2 -1
View File
@@ -12,7 +12,8 @@
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
"noEmit": true, "noEmit": true,
"declaration": true, "declaration": true,
"declarationMap": true "declarationMap": true,
"types": ["node"]
}, },
"exclude": ["node_modules/**/*", "dist/**/*", "test"] "exclude": ["node_modules/**/*", "dist/**/*", "test"]
} }