Files
minibits_wallet/src/services/db/instance.ts
T
minibits-cashandClaude Opus 4.8 14f8d1de8c feat(inflight): move inFlightRequests to SQLite (off-MST) [M2]
Relocate per-transaction in-flight request data (params for NUT-19
idempotent retry) from the MST MintProofsCounter to a dedicated SQLite
table, so retries work with no MST loaded — completing the off-MST set
needed for background NWC.

- schema/migration v29: inflight_requests (txId PK, mintUrl, keysetId,
  request JSON); added to cleanAll.
- inFlightRepo: add (INSERT OR REPLACE = set semantics) / get /
  getInFlightRequestsByMint / remove / seed (ON CONFLICT DO NOTHING).
- WalletStore: write/remove via Database (receive/send/mint paths).
- inFlightOperations: enumerate via a flat Database.getInFlightRequestsByMint
  query instead of the mint.proofsCountersWithInFlightRequests nested loop;
  removeInFlightRequest via Database; queue guard uses the DB count.
- Mint model: remove inFlightRequests map, InFlightRequestModel, all the
  in-flight actions/views (counter + mint level). The InFlightRequest TYPE
  is kept (WalletStore option signatures). MintProofsCounter is now just
  {keyset, unit, counter}. migrateSnapshot strips inFlightRequests AND
  meltCounterValues from old snapshots.
- one-time seed (rootStoreModelVersion 34->35): _runMigrations reads the
  raw pre-upgrade snapshot for any in-flight requests; idempotent.
- tests: __tests__/inFlightRequests.test.ts.

Full suite green (15 suites / 163 tests). With M1+M2, the MintProofsCounter
sub-model now carries only the counter (itself SQLite-authoritative) — a
candidate to collapse later.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-05 09:50:56 +02:00

81 lines
2.3 KiB
TypeScript

import {DbConnection, open, SQLBatchTuple} from './connection'
import {createSchemaQueries} from './schema'
import {_dbVersion, readDatabaseVersion, seedDatabaseVersion, runMigrations} from './migrations'
import {dbError} from './errors'
import {log} from '../logService'
let _db: DbConnection
export const getInstance = function () {
if (!_db) {
// 1. creates database
_db = _createDatabaseInstance() as DbConnection
// 2. Runs possible migrations and sets version
_createOrUpdateSchema(_db)
}
return _db
}
const _createDatabaseInstance = function () {
try {
const instance = open({name: 'minibits.db'})
return instance as DbConnection
} catch (e: any) {
throw dbError('Could not create or open database', e)
}
}
const _createOrUpdateSchema = function (db: DbConnection) {
try {
const {rowsAffected} = db.executeBatch(createSchemaQueries)
if (rowsAffected && rowsAffected > 0) {
log.info('[_createOrUpdateSchema] New database schema created')
}
let version = readDatabaseVersion(db)
if (version === null) {
// Fresh database: the schema was just created at the latest shape, so
// seed the version row and skip migrations.
seedDatabaseVersion(db)
version = _dbVersion
}
log.info('[_createOrUpdateSchema]', `Device database version: ${version}`)
// Trigger migrations if there is versions mismatch
if (version < _dbVersion) {
runMigrations(db)
}
} catch (e: any) {
throw dbError('Could not create or update database schema', e)
}
}
export const cleanAll = function () {
const dropQueries = [
['DROP TABLE transactions'],
['DROP TABLE proofs'],
['DROP TABLE dbversion'],
// IF EXISTS: these tables were added by later migrations, so a very old DB
// may lack them; without the guard a missing table aborts the atomic batch.
['DROP TABLE IF EXISTS reservations'],
['DROP TABLE IF EXISTS mint_counters'],
['DROP TABLE IF EXISTS melt_recovery'],
['DROP TABLE IF EXISTS inflight_requests'],
] as SQLBatchTuple[]
try {
const db = getInstance()
const {rowsAffected} = db.executeBatch(dropQueries)
if (rowsAffected && rowsAffected > 0) {
log.info('[cleanAll]', 'Database tables were deleted')
}
} catch (e: any) {
throw dbError('Could not delete database schema', e)
}
}