duckdb-terminal 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,505 @@
1
+ # DuckDB Terminal
2
+
3
+ A browser-based SQL Terminal for [DuckDB](https://duckdb.org/) powered by [Ghostty](https://ghostty.org/) terminal emulator.
4
+
5
+ ## Features
6
+
7
+ - **Full SQL REPL** - Execute SQL queries with multi-line support
8
+ - **Command History** - Navigate previous commands with arrow keys (persisted in IndexedDB)
9
+ - **Auto-Complete** - Tab completion for SQL keywords, table names, and functions
10
+ - **Multiple Output Modes** - Table, CSV, TSV, or JSON output formats
11
+ - **Clipboard Support** - Copy query results to clipboard in any output format
12
+ - **Result Pagination** - Navigate large result sets page by page
13
+ - **Syntax Highlighting** - Color-coded SQL keywords, strings, and numbers
14
+ - **Clickable URLs** - Automatically detect and make URLs clickable in results
15
+ - **File Loading** - Load CSV, Parquet, and JSON files via drag-and-drop or file picker
16
+ - **Dark/Light Themes** - Switchable themes with custom theme support
17
+ - **Customizable Prompts** - Configure primary and continuation prompts
18
+ - **Dot Commands** - Terminal commands like `.help`, `.tables`, `.schema`
19
+ - **Query Timing** - Optional execution time display
20
+ - **Persistent Storage** - Optional OPFS storage for data persistence
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install duckdb-terminal
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ### As a Library
31
+
32
+ ```typescript
33
+ import { createTerminal } from 'duckdb-terminal';
34
+
35
+ const Terminal = await createTerminal({
36
+ container: '#terminal',
37
+ theme: 'dark',
38
+ });
39
+ ```
40
+
41
+ ### As a Standalone App
42
+
43
+ ```bash
44
+ git clone https://github.com/tobilg/duckdb-terminal.git
45
+ cd duckdb-terminal
46
+ npm install
47
+ npm run dev
48
+ ```
49
+
50
+ Open http://localhost:5173 in your browser.
51
+
52
+ ## Configuration
53
+
54
+ ```typescript
55
+ interface TerminalConfig {
56
+ // Container element or CSS selector
57
+ container: HTMLElement | string;
58
+
59
+ // Font family (default: 'Fira Code', 'Cascadia Code', etc.)
60
+ fontFamily?: string;
61
+
62
+ // Font size in pixels (default: 14)
63
+ fontSize?: number;
64
+
65
+ // Theme: 'dark' | 'light' | Theme (default: 'dark')
66
+ theme?: 'dark' | 'light' | Theme;
67
+
68
+ // Storage: 'memory' | 'opfs' (default: 'memory')
69
+ storage?: 'memory' | 'opfs';
70
+
71
+ // Database path for OPFS storage
72
+ databasePath?: string;
73
+
74
+ // Show welcome message (default: true)
75
+ // When enabled, displays loading progress during DuckDB initialization
76
+ welcomeMessage?: boolean;
77
+
78
+ // Primary prompt string (default: '🦆 ')
79
+ prompt?: string;
80
+
81
+ // Continuation prompt for multi-line SQL (default: ' > ')
82
+ continuationPrompt?: string;
83
+
84
+ // Enable clickable URL detection (default: true)
85
+ linkDetection?: boolean;
86
+
87
+ // Scrollback buffer size in bytes (default: 10485760 = 10MB)
88
+ scrollback?: number;
89
+ }
90
+ ```
91
+
92
+ ## Terminal Commands
93
+
94
+ | Command | Description |
95
+ |---------|-------------|
96
+ | `.help` | Show available commands |
97
+ | `.clear` | Clear the terminal |
98
+ | `.tables` | List all tables |
99
+ | `.schema <table>` | Show table schema |
100
+ | `.timer on\|off` | Toggle query timing |
101
+ | `.mode table\|csv\|tsv\|json` | Set output format |
102
+ | `.copy` | Copy last query results to clipboard |
103
+ | `.pagesize <n>` | Set pagination size (0 to disable) |
104
+ | `.theme dark\|light` | Switch color theme (clears screen) |
105
+ | `.highlight on\|off` | Toggle syntax highlighting |
106
+ | `.links on\|off` | Toggle clickable URL detection |
107
+ | `.files [list\|add\|remove]` | Manage loaded files (list, add, or remove) |
108
+ | `.open` | Open file picker to load data files |
109
+ | `.prompt [primary [cont]]` | Get or set the command prompt |
110
+ | `.examples` | Show example queries |
111
+ | `.reset` | Reset database and all settings to defaults |
112
+
113
+ ## Keyboard Shortcuts
114
+
115
+ | Key | Action |
116
+ |-----|--------|
117
+ | `Enter` | Execute command/SQL |
118
+ | `Tab` | Auto-complete |
119
+ | `↑` / `↓` | Navigate history |
120
+ | `←` / `→` | Move cursor |
121
+ | `Ctrl+A` | Move to start of line |
122
+ | `Ctrl+E` | Move to end of line |
123
+ | `Ctrl+K` | Clear to end of line |
124
+ | `Ctrl+U` | Clear entire line |
125
+ | `Ctrl+C` | Cancel current input |
126
+
127
+ ## Example Usage
128
+
129
+ ```sql
130
+ -- Create a table
131
+ CREATE TABLE users (
132
+ id INTEGER PRIMARY KEY,
133
+ name VARCHAR,
134
+ email VARCHAR
135
+ );
136
+
137
+ -- Insert data
138
+ INSERT INTO users VALUES
139
+ (1, 'Alice', 'alice@example.com'),
140
+ (2, 'Bob', 'bob@example.com');
141
+
142
+ -- Query data
143
+ SELECT * FROM users WHERE name LIKE 'A%';
144
+
145
+ -- Use built-in functions
146
+ SELECT range(10), current_timestamp;
147
+ ```
148
+
149
+ ## Querying and Copying Results
150
+
151
+ The terminal supports four output formats and provides an easy way to copy query results to your clipboard.
152
+
153
+ ### Output Formats
154
+
155
+ Use the `.mode` command to switch between output formats:
156
+
157
+ ```sql
158
+ -- Table format (default) - human-readable ASCII table
159
+ .mode table
160
+ SELECT * FROM users;
161
+ +----+-------+-------------------+
162
+ | id | name | email |
163
+ +----+-------+-------------------+
164
+ | 1 | Alice | alice@example.com |
165
+ | 2 | Bob | bob@example.com |
166
+ +----+-------+-------------------+
167
+
168
+ -- CSV format - comma-separated values
169
+ .mode csv
170
+ SELECT * FROM users;
171
+ id,name,email
172
+ 1,Alice,alice@example.com
173
+ 2,Bob,bob@example.com
174
+
175
+ -- TSV format - tab-separated values
176
+ .mode tsv
177
+ SELECT * FROM users;
178
+ id name email
179
+ 1 Alice alice@example.com
180
+ 2 Bob bob@example.com
181
+
182
+ -- JSON format - array of objects
183
+ .mode json
184
+ SELECT * FROM users;
185
+ [
186
+ {"id": 1, "name": "Alice", "email": "alice@example.com"},
187
+ {"id": 2, "name": "Bob", "email": "bob@example.com"}
188
+ ]
189
+ ```
190
+
191
+ ### Copying Results
192
+
193
+ After running a query, use `.copy` to copy the results to your clipboard in the current output format:
194
+
195
+ ```sql
196
+ -- Run your query
197
+ SELECT * FROM users WHERE active = true;
198
+
199
+ -- Copy results to clipboard (uses current .mode format)
200
+ .copy
201
+ ```
202
+
203
+ The copied content respects your current output mode, so you can:
204
+ 1. Use `.mode csv` or `.mode tsv` then `.copy` to paste into a spreadsheet
205
+ 2. Use `.mode json` then `.copy` to paste into a JSON file or API tool
206
+ 3. Use `.mode table` then `.copy` to paste formatted output into documentation
207
+
208
+ ### Pagination for Large Results
209
+
210
+ For large result sets, enable pagination with `.pagesize`:
211
+
212
+ ```sql
213
+ -- Enable pagination (50 rows per page)
214
+ .pagesize 50
215
+
216
+ -- Run a query with many results
217
+ SELECT * FROM large_table;
218
+
219
+ -- Navigate pages:
220
+ -- n or Enter - next page
221
+ -- p - previous page
222
+ -- 1-9 - jump to page number
223
+ -- q - quit pagination
224
+ ```
225
+
226
+ Set `.pagesize 0` to disable pagination and show all results at once.
227
+
228
+ **Note:** Queries that already contain `LIMIT` or `OFFSET` clauses bypass pagination, giving you full control over result size.
229
+
230
+ ## Custom Themes
231
+
232
+ You can create custom themes by providing a `Theme` object instead of `'dark'` or `'light'`:
233
+
234
+ ```typescript
235
+ import { createTerminal, type Theme, type ThemeColors } from 'duckdb-terminal';
236
+
237
+ // Define custom colors
238
+ const myColors: ThemeColors = {
239
+ background: '#1a1b26', // Terminal background
240
+ foreground: '#a9b1d6', // Default text color
241
+ cursor: '#c0caf5', // Cursor color
242
+ selection: '#33467c', // Selection highlight
243
+ // Standard ANSI colors
244
+ black: '#15161e',
245
+ red: '#f7768e',
246
+ green: '#9ece6a',
247
+ yellow: '#e0af68',
248
+ blue: '#7aa2f7',
249
+ magenta: '#bb9af7',
250
+ cyan: '#7dcfff',
251
+ white: '#a9b1d6',
252
+ // Bright variants
253
+ brightBlack: '#414868',
254
+ brightRed: '#f7768e',
255
+ brightGreen: '#9ece6a',
256
+ brightYellow: '#e0af68',
257
+ brightBlue: '#7aa2f7',
258
+ brightMagenta: '#bb9af7',
259
+ brightCyan: '#7dcfff',
260
+ brightWhite: '#c0caf5',
261
+ };
262
+
263
+ // Create theme object
264
+ const tokyoNight: Theme = {
265
+ name: 'tokyo-night',
266
+ colors: myColors,
267
+ };
268
+
269
+ // Use custom theme
270
+ const Terminal = await createTerminal({
271
+ container: '#terminal',
272
+ theme: tokyoNight,
273
+ });
274
+
275
+ // You can also change theme at runtime
276
+ Terminal.setTheme(tokyoNight);
277
+ ```
278
+
279
+ ### ThemeColors Reference
280
+
281
+ | Property | Description | ANSI Code |
282
+ |----------|-------------|-----------|
283
+ | `background` | Terminal background color | - |
284
+ | `foreground` | Default text color | - |
285
+ | `cursor` | Cursor color | - |
286
+ | `selection` | Text selection highlight | - |
287
+ | `black` | ANSI black | `\x1b[30m` |
288
+ | `red` | ANSI red | `\x1b[31m` |
289
+ | `green` | ANSI green | `\x1b[32m` |
290
+ | `yellow` | ANSI yellow | `\x1b[33m` |
291
+ | `blue` | ANSI blue | `\x1b[34m` |
292
+ | `magenta` | ANSI magenta | `\x1b[35m` |
293
+ | `cyan` | ANSI cyan | `\x1b[36m` |
294
+ | `white` | ANSI white | `\x1b[37m` |
295
+ | `brightBlack` | Bright black (gray) | `\x1b[90m` |
296
+ | `brightRed` | Bright red | `\x1b[91m` |
297
+ | `brightGreen` | Bright green | `\x1b[92m` |
298
+ | `brightYellow` | Bright yellow | `\x1b[93m` |
299
+ | `brightBlue` | Bright blue | `\x1b[94m` |
300
+ | `brightMagenta` | Bright magenta | `\x1b[95m` |
301
+ | `brightCyan` | Bright cyan | `\x1b[96m` |
302
+ | `brightWhite` | Bright white | `\x1b[97m` |
303
+
304
+ ### Built-in Themes
305
+
306
+ The library exports two built-in themes that you can use directly or extend:
307
+
308
+ ```typescript
309
+ import { darkTheme, lightTheme } from 'duckdb-terminal';
310
+
311
+ // Use directly
312
+ const Terminal = await createTerminal({
313
+ container: '#terminal',
314
+ theme: darkTheme,
315
+ });
316
+
317
+ // Or extend
318
+ const myTheme: Theme = {
319
+ name: 'my-dark',
320
+ colors: {
321
+ ...darkTheme.colors,
322
+ background: '#000000', // Override specific colors
323
+ cursor: '#ff0000',
324
+ },
325
+ };
326
+ ```
327
+
328
+ ## API
329
+
330
+ ### createTerminal(config)
331
+
332
+ Creates and starts a DuckDB Terminal instance.
333
+
334
+ ```typescript
335
+ import { createTerminal } from 'duckdb-terminal';
336
+
337
+ const Terminal = await createTerminal({
338
+ container: document.getElementById('terminal'),
339
+ theme: 'dark',
340
+ });
341
+
342
+ // Write to terminal
343
+ Terminal.write('Hello, World!');
344
+ Terminal.writeln('With newline');
345
+
346
+ // Execute SQL programmatically
347
+ const result = await Terminal.executeSQL('SELECT 1+1 as answer');
348
+ console.log(result); // { columns: ['answer'], rows: [[2]], rowCount: 1, duration: 5 }
349
+
350
+ // Change theme
351
+ Terminal.setTheme('light');
352
+
353
+ // Clear terminal
354
+ Terminal.clear();
355
+
356
+ // Clean up when done (removes event listeners, closes database)
357
+ Terminal.destroy();
358
+ ```
359
+
360
+ ### Events
361
+
362
+ The Terminal emits events that you can subscribe to for monitoring and integrating with your application:
363
+
364
+ ```typescript
365
+ import { createTerminal, type TerminalEvents } from 'duckdb-terminal';
366
+
367
+ const Terminal = await createTerminal({
368
+ container: '#terminal',
369
+ theme: 'dark',
370
+ });
371
+
372
+ // Subscribe to events
373
+ Terminal.on('ready', () => {
374
+ console.log('Terminal is ready!');
375
+ });
376
+
377
+ Terminal.on('queryStart', ({ sql }) => {
378
+ console.log('Executing:', sql);
379
+ });
380
+
381
+ Terminal.on('queryEnd', ({ sql, result, error, duration }) => {
382
+ if (error) {
383
+ console.error('Query failed:', error);
384
+ } else {
385
+ console.log(`Query completed in ${duration}ms, ${result?.rowCount} rows`);
386
+ }
387
+ });
388
+
389
+ Terminal.on('stateChange', ({ state, previous }) => {
390
+ console.log(`State changed: ${previous} -> ${state}`);
391
+ });
392
+
393
+ Terminal.on('themeChange', ({ theme, previous }) => {
394
+ console.log(`Theme changed to: ${theme.name}`);
395
+ });
396
+
397
+ Terminal.on('fileLoaded', ({ filename, size, type }) => {
398
+ console.log(`Loaded file: ${filename} (${size} bytes)`);
399
+ });
400
+
401
+ Terminal.on('commandExecute', ({ command, args }) => {
402
+ console.log(`Command: ${command}`, args);
403
+ });
404
+
405
+ Terminal.on('error', ({ message, source }) => {
406
+ console.error(`Error from ${source}: ${message}`);
407
+ });
408
+
409
+ // Unsubscribe using the returned function
410
+ const unsubscribe = Terminal.on('queryEnd', handler);
411
+ unsubscribe(); // Stop listening
412
+
413
+ // Or use off() directly
414
+ Terminal.off('queryEnd', handler);
415
+ ```
416
+
417
+ #### Available Events
418
+
419
+ | Event | Payload | Description |
420
+ |-------|---------|-------------|
421
+ | `ready` | `{}` | Terminal is fully initialized |
422
+ | `queryStart` | `{ sql }` | SQL query execution started |
423
+ | `queryEnd` | `{ sql, result, error?, duration }` | SQL query completed or failed |
424
+ | `stateChange` | `{ state, previous }` | Terminal state changed (idle, collecting, executing, paginating) |
425
+ | `themeChange` | `{ theme, previous }` | Theme was changed |
426
+ | `fileLoaded` | `{ filename, size, type }` | File was loaded via drag-drop or picker |
427
+ | `commandExecute` | `{ command, args }` | Dot command was executed |
428
+ | `error` | `{ message, source }` | An error occurred |
429
+
430
+ ### Advanced Usage
431
+
432
+ ```typescript
433
+ import {
434
+ Terminal,
435
+ Database,
436
+ TerminalAdapter,
437
+ formatTable,
438
+ formatCSV,
439
+ formatJSON
440
+ } from 'duckdb-terminal';
441
+
442
+ // Use Database directly
443
+ const db = new Database({ storage: 'memory' });
444
+ await db.init();
445
+ const result = await db.executeQuery('SELECT 42');
446
+
447
+ // Use formatters
448
+ console.log(formatTable(result.columns, result.rows));
449
+ console.log(formatCSV(result.columns, result.rows));
450
+ console.log(formatJSON(result.columns, result.rows));
451
+
452
+ // Get completions
453
+ const suggestions = await db.getCompletions('SEL', 3);
454
+ ```
455
+
456
+ ## Browser Requirements
457
+
458
+ - Modern browser with WebAssembly support
459
+ - SharedArrayBuffer (requires COOP/COEP headers)
460
+
461
+ For development, Vite automatically sets the required headers. For production, configure your server:
462
+
463
+ ```
464
+ Cross-Origin-Opener-Policy: same-origin
465
+ Cross-Origin-Embedder-Policy: require-corp
466
+ ```
467
+
468
+ ## Building
469
+
470
+ ```bash
471
+ # Development
472
+ npm run dev
473
+
474
+ # Production build
475
+ npm run build
476
+
477
+ # Run tests
478
+ npm test
479
+
480
+ # Type checking
481
+ npm run typecheck
482
+ ```
483
+
484
+ ## Bundle Outputs
485
+
486
+ | File | Format | Usage |
487
+ |------|--------|-------|
488
+ | `dist/duckdb-terminal.js` | ESM | Modern bundlers |
489
+ | `dist/duckdb-terminal.umd.cjs` | UMD | Script tags, legacy |
490
+ | `dist/*.d.ts` | TypeScript | Type definitions |
491
+
492
+ ## Dependencies
493
+
494
+ - [@duckdb/duckdb-wasm](https://www.npmjs.com/package/@duckdb/duckdb-wasm) - DuckDB WebAssembly build
495
+ - [ghostty-web](https://www.npmjs.com/package/ghostty-web) - Ghostty terminal emulator for web
496
+
497
+ ## License
498
+
499
+ MIT
500
+
501
+ ## Credits
502
+
503
+ - [DuckDB](https://duckdb.org/) - The in-process analytical database
504
+ - [Ghostty](https://ghostty.org/) - Fast, native terminal emulator
505
+ - [ghostty-web](https://github.com/coder/ghostty-web) - Web port by Coder