diff --git a/packages/core/src/storage/index.ts b/packages/core/src/storage/index.ts index 6f27696c..7b943ffc 100644 --- a/packages/core/src/storage/index.ts +++ b/packages/core/src/storage/index.ts @@ -2,4 +2,5 @@ export * from './driver.js' export * from './memory/index.js' export * from './provider.js' export * from './repository/index.js' +export * from './sqlite/index.js' export * from './storage.js' diff --git a/packages/core/src/storage/sqlite/driver.ts b/packages/core/src/storage/sqlite/driver.ts new file mode 100644 index 00000000..b6478fc8 --- /dev/null +++ b/packages/core/src/storage/sqlite/driver.ts @@ -0,0 +1,168 @@ +import { getPlatform } from '../../platform.js' +import { BaseStorageDriver } from '../driver.js' +import { ISqliteDatabase, ISqliteStatement } from './types.js' + +const MIGRATIONS_TABLE_NAME = 'mtcute_migrations' +const MIGRATIONS_TABLE_SQL = ` +create table if not exists ${MIGRATIONS_TABLE_NAME} ( + repo text not null primary key, + version integer not null +); +`.trim() + +type MigrationFunction = (db: ISqliteDatabase) => void + +export abstract class BaseSqliteStorageDriver extends BaseStorageDriver { + db!: ISqliteDatabase + + private _pending: [ISqliteStatement, unknown[]][] = [] + private _runMany!: (stmts: [ISqliteStatement, unknown[]][]) => void + private _cleanup?: () => void + + private _migrations: Map> = new Map() + private _maxVersion: Map = new Map() + + // todo: remove in 1.0.0 + private _legacyMigrations: Map = new Map() + + registerLegacyMigration(repo: string, migration: MigrationFunction): void { + if (this.loaded) { + throw new Error('Cannot register migrations after loading') + } + + this._legacyMigrations.set(repo, migration) + } + + registerMigration(repo: string, version: number, migration: MigrationFunction): void { + if (this.loaded) { + throw new Error('Cannot register migrations after loading') + } + + let map = this._migrations.get(repo) + + if (!map) { + map = new Map() + this._migrations.set(repo, map) + } + + if (map.has(version)) { + throw new Error(`Migration for ${repo} version ${version} is already registered`) + } + + map.set(version, migration) + + const prevMax = this._maxVersion.get(repo) ?? 0 + + if (version > prevMax) { + this._maxVersion.set(repo, version) + } + } + + private _onLoad = new Set<(db: ISqliteDatabase) => void>() + + onLoad(cb: (db: ISqliteDatabase) => void): void { + if (this.loaded) { + cb(this.db) + } else { + this._onLoad.add(cb) + } + } + + _writeLater(stmt: ISqliteStatement, params: unknown[]): void { + this._pending.push([stmt, params]) + } + + private _runLegacyMigrations = false + + _initialize(): void { + const hasLegacyTables = this.db + .prepare("select name from sqlite_master where type = 'table' and name = 'kv'") + .get() + + if (hasLegacyTables) { + this._log.info('legacy tables detected, will run migrations') + this._runLegacyMigrations = true + } + + this.db.exec(MIGRATIONS_TABLE_SQL) + + const writeVersion = this.db.prepare( + `insert or replace into ${MIGRATIONS_TABLE_NAME} (repo, version) values (?, ?)`, + ) + const getVersion = this.db.prepare(`select version from ${MIGRATIONS_TABLE_NAME} where repo = ?`) + + const didUpgrade = new Set() + + for (const repo of this._migrations.keys()) { + const res = getVersion.get(repo) as { version: number } | undefined + + const startVersion = res?.version ?? 0 + let fromVersion = startVersion + + const migrations = this._migrations.get(repo)! + const targetVer = this._maxVersion.get(repo)! + + while (fromVersion < targetVer) { + const nextVersion = fromVersion + 1 + const migration = migrations.get(nextVersion) + + if (!migration) { + throw new Error(`No migration for ${repo} to version ${nextVersion}`) + } + + migration(this.db) + + fromVersion = nextVersion + didUpgrade.add(repo) + } + + if (fromVersion !== startVersion) { + writeVersion.run(repo, targetVer) + } + } + } + + abstract _createDatabase(): ISqliteDatabase + + _load(): void { + this.db = this._createDatabase() + + this._runMany = this.db.transaction((stmts: [ISqliteStatement, unknown[]][]) => { + stmts.forEach((stmt) => { + stmt[0].run(stmt[1]) + }) + }) + + this.db.transaction(() => this._initialize())() + + this._cleanup = getPlatform().beforeExit(() => { + this._save() + this._destroy() + }) + for (const cb of this._onLoad) cb(this.db) + + if (this._runLegacyMigrations) { + this.db.transaction(() => { + for (const migration of this._legacyMigrations.values()) { + migration(this.db) + } + + // in case _writeLater was used + this._runMany(this._pending) + })() + } + } + + _save(): void { + if (!this._pending.length) return + + this._runMany(this._pending) + this._pending = [] + } + + _destroy(): void { + this.db.close() + this._cleanup?.() + this._cleanup = undefined + } +} diff --git a/packages/core/src/storage/sqlite/index.ts b/packages/core/src/storage/sqlite/index.ts new file mode 100644 index 00000000..37218f7d --- /dev/null +++ b/packages/core/src/storage/sqlite/index.ts @@ -0,0 +1,19 @@ +import { ITelegramStorageProvider } from '../../highlevel/storage/provider.js' +import { IMtStorageProvider } from '../provider.js' +import { BaseSqliteStorageDriver } from './driver.js' +import { SqliteAuthKeysRepository } from './repository/auth-keys.js' +import { SqliteKeyValueRepository } from './repository/kv.js' +import { SqlitePeersRepository } from './repository/peers.js' +import { SqliteRefMessagesRepository } from './repository/ref-messages.js' + +export { BaseSqliteStorageDriver } +export * from './types.js' + +export class BaseSqliteStorage implements IMtStorageProvider, ITelegramStorageProvider { + constructor(readonly driver: BaseSqliteStorageDriver) {} + + readonly authKeys = new SqliteAuthKeysRepository(this.driver) + readonly kv = new SqliteKeyValueRepository(this.driver) + readonly refMessages = new SqliteRefMessagesRepository(this.driver) + readonly peers = new SqlitePeersRepository(this.driver) +} diff --git a/packages/sqlite/src/repository/auth-keys.ts b/packages/core/src/storage/sqlite/repository/auth-keys.ts similarity index 82% rename from packages/sqlite/src/repository/auth-keys.ts rename to packages/core/src/storage/sqlite/repository/auth-keys.ts index 06b4e2fd..147f8898 100644 --- a/packages/sqlite/src/repository/auth-keys.ts +++ b/packages/core/src/storage/sqlite/repository/auth-keys.ts @@ -1,8 +1,6 @@ -import { Statement } from 'better-sqlite3' - -import { IAuthKeysRepository } from '@mtcute/core' - -import { SqliteStorageDriver } from '../driver.js' +import { IAuthKeysRepository } from '../../repository/auth-keys.js' +import { BaseSqliteStorageDriver } from '../driver.js' +import { ISqliteStatement } from '../types.js' interface AuthKeyDto { dc: number @@ -15,7 +13,7 @@ interface TempAuthKeyDto extends AuthKeyDto { } export class SqliteAuthKeysRepository implements IAuthKeysRepository { - constructor(readonly _driver: SqliteStorageDriver) { + constructor(readonly _driver: BaseSqliteStorageDriver) { _driver.registerMigration('auth_keys', 1, (db) => { db.exec(` create table if not exists auth_keys ( @@ -47,8 +45,8 @@ export class SqliteAuthKeysRepository implements IAuthKeysRepository { }) } - private _set!: Statement - private _del!: Statement + private _set!: ISqliteStatement + private _del!: ISqliteStatement set(dc: number, key: Uint8Array | null): void { if (!key) { this._del.run(dc) @@ -59,7 +57,7 @@ export class SqliteAuthKeysRepository implements IAuthKeysRepository { this._set.run(dc, key) } - private _get!: Statement + private _get!: ISqliteStatement get(dc: number): Uint8Array | null { const row = this._get.get(dc) if (!row) return null @@ -67,8 +65,8 @@ export class SqliteAuthKeysRepository implements IAuthKeysRepository { return (row as AuthKeyDto).key } - private _setTemp!: Statement - private _delTemp!: Statement + private _setTemp!: ISqliteStatement + private _delTemp!: ISqliteStatement setTemp(dc: number, idx: number, key: Uint8Array | null, expires: number): void { if (!key) { this._delTemp.run(dc, idx) @@ -79,7 +77,7 @@ export class SqliteAuthKeysRepository implements IAuthKeysRepository { this._setTemp.run(dc, idx, key, expires) } - private _getTemp!: Statement + private _getTemp!: ISqliteStatement getTemp(dc: number, idx: number, now: number): Uint8Array | null { const row = this._getTemp.get(dc, idx, now) if (!row) return null @@ -87,13 +85,13 @@ export class SqliteAuthKeysRepository implements IAuthKeysRepository { return (row as TempAuthKeyDto).key } - private _delTempAll!: Statement + private _delTempAll!: ISqliteStatement deleteByDc(dc: number): void { this._del.run(dc) this._delTempAll.run(dc) } - private _delAll!: Statement + private _delAll!: ISqliteStatement deleteAll(): void { this._delAll.run() } diff --git a/packages/sqlite/src/repository/kv.ts b/packages/core/src/storage/sqlite/repository/kv.ts similarity index 84% rename from packages/sqlite/src/repository/kv.ts rename to packages/core/src/storage/sqlite/repository/kv.ts index 45632508..713249b1 100644 --- a/packages/sqlite/src/repository/kv.ts +++ b/packages/core/src/storage/sqlite/repository/kv.ts @@ -1,11 +1,13 @@ -import { Statement } from 'better-sqlite3' - -import { IKeyValueRepository } from '@mtcute/core' -import { CurrentUserService, DefaultDcsService, ServiceOptions, UpdatesStateService } from '@mtcute/core/utils.js' import { __tlReaderMap } from '@mtcute/tl/binary/reader.js' import { __tlWriterMap } from '@mtcute/tl/binary/writer.js' -import { SqliteStorageDriver } from '../driver.js' +import { CurrentUserService } from '../../../highlevel/storage/service/current-user.js' +import { UpdatesStateService } from '../../../highlevel/storage/service/updates.js' +import { IKeyValueRepository } from '../../repository/key-value.js' +import { ServiceOptions } from '../../service/base.js' +import { DefaultDcsService } from '../../service/default-dcs.js' +import { BaseSqliteStorageDriver } from '../driver.js' +import { ISqliteStatement } from '../types.js' interface KeyValueDto { key: string @@ -13,7 +15,7 @@ interface KeyValueDto { } export class SqliteKeyValueRepository implements IKeyValueRepository { - constructor(readonly _driver: SqliteStorageDriver) { + constructor(readonly _driver: BaseSqliteStorageDriver) { _driver.registerMigration('kv', 1, (db) => { db.exec(` create table key_value ( @@ -88,12 +90,12 @@ export class SqliteKeyValueRepository implements IKeyValueRepository { /* eslint-enable @typescript-eslint/no-unsafe-argument */ } - private _set!: Statement + private _set!: ISqliteStatement set(key: string, value: Uint8Array): void { this._driver._writeLater(this._set, [key, value]) } - private _get!: Statement + private _get!: ISqliteStatement get(key: string): Uint8Array | null { const res = this._get.get(key) if (!res) return null @@ -101,12 +103,12 @@ export class SqliteKeyValueRepository implements IKeyValueRepository { return (res as KeyValueDto).value } - private _del!: Statement + private _del!: ISqliteStatement delete(key: string): void { this._del.run(key) } - private _delAll!: Statement + private _delAll!: ISqliteStatement deleteAll(): void { this._delAll.run() } diff --git a/packages/sqlite/src/repository/peers.ts b/packages/core/src/storage/sqlite/repository/peers.ts similarity index 86% rename from packages/sqlite/src/repository/peers.ts rename to packages/core/src/storage/sqlite/repository/peers.ts index 54a527d4..4f5a78a1 100644 --- a/packages/sqlite/src/repository/peers.ts +++ b/packages/core/src/storage/sqlite/repository/peers.ts @@ -1,8 +1,6 @@ -import { Statement } from 'better-sqlite3' - -import { IPeersRepository } from '@mtcute/core' - -import { SqliteStorageDriver } from '../driver.js' +import { IPeersRepository } from '../../../highlevel/storage/repository/peers.js' +import { BaseSqliteStorageDriver } from '../driver.js' +import { ISqliteStatement } from '../types.js' interface PeerDto { id: number @@ -26,7 +24,7 @@ function mapPeerDto(dto: PeerDto): IPeersRepository.PeerInfo { } export class SqlitePeersRepository implements IPeersRepository { - constructor(readonly _driver: SqliteStorageDriver) { + constructor(readonly _driver: BaseSqliteStorageDriver) { _driver.registerMigration('peers', 1, (db) => { db.exec(` create table peers ( @@ -60,7 +58,7 @@ export class SqlitePeersRepository implements IPeersRepository { }) } - private _store!: Statement + private _store!: ISqliteStatement store(peer: IPeersRepository.PeerInfo): void { this._driver._writeLater(this._store, [ peer.id, @@ -73,7 +71,7 @@ export class SqlitePeersRepository implements IPeersRepository { ]) } - private _getById!: Statement + private _getById!: ISqliteStatement getById(id: number): IPeersRepository.PeerInfo | null { const row = this._getById.get(id) if (!row) return null @@ -81,7 +79,7 @@ export class SqlitePeersRepository implements IPeersRepository { return mapPeerDto(row as PeerDto) } - private _getByUsername!: Statement + private _getByUsername!: ISqliteStatement getByUsername(username: string): IPeersRepository.PeerInfo | null { const row = this._getByUsername.get(username) if (!row) return null @@ -89,7 +87,7 @@ export class SqlitePeersRepository implements IPeersRepository { return mapPeerDto(row as PeerDto) } - private _getByPhone!: Statement + private _getByPhone!: ISqliteStatement getByPhone(phone: string): IPeersRepository.PeerInfo | null { const row = this._getByPhone.get(phone) if (!row) return null @@ -97,7 +95,7 @@ export class SqlitePeersRepository implements IPeersRepository { return mapPeerDto(row as PeerDto) } - private _delAll!: Statement + private _delAll!: ISqliteStatement deleteAll(): void { this._delAll.run() } diff --git a/packages/sqlite/src/repository/ref-messages.ts b/packages/core/src/storage/sqlite/repository/ref-messages.ts similarity index 84% rename from packages/sqlite/src/repository/ref-messages.ts rename to packages/core/src/storage/sqlite/repository/ref-messages.ts index af4332af..ff42516c 100644 --- a/packages/sqlite/src/repository/ref-messages.ts +++ b/packages/core/src/storage/sqlite/repository/ref-messages.ts @@ -1,8 +1,7 @@ -import { Statement } from 'better-sqlite3' - import { IReferenceMessagesRepository } from '@mtcute/core' -import { SqliteStorageDriver } from '../driver.js' +import { BaseSqliteStorageDriver } from '../driver.js' +import { ISqliteStatement } from '../types.js' interface ReferenceMessageDto { peer_id: number @@ -11,7 +10,7 @@ interface ReferenceMessageDto { } export class SqliteRefMessagesRepository implements IReferenceMessagesRepository { - constructor(readonly _driver: SqliteStorageDriver) { + constructor(readonly _driver: BaseSqliteStorageDriver) { _driver.registerMigration('ref_messages', 1, (db) => { db.exec(` create table if not exists message_refs ( @@ -36,12 +35,12 @@ export class SqliteRefMessagesRepository implements IReferenceMessagesRepository }) } - private _store!: Statement + private _store!: ISqliteStatement store(peerId: number, chatId: number, msgId: number): void { this._store.run(peerId, chatId, msgId) } - private _getByPeer!: Statement + private _getByPeer!: ISqliteStatement getByPeer(peerId: number): [number, number] | null { const res = this._getByPeer.get(peerId) if (!res) return null @@ -51,19 +50,19 @@ export class SqliteRefMessagesRepository implements IReferenceMessagesRepository return [res_.chat_id, res_.msg_id] } - private _del!: Statement + private _del!: ISqliteStatement delete(chatId: number, msgIds: number[]): void { for (const msgId of msgIds) { this._driver._writeLater(this._del, [chatId, msgId]) } } - private _delByPeer!: Statement + private _delByPeer!: ISqliteStatement deleteByPeer(peerId: number): void { this._delByPeer.run(peerId) } - private _delAll!: Statement + private _delAll!: ISqliteStatement deleteAll(): void { this._delAll.run() } diff --git a/packages/core/src/storage/sqlite/types.ts b/packages/core/src/storage/sqlite/types.ts new file mode 100644 index 00000000..6527835d --- /dev/null +++ b/packages/core/src/storage/sqlite/types.ts @@ -0,0 +1,22 @@ +/** + * An abstract interface for a SQLite database. + * + * Roughly based on `better-sqlite3`'s `Database` class, + * (which can be used as-is), but only with the methods + * that are used by mtcute. + */ +export interface ISqliteDatabase { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + transaction any>(fn: F): F + + prepare(sql: string): ISqliteStatement + + exec(sql: string): void + close(): void +} + +export interface ISqliteStatement { + run(...params: BindParameters): void + get(...params: BindParameters): unknown + all(...params: BindParameters): unknown[] +} diff --git a/packages/dispatcher/package.json b/packages/dispatcher/package.json index 46385d5a..dd3b1735 100644 --- a/packages/dispatcher/package.json +++ b/packages/dispatcher/package.json @@ -26,13 +26,5 @@ }, "devDependencies": { "@mtcute/test": "workspace:^" - }, - "peerDependencies": { - "@mtcute/sqlite": "workspace:^" - }, - "peerDependenciesMeta": { - "@mtcute/sqlite": { - "optional": true - } } } diff --git a/packages/dispatcher/src/state/providers/sqlite.ts b/packages/dispatcher/src/state/providers/sqlite.ts index 38ebc5de..5b3423db 100644 --- a/packages/dispatcher/src/state/providers/sqlite.ts +++ b/packages/dispatcher/src/state/providers/sqlite.ts @@ -1,5 +1,4 @@ -import { MaybePromise } from '@mtcute/core' -import type { SqliteStorage, SqliteStorageDriver, Statement } from '@mtcute/sqlite' +import { BaseSqliteStorage, BaseSqliteStorageDriver, ISqliteStatement, MaybePromise } from '@mtcute/core' import { IStateStorageProvider } from '../provider.js' import { IStateRepository } from '../repository.js' @@ -15,7 +14,7 @@ interface RateLimitDto { } class SqliteStateRepository implements IStateRepository { - constructor(readonly _driver: SqliteStorageDriver) { + constructor(readonly _driver: BaseSqliteStorageDriver) { _driver.registerMigration('state', 1, (db) => { db.exec(` create table fsm_state ( @@ -49,12 +48,12 @@ class SqliteStateRepository implements IStateRepository { }) } - private _setState!: Statement + private _setState!: ISqliteStatement setState(key: string, state: string, ttl?: number | undefined): MaybePromise { this._setState.run(key, state, ttl ? Date.now() + ttl * 1000 : undefined) } - private _getState!: Statement + private _getState!: ISqliteStatement getState(key: string, now: number): MaybePromise { const res_ = this._getState.get(key) if (!res_) return null @@ -69,21 +68,21 @@ class SqliteStateRepository implements IStateRepository { return res.value } - private _deleteState!: Statement + private _deleteState!: ISqliteStatement deleteState(key: string): MaybePromise { this._deleteState.run(key) } - private _deleteOldState!: Statement - private _deleteOldRl!: Statement + private _deleteOldState!: ISqliteStatement + private _deleteOldRl!: ISqliteStatement vacuum(now: number): MaybePromise { this._deleteOldState.run(now) this._deleteOldRl.run(now) } - private _setRl!: Statement - private _getRl!: Statement - private _deleteRl!: Statement + private _setRl!: ISqliteStatement + private _getRl!: ISqliteStatement + private _deleteRl!: ISqliteStatement getRateLimit(key: string, now: number, limit: number, window: number): [number, number] { const val = this._getRl.get(key) as RateLimitDto | undefined @@ -117,9 +116,9 @@ class SqliteStateRepository implements IStateRepository { } export class SqliteStateStorage implements IStateStorageProvider { - constructor(readonly driver: SqliteStorageDriver) {} + constructor(readonly driver: BaseSqliteStorageDriver) {} - static from(provider: SqliteStorage) { + static from(provider: BaseSqliteStorage) { return new SqliteStateStorage(provider.driver) } diff --git a/packages/sqlite/src/driver.ts b/packages/sqlite/src/driver.ts index 42ddeb83..fcfc4cb7 100644 --- a/packages/sqlite/src/driver.ts +++ b/packages/sqlite/src/driver.ts @@ -1,7 +1,6 @@ -import sqlite3, { Database, Options, Statement } from 'better-sqlite3' +import sqlite3, { Options } from 'better-sqlite3' -import { BaseStorageDriver } from '@mtcute/core' -import { getPlatform } from '@mtcute/core/platform.js' +import { BaseSqliteStorageDriver, ISqliteDatabase } from '@mtcute/core' export interface SqliteStorageDriverOptions { /** @@ -22,19 +21,7 @@ export interface SqliteStorageDriverOptions { options?: Options } -const MIGRATIONS_TABLE_NAME = 'mtcute_migrations' -const MIGRATIONS_TABLE_SQL = ` -create table if not exists ${MIGRATIONS_TABLE_NAME} ( - repo text not null primary key, - version integer not null -); -`.trim() - -type MigrationFunction = (db: Database) => void - -export class SqliteStorageDriver extends BaseStorageDriver { - db!: Database - +export class SqliteStorageDriver extends BaseSqliteStorageDriver { constructor( readonly filename = ':memory:', readonly params?: SqliteStorageDriverOptions, @@ -42,159 +29,16 @@ export class SqliteStorageDriver extends BaseStorageDriver { super() } - private _pending: [Statement, unknown[]][] = [] - private _runMany!: (stmts: [Statement, unknown[]][]) => void - private _cleanup?: () => void - - private _migrations: Map> = new Map() - private _maxVersion: Map = new Map() - - // todo: remove in 1.0.0 + remove direct dep on @mtcute/tl - private _legacyMigrations: Map = new Map() - - registerLegacyMigration(repo: string, migration: MigrationFunction): void { - if (this.loaded) { - throw new Error('Cannot register migrations after loading') - } - - this._legacyMigrations.set(repo, migration) - } - - registerMigration(repo: string, version: number, migration: MigrationFunction): void { - if (this.loaded) { - throw new Error('Cannot register migrations after loading') - } - - let map = this._migrations.get(repo) - - if (!map) { - map = new Map() - this._migrations.set(repo, map) - } - - if (map.has(version)) { - throw new Error(`Migration for ${repo} version ${version} is already registered`) - } - - map.set(version, migration) - - const prevMax = this._maxVersion.get(repo) ?? 0 - - if (version > prevMax) { - this._maxVersion.set(repo, version) - } - } - - private _onLoad = new Set<(db: Database) => void>() - - onLoad(cb: (db: Database) => void): void { - if (this.loaded) { - cb(this.db) - } else { - this._onLoad.add(cb) - } - } - - _writeLater(stmt: Statement, params: unknown[]): void { - this._pending.push([stmt, params]) - } - - private _runLegacyMigrations = false - - _initialize(): void { - const hasLegacyTables = this.db - .prepare("select name from sqlite_master where type = 'table' and name = 'kv'") - .get() - - if (hasLegacyTables) { - this._log.info('legacy tables detected, will run migrations') - this._runLegacyMigrations = true - } - - this.db.exec(MIGRATIONS_TABLE_SQL) - - const writeVersion = this.db.prepare( - `insert or replace into ${MIGRATIONS_TABLE_NAME} (repo, version) values (?, ?)`, - ) - const getVersion = this.db.prepare(`select version from ${MIGRATIONS_TABLE_NAME} where repo = ?`) - - const didUpgrade = new Set() - - for (const repo of this._migrations.keys()) { - const res = getVersion.get(repo) as { version: number } | undefined - - const startVersion = res?.version ?? 0 - let fromVersion = startVersion - - const migrations = this._migrations.get(repo)! - const targetVer = this._maxVersion.get(repo)! - - while (fromVersion < targetVer) { - const nextVersion = fromVersion + 1 - const migration = migrations.get(nextVersion) - - if (!migration) { - throw new Error(`No migration for ${repo} to version ${nextVersion}`) - } - - migration(this.db) - - fromVersion = nextVersion - didUpgrade.add(repo) - } - - if (fromVersion !== startVersion) { - writeVersion.run(repo, targetVer) - } - } - } - - _load(): void { - this.db = sqlite3(this.filename, { + _createDatabase(): ISqliteDatabase { + const db = sqlite3(this.filename, { ...this.params?.options, verbose: this._log.mgr.level >= 5 ? (this._log.verbose as Options['verbose']) : undefined, }) if (!this.params?.disableWal) { - this.db.pragma('journal_mode = WAL') + db.pragma('journal_mode = WAL') } - this._runMany = this.db.transaction((stmts: [Statement, unknown[]][]) => { - stmts.forEach((stmt) => { - stmt[0].run(stmt[1]) - }) - }) - - this.db.transaction(() => this._initialize())() - - this._cleanup = getPlatform().beforeExit(() => { - this._save() - this._destroy() - }) - for (const cb of this._onLoad) cb(this.db) - - if (this._runLegacyMigrations) { - this.db.transaction(() => { - for (const migration of this._legacyMigrations.values()) { - migration(this.db) - } - - // in case _writeLater was used - this._runMany(this._pending) - })() - } - } - - _save(): void { - if (!this._pending.length) return - - this._runMany(this._pending) - this._pending = [] - } - - _destroy(): void { - this.db.close() - this._cleanup?.() - this._cleanup = undefined + return db as ISqliteDatabase } } diff --git a/packages/sqlite/src/index.ts b/packages/sqlite/src/index.ts index 2fb42852..516b319f 100644 --- a/packages/sqlite/src/index.ts +++ b/packages/sqlite/src/index.ts @@ -1,24 +1,15 @@ -import { IMtStorageProvider, ITelegramStorageProvider } from '@mtcute/core' +import { BaseSqliteStorage } from '@mtcute/core' import { SqliteStorageDriver, SqliteStorageDriverOptions } from './driver.js' -import { SqliteAuthKeysRepository } from './repository/auth-keys.js' -import { SqliteKeyValueRepository } from './repository/kv.js' -import { SqlitePeersRepository } from './repository/peers.js' -import { SqliteRefMessagesRepository } from './repository/ref-messages.js' export { SqliteStorageDriver } from './driver.js' export type { Statement } from 'better-sqlite3' -export class SqliteStorage implements IMtStorageProvider, ITelegramStorageProvider { +export class SqliteStorage extends BaseSqliteStorage { constructor( readonly filename = ':memory:', readonly params?: SqliteStorageDriverOptions, - ) {} - - readonly driver = new SqliteStorageDriver(this.filename, this.params) - - readonly authKeys = new SqliteAuthKeysRepository(this.driver) - readonly kv = new SqliteKeyValueRepository(this.driver) - readonly refMessages = new SqliteRefMessagesRepository(this.driver) - readonly peers = new SqlitePeersRepository(this.driver) + ) { + super(new SqliteStorageDriver(filename, params)) + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e62e9818..814de55d 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -205,9 +205,6 @@ importers: '@mtcute/core': specifier: workspace:^ version: link:../core - '@mtcute/sqlite': - specifier: workspace:^ - version: link:../sqlite events: specifier: 3.2.0 version: 3.2.0