Created
August 26, 2025 04:08
-
-
Save tubackkhoa/b897eae07544f8b1c7acb3129d975390 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * 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