Files
wikideck/server/market/custody-storage.ts
T
2026-10-20 15:21:47 -04:00

46 lines
2.7 KiB
TypeScript
Executable File

import { DatabaseSync } from 'node:sqlite';
import { readFile, open, lstat, mkdir } from 'node:fs/promises';
import { join } from 'node:path';
import { CustodyMarket } from './custody.ts';
import { PostgresCustody, type CustodyOptions } from './postgres-custody.ts';
import { custodyStorage, postgresConfig } from './postgres-config.ts';
/** No automatic SQLite reinterpretation or fallback after a database connection/configuration failure. */
export async function openCustodyStorage(directory: string, options: CustodyOptions, env = process.env): Promise<CustodyMarket | PostgresCustody> {
const storage = custodyStorage(env), marker = join(directory, 'testnet', 'custody-postgres.json');
await mkdir(join(directory, 'testnet'), { recursive: true, mode: 0o700 });
let existing: { version: number; custodianId: string } | undefined;
try { existing = JSON.parse(await readFile(marker, 'utf8')); }
catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e; }
if (storage === 'sqlite') {
if (existing) throw new Error('Custody data directory is bound to PostgreSQL; SQLite fallback prohibited');
return CustodyMarket.open(directory, options);
}
const source = join(directory, 'testnet', 'custody-sandbox.sqlite');
let frozen: string | undefined;
try {
const info = await lstat(source);
if (!info.isFile() || info.isSymbolicLink()) throw new Error('Invalid custody source file');
const db = new DatabaseSync(source, { readOnly: true });
try {
frozen = (db.prepare("SELECT value FROM custody_meta WHERE key='postgres_cutover'").get() as { value: string } | undefined)?.value;
if (!frozen) throw new Error('Existing SQLite custody requires explicit stopped-node import before PostgreSQL activation');
} finally { db.close(); }
} catch (e) { if ((e as NodeJS.ErrnoException).code !== 'ENOENT') throw e; }
const market = await PostgresCustody.open(await postgresConfig(env), options);
try {
if (frozen && await market.importSource() !== frozen) throw new Error('PostgreSQL import does not match local frozen custody source');
if (existing && (existing.version !== 1 || existing.custodianId !== market.id)) throw new Error('PostgreSQL custodian differs from data-directory binding');
if (!existing) {
const handle = await open(marker, 'wx', 0o600);
try { await handle.writeFile(JSON.stringify({ version: 1, custodianId: market.id })); await handle.sync(); }
finally { await handle.close(); }
if (process.platform !== 'win32') {
const parent = await open(join(directory, 'testnet'), 'r');
try { await parent.sync(); } finally { await parent.close(); }
}
}
return market;
} catch (e) { await market.close(); throw e; }
}