"""Thin sqlite3 wrapper: one connection per process, guarded by a lock. Why sqlite3 instead of furtka-core's flat-JSON-plus-flock convention: that convention works because each file is effectively single-writer. This gateway's defining requirement is many unrelated accounts/boxes registering and publishing routes concurrently — a locked JSON file would serialize every tenant's writes against every other tenant's. sqlite3 (stdlib, zero extra dependencies) gives real concurrent relational access instead. """ from __future__ import annotations import sqlite3 import threading from pathlib import Path from control_plane import paths _SCHEMA_PATH = Path(__file__).parent / "schema.sql" class Database: def __init__(self, db_path: Path | None = None) -> None: path = db_path or paths.db_path() path.parent.mkdir(parents=True, exist_ok=True) self._lock = threading.Lock() self._conn = sqlite3.connect(path, check_same_thread=False) self._conn.row_factory = sqlite3.Row self._conn.execute("PRAGMA foreign_keys = ON") with self._lock: self._conn.executescript(_SCHEMA_PATH.read_text()) self._conn.commit() def execute(self, sql: str, params: tuple = ()) -> None: with self._lock: self._conn.execute(sql, params) self._conn.commit() def query_one(self, sql: str, params: tuple = ()) -> sqlite3.Row | None: with self._lock: return self._conn.execute(sql, params).fetchone() def query_all(self, sql: str, params: tuple = ()) -> list[sqlite3.Row]: with self._lock: return self._conn.execute(sql, params).fetchall() def close(self) -> None: with self._lock: self._conn.close() def row_to_dict(row: sqlite3.Row | None) -> dict | None: if row is None: return None return dict(zip(row.keys(), row, strict=True))