feat(db): provide better-sqlite3 connection via DI token and complete DatabaseService surface

This commit is contained in:
jubnl
2026-07-25 18:14:37 +02:00
parent 7059b686ac
commit 718022c7ba
7 changed files with 128 additions and 16 deletions
+1
View File
@@ -167,3 +167,4 @@ try {
}
export { db, closeDb, reinitialize, getPlaceWithTags, canAccessTrip, isOwner };
export type { TripAccess, PlaceWithTags };
+7 -3
View File
@@ -1,13 +1,17 @@
import { Global, Module } from '@nestjs/common';
import { db } from '../../db/database';
import { DatabaseService } from './database.service';
import { DATABASE_CONNECTION } from './database.tokens';
/**
* Global so every migrated module can inject DatabaseService without re-importing.
* Wraps the existing better-sqlite3 singleton (no new connection).
* DATABASE_CONNECTION resolves to the existing better-sqlite3 singleton Proxy
* (no new connection, single writer preserved); tests can override the token
* to hand the container a throwaway connection.
*/
@Global()
@Module({
providers: [DatabaseService],
exports: [DatabaseService],
providers: [{ provide: DATABASE_CONNECTION, useFactory: () => db }, DatabaseService],
exports: [DatabaseService, DATABASE_CONNECTION],
})
export class DatabaseModule {}
+34 -11
View File
@@ -1,39 +1,62 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import type Database from 'better-sqlite3';
import { db } from '../../db/database';
import { canAccessTrip, getPlaceWithTags, isOwner } from '../../db/database';
import type { PlaceWithTags, TripAccess } from '../../db/database';
import { DATABASE_CONNECTION } from './database.tokens';
export type { PlaceWithTags, TripAccess };
/**
* Injectable wrapper around TREK's existing better-sqlite3 connection.
*
* `db` is a Proxy onto the singleton connection the legacy app already uses
* (WAL enabled), so Nest modules share the exact same connection — no second
* connection, no split state, single writer preserved.
* The injected connection is the Proxy onto the singleton the legacy app
* already uses (WAL enabled), so Nest modules share the exact same
* connection — no second connection, no split state, single writer preserved.
*/
@Injectable()
export class DatabaseService {
constructor(@Inject(DATABASE_CONNECTION) private readonly conn: Database.Database) {}
/** The shared better-sqlite3 connection (same singleton the legacy app uses). */
get connection(): Database.Database {
return db;
return this.conn;
}
prepare(sql: string): Database.Statement {
return db.prepare(sql);
return this.conn.prepare(sql);
}
get<T = unknown>(sql: string, ...params: unknown[]): T | undefined {
return db.prepare(sql).get(...params) as T | undefined;
return this.conn.prepare(sql).get(...params) as T | undefined;
}
all<T = unknown>(sql: string, ...params: unknown[]): T[] {
return db.prepare(sql).all(...params) as T[];
return this.conn.prepare(sql).all(...params) as T[];
}
run(sql: string, ...params: unknown[]): Database.RunResult {
return db.prepare(sql).run(...params);
return this.conn.prepare(sql).run(...params);
}
/** Run `fn` inside a synchronous better-sqlite3 transaction. */
transaction<T>(fn: (conn: Database.Database) => T): T {
return db.transaction(() => fn(db))();
return this.conn.transaction(() => fn(this.conn))();
}
// Trip-access helpers delegate to the db/database exports (not this.conn):
// tests vi.mock that module and stub the helpers themselves, so the stubs
// must keep flowing through here.
/** Trip visible to the user (owner or member); undefined when no access. */
canAccessTrip(tripId: number | string, userId: number): TripAccess | undefined {
return canAccessTrip(tripId, userId);
}
isOwner(tripId: number | string, userId: number): boolean {
return isOwner(tripId, userId);
}
getPlaceWithTags(placeId: number | string): PlaceWithTags | null {
return getPlaceWithTags(placeId);
}
}
@@ -0,0 +1,6 @@
/**
* Injection token for the shared better-sqlite3 connection. Provided by
* DatabaseModule from the existing singleton Proxy in src/db/database.ts,
* so it stays valid across backup-restore/demo-reset connection swaps.
*/
export const DATABASE_CONNECTION = Symbol('DATABASE_CONNECTION');