feat(beadboard-550.1): add DoltClient with connection pooling and metadata config
- src/lib/dolt-client.ts: getDoltConnection(projectRoot) returns cached mysql2 Pool - Reads host/port/database from .beads/metadata.json (no hardcoded values) - DoltConnectionError typed error for missing metadata or unreachable server - Tests connectivity on first connection; caches pool per resolved projectRoot - connectionLimit: 5 for Next.js server-side concurrency Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
bb1231860e
commit
91b4777a7c
1 changed files with 92 additions and 0 deletions
92
src/lib/dolt-client.ts
Normal file
92
src/lib/dolt-client.ts
Normal file
|
|
@ -0,0 +1,92 @@
|
||||||
|
import fs from 'node:fs/promises';
|
||||||
|
import path from 'node:path';
|
||||||
|
|
||||||
|
import mysql from 'mysql2/promise';
|
||||||
|
|
||||||
|
export class DoltConnectionError extends Error {
|
||||||
|
constructor(message: string, public readonly cause?: unknown) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'DoltConnectionError';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DoltMetadata {
|
||||||
|
dolt_server_port: number;
|
||||||
|
dolt_database: string;
|
||||||
|
dolt_server_host?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readDoltMetadata(projectRoot: string): Promise<DoltMetadata> {
|
||||||
|
const metadataPath = path.join(projectRoot, '.beads', 'metadata.json');
|
||||||
|
let raw: string;
|
||||||
|
try {
|
||||||
|
raw = await fs.readFile(metadataPath, 'utf-8');
|
||||||
|
} catch (err) {
|
||||||
|
throw new DoltConnectionError(
|
||||||
|
`Cannot read Dolt metadata at ${metadataPath} — is this a bd (beads) project?`,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let parsed: Record<string, unknown>;
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw) as Record<string, unknown>;
|
||||||
|
} catch (err) {
|
||||||
|
throw new DoltConnectionError(`Invalid JSON in ${metadataPath}`, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
const port = parsed.dolt_server_port;
|
||||||
|
const database = parsed.dolt_database;
|
||||||
|
if (typeof port !== 'number' || typeof database !== 'string') {
|
||||||
|
throw new DoltConnectionError(
|
||||||
|
`${metadataPath} is missing required fields: dolt_server_port (number) and dolt_database (string)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dolt_server_port: port,
|
||||||
|
dolt_database: database,
|
||||||
|
dolt_server_host: typeof parsed.dolt_server_host === 'string' ? parsed.dolt_server_host : undefined,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache pools per resolved project root to avoid reconnecting on every request
|
||||||
|
const poolCache = new Map<string, mysql.Pool>();
|
||||||
|
|
||||||
|
export async function getDoltConnection(projectRoot: string): Promise<mysql.Pool> {
|
||||||
|
const key = path.resolve(projectRoot);
|
||||||
|
|
||||||
|
const existing = poolCache.get(key);
|
||||||
|
if (existing) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
|
||||||
|
const metadata = await readDoltMetadata(projectRoot);
|
||||||
|
const host = metadata.dolt_server_host ?? '127.0.0.1';
|
||||||
|
|
||||||
|
const pool = mysql.createPool({
|
||||||
|
host,
|
||||||
|
port: metadata.dolt_server_port,
|
||||||
|
database: metadata.dolt_database,
|
||||||
|
user: 'root',
|
||||||
|
password: '',
|
||||||
|
connectionLimit: 5,
|
||||||
|
waitForConnections: true,
|
||||||
|
queueLimit: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify server is reachable before caching — fail fast with a typed error
|
||||||
|
try {
|
||||||
|
const conn = await pool.getConnection();
|
||||||
|
conn.release();
|
||||||
|
} catch (err) {
|
||||||
|
await pool.end().catch(() => {});
|
||||||
|
throw new DoltConnectionError(
|
||||||
|
`Cannot connect to Dolt SQL server at ${host}:${metadata.dolt_server_port} (database: ${metadata.dolt_database})`,
|
||||||
|
err
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
poolCache.set(key, pool);
|
||||||
|
return pool;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue