Skip to content

Instantly share code, notes, and snippets.

@tubackkhoa
Created August 26, 2025 04:08
Show Gist options
  • Save tubackkhoa/b897eae07544f8b1c7acb3129d975390 to your computer and use it in GitHub Desktop.
Save tubackkhoa/b897eae07544f8b1c7acb3129d975390 to your computer and use it in GitHub Desktop.
/**
* DuckDB HTTPServer wrapper (NDJSON parser)
* Supports Basic Auth + API Key
*
* Example server:
* curl -u user:pass -H "X-API-Key: secretkey" \
* "http://localhost:9999?query=select * from transactions limit 2"
*/
export type RowData = Record<string, any>;
export interface ConnectionOptions {
baseUrl: string;
username?: string;
password?: string;
apiKey?: string;
}
export class Connection {
private baseUrl: string;
private headers: HeadersInit;
constructor(options: ConnectionOptions) {
this.baseUrl = options.baseUrl.replace(/\/$/, ''); // strip trailing /
this.headers = {};
if (options.username && options.password) {
const token = Buffer.from(
`${options.username}:${options.password}`
).toString('base64');
this.headers['Authorization'] = `Basic ${token}`;
}
if (options.apiKey) {
this.headers['X-API-Key'] = options.apiKey;
}
}
private async _getQuery(sql: string): Promise<RowData[]> {
const url = `${this.baseUrl}?query=${encodeURIComponent(sql)}`;
const res = await fetch(url, { headers: this.headers });
if (!res.ok) {
throw new Error(`DuckDB HTTP error ${res.status}: ${await res.text()}`);
}
const text = await res.text();
const rows: RowData[] = [];
for (const line of text.split('\n')) {
if (line.trim() === '') continue;
try {
rows.push(JSON.parse(line));
} catch (e) {
console.error('Failed to parse NDJSON line:', line, e);
}
}
return rows;
}
async all(sql: string): Promise<RowData[]> {
return this._getQuery(sql);
}
async each(sql: string, cb: (row: RowData) => void): Promise<void> {
const rows = await this._getQuery(sql);
for (const row of rows) cb(row);
}
async exec(sql: string): Promise<void> {
await this._getQuery(sql);
}
async run(sql: string): Promise<void> {
await this._getQuery(sql);
}
}
export class Database {
private options: ConnectionOptions;
private constructor(options: ConnectionOptions) {
this.options = options;
}
static async connect(options: ConnectionOptions): Promise<Database> {
const db = new Database(options);
// health check
await db.all('SELECT 1');
return db;
}
connect(): Connection {
return new Connection(this.options);
}
async all(sql: string): Promise<RowData[]> {
return new Connection(this.options).all(sql);
}
async exec(sql: string): Promise<void> {
return new Connection(this.options).exec(sql);
}
}
// -----------------------------
// Example usage
// -----------------------------
(async () => {
const db = await Database.connect({
baseUrl: 'http://localhost:9999',
apiKey: 'secretkey'
});
const rows = await db.all('SELECT * FROM transactions LIMIT 2');
console.log('Rows:', rows);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment