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.
@@ -0,0 +1,3407 @@
1
+ var st = Object.defineProperty;
2
+ var rt = (i, t, e) => t in i ? st(i, t, { enumerable: !0, configurable: !0, writable: !0, value: e }) : i[t] = e;
3
+ var a = (i, t, e) => rt(i, typeof t != "symbol" ? t + "" : t, e);
4
+ import { Ghostty as nt, Terminal as ot, FitAddon as at } from "ghostty-web";
5
+ import * as R from "@duckdb/duckdb-wasm";
6
+ const lt = "https://cdn.jsdelivr.net/npm/ghostty-web", ht = () => `${lt}@0.3.0/dist/ghostty-vt.wasm`;
7
+ class ct {
8
+ constructor() {
9
+ a(this, "terminal");
10
+ a(this, "fitAddon");
11
+ a(this, "ghostty");
12
+ a(this, "dataHandler");
13
+ a(this, "resizeHandler");
14
+ a(this, "currentTheme", null);
15
+ a(this, "initialized", !1);
16
+ a(this, "container", null);
17
+ a(this, "options", {});
18
+ a(this, "resizeObserver", null);
19
+ a(this, "mobileInput", null);
20
+ /**
21
+ * Handles window resize events.
22
+ * @internal
23
+ */
24
+ a(this, "handleResize", () => {
25
+ this.fit();
26
+ });
27
+ }
28
+ /**
29
+ * Initializes the Ghostty terminal and mounts it to the container.
30
+ *
31
+ * This method:
32
+ * 1. Initializes the Ghostty WASM module
33
+ * 2. Creates the terminal with the specified options
34
+ * 3. Loads the FitAddon for auto-resizing
35
+ * 4. Mounts the terminal to the container
36
+ * 5. Sets up resize observers
37
+ *
38
+ * @param container - The HTML element to mount the terminal into
39
+ * @param options - Configuration options for the terminal
40
+ * @returns A promise that resolves when initialization is complete
41
+ *
42
+ * @example
43
+ * ```typescript
44
+ * const adapter = new TerminalAdapter();
45
+ * await adapter.init(document.getElementById('terminal'), {
46
+ * fontSize: 14,
47
+ * fontFamily: 'JetBrains Mono',
48
+ * theme: darkTheme,
49
+ * });
50
+ * ```
51
+ */
52
+ async init(t, e = {}) {
53
+ this.initialized || (this.container = t, this.options = e, this.currentTheme = e.theme ?? null, this.ghostty = await nt.load(ht()), this.createTerminal(), this.initialized = !0, this.focus());
54
+ }
55
+ /**
56
+ * Creates or recreates the terminal instance.
57
+ * @internal
58
+ */
59
+ createTerminal() {
60
+ this.container && (this.terminal = new ot({
61
+ ghostty: this.ghostty,
62
+ cursorBlink: !0,
63
+ fontSize: this.options.fontSize ?? 14,
64
+ fontFamily: this.options.fontFamily ?? '"Fira Code", "Cascadia Code", "JetBrains Mono", Consolas, monospace',
65
+ theme: this.currentTheme ? this.themeToGhostty(this.currentTheme) : void 0,
66
+ // Scrollback is in bytes, not lines. 10MB is a reasonable default.
67
+ scrollback: this.options.scrollback ?? 10 * 1024 * 1024
68
+ }), this.fitAddon = new at(), this.terminal.loadAddon(this.fitAddon), this.container.innerHTML = "", this.terminal.open(this.container), this.fit(), window.addEventListener("resize", this.handleResize), typeof ResizeObserver < "u" && (this.resizeObserver?.disconnect(), this.resizeObserver = new ResizeObserver(() => {
69
+ this.fit();
70
+ }), this.resizeObserver.observe(this.container)), this.terminal.onData((t) => {
71
+ this.dataHandler?.(t);
72
+ }), this.terminal.onResize(({ cols: t, rows: e }) => {
73
+ this.resizeHandler?.(t, e);
74
+ }), this.setupMobileInput());
75
+ }
76
+ /**
77
+ * Sets up mobile-specific functionality: touch scrolling and keyboard input.
78
+ * @internal
79
+ */
80
+ setupMobileInput() {
81
+ this.container && this.isTouchDevice() && (this.setupTouchScrolling(), this.setupMobileKeyboard());
82
+ }
83
+ /**
84
+ * Creates a hidden textarea for mobile keyboard input.
85
+ * Appended to document.body to avoid interfering with terminal touch events.
86
+ * @internal
87
+ */
88
+ setupMobileKeyboard() {
89
+ this.mobileInput?.remove();
90
+ const t = document.createElement("textarea");
91
+ t.className = "mobile-keyboard-input", t.setAttribute("autocapitalize", "off"), t.setAttribute("autocomplete", "off"), t.setAttribute("autocorrect", "off"), t.setAttribute("spellcheck", "false"), t.setAttribute("enterkeyhint", "send"), t.setAttribute("aria-label", "Terminal input"), t.style.cssText = `
92
+ position: fixed;
93
+ bottom: 0;
94
+ left: 50%;
95
+ transform: translateX(-50%);
96
+ width: 1px;
97
+ height: 1px;
98
+ opacity: 0;
99
+ font-size: 16px;
100
+ border: none;
101
+ outline: none;
102
+ resize: none;
103
+ background: transparent;
104
+ color: transparent;
105
+ z-index: -1;
106
+ `, t.addEventListener("input", () => {
107
+ const e = t.value;
108
+ e && this.dataHandler && this.dataHandler(e), t.value = "";
109
+ }), t.addEventListener("keydown", (e) => {
110
+ e.key.length === 1 && !e.ctrlKey && !e.altKey && !e.metaKey || (e.key === "Enter" ? (e.preventDefault(), this.dataHandler?.("\r"), t.value = "", t.blur()) : e.key === "Backspace" ? (e.preventDefault(), this.dataHandler?.("")) : e.key === "Tab" ? (e.preventDefault(), this.dataHandler?.(" ")) : e.key === "ArrowUp" ? (e.preventDefault(), this.dataHandler?.("\x1B[A")) : e.key === "ArrowDown" ? (e.preventDefault(), this.dataHandler?.("\x1B[B")) : e.key === "ArrowLeft" ? (e.preventDefault(), this.dataHandler?.("\x1B[D")) : e.key === "ArrowRight" && (e.preventDefault(), this.dataHandler?.("\x1B[C")));
111
+ }), document.body.appendChild(t), this.mobileInput = t;
112
+ }
113
+ /**
114
+ * Sets up touch-based scrolling for the terminal.
115
+ * Translates touch gestures into scroll commands.
116
+ * @internal
117
+ */
118
+ setupTouchScrolling() {
119
+ if (!this.container) return;
120
+ let t = 0, e = 0, s = !1;
121
+ const n = (l) => {
122
+ l.touches.length === 1 && (t = l.touches[0].clientY, e = t, s = !1);
123
+ }, r = (l) => {
124
+ if (l.touches.length !== 1) return;
125
+ const h = l.touches[0].clientY, d = e - h;
126
+ if (!s && Math.abs(h - t) > 10 && (s = !0), s) {
127
+ l.preventDefault();
128
+ const f = Math.round(d / 20);
129
+ f !== 0 && this.terminal && this.terminal.scrollLines?.(f), e = h;
130
+ }
131
+ }, o = () => {
132
+ s = !1;
133
+ };
134
+ this.container.addEventListener("touchstart", n, { passive: !0 }), this.container.addEventListener("touchmove", r, { passive: !1 }), this.container.addEventListener("touchend", o, { passive: !0 });
135
+ }
136
+ /**
137
+ * Converts a Theme object to Ghostty's theme format.
138
+ *
139
+ * @internal
140
+ * @param theme - The theme to convert
141
+ * @returns The theme in Ghostty's expected format
142
+ */
143
+ themeToGhostty(t) {
144
+ return {
145
+ background: t.colors.background,
146
+ foreground: t.colors.foreground,
147
+ cursor: t.colors.cursor,
148
+ black: t.colors.black,
149
+ red: t.colors.red,
150
+ green: t.colors.green,
151
+ yellow: t.colors.yellow,
152
+ blue: t.colors.blue,
153
+ magenta: t.colors.magenta,
154
+ cyan: t.colors.cyan,
155
+ white: t.colors.white,
156
+ brightBlack: t.colors.brightBlack,
157
+ brightRed: t.colors.brightRed,
158
+ brightGreen: t.colors.brightGreen,
159
+ brightYellow: t.colors.brightYellow,
160
+ brightBlue: t.colors.brightBlue,
161
+ brightMagenta: t.colors.brightMagenta,
162
+ brightCyan: t.colors.brightCyan,
163
+ brightWhite: t.colors.brightWhite
164
+ };
165
+ }
166
+ /**
167
+ * Fits the terminal to its container dimensions.
168
+ *
169
+ * This method should be called when the container size changes.
170
+ * It's automatically called on window resize, but you may need
171
+ * to call it manually after dynamic layout changes.
172
+ *
173
+ * @example
174
+ * ```typescript
175
+ * // After changing container size
176
+ * container.style.height = '500px';
177
+ * adapter.fit();
178
+ * ```
179
+ */
180
+ fit() {
181
+ this.fitAddon && this.fitAddon.fit();
182
+ }
183
+ /**
184
+ * Writes text to the terminal without a trailing newline.
185
+ *
186
+ * @param text - The text to write
187
+ *
188
+ * @example
189
+ * ```typescript
190
+ * adapter.write('Hello');
191
+ * adapter.write(' World');
192
+ * ```
193
+ */
194
+ write(t) {
195
+ this.terminal?.write(t);
196
+ }
197
+ /**
198
+ * Writes text to the terminal followed by a newline.
199
+ *
200
+ * @param text - The text to write
201
+ *
202
+ * @example
203
+ * ```typescript
204
+ * adapter.writeln('First line');
205
+ * adapter.writeln('Second line');
206
+ * ```
207
+ */
208
+ writeln(t) {
209
+ this.terminal?.writeln(t);
210
+ }
211
+ /**
212
+ * Clears the terminal screen and moves cursor to top-left.
213
+ *
214
+ * @example
215
+ * ```typescript
216
+ * adapter.clear();
217
+ * adapter.writeln('Fresh start!');
218
+ * ```
219
+ */
220
+ clear() {
221
+ this.terminal?.write("\x1B[2J\x1B[H");
222
+ }
223
+ /**
224
+ * Gives keyboard focus to the terminal.
225
+ * On mobile devices, focuses the hidden input to trigger the virtual keyboard.
226
+ *
227
+ * @example
228
+ * ```typescript
229
+ * adapter.focus();
230
+ * ```
231
+ */
232
+ focus() {
233
+ this.mobileInput && this.isTouchDevice() ? this.mobileInput.focus() : this.terminal?.focus();
234
+ }
235
+ /**
236
+ * Checks if the current device supports touch input.
237
+ * @internal
238
+ */
239
+ isTouchDevice() {
240
+ return "ontouchstart" in window || navigator.maxTouchPoints > 0;
241
+ }
242
+ /**
243
+ * Registers a callback to receive user input data.
244
+ *
245
+ * The callback is invoked whenever the user types in the terminal.
246
+ * This includes regular characters, control sequences, and escape codes.
247
+ *
248
+ * @param handler - The callback function to handle input data
249
+ *
250
+ * @example
251
+ * ```typescript
252
+ * adapter.onData((data) => {
253
+ * if (data === '\r') {
254
+ * console.log('User pressed Enter');
255
+ * } else {
256
+ * console.log('User typed:', data);
257
+ * }
258
+ * });
259
+ * ```
260
+ */
261
+ onData(t) {
262
+ this.dataHandler = t;
263
+ }
264
+ /**
265
+ * Registers a callback to receive terminal resize events.
266
+ *
267
+ * @param handler - The callback function receiving new dimensions
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * adapter.onResize((cols, rows) => {
272
+ * console.log(`Terminal resized to ${cols}x${rows}`);
273
+ * });
274
+ * ```
275
+ */
276
+ onResize(t) {
277
+ this.resizeHandler = t;
278
+ }
279
+ /**
280
+ * The number of columns (characters per line) in the terminal.
281
+ *
282
+ * @returns The current column count, or 80 if not initialized
283
+ */
284
+ get cols() {
285
+ return this.terminal?.cols ?? 80;
286
+ }
287
+ /**
288
+ * The number of rows (lines) in the terminal.
289
+ *
290
+ * @returns The current row count, or 24 if not initialized
291
+ */
292
+ get rows() {
293
+ return this.terminal?.rows ?? 24;
294
+ }
295
+ /**
296
+ * Sets the terminal color theme.
297
+ *
298
+ * Since Ghostty-web doesn't fully support runtime theme changes,
299
+ * this method recreates the terminal with the new theme.
300
+ *
301
+ * @param theme - The theme to apply
302
+ *
303
+ * @example
304
+ * ```typescript
305
+ * adapter.setTheme({
306
+ * name: 'dark',
307
+ * colors: {
308
+ * background: '#1e1e1e',
309
+ * foreground: '#d4d4d4',
310
+ * // ... other colors
311
+ * },
312
+ * });
313
+ * ```
314
+ */
315
+ setTheme(t) {
316
+ this.currentTheme = t, !(!this.initialized || !this.container) && (window.removeEventListener("resize", this.handleResize), this.terminal?.dispose(), this.createTerminal(), this.focus());
317
+ }
318
+ /**
319
+ * Gets the current terminal theme.
320
+ *
321
+ * @returns The current theme, or null if no theme is set
322
+ */
323
+ getTheme() {
324
+ return this.currentTheme;
325
+ }
326
+ /**
327
+ * Disposes of the terminal and cleans up resources.
328
+ *
329
+ * After calling this method, the adapter cannot be used again.
330
+ * Create a new instance if you need another terminal.
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * adapter.dispose();
335
+ * // adapter is no longer usable
336
+ * ```
337
+ */
338
+ dispose() {
339
+ window.removeEventListener("resize", this.handleResize), this.resizeObserver?.disconnect(), this.resizeObserver = null, this.mobileInput?.remove(), this.mobileInput = null, this.terminal?.dispose(), this.initialized = !1, this.container = null;
340
+ }
341
+ }
342
+ class ut {
343
+ constructor(t = {}) {
344
+ a(this, "db", null);
345
+ a(this, "conn", null);
346
+ a(this, "worker", null);
347
+ a(this, "initialized", !1);
348
+ a(this, "options");
349
+ this.options = {
350
+ storage: "memory",
351
+ databasePath: ":memory:",
352
+ ...t
353
+ };
354
+ }
355
+ /**
356
+ * Initializes the DuckDB database.
357
+ *
358
+ * This method performs the following setup:
359
+ * 1. Downloads the appropriate DuckDB WASM bundle
360
+ * 2. Creates a Web Worker for database operations
361
+ * 3. Instantiates the DuckDB instance
362
+ * 4. Opens the database (in-memory or OPFS-backed)
363
+ * 5. Creates a connection for query execution
364
+ *
365
+ * @returns A promise that resolves when initialization is complete
366
+ *
367
+ * @throws Error if initialization fails (network issues, WASM loading, etc.)
368
+ *
369
+ * @example
370
+ * ```typescript
371
+ * const db = new Database();
372
+ * await db.init();
373
+ * console.log('Database ready:', db.isReady());
374
+ * ```
375
+ */
376
+ async init() {
377
+ if (this.initialized)
378
+ return;
379
+ const t = R.getJsDelivrBundles(), e = await R.selectBundle(t), s = URL.createObjectURL(
380
+ new Blob([`importScripts("${e.mainWorker}");`], {
381
+ type: "text/javascript"
382
+ })
383
+ );
384
+ this.worker = new Worker(s);
385
+ const n = new R.ConsoleLogger();
386
+ this.db = new R.AsyncDuckDB(n, this.worker), await this.db.instantiate(e.mainModule, e.pthreadWorker), URL.revokeObjectURL(s);
387
+ const r = {
388
+ castDecimalToDouble: !0
389
+ };
390
+ this.options.storage === "opfs" && this.options.databasePath ? await this.db.open({
391
+ path: this.options.databasePath,
392
+ accessMode: R.DuckDBAccessMode.READ_WRITE,
393
+ query: r
394
+ }) : await this.db.open({
395
+ path: ":memory:",
396
+ query: r
397
+ }), this.conn = await this.db.connect(), this.initialized = !0;
398
+ }
399
+ /**
400
+ * Executes a SQL query and returns the results.
401
+ *
402
+ * @param sql - The SQL statement to execute
403
+ * @returns A promise that resolves to the query result containing columns, rows, row count, and duration
404
+ *
405
+ * @throws Error if the database is not initialized
406
+ * @throws Error if the SQL query is invalid or fails
407
+ *
408
+ * @example SELECT query
409
+ * ```typescript
410
+ * const result = await db.executeQuery('SELECT * FROM users WHERE age > 18;');
411
+ * console.log('Columns:', result.columns); // ['id', 'name', 'age']
412
+ * console.log('Rows:', result.rows); // [[1, 'Alice', 25], [2, 'Bob', 30]]
413
+ * console.log('Duration:', result.duration); // 5.23 (milliseconds)
414
+ * ```
415
+ *
416
+ * @example DDL statement
417
+ * ```typescript
418
+ * await db.executeQuery('CREATE TABLE products (id INTEGER, name VARCHAR);');
419
+ * ```
420
+ */
421
+ async executeQuery(t) {
422
+ if (!this.conn)
423
+ throw new Error("Database not initialized");
424
+ const e = performance.now(), s = await this.conn.query(t), n = performance.now() - e, r = s.schema.fields.map((h) => h.name), o = [];
425
+ for (let h = 0; h < s.numRows; h++) {
426
+ const d = [];
427
+ for (let f = 0; f < r.length; f++) {
428
+ const m = s.getChildAt(f);
429
+ d.push(m?.get(h));
430
+ }
431
+ o.push(d);
432
+ }
433
+ const l = typeof s.numRows == "bigint" ? Number(s.numRows) : s.numRows;
434
+ return {
435
+ columns: r,
436
+ rows: o,
437
+ rowCount: l,
438
+ duration: n
439
+ };
440
+ }
441
+ /**
442
+ * Gets auto-completion suggestions for the current input.
443
+ *
444
+ * Provides suggestions for:
445
+ * - SQL keywords (SELECT, FROM, WHERE, etc.)
446
+ * - Table names from the current database
447
+ * - Common SQL functions
448
+ *
449
+ * @param text - The current input text
450
+ * @param cursorPosition - The cursor position within the text
451
+ * @returns A promise that resolves to an array of completion suggestions
452
+ *
453
+ * @example
454
+ * ```typescript
455
+ * const suggestions = await db.getCompletions('SEL', 3);
456
+ * console.log(suggestions);
457
+ * // [{ value: 'SELECT', type: 'keyword' }]
458
+ * ```
459
+ */
460
+ async getCompletions(t, e) {
461
+ if (!this.db)
462
+ return [];
463
+ try {
464
+ const n = t.substring(0, e).match(/[\w.]*$/), r = n ? n[0].toLowerCase() : "";
465
+ if (!r)
466
+ return [];
467
+ const o = [], l = [
468
+ "SELECT",
469
+ "FROM",
470
+ "WHERE",
471
+ "AND",
472
+ "OR",
473
+ "NOT",
474
+ "INSERT",
475
+ "INTO",
476
+ "VALUES",
477
+ "UPDATE",
478
+ "SET",
479
+ "DELETE",
480
+ "CREATE",
481
+ "TABLE",
482
+ "DROP",
483
+ "ALTER",
484
+ "INDEX",
485
+ "VIEW",
486
+ "JOIN",
487
+ "LEFT",
488
+ "RIGHT",
489
+ "INNER",
490
+ "OUTER",
491
+ "ON",
492
+ "GROUP",
493
+ "BY",
494
+ "ORDER",
495
+ "HAVING",
496
+ "LIMIT",
497
+ "OFFSET",
498
+ "UNION",
499
+ "EXCEPT",
500
+ "INTERSECT",
501
+ "AS",
502
+ "DISTINCT",
503
+ "ALL",
504
+ "NULL",
505
+ "TRUE",
506
+ "FALSE",
507
+ "CASE",
508
+ "WHEN",
509
+ "THEN",
510
+ "ELSE",
511
+ "END",
512
+ "IS",
513
+ "IN",
514
+ "BETWEEN",
515
+ "LIKE",
516
+ "EXISTS",
517
+ "COUNT",
518
+ "SUM",
519
+ "AVG",
520
+ "MIN",
521
+ "MAX",
522
+ "CAST",
523
+ "COALESCE",
524
+ "NULLIF"
525
+ ];
526
+ for (const d of l)
527
+ d.toLowerCase().startsWith(r) && o.push({ value: d, type: "keyword" });
528
+ try {
529
+ const d = await this.executeQuery(
530
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'main'"
531
+ );
532
+ for (const f of d.rows) {
533
+ const m = String(f[0]);
534
+ m.toLowerCase().startsWith(r) && o.push({ value: m, type: "table" });
535
+ }
536
+ } catch {
537
+ }
538
+ const h = [
539
+ "abs",
540
+ "ceil",
541
+ "floor",
542
+ "round",
543
+ "sqrt",
544
+ "log",
545
+ "exp",
546
+ "power",
547
+ "length",
548
+ "lower",
549
+ "upper",
550
+ "trim",
551
+ "ltrim",
552
+ "rtrim",
553
+ "replace",
554
+ "substring",
555
+ "concat",
556
+ "now",
557
+ "current_date",
558
+ "current_time",
559
+ "current_timestamp",
560
+ "date_part",
561
+ "date_trunc",
562
+ "extract",
563
+ "array_agg",
564
+ "string_agg",
565
+ "list_agg",
566
+ "first",
567
+ "last",
568
+ "any_value"
569
+ ];
570
+ for (const d of h)
571
+ d.toLowerCase().startsWith(r) && o.push({ value: d, type: "function" });
572
+ return o.sort((d, f) => {
573
+ const m = d.value.toLowerCase() === r, P = f.value.toLowerCase() === r;
574
+ return m && !P ? -1 : !m && P ? 1 : d.value.localeCompare(f.value);
575
+ }), o.slice(0, 20);
576
+ } catch {
577
+ return [];
578
+ }
579
+ }
580
+ /**
581
+ * Gets a list of all tables in the database.
582
+ *
583
+ * Queries the information_schema to retrieve table names from the 'main' schema.
584
+ *
585
+ * @returns A promise that resolves to an array of table names
586
+ *
587
+ * @example
588
+ * ```typescript
589
+ * const tables = await db.getTables();
590
+ * console.log('Tables:', tables); // ['users', 'products', 'orders']
591
+ * ```
592
+ */
593
+ async getTables() {
594
+ if (!this.conn)
595
+ return [];
596
+ try {
597
+ return (await this.executeQuery(
598
+ "SELECT table_name FROM information_schema.tables WHERE table_schema = 'main' ORDER BY table_name"
599
+ )).rows.map((e) => String(e[0]));
600
+ } catch {
601
+ return [];
602
+ }
603
+ }
604
+ /**
605
+ * Gets the schema (column definitions) for a specific table.
606
+ *
607
+ * @param tableName - The name of the table to get the schema for
608
+ * @returns A promise that resolves to an array of column definitions
609
+ *
610
+ * @example
611
+ * ```typescript
612
+ * const schema = await db.getTableSchema('users');
613
+ * console.log(schema);
614
+ * // [
615
+ * // { name: 'id', type: 'INTEGER' },
616
+ * // { name: 'name', type: 'VARCHAR' },
617
+ * // { name: 'email', type: 'VARCHAR' }
618
+ * // ]
619
+ * ```
620
+ */
621
+ async getTableSchema(t) {
622
+ if (!this.conn)
623
+ return [];
624
+ try {
625
+ const e = t.replace(/'/g, "''");
626
+ return (await this.executeQuery(
627
+ `SELECT column_name, data_type FROM information_schema.columns WHERE table_name = '${e}' ORDER BY ordinal_position`
628
+ )).rows.map((n) => ({
629
+ name: String(n[0]),
630
+ type: String(n[1])
631
+ }));
632
+ } catch {
633
+ return [];
634
+ }
635
+ }
636
+ /**
637
+ * Registers a file in DuckDB's virtual filesystem.
638
+ *
639
+ * This allows you to load external files (CSV, Parquet, JSON) into DuckDB
640
+ * and query them using functions like `read_csv()`, `read_parquet()`, etc.
641
+ *
642
+ * @param filename - The virtual filename to register (e.g., 'data.csv')
643
+ * @param data - The file contents as a Uint8Array
644
+ *
645
+ * @throws Error if the database is not initialized
646
+ *
647
+ * @example
648
+ * ```typescript
649
+ * // Load a CSV file
650
+ * const response = await fetch('https://example.com/data.csv');
651
+ * const data = new Uint8Array(await response.arrayBuffer());
652
+ * await db.registerFile('data.csv', data);
653
+ *
654
+ * // Query the file
655
+ * const result = await db.executeQuery("SELECT * FROM read_csv('data.csv');");
656
+ * ```
657
+ */
658
+ async registerFile(t, e) {
659
+ if (!this.db)
660
+ throw new Error("Database not initialized");
661
+ await this.db.registerFileBuffer(t, e);
662
+ }
663
+ /**
664
+ * Removes a file from DuckDB's virtual filesystem.
665
+ *
666
+ * @param filename - The filename to remove
667
+ *
668
+ * @throws Error if the database is not initialized
669
+ *
670
+ * @example
671
+ * ```typescript
672
+ * await db.dropFile('data.csv');
673
+ * ```
674
+ */
675
+ async dropFile(t) {
676
+ if (!this.db)
677
+ throw new Error("Database not initialized");
678
+ await this.db.dropFile(t);
679
+ }
680
+ /**
681
+ * Checks if the database is initialized and ready for queries.
682
+ *
683
+ * @returns True if the database is ready, false otherwise
684
+ *
685
+ * @example
686
+ * ```typescript
687
+ * if (db.isReady()) {
688
+ * const result = await db.executeQuery('SELECT 1;');
689
+ * }
690
+ * ```
691
+ */
692
+ isReady() {
693
+ return this.initialized && this.conn !== null;
694
+ }
695
+ /**
696
+ * Closes the database connection and releases resources.
697
+ *
698
+ * This method:
699
+ * 1. Closes the database connection
700
+ * 2. Terminates the DuckDB instance
701
+ * 3. Terminates the Web Worker
702
+ *
703
+ * After calling this method, {@link init} must be called again before
704
+ * executing any queries.
705
+ *
706
+ * @returns A promise that resolves when cleanup is complete
707
+ *
708
+ * @example
709
+ * ```typescript
710
+ * await db.close();
711
+ * console.log('Database closed:', !db.isReady());
712
+ * ```
713
+ */
714
+ async close() {
715
+ this.conn && (await this.conn.close(), this.conn = null), this.db && (await this.db.terminate(), this.db = null), this.worker && (this.worker.terminate(), this.worker = null), this.initialized = !1;
716
+ }
717
+ }
718
+ const v = "\x1B[0m", dt = "\x1B[1m", ft = "\x1B[2m", mt = "\x1B[30m", g = "\x1B[31m", b = "\x1B[32m", N = "\x1B[33m", U = "\x1B[34m", j = "\x1B[35m", S = "\x1B[36m", F = "\x1B[37m", w = "\x1B[90m", gt = "\x1B[91m", pt = "\x1B[92m", wt = "\x1B[93m", bt = "\x1B[94m", Tt = "\x1B[95m", Et = "\x1B[96m", yt = "\x1B[97m", St = "\x1B[C", x = "\x1B[D", C = "\x1B[K";
719
+ function Lt(i) {
720
+ return `\x1B[${i}C`;
721
+ }
722
+ function T(i) {
723
+ return `\x1B[${i}D`;
724
+ }
725
+ const Rt = {
726
+ black: mt,
727
+ red: g,
728
+ green: b,
729
+ yellow: N,
730
+ blue: U,
731
+ magenta: j,
732
+ cyan: S,
733
+ white: F,
734
+ brightBlack: w,
735
+ brightRed: gt,
736
+ brightGreen: pt,
737
+ brightYellow: wt,
738
+ brightBlue: bt,
739
+ brightMagenta: Tt,
740
+ brightCyan: Et,
741
+ brightWhite: yt
742
+ };
743
+ function c(i, t) {
744
+ return `${Rt[t] || t}${i}${v}`;
745
+ }
746
+ function y(i) {
747
+ return `${dt}${i}${v}`;
748
+ }
749
+ function u(i) {
750
+ return `${ft}${i}${v}`;
751
+ }
752
+ const At = 64 * 1024;
753
+ class Ct {
754
+ constructor() {
755
+ a(this, "buffer", "");
756
+ a(this, "cursorPos", 0);
757
+ a(this, "maxSize", At);
758
+ }
759
+ /**
760
+ * Set the prompt length for cursor calculations (reserved for future use)
761
+ */
762
+ setPromptLength(t) {
763
+ }
764
+ /**
765
+ * Sets the maximum buffer size.
766
+ *
767
+ * @param size - Maximum size in bytes (defaults to MAX_BUFFER_SIZE)
768
+ */
769
+ setMaxSize(t) {
770
+ this.maxSize = t;
771
+ }
772
+ /**
773
+ * Returns the maximum buffer size.
774
+ *
775
+ * @returns The maximum size in bytes
776
+ */
777
+ getMaxSize() {
778
+ return this.maxSize;
779
+ }
780
+ /**
781
+ * Returns the current buffer content.
782
+ *
783
+ * @returns The full text content of the buffer
784
+ */
785
+ getContent() {
786
+ return this.buffer;
787
+ }
788
+ /**
789
+ * Returns the current cursor position within the buffer.
790
+ *
791
+ * @returns The zero-based cursor position
792
+ */
793
+ getCursorPos() {
794
+ return this.cursorPos;
795
+ }
796
+ /**
797
+ * Clears the buffer and resets cursor to position 0.
798
+ */
799
+ clear() {
800
+ this.buffer = "", this.cursorPos = 0;
801
+ }
802
+ /**
803
+ * Sets the buffer content and moves cursor to end.
804
+ *
805
+ * Used for history navigation when replacing the current line with a previous command.
806
+ *
807
+ * @param content - The new buffer content
808
+ */
809
+ setContent(t) {
810
+ t.length > this.maxSize && (t = t.substring(0, this.maxSize)), this.buffer = t, this.cursorPos = t.length;
811
+ }
812
+ /**
813
+ * Inserts text at the cursor position.
814
+ *
815
+ * @param char - The character(s) to insert
816
+ * @returns VT100 escape sequences to update the terminal display
817
+ */
818
+ insert(t) {
819
+ if (this.buffer.length + t.length > this.maxSize) {
820
+ const n = this.maxSize - this.buffer.length;
821
+ if (n <= 0)
822
+ return "";
823
+ t = t.substring(0, n);
824
+ }
825
+ const e = this.buffer.substring(0, this.cursorPos), s = this.buffer.substring(this.cursorPos);
826
+ return this.buffer = e + t + s, this.cursorPos += t.length, s.length === 0 ? t : t + s + T(s.length);
827
+ }
828
+ /**
829
+ * Deletes the character before the cursor (backspace operation).
830
+ *
831
+ * @returns VT100 escape sequences to update the terminal display, or empty string if at start
832
+ */
833
+ backspace() {
834
+ if (this.cursorPos === 0)
835
+ return "";
836
+ const t = this.buffer.substring(0, this.cursorPos - 1), e = this.buffer.substring(this.cursorPos);
837
+ return this.buffer = t + e, this.cursorPos--, e.length === 0 ? "\b \b" : x + e + " " + T(e.length + 1);
838
+ }
839
+ /**
840
+ * Deletes the character at the cursor position (delete key operation).
841
+ *
842
+ * @returns VT100 escape sequences to update the terminal display, or empty string if at end
843
+ */
844
+ delete() {
845
+ if (this.cursorPos >= this.buffer.length)
846
+ return "";
847
+ const t = this.buffer.substring(0, this.cursorPos), e = this.buffer.substring(this.cursorPos + 1);
848
+ return this.buffer = t + e, e + " " + T(e.length + 1);
849
+ }
850
+ /**
851
+ * Moves the cursor one position to the left.
852
+ *
853
+ * @returns VT100 escape sequence, or empty string if already at start
854
+ */
855
+ moveLeft() {
856
+ return this.cursorPos > 0 ? (this.cursorPos--, x) : "";
857
+ }
858
+ /**
859
+ * Moves the cursor one position to the right.
860
+ *
861
+ * @returns VT100 escape sequence, or empty string if already at end
862
+ */
863
+ moveRight() {
864
+ return this.cursorPos < this.buffer.length ? (this.cursorPos++, St) : "";
865
+ }
866
+ /**
867
+ * Moves the cursor to the start of the line (Home key / Ctrl+A).
868
+ *
869
+ * @returns VT100 escape sequence, or empty string if already at start
870
+ */
871
+ moveToStart() {
872
+ if (this.cursorPos === 0)
873
+ return "";
874
+ const t = this.cursorPos;
875
+ return this.cursorPos = 0, T(t);
876
+ }
877
+ /**
878
+ * Moves the cursor to the end of the line (End key / Ctrl+E).
879
+ *
880
+ * @returns VT100 escape sequence, or empty string if already at end
881
+ */
882
+ moveToEnd() {
883
+ if (this.cursorPos === this.buffer.length)
884
+ return "";
885
+ const t = this.buffer.length - this.cursorPos;
886
+ return this.cursorPos = this.buffer.length, Lt(t);
887
+ }
888
+ /**
889
+ * Clears from cursor position to end of line (Ctrl+K).
890
+ *
891
+ * @returns VT100 escape sequence to clear the terminal
892
+ */
893
+ clearToEnd() {
894
+ return this.buffer = this.buffer.substring(0, this.cursorPos), C;
895
+ }
896
+ /**
897
+ * Clears the entire line and resets buffer (Ctrl+U).
898
+ *
899
+ * @returns VT100 escape sequences to clear the terminal line
900
+ */
901
+ clearLine() {
902
+ const t = this.moveToStart() + C;
903
+ return this.buffer = "", this.cursorPos = 0, t;
904
+ }
905
+ /**
906
+ * Returns the word immediately before the cursor for auto-completion.
907
+ *
908
+ * A word is defined as a sequence of word characters (`\w`) and dots.
909
+ *
910
+ * @returns The word before cursor, or empty string if none
911
+ */
912
+ getWordBeforeCursor() {
913
+ const e = this.buffer.substring(0, this.cursorPos).match(/[\w.]*$/);
914
+ return e ? e[0] : "";
915
+ }
916
+ /**
917
+ * Replaces the word before the cursor with a completion string.
918
+ *
919
+ * @param completion - The completion string to insert
920
+ * @returns VT100 escape sequences to update the terminal display
921
+ */
922
+ replaceWordBeforeCursor(t) {
923
+ const e = this.getWordBeforeCursor(), s = this.cursorPos - e.length, n = this.buffer.substring(0, s), r = this.buffer.substring(this.cursorPos);
924
+ this.buffer = n + t + r, this.cursorPos = s + t.length;
925
+ let o = "";
926
+ return e.length > 0 && (o += T(e.length)), o += C, o += t + r, r.length > 0 && (o += T(r.length)), o;
927
+ }
928
+ /**
929
+ * Checks if the buffer is empty.
930
+ *
931
+ * @returns True if buffer has no content
932
+ */
933
+ isEmpty() {
934
+ return this.buffer.length === 0;
935
+ }
936
+ /**
937
+ * Checks if the buffer contains only whitespace.
938
+ *
939
+ * @returns True if buffer is empty or contains only whitespace
940
+ */
941
+ isBlank() {
942
+ return this.buffer.trim().length === 0;
943
+ }
944
+ }
945
+ const It = "duckdb-terminal", p = "history", A = 1e3;
946
+ class Pt {
947
+ constructor() {
948
+ a(this, "db", null);
949
+ a(this, "history", []);
950
+ a(this, "cursor", -1);
951
+ a(this, "currentInput", "");
952
+ a(this, "initialized", !1);
953
+ }
954
+ /**
955
+ * Initializes the history store by opening IndexedDB and loading existing history.
956
+ *
957
+ * This method must be called before using other methods. It loads any
958
+ * previously stored commands from IndexedDB. If IndexedDB is unavailable
959
+ * (e.g., in private browsing), the store falls back to in-memory storage.
960
+ *
961
+ * @returns A promise that resolves when initialization is complete
962
+ *
963
+ * @example
964
+ * ```typescript
965
+ * const history = new HistoryStore();
966
+ * await history.init();
967
+ * // Now ready to use
968
+ * ```
969
+ */
970
+ async init() {
971
+ if (!this.initialized)
972
+ try {
973
+ this.db = await this.openDatabase(), this.history = await this.loadHistory(), this.history.length > A && (this.history = this.history.slice(-A), this.db && await this.trimHistory()), this.cursor = this.history.length, this.initialized = !0;
974
+ } catch (t) {
975
+ console.warn("IndexedDB not available, using in-memory history:", t), this.initialized = !0;
976
+ }
977
+ }
978
+ /**
979
+ * Open IndexedDB database
980
+ */
981
+ openDatabase() {
982
+ return new Promise((t, e) => {
983
+ const s = indexedDB.open(It, 1);
984
+ s.onerror = () => e(s.error), s.onsuccess = () => t(s.result), s.onupgradeneeded = (n) => {
985
+ const r = n.target.result;
986
+ r.objectStoreNames.contains(p) || r.createObjectStore(p, {
987
+ keyPath: "id",
988
+ autoIncrement: !0
989
+ });
990
+ };
991
+ });
992
+ }
993
+ /**
994
+ * Load history from IndexedDB
995
+ */
996
+ loadHistory() {
997
+ return new Promise((t, e) => {
998
+ if (!this.db) {
999
+ t([]);
1000
+ return;
1001
+ }
1002
+ const r = this.db.transaction(p, "readonly").objectStore(p).getAll();
1003
+ r.onerror = () => e(r.error), r.onsuccess = () => {
1004
+ const o = r.result;
1005
+ o.sort((l, h) => l.id - h.id), t(o.map((l) => l.command));
1006
+ };
1007
+ });
1008
+ }
1009
+ /**
1010
+ * Adds a command to the history.
1011
+ *
1012
+ * The command is stored both in memory and persisted to IndexedDB.
1013
+ * Empty commands and consecutive duplicates are ignored.
1014
+ *
1015
+ * @param command - The command to add to history
1016
+ * @returns A promise that resolves when the command has been persisted
1017
+ *
1018
+ * @example
1019
+ * ```typescript
1020
+ * await history.add('SELECT * FROM users;');
1021
+ * await history.add('SELECT * FROM users;'); // Ignored (duplicate)
1022
+ * await history.add(''); // Ignored (empty)
1023
+ * ```
1024
+ */
1025
+ async add(t) {
1026
+ const e = t.trim();
1027
+ if (e) {
1028
+ if (this.history.length > 0 && this.history[this.history.length - 1] === e) {
1029
+ this.reset();
1030
+ return;
1031
+ }
1032
+ for (this.history.push(e); this.history.length > A; )
1033
+ this.history.shift();
1034
+ if (this.db)
1035
+ try {
1036
+ await this.saveCommand(e), await this.trimHistory();
1037
+ } catch (s) {
1038
+ console.warn("Failed to save history:", s);
1039
+ }
1040
+ this.reset();
1041
+ }
1042
+ }
1043
+ /**
1044
+ * Save command to IndexedDB
1045
+ */
1046
+ saveCommand(t) {
1047
+ return new Promise((e, s) => {
1048
+ if (!this.db) {
1049
+ e();
1050
+ return;
1051
+ }
1052
+ const o = this.db.transaction(p, "readwrite").objectStore(p).add({ command: t });
1053
+ o.onerror = () => s(o.error), o.onsuccess = () => e();
1054
+ });
1055
+ }
1056
+ /**
1057
+ * Trim history in IndexedDB to max size.
1058
+ *
1059
+ * Uses a single transaction for all deletions for better performance.
1060
+ */
1061
+ trimHistory() {
1062
+ return new Promise((t, e) => {
1063
+ if (!this.db) {
1064
+ t();
1065
+ return;
1066
+ }
1067
+ const s = this.db.transaction(p, "readwrite"), n = s.objectStore(p), r = n.count();
1068
+ r.onsuccess = () => {
1069
+ const o = r.result;
1070
+ if (o <= A) {
1071
+ t();
1072
+ return;
1073
+ }
1074
+ const l = o - A, h = n.getAllKeys();
1075
+ h.onsuccess = () => {
1076
+ const f = h.result.slice(0, l);
1077
+ if (f.length === 0) {
1078
+ t();
1079
+ return;
1080
+ }
1081
+ s.oncomplete = () => t(), s.onerror = () => e(s.error);
1082
+ for (const m of f)
1083
+ n.delete(m);
1084
+ }, h.onerror = () => e(h.error);
1085
+ }, r.onerror = () => e(r.error);
1086
+ });
1087
+ }
1088
+ /**
1089
+ * Navigates to the previous command in history.
1090
+ *
1091
+ * When called from the end of history (most recent position), this method
1092
+ * saves the current input so it can be restored when navigating forward.
1093
+ *
1094
+ * @param currentInput - The current input buffer content to preserve
1095
+ * @returns The previous command, or null if at the beginning of history
1096
+ *
1097
+ * @example
1098
+ * ```typescript
1099
+ * // Assuming history has ['SELECT 1;', 'SELECT 2;']
1100
+ * const cmd1 = history.previous('SELECT 3'); // Returns 'SELECT 2;'
1101
+ * const cmd2 = history.previous(''); // Returns 'SELECT 1;'
1102
+ * const cmd3 = history.previous(''); // Returns null (at beginning)
1103
+ * ```
1104
+ */
1105
+ previous(t) {
1106
+ return this.history.length === 0 ? null : (this.cursor === this.history.length && (this.currentInput = t), this.cursor > 0 ? (this.cursor--, this.history[this.cursor]) : null);
1107
+ }
1108
+ /**
1109
+ * Navigates to the next command in history.
1110
+ *
1111
+ * When reaching the end of history, returns the previously saved current input.
1112
+ *
1113
+ * @returns The next command, the saved current input if at the end, or null if beyond
1114
+ *
1115
+ * @example
1116
+ * ```typescript
1117
+ * // After navigating backward with previous()
1118
+ * const cmd1 = history.next(); // Returns next newer command
1119
+ * const cmd2 = history.next(); // Returns original input when at end
1120
+ * ```
1121
+ */
1122
+ next() {
1123
+ return this.cursor < this.history.length - 1 ? (this.cursor++, this.history[this.cursor]) : this.cursor === this.history.length - 1 ? (this.cursor++, this.currentInput) : null;
1124
+ }
1125
+ /**
1126
+ * Resets the history cursor to the end and clears the saved current input.
1127
+ *
1128
+ * This should be called after a command is executed to prepare for new navigation.
1129
+ */
1130
+ reset() {
1131
+ this.cursor = this.history.length, this.currentInput = "";
1132
+ }
1133
+ /**
1134
+ * Returns a copy of all commands in history.
1135
+ *
1136
+ * @returns An array of all stored commands, oldest first
1137
+ */
1138
+ getAll() {
1139
+ return [...this.history];
1140
+ }
1141
+ /**
1142
+ * Clears all history from both memory and IndexedDB.
1143
+ *
1144
+ * @returns A promise that resolves when the history has been cleared
1145
+ */
1146
+ async clear() {
1147
+ if (this.history = [], this.cursor = 0, this.currentInput = "", this.db)
1148
+ return new Promise((t, e) => {
1149
+ const r = this.db.transaction(p, "readwrite").objectStore(p).clear();
1150
+ r.onerror = () => e(r.error), r.onsuccess = () => t();
1151
+ });
1152
+ }
1153
+ }
1154
+ const Nt = 50;
1155
+ function B(i) {
1156
+ let t = 0;
1157
+ for (const e of i) {
1158
+ const s = e.charCodeAt(0);
1159
+ s >= 4352 && s <= 4447 || s >= 11904 && s <= 42191 || s >= 44032 && s <= 55203 || s >= 63744 && s <= 64255 || s >= 65040 && s <= 65055 || s >= 65072 && s <= 65135 || s >= 65280 && s <= 65376 || s >= 65504 && s <= 65510 ? t += 2 : t += 1;
1160
+ }
1161
+ return t;
1162
+ }
1163
+ function _(i, t) {
1164
+ const e = B(i), s = t - e;
1165
+ return s <= 0 ? i : i + " ".repeat(s);
1166
+ }
1167
+ function z(i, t) {
1168
+ if (B(i) <= t) return i;
1169
+ let e = "", s = 0;
1170
+ for (const n of i) {
1171
+ const r = n.charCodeAt(0) >= 4352 && n.charCodeAt(0) <= 65535 ? 2 : 1;
1172
+ if (s + r + 1 > t) break;
1173
+ e += n, s += r;
1174
+ }
1175
+ return e + "…";
1176
+ }
1177
+ function Y(i) {
1178
+ return i >= Number.MIN_SAFE_INTEGER && i <= Number.MAX_SAFE_INTEGER ? Number(i) : i.toString();
1179
+ }
1180
+ function Bt(i) {
1181
+ const t = [];
1182
+ if (i.months) {
1183
+ const e = Math.floor(i.months / 12), s = i.months % 12;
1184
+ e && t.push(`${e} year${e !== 1 ? "s" : ""}`), s && t.push(`${s} month${s !== 1 ? "s" : ""}`);
1185
+ }
1186
+ if (i.days && t.push(`${i.days} day${i.days !== 1 ? "s" : ""}`), i.micros) {
1187
+ const e = typeof i.micros == "bigint" ? Number(i.micros) : i.micros, s = Math.floor(e / 36e8), n = Math.floor(e % 36e8 / 6e7), r = e % 6e7 / 1e6;
1188
+ (s || n || r) && t.push(`${String(s).padStart(2, "0")}:${String(n).padStart(2, "0")}:${r.toFixed(6).padStart(9, "0")}`);
1189
+ }
1190
+ return t.length ? t.join(" ") : "00:00:00";
1191
+ }
1192
+ function vt(i) {
1193
+ if (typeof i != "object" || i === null) return !1;
1194
+ const t = i;
1195
+ return "months" in t || "days" in t || "micros" in t;
1196
+ }
1197
+ function V(i, t) {
1198
+ return typeof t == "bigint" ? Y(t) : t instanceof Date ? t.toISOString() : t instanceof Map ? Object.fromEntries(t) : t instanceof Uint8Array ? "\\x" + Array.from(t).map((e) => e.toString(16).padStart(2, "0")).join("") : t;
1199
+ }
1200
+ function L(i, t) {
1201
+ return i == null ? t : typeof i == "string" ? i : typeof i == "boolean" ? i ? "true" : "false" : typeof i == "bigint" ? String(Y(i)) : i instanceof Date ? i.toISOString() : i instanceof Map ? "{" + Array.from(i.entries()).map(
1202
+ ([s, n]) => `${L(s, t)}: ${L(n, t)}`
1203
+ ).join(", ") + "}" : i instanceof Uint8Array ? "\\x" + Array.from(i).map((e) => e.toString(16).padStart(2, "0")).join("") : Array.isArray(i) ? "[" + i.map((e) => L(e, t)).join(", ") + "]" : vt(i) ? Bt(i) : typeof i == "object" ? JSON.stringify(i, V) : String(i);
1204
+ }
1205
+ function k(i, t, e = {}) {
1206
+ const { maxColumnWidth: s = Nt, nullValue: n = "NULL" } = e;
1207
+ if (i.length === 0)
1208
+ return "";
1209
+ const r = t.map(
1210
+ (h) => h.map((d) => {
1211
+ const f = L(d, n);
1212
+ return s ? z(f, s) : f;
1213
+ })
1214
+ ), o = i.map((h, d) => {
1215
+ const f = B(h), m = r.reduce((P, et) => {
1216
+ const it = B(et[d] ?? "");
1217
+ return Math.max(P, it);
1218
+ }, 0);
1219
+ return Math.min(Math.max(f, m), s);
1220
+ }), l = [];
1221
+ l.push("┌" + o.map((h) => "─".repeat(h + 2)).join("┬") + "┐"), l.push(
1222
+ "│" + i.map((h, d) => " " + _(z(h, o[d]), o[d]) + " ").join("│") + "│"
1223
+ ), l.push("├" + o.map((h) => "─".repeat(h + 2)).join("┼") + "┤");
1224
+ for (const h of r)
1225
+ l.push(
1226
+ "│" + h.map((d, f) => " " + _(d, o[f]) + " ").join("│") + "│"
1227
+ );
1228
+ return l.push("└" + o.map((h) => "─".repeat(h + 2)).join("┴") + "┘"), l.join(`
1229
+ `);
1230
+ }
1231
+ function O(i, t) {
1232
+ const e = (n) => {
1233
+ const r = L(n, "");
1234
+ return r.includes(",") || r.includes('"') || r.includes(`
1235
+ `) ? '"' + r.replace(/"/g, '""') + '"' : r;
1236
+ }, s = [];
1237
+ s.push(i.map(e).join(","));
1238
+ for (const n of t)
1239
+ s.push(n.map(e).join(","));
1240
+ return s.join(`
1241
+ `);
1242
+ }
1243
+ function $(i, t) {
1244
+ const e = (n) => L(n, "").replace(/\t/g, " ").replace(/\n/g, " "), s = [];
1245
+ s.push(i.map(e).join(" "));
1246
+ for (const n of t)
1247
+ s.push(n.map(e).join(" "));
1248
+ return s.join(`
1249
+ `);
1250
+ }
1251
+ function D(i, t) {
1252
+ const e = t.map((s) => {
1253
+ const n = {};
1254
+ return i.forEach((r, o) => {
1255
+ n[r] = s[o] ?? null;
1256
+ }), n;
1257
+ });
1258
+ return JSON.stringify(e, V, 2);
1259
+ }
1260
+ const M = 100 * 1024 * 1024;
1261
+ class kt extends Error {
1262
+ constructor(t, e, s = M) {
1263
+ super(
1264
+ `File "${t}" (${I(e)}) exceeds maximum size of ${I(s)}`
1265
+ ), this.filename = t, this.size = e, this.maxSize = s, this.name = "FileSizeError";
1266
+ }
1267
+ }
1268
+ function Ot(i, t = M) {
1269
+ if (i.size > t)
1270
+ throw new kt(i.name, i.size, t);
1271
+ }
1272
+ async function K(i) {
1273
+ return new Promise((t) => {
1274
+ const e = document.createElement("input");
1275
+ e.type = "file", e.multiple = i?.multiple ?? !0, e.accept = i?.accept ?? ".csv,.parquet,.json,.db,.duckdb", e.onchange = () => {
1276
+ const s = Array.from(e.files ?? []);
1277
+ t(s);
1278
+ }, e.oncancel = () => {
1279
+ t([]);
1280
+ }, e.click();
1281
+ });
1282
+ }
1283
+ async function $t(i, t = M) {
1284
+ return Ot(i, t), new Promise((e, s) => {
1285
+ const n = new FileReader();
1286
+ n.onload = () => {
1287
+ e(new Uint8Array(n.result));
1288
+ }, n.onerror = () => s(n.error), n.readAsArrayBuffer(i);
1289
+ });
1290
+ }
1291
+ function I(i) {
1292
+ if (i === 0) return "0 B";
1293
+ const t = 1024, e = ["B", "KB", "MB", "GB"], s = Math.floor(Math.log(i) / Math.log(t));
1294
+ return parseFloat((i / Math.pow(t, s)).toFixed(2)) + " " + e[s];
1295
+ }
1296
+ function X(i) {
1297
+ const t = i.lastIndexOf(".");
1298
+ return t === -1 ? "" : i.substring(t + 1).toLowerCase();
1299
+ }
1300
+ function Dt(i) {
1301
+ return {
1302
+ name: i.name,
1303
+ size: i.size,
1304
+ type: i.type || X(i.name),
1305
+ lastModified: new Date(i.lastModified)
1306
+ };
1307
+ }
1308
+ function Ut(i, t) {
1309
+ const e = (r) => {
1310
+ r.preventDefault(), r.stopPropagation(), i.classList.add("drag-over");
1311
+ }, s = (r) => {
1312
+ r.preventDefault(), r.stopPropagation(), i.classList.remove("drag-over");
1313
+ }, n = (r) => {
1314
+ r.preventDefault(), r.stopPropagation(), i.classList.remove("drag-over");
1315
+ const o = Array.from(r.dataTransfer?.files ?? []);
1316
+ o.length > 0 && t(o);
1317
+ };
1318
+ return i.addEventListener("dragover", e), i.addEventListener("dragleave", s), i.addEventListener("drop", n), () => {
1319
+ i.removeEventListener("dragover", e), i.removeEventListener("dragleave", s), i.removeEventListener("drop", n);
1320
+ };
1321
+ }
1322
+ async function J(i) {
1323
+ try {
1324
+ if (navigator.clipboard && navigator.clipboard.writeText)
1325
+ return await navigator.clipboard.writeText(i), !0;
1326
+ const t = document.createElement("textarea");
1327
+ t.value = i, t.style.position = "fixed", t.style.left = "-9999px", t.style.top = "-9999px", document.body.appendChild(t), t.select();
1328
+ try {
1329
+ return document.execCommand("copy"), !0;
1330
+ } finally {
1331
+ document.body.removeChild(t);
1332
+ }
1333
+ } catch {
1334
+ return !1;
1335
+ }
1336
+ }
1337
+ async function Ft() {
1338
+ try {
1339
+ return navigator.clipboard && navigator.clipboard.readText ? await navigator.clipboard.readText() : null;
1340
+ } catch {
1341
+ return null;
1342
+ }
1343
+ }
1344
+ function ye() {
1345
+ return !!(navigator.clipboard && navigator.clipboard.writeText);
1346
+ }
1347
+ const Mt = /* @__PURE__ */ new Set([
1348
+ "SELECT",
1349
+ "FROM",
1350
+ "WHERE",
1351
+ "AND",
1352
+ "OR",
1353
+ "NOT",
1354
+ "IN",
1355
+ "IS",
1356
+ "NULL",
1357
+ "INSERT",
1358
+ "INTO",
1359
+ "VALUES",
1360
+ "UPDATE",
1361
+ "SET",
1362
+ "DELETE",
1363
+ "CREATE",
1364
+ "TABLE",
1365
+ "DROP",
1366
+ "ALTER",
1367
+ "INDEX",
1368
+ "VIEW",
1369
+ "DATABASE",
1370
+ "SCHEMA",
1371
+ "JOIN",
1372
+ "LEFT",
1373
+ "RIGHT",
1374
+ "INNER",
1375
+ "OUTER",
1376
+ "FULL",
1377
+ "CROSS",
1378
+ "ON",
1379
+ "USING",
1380
+ "GROUP",
1381
+ "BY",
1382
+ "ORDER",
1383
+ "HAVING",
1384
+ "LIMIT",
1385
+ "OFFSET",
1386
+ "UNION",
1387
+ "EXCEPT",
1388
+ "INTERSECT",
1389
+ "ALL",
1390
+ "DISTINCT",
1391
+ "AS",
1392
+ "CASE",
1393
+ "WHEN",
1394
+ "THEN",
1395
+ "ELSE",
1396
+ "END",
1397
+ "BETWEEN",
1398
+ "LIKE",
1399
+ "ILIKE",
1400
+ "EXISTS",
1401
+ "ANY",
1402
+ "SOME",
1403
+ "TRUE",
1404
+ "FALSE",
1405
+ "ASC",
1406
+ "DESC",
1407
+ "NULLS",
1408
+ "FIRST",
1409
+ "LAST",
1410
+ "WITH",
1411
+ "RECURSIVE",
1412
+ "OVER",
1413
+ "PARTITION",
1414
+ "WINDOW",
1415
+ "CAST",
1416
+ "COALESCE",
1417
+ "NULLIF",
1418
+ "EXTRACT",
1419
+ "INTERVAL",
1420
+ "PRIMARY",
1421
+ "KEY",
1422
+ "FOREIGN",
1423
+ "REFERENCES",
1424
+ "UNIQUE",
1425
+ "CHECK",
1426
+ "DEFAULT",
1427
+ "CONSTRAINT",
1428
+ "CASCADE",
1429
+ "RESTRICT",
1430
+ "NO",
1431
+ "ACTION",
1432
+ "BEGIN",
1433
+ "COMMIT",
1434
+ "ROLLBACK",
1435
+ "TRANSACTION",
1436
+ "SAVEPOINT",
1437
+ "GRANT",
1438
+ "REVOKE",
1439
+ "PRIVILEGES",
1440
+ "TO",
1441
+ "PUBLIC",
1442
+ "IF",
1443
+ "REPLACE",
1444
+ "TEMPORARY",
1445
+ "TEMP",
1446
+ "VIRTUAL",
1447
+ "MATERIALIZED"
1448
+ ]), xt = /* @__PURE__ */ new Set([
1449
+ "INTEGER",
1450
+ "INT",
1451
+ "BIGINT",
1452
+ "SMALLINT",
1453
+ "TINYINT",
1454
+ "HUGEINT",
1455
+ "FLOAT",
1456
+ "DOUBLE",
1457
+ "REAL",
1458
+ "DECIMAL",
1459
+ "NUMERIC",
1460
+ "VARCHAR",
1461
+ "CHAR",
1462
+ "TEXT",
1463
+ "STRING",
1464
+ "BLOB",
1465
+ "BYTEA",
1466
+ "BOOLEAN",
1467
+ "BOOL",
1468
+ "BIT",
1469
+ "DATE",
1470
+ "TIME",
1471
+ "TIMESTAMP",
1472
+ "DATETIME",
1473
+ "TIMESTAMPTZ",
1474
+ "UUID",
1475
+ "JSON",
1476
+ "ARRAY",
1477
+ "LIST",
1478
+ "MAP",
1479
+ "STRUCT"
1480
+ ]), _t = /* @__PURE__ */ new Set([
1481
+ "COUNT",
1482
+ "SUM",
1483
+ "AVG",
1484
+ "MIN",
1485
+ "MAX",
1486
+ "TOTAL",
1487
+ "ABS",
1488
+ "CEIL",
1489
+ "CEILING",
1490
+ "FLOOR",
1491
+ "ROUND",
1492
+ "TRUNC",
1493
+ "TRUNCATE",
1494
+ "SQRT",
1495
+ "POWER",
1496
+ "POW",
1497
+ "EXP",
1498
+ "LOG",
1499
+ "LOG10",
1500
+ "LOG2",
1501
+ "LN",
1502
+ "SIN",
1503
+ "COS",
1504
+ "TAN",
1505
+ "ASIN",
1506
+ "ACOS",
1507
+ "ATAN",
1508
+ "ATAN2",
1509
+ "LENGTH",
1510
+ "CHAR_LENGTH",
1511
+ "OCTET_LENGTH",
1512
+ "BIT_LENGTH",
1513
+ "UPPER",
1514
+ "LOWER",
1515
+ "INITCAP",
1516
+ "TRIM",
1517
+ "LTRIM",
1518
+ "RTRIM",
1519
+ "SUBSTR",
1520
+ "SUBSTRING",
1521
+ "LEFT",
1522
+ "RIGHT",
1523
+ "LPAD",
1524
+ "RPAD",
1525
+ "CONCAT",
1526
+ "CONCAT_WS",
1527
+ "REPLACE",
1528
+ "REVERSE",
1529
+ "REPEAT",
1530
+ "POSITION",
1531
+ "STRPOS",
1532
+ "INSTR",
1533
+ "LOCATE",
1534
+ "SPLIT_PART",
1535
+ "STRING_SPLIT",
1536
+ "STRING_AGG",
1537
+ "LISTAGG",
1538
+ "ARRAY_AGG",
1539
+ "NOW",
1540
+ "CURRENT_DATE",
1541
+ "CURRENT_TIME",
1542
+ "CURRENT_TIMESTAMP",
1543
+ "DATE_PART",
1544
+ "DATE_TRUNC",
1545
+ "DATE_DIFF",
1546
+ "DATE_ADD",
1547
+ "DATE_SUB",
1548
+ "YEAR",
1549
+ "MONTH",
1550
+ "DAY",
1551
+ "HOUR",
1552
+ "MINUTE",
1553
+ "SECOND",
1554
+ "COALESCE",
1555
+ "NULLIF",
1556
+ "IFNULL",
1557
+ "NVL",
1558
+ "IIF",
1559
+ "GREATEST",
1560
+ "LEAST",
1561
+ "RANDOM",
1562
+ "SETSEED",
1563
+ "ROW_NUMBER",
1564
+ "RANK",
1565
+ "DENSE_RANK",
1566
+ "NTILE",
1567
+ "LAG",
1568
+ "LEAD",
1569
+ "FIRST_VALUE",
1570
+ "LAST_VALUE",
1571
+ "NTH_VALUE",
1572
+ "TYPEOF",
1573
+ "TRY_CAST",
1574
+ "UNNEST",
1575
+ "GENERATE_SERIES",
1576
+ "RANGE",
1577
+ "READ_CSV",
1578
+ "READ_PARQUET",
1579
+ "READ_JSON"
1580
+ ]);
1581
+ function Z(i) {
1582
+ const t = [];
1583
+ let e = 0;
1584
+ for (; e < i.length; ) {
1585
+ const s = i[e];
1586
+ if (/\s/.test(s)) {
1587
+ const r = e;
1588
+ for (; e < i.length && /\s/.test(i[e]); )
1589
+ e++;
1590
+ t.push({ type: "whitespace", value: i.slice(r, e), start: r, end: e });
1591
+ continue;
1592
+ }
1593
+ if (i.slice(e, e + 2) === "--") {
1594
+ const r = e;
1595
+ for (; e < i.length && i[e] !== `
1596
+ `; )
1597
+ e++;
1598
+ t.push({ type: "comment", value: i.slice(r, e), start: r, end: e });
1599
+ continue;
1600
+ }
1601
+ if (i.slice(e, e + 2) === "/*") {
1602
+ const r = e;
1603
+ for (e += 2; e < i.length - 1 && i.slice(e, e + 2) !== "*/"; )
1604
+ e++;
1605
+ e += 2, t.push({ type: "comment", value: i.slice(r, e), start: r, end: e });
1606
+ continue;
1607
+ }
1608
+ if (s === "'") {
1609
+ const r = e;
1610
+ for (e++; e < i.length; )
1611
+ if (i[e] === "'" && i[e + 1] === "'")
1612
+ e += 2;
1613
+ else if (i[e] === "'") {
1614
+ e++;
1615
+ break;
1616
+ } else
1617
+ e++;
1618
+ t.push({ type: "string", value: i.slice(r, e), start: r, end: e });
1619
+ continue;
1620
+ }
1621
+ if (s === '"') {
1622
+ const r = e;
1623
+ for (e++; e < i.length && i[e] !== '"'; )
1624
+ e++;
1625
+ e++, t.push({ type: "identifier", value: i.slice(r, e), start: r, end: e });
1626
+ continue;
1627
+ }
1628
+ if (/[0-9]/.test(s) || s === "." && /[0-9]/.test(i[e + 1] || "")) {
1629
+ const r = e;
1630
+ for (; e < i.length && /[0-9]/.test(i[e]); )
1631
+ e++;
1632
+ if (i[e] === "." && /[0-9]/.test(i[e + 1] || ""))
1633
+ for (e++; e < i.length && /[0-9]/.test(i[e]); )
1634
+ e++;
1635
+ if (i[e] === "e" || i[e] === "E")
1636
+ for (e++, (i[e] === "+" || i[e] === "-") && e++; e < i.length && /[0-9]/.test(i[e]); )
1637
+ e++;
1638
+ t.push({ type: "number", value: i.slice(r, e), start: r, end: e });
1639
+ continue;
1640
+ }
1641
+ if (/[a-zA-Z_]/.test(s)) {
1642
+ const r = e;
1643
+ for (; e < i.length && /[a-zA-Z0-9_]/.test(i[e]); )
1644
+ e++;
1645
+ const o = i.slice(r, e), l = o.toUpperCase();
1646
+ let h = "identifier";
1647
+ Mt.has(l) ? h = "keyword" : xt.has(l) ? h = "type" : _t.has(l) && (h = "function"), t.push({ type: h, value: o, start: r, end: e });
1648
+ continue;
1649
+ }
1650
+ const n = e;
1651
+ e++, t.push({ type: "operator", value: i.slice(n, e), start: n, end: e });
1652
+ }
1653
+ return t;
1654
+ }
1655
+ function zt(i) {
1656
+ switch (i) {
1657
+ case "keyword":
1658
+ return U;
1659
+ case "type":
1660
+ return S;
1661
+ case "function":
1662
+ return N;
1663
+ case "string":
1664
+ return b;
1665
+ case "number":
1666
+ return j;
1667
+ case "comment":
1668
+ return w;
1669
+ case "operator":
1670
+ return F;
1671
+ case "identifier":
1672
+ case "whitespace":
1673
+ default:
1674
+ return "";
1675
+ }
1676
+ }
1677
+ function H(i) {
1678
+ const t = Z(i);
1679
+ let e = "";
1680
+ for (const s of t) {
1681
+ const n = zt(s.type);
1682
+ n ? e += n + s.value + v : e += s.value;
1683
+ }
1684
+ return e;
1685
+ }
1686
+ function Se(i) {
1687
+ const t = i.trim();
1688
+ return t ? [...Z(t)].reverse().find((n) => n.type !== "whitespace")?.value === ";" : !1;
1689
+ }
1690
+ function Ht(i, t) {
1691
+ let e = null;
1692
+ const s = function(...n) {
1693
+ e !== null && clearTimeout(e), e = setTimeout(() => {
1694
+ i.apply(this, n), e = null;
1695
+ }, t);
1696
+ };
1697
+ return s.cancel = () => {
1698
+ e !== null && (clearTimeout(e), e = null);
1699
+ }, s;
1700
+ }
1701
+ function Gt(i) {
1702
+ const t = [];
1703
+ let e = "", s = null, n = !1, r = !1;
1704
+ for (let o = 0; o < i.length; o++) {
1705
+ const l = i[o];
1706
+ if (n) {
1707
+ switch (l) {
1708
+ case "n":
1709
+ e += `
1710
+ `;
1711
+ break;
1712
+ case "t":
1713
+ e += " ";
1714
+ break;
1715
+ case "r":
1716
+ e += "\r";
1717
+ break;
1718
+ case "\\":
1719
+ e += "\\";
1720
+ break;
1721
+ case '"':
1722
+ e += '"';
1723
+ break;
1724
+ case "'":
1725
+ e += "'";
1726
+ break;
1727
+ case " ":
1728
+ e += " ";
1729
+ break;
1730
+ default:
1731
+ e += "\\" + l;
1732
+ }
1733
+ n = !1;
1734
+ continue;
1735
+ }
1736
+ if (l === "\\" && s !== "'") {
1737
+ n = !0;
1738
+ continue;
1739
+ }
1740
+ if ((l === '"' || l === "'") && !s) {
1741
+ s = l, r = !0;
1742
+ continue;
1743
+ }
1744
+ if (l === s) {
1745
+ s = null;
1746
+ continue;
1747
+ }
1748
+ if (l === " " && !s) {
1749
+ (e || r) && (t.push(e), e = "", r = !1);
1750
+ continue;
1751
+ }
1752
+ e += l;
1753
+ }
1754
+ return (e || r) && t.push(e), t;
1755
+ }
1756
+ function Qt(i) {
1757
+ const t = Gt(i), e = t[0]?.toLowerCase() ?? "", s = t.slice(1);
1758
+ return { command: e, args: s, allArgs: t };
1759
+ }
1760
+ const E = /https?:\/\/[^\s<>"\])\[}{'|^`\\]+/gi, Wt = "\x1B]8;;", jt = "\x07", Yt = "\x1B]8;;\x07";
1761
+ function Le(i) {
1762
+ return E.lastIndex = 0, E.test(i);
1763
+ }
1764
+ function Re(i) {
1765
+ return E.lastIndex = 0, i.match(E) || [];
1766
+ }
1767
+ function q(i, t) {
1768
+ return `${Wt}${i}${jt}${t ?? i}${Yt}`;
1769
+ }
1770
+ function Vt(i) {
1771
+ return E.lastIndex = 0, i.replace(E, (t) => q(t));
1772
+ }
1773
+ function Ae(i) {
1774
+ try {
1775
+ return new URL(i), !0;
1776
+ } catch {
1777
+ return !1;
1778
+ }
1779
+ }
1780
+ function Kt(i, t = 60) {
1781
+ if (i.length <= t)
1782
+ return i;
1783
+ try {
1784
+ const e = new URL(i), s = e.host, n = e.pathname + e.search + e.hash, r = s.length, o = t - r - 8;
1785
+ if (o < 10)
1786
+ return i.substring(0, t - 3) + "...";
1787
+ const l = n.substring(0, Math.floor(o / 2)), h = n.substring(n.length - Math.floor(o / 2));
1788
+ return `${e.protocol}//${s}${l}...${h}`;
1789
+ } catch {
1790
+ return i.substring(0, t - 3) + "...";
1791
+ }
1792
+ }
1793
+ class Xt {
1794
+ constructor() {
1795
+ a(this, "enabled", !0);
1796
+ a(this, "maxURLLength", 80);
1797
+ }
1798
+ /**
1799
+ * Enable or disable URL linking
1800
+ */
1801
+ setEnabled(t) {
1802
+ this.enabled = t;
1803
+ }
1804
+ /**
1805
+ * Check if URL linking is enabled
1806
+ */
1807
+ isEnabled() {
1808
+ return this.enabled;
1809
+ }
1810
+ /**
1811
+ * Set maximum URL display length
1812
+ */
1813
+ setMaxURLLength(t) {
1814
+ this.maxURLLength = t;
1815
+ }
1816
+ /**
1817
+ * Process text and add hyperlinks to URLs
1818
+ */
1819
+ process(t) {
1820
+ return this.enabled ? Vt(t) : t;
1821
+ }
1822
+ /**
1823
+ * Process text with truncated URL display
1824
+ */
1825
+ processWithTruncation(t) {
1826
+ return this.enabled ? (E.lastIndex = 0, t.replace(E, (e) => {
1827
+ const s = Kt(e, this.maxURLLength);
1828
+ return q(e, s);
1829
+ })) : t;
1830
+ }
1831
+ }
1832
+ const Jt = {
1833
+ background: "#1e1e1e",
1834
+ foreground: "#d4d4d4",
1835
+ cursor: "#d4d4d4",
1836
+ selection: "#264f78",
1837
+ black: "#000000",
1838
+ red: "#cd3131",
1839
+ green: "#0dbc79",
1840
+ yellow: "#e5e510",
1841
+ blue: "#2472c8",
1842
+ magenta: "#bc3fbc",
1843
+ cyan: "#11a8cd",
1844
+ white: "#e5e5e5",
1845
+ brightBlack: "#666666",
1846
+ brightRed: "#f14c4c",
1847
+ brightGreen: "#23d18b",
1848
+ brightYellow: "#f5f543",
1849
+ brightBlue: "#3b8eea",
1850
+ brightMagenta: "#d670d6",
1851
+ brightCyan: "#29b8db",
1852
+ brightWhite: "#ffffff"
1853
+ }, Zt = {
1854
+ background: "#ffffff",
1855
+ foreground: "#1e1e1e",
1856
+ cursor: "#1e1e1e",
1857
+ selection: "#add6ff",
1858
+ black: "#000000",
1859
+ red: "#cd3131",
1860
+ green: "#008000",
1861
+ yellow: "#795e00",
1862
+ blue: "#0451a5",
1863
+ magenta: "#bc05bc",
1864
+ cyan: "#0598bc",
1865
+ white: "#e5e5e5",
1866
+ brightBlack: "#666666",
1867
+ brightRed: "#cd3131",
1868
+ brightGreen: "#14ce14",
1869
+ brightYellow: "#b5ba00",
1870
+ brightBlue: "#0451a5",
1871
+ brightMagenta: "#bc05bc",
1872
+ brightCyan: "#0598bc",
1873
+ brightWhite: "#a5a5a5"
1874
+ }, qt = {
1875
+ name: "dark",
1876
+ colors: Jt
1877
+ }, te = {
1878
+ name: "light",
1879
+ colors: Zt
1880
+ };
1881
+ function G(i) {
1882
+ return i === "light" ? te : qt;
1883
+ }
1884
+ function ee() {
1885
+ try {
1886
+ const i = localStorage.getItem("duckdb-terminal-theme");
1887
+ if (i === "light" || i === "dark")
1888
+ return i;
1889
+ } catch {
1890
+ }
1891
+ return "dark";
1892
+ }
1893
+ function ie(i) {
1894
+ try {
1895
+ localStorage.setItem("duckdb-terminal-theme", i);
1896
+ } catch {
1897
+ }
1898
+ }
1899
+ const Q = "🦆 ", W = " > ";
1900
+ class se {
1901
+ constructor(t) {
1902
+ a(this, "terminalAdapter");
1903
+ a(this, "database");
1904
+ a(this, "inputBuffer");
1905
+ a(this, "history");
1906
+ a(this, "state", "idle");
1907
+ a(this, "collectedSQL", []);
1908
+ a(this, "commands", /* @__PURE__ */ new Map());
1909
+ a(this, "showTimer", !1);
1910
+ a(this, "outputMode", "table");
1911
+ a(this, "currentThemeName");
1912
+ a(this, "customTheme", null);
1913
+ a(this, "config");
1914
+ a(this, "loadedFiles", /* @__PURE__ */ new Map());
1915
+ a(this, "syntaxHighlighting", !0);
1916
+ a(this, "lastQueryResult", null);
1917
+ a(this, "linkProvider");
1918
+ // Event emitter
1919
+ a(this, "eventListeners", /* @__PURE__ */ new Map());
1920
+ // Pagination state (0 = disabled by default)
1921
+ a(this, "pageSize", 0);
1922
+ a(this, "paginationQuery", null);
1923
+ a(this, "currentPage", 0);
1924
+ a(this, "totalRows", 0);
1925
+ // Prompt customization
1926
+ a(this, "prompt");
1927
+ a(this, "continuationPrompt");
1928
+ // Debounced syntax highlighting (150ms delay to avoid excessive redraws)
1929
+ a(this, "debouncedHighlight", Ht(() => this.redrawLineHighlighted(), 150));
1930
+ // Query queue to prevent race conditions
1931
+ a(this, "queryQueue", Promise.resolve(null));
1932
+ // Cleanup function for drag-and-drop
1933
+ a(this, "dragDropCleanup", null);
1934
+ this.config = t, this.terminalAdapter = new ct(), this.database = new ut({
1935
+ storage: t.storage ?? "memory",
1936
+ databasePath: t.databasePath
1937
+ }), this.inputBuffer = new Ct(), this.history = new Pt(), this.linkProvider = new Xt(), t.linkDetection === !1 && this.linkProvider.setEnabled(!1), this.prompt = t.prompt ?? Q, this.continuationPrompt = t.continuationPrompt ?? W, typeof t.theme == "object" ? (this.customTheme = t.theme, this.currentThemeName = "custom") : this.currentThemeName = t.theme ?? ee(), this.registerCommands();
1938
+ }
1939
+ // ==================== Event Emitter ====================
1940
+ /**
1941
+ * Subscribes to a terminal event.
1942
+ *
1943
+ * The terminal emits various events during its lifecycle that you can
1944
+ * subscribe to for monitoring, logging, or integrating with your application.
1945
+ *
1946
+ * @typeParam K - The event type key from {@link TerminalEvents}
1947
+ * @param event - The event name to subscribe to
1948
+ * @param listener - The callback function to invoke when the event occurs
1949
+ * @returns An unsubscribe function that removes the listener when called
1950
+ *
1951
+ * @example Subscribe to query events
1952
+ * ```typescript
1953
+ * const unsubscribe = terminal.on('queryEnd', ({ sql, result, duration }) => {
1954
+ * console.log(`Query completed in ${duration}ms`);
1955
+ * });
1956
+ *
1957
+ * // Later, to stop listening:
1958
+ * unsubscribe();
1959
+ * ```
1960
+ *
1961
+ * @example Monitor state changes
1962
+ * ```typescript
1963
+ * terminal.on('stateChange', ({ state, previous }) => {
1964
+ * console.log(`Terminal state: ${previous} -> ${state}`);
1965
+ * });
1966
+ * ```
1967
+ */
1968
+ on(t, e) {
1969
+ return this.eventListeners.has(t) || this.eventListeners.set(t, /* @__PURE__ */ new Set()), this.eventListeners.get(t).add(e), () => this.off(t, e);
1970
+ }
1971
+ /**
1972
+ * Unsubscribes a listener from a terminal event.
1973
+ *
1974
+ * This is an alternative to using the unsubscribe function returned by {@link on}.
1975
+ *
1976
+ * @typeParam K - The event type key from {@link TerminalEvents}
1977
+ * @param event - The event name to unsubscribe from
1978
+ * @param listener - The callback function to remove
1979
+ *
1980
+ * @example
1981
+ * ```typescript
1982
+ * const handler = ({ sql }) => console.log(sql);
1983
+ * terminal.on('queryStart', handler);
1984
+ *
1985
+ * // Later:
1986
+ * terminal.off('queryStart', handler);
1987
+ * ```
1988
+ */
1989
+ off(t, e) {
1990
+ this.eventListeners.get(t)?.delete(e);
1991
+ }
1992
+ /**
1993
+ * Emits an event to all registered listeners.
1994
+ *
1995
+ * @internal
1996
+ * @typeParam K - The event type key from {@link TerminalEvents}
1997
+ * @param event - The event name to emit
1998
+ * @param payload - The event payload to pass to listeners
1999
+ */
2000
+ emit(t, e) {
2001
+ this.eventListeners.get(t)?.forEach((s) => {
2002
+ try {
2003
+ s(e);
2004
+ } catch (n) {
2005
+ console.error(`Error in ${t} event listener:`, n);
2006
+ }
2007
+ });
2008
+ }
2009
+ /**
2010
+ * Sets the terminal state and emits a stateChange event.
2011
+ *
2012
+ * @internal
2013
+ * @param newState - The new terminal state
2014
+ */
2015
+ setState(t) {
2016
+ const e = this.state;
2017
+ e !== t && (this.state = t, this.emit("stateChange", { state: t, previous: e }));
2018
+ }
2019
+ // ==================== Syntax Highlighting ====================
2020
+ /**
2021
+ * Returns SQL with syntax highlighting applied.
2022
+ *
2023
+ * @param sql - The SQL string to highlight
2024
+ * @returns The SQL with VT100 color codes applied, or the original SQL if highlighting is disabled
2025
+ */
2026
+ getHighlightedSQL(t) {
2027
+ return this.syntaxHighlighting ? H(t) : t;
2028
+ }
2029
+ /**
2030
+ * Redraws the current input line with syntax highlighting applied.
2031
+ *
2032
+ * This method clears the current line content and rewrites it with
2033
+ * color codes for SQL keywords, strings, numbers, etc. Called on
2034
+ * delimiter characters (space, semicolon, parentheses, comma) via debouncing.
2035
+ */
2036
+ redrawLineHighlighted() {
2037
+ if (!this.syntaxHighlighting)
2038
+ return;
2039
+ const t = this.inputBuffer.getContent();
2040
+ if (t.length === 0)
2041
+ return;
2042
+ const e = this.inputBuffer.getCursorPos(), s = H(t);
2043
+ e > 0 && this.write(T(e)), this.write(C), this.write(s);
2044
+ const n = t.length - e;
2045
+ n > 0 && this.write(T(n));
2046
+ }
2047
+ /**
2048
+ * Returns the current theme object for terminal styling.
2049
+ *
2050
+ * @returns The custom theme if set, otherwise the built-in theme ('dark' or 'light')
2051
+ */
2052
+ getCurrentThemeObject() {
2053
+ return this.customTheme ? this.customTheme : G(this.currentThemeName);
2054
+ }
2055
+ /**
2056
+ * Initializes and starts the terminal.
2057
+ *
2058
+ * This method performs the following initialization steps:
2059
+ * 1. Resolves the container element
2060
+ * 2. Initializes the terminal adapter (Ghostty), database (DuckDB), and history store in parallel
2061
+ * 3. Sets up input handling and drag-and-drop file loading
2062
+ * 4. Displays the welcome message (if enabled)
2063
+ * 5. Shows the command prompt
2064
+ * 6. Emits the 'ready' event
2065
+ *
2066
+ * @returns A promise that resolves when the terminal is fully initialized
2067
+ *
2068
+ * @throws Error if the container element cannot be found
2069
+ * @throws Error if DuckDB WASM initialization fails
2070
+ *
2071
+ * @example
2072
+ * ```typescript
2073
+ * const terminal = new DuckDBTerminal({ container: '#terminal' });
2074
+ * await terminal.start();
2075
+ * console.log('Terminal is ready!');
2076
+ * ```
2077
+ *
2078
+ * @fires ready - Emitted when initialization is complete
2079
+ */
2080
+ async start() {
2081
+ const t = this.resolveContainer();
2082
+ await this.terminalAdapter.init(t, {
2083
+ fontFamily: this.config.fontFamily,
2084
+ fontSize: this.config.fontSize,
2085
+ theme: this.getCurrentThemeObject(),
2086
+ scrollback: this.config.scrollback
2087
+ }), this.config.welcomeMessage !== !1 && (this.writeln(y("DuckDB Terminal") + " v0.1.0"), this.write(u("Loading DuckDB WASM..."))), await Promise.all([
2088
+ this.database.init(),
2089
+ this.history.init()
2090
+ ]), this.config.welcomeMessage !== !1 && (this.write("\r" + C), this.writeln(u("Powered by DuckDB WASM and Ghostty")), this.writeln(""), this.writeln("Type " + c(".help", S) + " for available commands"), this.writeln("Enter SQL statements ending with " + c(";", N)), this.writeln("")), this.terminalAdapter.onData(this.handleInput.bind(this)), this.dragDropCleanup = Ut(t, (e) => {
2091
+ this.handleDroppedFiles(e);
2092
+ }), this.showPrompt(), this.emit("ready", {});
2093
+ }
2094
+ /**
2095
+ * Cleans up resources and event listeners.
2096
+ *
2097
+ * Call this method when disposing of the terminal to prevent memory leaks.
2098
+ * This removes drag-and-drop handlers and clears internal state.
2099
+ */
2100
+ destroy() {
2101
+ this.dragDropCleanup && (this.dragDropCleanup(), this.dragDropCleanup = null), this.eventListeners.clear(), this.debouncedHighlight.cancel();
2102
+ }
2103
+ /**
2104
+ * Processes files dropped onto the terminal via drag-and-drop.
2105
+ *
2106
+ * Uses Promise.allSettled to ensure all files are attempted even if some fail.
2107
+ *
2108
+ * @param files - Array of File objects to load into DuckDB
2109
+ */
2110
+ async handleDroppedFiles(t) {
2111
+ const e = await Promise.allSettled(
2112
+ t.map((r) => this.loadFile(r))
2113
+ ), s = e.filter((r) => r.status === "fulfilled" && r.value).length, n = e.length - s;
2114
+ n > 0 && s > 0 && this.writeln(
2115
+ u(`Loaded ${s} of ${e.length} files (${n} failed)`)
2116
+ ), this.showPrompt();
2117
+ }
2118
+ /**
2119
+ * Loads a file into DuckDB's virtual filesystem.
2120
+ *
2121
+ * Registers the file and displays usage hints for supported file types
2122
+ * (CSV, Parquet, JSON). Emits a 'fileLoaded' event on success.
2123
+ *
2124
+ * @param file - The file to load
2125
+ * @returns True if the file was loaded successfully, false otherwise
2126
+ */
2127
+ async loadFile(t) {
2128
+ const e = X(t.name), s = Dt(t);
2129
+ try {
2130
+ const n = await $t(t);
2131
+ await this.database.registerFile(t.name, n), this.loadedFiles.set(t.name, s), this.emit("fileLoaded", {
2132
+ filename: t.name,
2133
+ size: t.size,
2134
+ type: e
2135
+ }), this.writeln(""), this.writeln(
2136
+ c(`Loaded: ${t.name}`, b) + ` (${I(t.size)})`
2137
+ );
2138
+ const r = t.name.replace(/'/g, "''");
2139
+ if (e === "csv") {
2140
+ const o = t.name.replace(/\.[^.]+$/, "").replace(/[^a-zA-Z0-9_]/g, "_");
2141
+ this.writeln(
2142
+ u(`Hint: SELECT * FROM read_csv('${r}');`)
2143
+ ), this.writeln(
2144
+ u(` or: CREATE TABLE ${o} AS SELECT * FROM read_csv('${r}');`)
2145
+ );
2146
+ } else e === "parquet" ? this.writeln(
2147
+ u(`Hint: SELECT * FROM read_parquet('${r}');`)
2148
+ ) : e === "json" && this.writeln(
2149
+ u(`Hint: SELECT * FROM read_json('${r}');`)
2150
+ );
2151
+ return !0;
2152
+ } catch (n) {
2153
+ const r = n instanceof Error ? n.message : String(n);
2154
+ return this.writeln(c(`Error loading ${t.name}: ${r}`, g)), !1;
2155
+ }
2156
+ }
2157
+ /**
2158
+ * Resolves the container element from the configuration.
2159
+ *
2160
+ * @returns The resolved HTML element to attach the terminal to
2161
+ * @throws Error if the container selector doesn't match any element
2162
+ */
2163
+ resolveContainer() {
2164
+ if (typeof this.config.container == "string") {
2165
+ const t = document.querySelector(this.config.container);
2166
+ if (!t)
2167
+ throw new Error(`Container not found: ${this.config.container}`);
2168
+ return t;
2169
+ }
2170
+ return this.config.container;
2171
+ }
2172
+ /**
2173
+ * Registers all built-in dot commands (.help, .tables, .schema, etc.).
2174
+ *
2175
+ * Called during construction to populate the commands map with handlers
2176
+ * for terminal commands.
2177
+ */
2178
+ registerCommands() {
2179
+ this.commands.set(".help", {
2180
+ name: ".help",
2181
+ description: "Show available commands",
2182
+ handler: () => this.cmdHelp()
2183
+ }), this.commands.set(".clear", {
2184
+ name: ".clear",
2185
+ description: "Clear the terminal",
2186
+ handler: () => this.clear()
2187
+ }), this.commands.set(".tables", {
2188
+ name: ".tables",
2189
+ description: "List all tables",
2190
+ handler: () => this.cmdTables()
2191
+ }), this.commands.set(".schema", {
2192
+ name: ".schema",
2193
+ description: "Show table schema",
2194
+ usage: ".schema <table_name>",
2195
+ handler: (t) => this.cmdSchema(t)
2196
+ }), this.commands.set(".timer", {
2197
+ name: ".timer",
2198
+ description: "Toggle query timing",
2199
+ usage: ".timer on|off",
2200
+ handler: (t) => this.cmdTimer(t)
2201
+ }), this.commands.set(".mode", {
2202
+ name: ".mode",
2203
+ description: "Set output mode",
2204
+ usage: ".mode table|csv|tsv|json",
2205
+ handler: (t) => this.cmdMode(t)
2206
+ }), this.commands.set(".theme", {
2207
+ name: ".theme",
2208
+ description: "Set color theme (clears screen)",
2209
+ usage: ".theme dark|light",
2210
+ handler: (t) => this.cmdTheme(t)
2211
+ }), this.commands.set(".examples", {
2212
+ name: ".examples",
2213
+ description: "Show example queries",
2214
+ handler: () => this.cmdExamples()
2215
+ }), this.commands.set(".files", {
2216
+ name: ".files",
2217
+ description: "Manage loaded files",
2218
+ usage: ".files [list|add|remove <name|index>]",
2219
+ handler: (t) => this.cmdFiles(t)
2220
+ }), this.commands.set(".open", {
2221
+ name: ".open",
2222
+ description: "Open a file picker to load files",
2223
+ handler: () => this.cmdOpen()
2224
+ }), this.commands.set(".copy", {
2225
+ name: ".copy",
2226
+ description: "Copy last result to clipboard",
2227
+ handler: async () => {
2228
+ await this.copyLastResult();
2229
+ }
2230
+ }), this.commands.set(".highlight", {
2231
+ name: ".highlight",
2232
+ description: "Toggle syntax highlighting",
2233
+ usage: ".highlight on|off",
2234
+ handler: (t) => this.cmdHighlight(t)
2235
+ }), this.commands.set(".links", {
2236
+ name: ".links",
2237
+ description: "Toggle clickable URL detection",
2238
+ usage: ".links on|off",
2239
+ handler: (t) => this.cmdLinks(t)
2240
+ }), this.commands.set(".pagesize", {
2241
+ name: ".pagesize",
2242
+ description: "Enable pagination for large results (default: off)",
2243
+ usage: ".pagesize <number> (0 = disabled)",
2244
+ handler: (t) => this.cmdPageSize(t)
2245
+ }), this.commands.set(".reset", {
2246
+ name: ".reset",
2247
+ description: "Reset database and all settings to defaults",
2248
+ handler: () => this.cmdReset()
2249
+ }), this.commands.set(".prompt", {
2250
+ name: ".prompt",
2251
+ description: "Get or set the command prompt",
2252
+ usage: ".prompt [primary [continuation]]",
2253
+ handler: (t) => this.cmdPrompt(t)
2254
+ });
2255
+ }
2256
+ /**
2257
+ * Displays the command prompt (primary or continuation based on state).
2258
+ *
2259
+ * Sets terminal state to 'idle' if not currently collecting multi-line SQL.
2260
+ */
2261
+ showPrompt() {
2262
+ const t = this.state === "collecting" ? this.continuationPrompt : this.prompt;
2263
+ this.inputBuffer.setPromptLength(t.length), this.write(c(t, b)), this.state !== "collecting" && this.setState("idle");
2264
+ }
2265
+ /**
2266
+ * Handles raw terminal input data.
2267
+ *
2268
+ * Routes input to the appropriate handler based on terminal state
2269
+ * (executing, paginating, or normal input mode). Processes escape
2270
+ * sequences separately from regular character input.
2271
+ *
2272
+ * @param data - The raw input data from the terminal
2273
+ */
2274
+ handleInput(t) {
2275
+ if (this.state !== "executing") {
2276
+ if (this.state === "paginating") {
2277
+ this.handlePaginationInput(t);
2278
+ return;
2279
+ }
2280
+ if (t.startsWith("\x1B[")) {
2281
+ this.handleEscapeSequence(t);
2282
+ return;
2283
+ }
2284
+ for (const e of t)
2285
+ this.handleChar(e);
2286
+ }
2287
+ }
2288
+ /**
2289
+ * Handles input during pagination mode.
2290
+ *
2291
+ * Supports: [n]ext page, [p]revious page, [q]uit, page number entry,
2292
+ * and arrow keys for navigation.
2293
+ *
2294
+ * @param data - The input character or escape sequence
2295
+ */
2296
+ async handlePaginationInput(t) {
2297
+ const e = Math.ceil(this.totalRows / this.pageSize), s = t.toLowerCase();
2298
+ if (s === "n" || t === "\x1B[B") {
2299
+ this.currentPage < e - 1 ? (this.currentPage++, await this.executePaginatedQuery()) : this.writeln(u("Already on last page"));
2300
+ return;
2301
+ }
2302
+ if (s === "p" || t === "\x1B[A") {
2303
+ this.currentPage > 0 ? (this.currentPage--, await this.executePaginatedQuery()) : this.writeln(u("Already on first page"));
2304
+ return;
2305
+ }
2306
+ if (s === "q" || s === "\x1B" || s === "") {
2307
+ this.writeln(""), this.exitPagination(), this.writeln(""), this.showPrompt();
2308
+ return;
2309
+ }
2310
+ if (s === "\r" || s === `
2311
+ `) {
2312
+ const n = this.inputBuffer.getContent().trim();
2313
+ if (n) {
2314
+ const r = parseInt(n, 10);
2315
+ !isNaN(r) && r >= 1 && r <= e ? (this.currentPage = r - 1, this.inputBuffer.clear(), await this.executePaginatedQuery()) : (this.writeln(c(`Invalid page number. Enter 1-${e}`, g)), this.inputBuffer.clear());
2316
+ }
2317
+ return;
2318
+ }
2319
+ if (/^\d$/.test(s)) {
2320
+ this.write(this.inputBuffer.insert(s));
2321
+ return;
2322
+ }
2323
+ if (s === "" || s === "\b") {
2324
+ this.write(this.inputBuffer.backspace());
2325
+ return;
2326
+ }
2327
+ }
2328
+ /**
2329
+ * Processes a single character of user input.
2330
+ *
2331
+ * Handles special characters (Enter, Backspace, Tab, Ctrl sequences)
2332
+ * and regular printable characters including Unicode.
2333
+ *
2334
+ * @param char - The single character to process
2335
+ */
2336
+ handleChar(t) {
2337
+ const e = t.charCodeAt(0);
2338
+ switch (t) {
2339
+ case "\r":
2340
+ // Enter
2341
+ case `
2342
+ `:
2343
+ this.handleEnter();
2344
+ return;
2345
+ case "":
2346
+ // Backspace
2347
+ case "\b":
2348
+ this.write(this.inputBuffer.backspace());
2349
+ return;
2350
+ case "\x1B":
2351
+ return;
2352
+ // Will be handled by escape sequence
2353
+ case " ":
2354
+ this.handleTab();
2355
+ return;
2356
+ case "":
2357
+ this.handleCtrlC();
2358
+ return;
2359
+ case "":
2360
+ this.handlePaste();
2361
+ return;
2362
+ case "":
2363
+ this.write(this.inputBuffer.moveToStart());
2364
+ return;
2365
+ case "":
2366
+ this.write(this.inputBuffer.moveToEnd());
2367
+ return;
2368
+ case "\v":
2369
+ this.write(this.inputBuffer.clearToEnd());
2370
+ return;
2371
+ case "":
2372
+ this.write(this.inputBuffer.clearLine());
2373
+ return;
2374
+ }
2375
+ t !== "\x1B" && (e >= 32 && e < 127 ? (this.write(this.inputBuffer.insert(t)), this.isHighlightTrigger(t) && this.debouncedHighlight()) : e >= 128 && this.write(this.inputBuffer.insert(t)));
2376
+ }
2377
+ /**
2378
+ * Checks if a character should trigger syntax highlighting redraw.
2379
+ *
2380
+ * @param char - The character to check
2381
+ * @returns True if the character is a highlighting trigger (space, semicolon, parentheses, comma)
2382
+ */
2383
+ isHighlightTrigger(t) {
2384
+ return t === " " || t === ";" || t === "(" || t === ")" || t === ",";
2385
+ }
2386
+ /**
2387
+ * Processes VT100 escape sequences for special keys.
2388
+ *
2389
+ * Handles arrow keys (up/down for history, left/right for cursor),
2390
+ * Home, End, and Delete keys.
2391
+ *
2392
+ * @param seq - The escape sequence string (e.g., '\x1b[A' for Arrow Up)
2393
+ */
2394
+ handleEscapeSequence(t) {
2395
+ switch (t) {
2396
+ case "\x1B[A":
2397
+ this.handleArrowUp();
2398
+ break;
2399
+ case "\x1B[B":
2400
+ this.handleArrowDown();
2401
+ break;
2402
+ case "\x1B[C":
2403
+ this.write(this.inputBuffer.moveRight());
2404
+ break;
2405
+ case "\x1B[D":
2406
+ this.write(this.inputBuffer.moveLeft());
2407
+ break;
2408
+ case "\x1B[H":
2409
+ this.write(this.inputBuffer.moveToStart());
2410
+ break;
2411
+ case "\x1B[F":
2412
+ this.write(this.inputBuffer.moveToEnd());
2413
+ break;
2414
+ case "\x1B[3~":
2415
+ this.write(this.inputBuffer.delete());
2416
+ break;
2417
+ }
2418
+ }
2419
+ /**
2420
+ * Handles the Enter key press.
2421
+ *
2422
+ * For dot commands, executes immediately. For SQL, collects lines until
2423
+ * a semicolon terminates the statement, then executes.
2424
+ */
2425
+ async handleEnter() {
2426
+ const t = this.inputBuffer.getContent();
2427
+ if (this.writeln(""), t.trim() === "" && this.state !== "collecting") {
2428
+ this.showPrompt();
2429
+ return;
2430
+ }
2431
+ if (t.trim().startsWith(".") && this.state !== "collecting") {
2432
+ this.writeln(""), await this.history.add(t.trim()), await this.executeCommand(t.trim()), this.inputBuffer.clear(), this.writeln(""), this.showPrompt();
2433
+ return;
2434
+ }
2435
+ this.collectedSQL.push(t);
2436
+ const e = this.collectedSQL.join(`
2437
+ `).trim();
2438
+ e.endsWith(";") ? (this.writeln(""), await this.executeSQL(e), this.collectedSQL = [], this.state !== "paginating" && this.setState("idle")) : this.setState("collecting"), this.inputBuffer.clear(), this.state !== "paginating" && (e.endsWith(";") && this.writeln(""), this.showPrompt());
2439
+ }
2440
+ /**
2441
+ * Navigates to the previous command in history (Arrow Up).
2442
+ */
2443
+ handleArrowUp() {
2444
+ const t = this.history.previous(this.inputBuffer.getContent());
2445
+ t !== null && (this.write(this.inputBuffer.clearLine()), this.inputBuffer.setContent(t), this.write(this.getHighlightedSQL(t)));
2446
+ }
2447
+ /**
2448
+ * Navigates to the next command in history (Arrow Down).
2449
+ */
2450
+ handleArrowDown() {
2451
+ const t = this.history.next();
2452
+ t !== null && (this.write(this.inputBuffer.clearLine()), this.inputBuffer.setContent(t), this.write(this.getHighlightedSQL(t)));
2453
+ }
2454
+ /**
2455
+ * Handles Tab key for auto-completion.
2456
+ *
2457
+ * Provides suggestions for SQL keywords, table names, and functions.
2458
+ * Single match is applied directly; multiple matches are displayed.
2459
+ */
2460
+ async handleTab() {
2461
+ const t = this.inputBuffer.getContent(), e = this.inputBuffer.getCursorPos(), s = await this.database.getCompletions(t, e);
2462
+ if (s.length !== 0)
2463
+ if (s.length === 1)
2464
+ this.write(this.inputBuffer.replaceWordBeforeCursor(s[0].value)), this.redrawLineHighlighted();
2465
+ else {
2466
+ this.writeln("");
2467
+ const n = s.map((h) => {
2468
+ const d = h.type === "keyword" ? U : h.type === "table" ? b : h.type === "function" ? N : F;
2469
+ return c(h.value, d);
2470
+ }).join(" ");
2471
+ this.writeln(n);
2472
+ const r = this.findCommonPrefix(s.map((h) => h.value)), o = this.inputBuffer.getWordBeforeCursor();
2473
+ r.length > o.length && this.write(this.inputBuffer.replaceWordBeforeCursor(r));
2474
+ const l = this.state === "collecting" ? this.continuationPrompt : this.prompt;
2475
+ this.write(c(l, b)), this.write(this.getHighlightedSQL(this.inputBuffer.getContent()));
2476
+ }
2477
+ }
2478
+ /**
2479
+ * Finds the longest common prefix among an array of strings.
2480
+ *
2481
+ * Used for auto-completion to expand to the longest unambiguous prefix.
2482
+ *
2483
+ * @param strings - Array of strings to find common prefix for
2484
+ * @returns The longest common prefix (case-insensitive comparison)
2485
+ */
2486
+ findCommonPrefix(t) {
2487
+ if (t.length === 0) return "";
2488
+ if (t.length === 1) return t[0];
2489
+ let e = t[0];
2490
+ for (let s = 1; s < t.length; s++)
2491
+ for (; !t[s].toLowerCase().startsWith(e.toLowerCase()); )
2492
+ if (e = e.slice(0, -1), e === "") return "";
2493
+ return e;
2494
+ }
2495
+ /**
2496
+ * Handles Ctrl+C to cancel current input or multi-line collection.
2497
+ */
2498
+ handleCtrlC() {
2499
+ this.writeln("^C"), this.inputBuffer.clear(), this.collectedSQL = [], this.setState("idle"), this.showPrompt();
2500
+ }
2501
+ /**
2502
+ * Handles Ctrl+V to paste content from clipboard.
2503
+ *
2504
+ * Inserts text character-by-character, skipping newlines.
2505
+ */
2506
+ async handlePaste() {
2507
+ const t = await Ft();
2508
+ if (t)
2509
+ for (const e of t)
2510
+ e === `
2511
+ ` || e === "\r" || this.write(this.inputBuffer.insert(e));
2512
+ }
2513
+ /**
2514
+ * Copy last query result to clipboard
2515
+ */
2516
+ async copyLastResult() {
2517
+ if (!this.lastQueryResult)
2518
+ return this.writeln(u("No query result to copy")), !1;
2519
+ let t;
2520
+ switch (this.outputMode) {
2521
+ case "csv":
2522
+ t = O(this.lastQueryResult.columns, this.lastQueryResult.rows);
2523
+ break;
2524
+ case "tsv":
2525
+ t = $(this.lastQueryResult.columns, this.lastQueryResult.rows);
2526
+ break;
2527
+ case "json":
2528
+ t = D(this.lastQueryResult.columns, this.lastQueryResult.rows);
2529
+ break;
2530
+ default:
2531
+ t = k(this.lastQueryResult.columns, this.lastQueryResult.rows);
2532
+ }
2533
+ const e = await J(t);
2534
+ return e ? this.writeln(c("Result copied to clipboard", b)) : this.writeln(c("Failed to copy to clipboard", g)), e;
2535
+ }
2536
+ /**
2537
+ * Executes a dot command (e.g., .help, .tables, .schema).
2538
+ *
2539
+ * Parses the command and arguments, looks up the handler, and executes it.
2540
+ * Emits 'commandExecute' event on execution and 'error' on failure.
2541
+ *
2542
+ * @param input - The full command string including arguments
2543
+ */
2544
+ async executeCommand(t) {
2545
+ const { command: e, args: s } = Qt(t), n = this.commands.get(e);
2546
+ if (!n) {
2547
+ this.writeln(c(`Unknown command: ${e}`, g)), this.writeln("Type .help for available commands"), this.emit("error", { message: `Unknown command: ${e}`, source: "command" });
2548
+ return;
2549
+ }
2550
+ this.emit("commandExecute", { command: e, args: s });
2551
+ try {
2552
+ await n.handler(s, this);
2553
+ } catch (r) {
2554
+ const o = r instanceof Error ? r.message : String(r);
2555
+ this.writeln(c(`Error: ${o}`, g)), this.emit("error", { message: o, source: "command" });
2556
+ }
2557
+ }
2558
+ /**
2559
+ * Executes a SQL query and displays the results.
2560
+ *
2561
+ * This method executes the provided SQL statement against the DuckDB database,
2562
+ * displays the results in the configured output format (table, CSV, or JSON),
2563
+ * and adds the query to the command history.
2564
+ *
2565
+ * For large result sets (when pagination is enabled via `.pagesize`), the method
2566
+ * will automatically paginate the results and enter pagination mode.
2567
+ *
2568
+ * @param sql - The SQL statement to execute (should end with a semicolon)
2569
+ * @returns A promise that resolves to the query result, or null if an error occurred
2570
+ *
2571
+ * @fires queryStart - Emitted before query execution begins
2572
+ * @fires queryEnd - Emitted after query execution completes (success or failure)
2573
+ * @fires error - Emitted if the query fails
2574
+ *
2575
+ * @example Execute a SELECT query
2576
+ * ```typescript
2577
+ * const result = await terminal.executeSQL('SELECT * FROM users;');
2578
+ * if (result) {
2579
+ * console.log(`Retrieved ${result.rowCount} rows in ${result.duration}ms`);
2580
+ * console.log('Columns:', result.columns);
2581
+ * console.log('First row:', result.rows[0]);
2582
+ * }
2583
+ * ```
2584
+ *
2585
+ * @example Execute DDL statements
2586
+ * ```typescript
2587
+ * await terminal.executeSQL('CREATE TABLE products (id INTEGER, name VARCHAR);');
2588
+ * await terminal.executeSQL("INSERT INTO products VALUES (1, 'Widget');");
2589
+ * ```
2590
+ */
2591
+ async executeSQL(t) {
2592
+ const e = this.queryQueue.then(() => this.executeSQLInternal(t));
2593
+ return this.queryQueue = e.catch(() => null), e;
2594
+ }
2595
+ /**
2596
+ * Internal SQL execution logic.
2597
+ *
2598
+ * @internal
2599
+ * @param sql - The SQL statement to execute
2600
+ * @returns The query result, or null on error
2601
+ */
2602
+ async executeSQLInternal(t) {
2603
+ this.setState("executing");
2604
+ const e = performance.now();
2605
+ this.emit("queryStart", { sql: t });
2606
+ try {
2607
+ await this.history.add(t);
2608
+ const s = t.trim().replace(/;+$/, ""), n = /^\s*SELECT\s/i.test(s), r = /\b(LIMIT|OFFSET)\b/i.test(s);
2609
+ if (this.pageSize > 0 && n && !r) {
2610
+ const h = `SELECT COUNT(*) as cnt FROM (${s}) AS _count_subquery`, f = (await this.database.executeQuery(h)).rows[0]?.[0], m = typeof f == "bigint" ? Number(f) : f ?? 0;
2611
+ if (m > this.pageSize)
2612
+ return this.paginationQuery = s, this.totalRows = m, this.currentPage = 0, await this.executePaginatedQuery();
2613
+ }
2614
+ const o = await this.database.executeQuery(t), l = performance.now() - e;
2615
+ return this.lastQueryResult = o, o.columns.length > 0 && this.displayResult(o), this.showTimer && this.writeln(
2616
+ u(`Time: ${o.duration.toFixed(2)}ms`)
2617
+ ), this.emit("queryEnd", { sql: t, result: o, duration: l }), o;
2618
+ } catch (s) {
2619
+ const n = s instanceof Error ? s.message : String(s), r = performance.now() - e;
2620
+ if (this.writeln(c(`Error: ${n}`, g)), t.includes(`
2621
+ `) || t.length > 80) {
2622
+ const o = t.length > 200 ? t.substring(0, 200) + "..." : t;
2623
+ this.writeln(u(` Query: ${o.replace(/\n/g, " ")}`));
2624
+ }
2625
+ return this.emit("queryEnd", { sql: t, result: null, error: n, duration: r }), this.emit("error", { message: n, source: "query" }), null;
2626
+ } finally {
2627
+ this.state !== "paginating" && this.setState("idle");
2628
+ }
2629
+ }
2630
+ /**
2631
+ * Executes the current paginated query for the current page.
2632
+ *
2633
+ * Adds LIMIT/OFFSET to the stored query and displays results with
2634
+ * pagination controls.
2635
+ *
2636
+ * @returns The query result for the current page, or null on error
2637
+ */
2638
+ async executePaginatedQuery() {
2639
+ if (!this.paginationQuery) return null;
2640
+ const t = this.currentPage * this.pageSize, e = `${this.paginationQuery} LIMIT ${this.pageSize} OFFSET ${t}`;
2641
+ try {
2642
+ const s = await this.database.executeQuery(e);
2643
+ this.lastQueryResult = s, s.columns.length > 0 && this.displayResult(s);
2644
+ const n = Math.ceil(this.totalRows / this.pageSize), r = t + 1, o = Math.min(t + this.pageSize, this.totalRows);
2645
+ return this.writeln(""), this.writeln(
2646
+ u(`Showing rows ${r}-${o} of ${this.totalRows} (page ${this.currentPage + 1}/${n})`)
2647
+ ), this.writeln(
2648
+ c(" [n]ext [p]rev [q]uit or enter page number", S)
2649
+ ), this.setState("paginating"), s;
2650
+ } catch (s) {
2651
+ const n = s instanceof Error ? s.message : String(s);
2652
+ if (this.writeln(c(`Error: ${n}`, g)), e.length > 80) {
2653
+ const r = e.length > 200 ? e.substring(0, 200) + "..." : e;
2654
+ this.writeln(u(` Query: ${r.replace(/\n/g, " ")}`));
2655
+ }
2656
+ return this.exitPagination(), null;
2657
+ }
2658
+ }
2659
+ /**
2660
+ * Exits pagination mode and resets pagination state.
2661
+ */
2662
+ exitPagination() {
2663
+ this.paginationQuery = null, this.currentPage = 0, this.totalRows = 0, this.setState("idle");
2664
+ }
2665
+ /**
2666
+ * Displays a query result in the current output mode.
2667
+ *
2668
+ * Formats and writes the result as table, CSV, TSV, or JSON based on
2669
+ * the current outputMode setting.
2670
+ *
2671
+ * @param result - The query result to display
2672
+ */
2673
+ displayResult(t) {
2674
+ switch (this.outputMode) {
2675
+ case "csv":
2676
+ this.writeln(O(t.columns, t.rows));
2677
+ break;
2678
+ case "tsv":
2679
+ this.writeln($(t.columns, t.rows));
2680
+ break;
2681
+ case "json":
2682
+ this.writeln(D(t.columns, t.rows));
2683
+ break;
2684
+ case "table":
2685
+ default:
2686
+ this.writeln(k(t.columns, t.rows));
2687
+ break;
2688
+ }
2689
+ this.writeln(
2690
+ u(
2691
+ `${t.rowCount} row${t.rowCount !== 1 ? "s" : ""}`
2692
+ )
2693
+ );
2694
+ }
2695
+ // Command handlers
2696
+ cmdHelp() {
2697
+ this.writeln(y("Available commands:")), this.writeln("");
2698
+ for (const t of this.commands.values()) {
2699
+ const e = t.usage ? ` ${u(t.usage)}` : "";
2700
+ this.writeln(` ${c(t.name, S)} ${t.description}${e}`);
2701
+ }
2702
+ this.writeln(""), this.writeln("SQL statements must end with a semicolon (;)");
2703
+ }
2704
+ async cmdTables() {
2705
+ const t = await this.database.getTables();
2706
+ if (t.length === 0) {
2707
+ this.writeln(u("No tables found"));
2708
+ return;
2709
+ }
2710
+ for (const e of t)
2711
+ this.writeln(e);
2712
+ }
2713
+ async cmdSchema(t) {
2714
+ if (t.length === 0) {
2715
+ this.writeln("Usage: .schema <table_name>");
2716
+ return;
2717
+ }
2718
+ const e = await this.database.getTableSchema(t[0]);
2719
+ if (e.length === 0) {
2720
+ this.writeln(u(`Table not found: ${t[0]}`));
2721
+ return;
2722
+ }
2723
+ for (const s of e)
2724
+ this.writeln(` ${s.name} ${u(s.type)}`);
2725
+ }
2726
+ cmdTimer(t) {
2727
+ if (t.length === 0) {
2728
+ this.writeln(`Timer is ${this.showTimer ? "on" : "off"}`);
2729
+ return;
2730
+ }
2731
+ const e = t[0].toLowerCase();
2732
+ e === "on" ? (this.showTimer = !0, this.writeln("Timer is now on")) : e === "off" ? (this.showTimer = !1, this.writeln("Timer is now off")) : this.writeln("Usage: .timer on|off");
2733
+ }
2734
+ cmdMode(t) {
2735
+ if (t.length === 0) {
2736
+ this.writeln(`Output mode: ${this.outputMode}`);
2737
+ return;
2738
+ }
2739
+ const e = t[0].toLowerCase();
2740
+ e === "table" || e === "csv" || e === "tsv" || e === "json" ? (this.outputMode = e, this.writeln(`Output mode set to ${e}`)) : this.writeln("Usage: .mode table|csv|tsv|json");
2741
+ }
2742
+ cmdTheme(t) {
2743
+ if (t.length === 0) {
2744
+ const s = this.customTheme ? this.customTheme.name : this.currentThemeName;
2745
+ this.writeln(`Theme: ${s}`);
2746
+ return;
2747
+ }
2748
+ const e = t[0].toLowerCase();
2749
+ e === "dark" || e === "light" ? this.setTheme(e) : this.writeln("Usage: .theme dark|light");
2750
+ }
2751
+ cmdExamples() {
2752
+ this.writeln(y("Example queries:")), this.writeln(""), this.writeln(c(" -- Create a table", w)), this.writeln(" " + this.getHighlightedSQL("CREATE TABLE users (id INTEGER, name VARCHAR);")), this.writeln(""), this.writeln(c(" -- Insert data", w)), this.writeln(" " + this.getHighlightedSQL("INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');")), this.writeln(""), this.writeln(c(" -- Query data", w)), this.writeln(" " + this.getHighlightedSQL("SELECT * FROM users WHERE name LIKE 'A%';")), this.writeln(""), this.writeln(c(" -- Use built-in functions", w)), this.writeln(" " + this.getHighlightedSQL("SELECT range(10), current_timestamp;"));
2753
+ }
2754
+ async cmdFiles(t) {
2755
+ const e = t[0]?.toLowerCase() ?? "list";
2756
+ if (e === "list") {
2757
+ if (this.loadedFiles.size === 0) {
2758
+ this.writeln(u("No files loaded")), this.writeln(u("Use .open or drag-and-drop to add files"));
2759
+ return;
2760
+ }
2761
+ this.writeln(y("Loaded files:"));
2762
+ let s = 1;
2763
+ for (const [n, r] of this.loadedFiles)
2764
+ this.writeln(` ${u(`${s}.`)} ${n} ${u(`(${I(r.size)})`)}`), s++;
2765
+ } else if (e === "add")
2766
+ await this.cmdOpen();
2767
+ else if (e === "remove" || e === "rm") {
2768
+ const s = t.slice(1).join(" ");
2769
+ if (!s) {
2770
+ this.writeln("Usage: .files remove <filename|index>");
2771
+ return;
2772
+ }
2773
+ const n = parseInt(s, 10);
2774
+ let r = null;
2775
+ if (!isNaN(n) && n >= 1) {
2776
+ const o = Array.from(this.loadedFiles.keys());
2777
+ if (n <= o.length)
2778
+ r = o[n - 1];
2779
+ else {
2780
+ this.writeln(c(`Invalid index: ${n}. Use .files list to see available files.`, "red"));
2781
+ return;
2782
+ }
2783
+ } else if (this.loadedFiles.has(s))
2784
+ r = s;
2785
+ else {
2786
+ this.writeln(c(`File not found: ${s}`, "red"));
2787
+ return;
2788
+ }
2789
+ if (r)
2790
+ try {
2791
+ await this.database.dropFile(r), this.loadedFiles.delete(r), this.writeln(c(`Removed: ${r}`, "green"));
2792
+ } catch (o) {
2793
+ this.writeln(c(`Error removing file: ${o}`, "red"));
2794
+ }
2795
+ } else
2796
+ this.writeln("Usage: .files [list|add|remove <name|index>]");
2797
+ }
2798
+ async cmdOpen() {
2799
+ this.writeln(u("Opening file picker..."));
2800
+ const t = await K({
2801
+ multiple: !0,
2802
+ accept: ".csv,.parquet,.json,.db,.duckdb"
2803
+ });
2804
+ if (t.length === 0) {
2805
+ this.writeln(u("No files selected"));
2806
+ return;
2807
+ }
2808
+ for (const e of t)
2809
+ await this.loadFile(e);
2810
+ }
2811
+ cmdHighlight(t) {
2812
+ if (t.length === 0) {
2813
+ this.writeln(`Syntax highlighting is ${this.syntaxHighlighting ? "on" : "off"}`);
2814
+ return;
2815
+ }
2816
+ const e = t[0].toLowerCase();
2817
+ e === "on" ? (this.syntaxHighlighting = !0, this.writeln("Syntax highlighting is now on")) : e === "off" ? (this.syntaxHighlighting = !1, this.writeln("Syntax highlighting is now off")) : this.writeln("Usage: .highlight on|off");
2818
+ }
2819
+ cmdLinks(t) {
2820
+ if (t.length === 0) {
2821
+ this.writeln(`URL link detection is ${this.linkProvider.isEnabled() ? "on" : "off"}`);
2822
+ return;
2823
+ }
2824
+ const e = t[0].toLowerCase();
2825
+ e === "on" ? (this.linkProvider.setEnabled(!0), this.writeln("URL link detection is now on")) : e === "off" ? (this.linkProvider.setEnabled(!1), this.writeln("URL link detection is now off")) : this.writeln("Usage: .links on|off");
2826
+ }
2827
+ cmdPageSize(t) {
2828
+ if (t.length === 0) {
2829
+ this.pageSize === 0 ? this.writeln("Pagination is disabled (showing all rows)") : this.writeln(`Page size: ${this.pageSize} rows`);
2830
+ return;
2831
+ }
2832
+ const e = parseInt(t[0], 10);
2833
+ if (isNaN(e) || e < 0) {
2834
+ this.writeln("Usage: .pagesize <number> (0 = no pagination)");
2835
+ return;
2836
+ }
2837
+ this.pageSize = e, e === 0 ? this.writeln("Pagination disabled (will show all rows)") : this.writeln(`Page size set to ${e} rows`);
2838
+ }
2839
+ async cmdReset() {
2840
+ try {
2841
+ const t = await this.database.getTables(), e = t.length;
2842
+ for (const n of t)
2843
+ await this.database.executeQuery(`DROP TABLE IF EXISTS "${n}";`);
2844
+ const s = this.loadedFiles.size;
2845
+ for (const n of this.loadedFiles.keys())
2846
+ try {
2847
+ await this.database.dropFile(n);
2848
+ } catch {
2849
+ }
2850
+ this.loadedFiles.clear(), this.lastQueryResult = null, this.history.reset(), this.showTimer = !1, this.outputMode = "table", this.syntaxHighlighting = !0, this.pageSize = 0, this.linkProvider.setEnabled(!0), this.prompt = Q, this.continuationPrompt = W, this.paginationQuery = null, this.currentPage = 0, this.totalRows = 0, this.writeln(
2851
+ c(
2852
+ `Reset complete: dropped ${e} table${e !== 1 ? "s" : ""}, cleared ${s} file${s !== 1 ? "s" : ""}, settings restored to defaults`,
2853
+ b
2854
+ )
2855
+ );
2856
+ } catch (t) {
2857
+ const e = t instanceof Error ? t.message : String(t);
2858
+ this.writeln(c(`Error during reset: ${e}`, g));
2859
+ }
2860
+ }
2861
+ cmdPrompt(t) {
2862
+ if (t.length === 0) {
2863
+ this.writeln(`Primary prompt: "${this.prompt}"`), this.writeln(`Continuation prompt: "${this.continuationPrompt}"`);
2864
+ return;
2865
+ }
2866
+ const e = t[0];
2867
+ this.prompt = e, t.length >= 2 ? (this.continuationPrompt = t[1], this.writeln(`Prompts set to "${this.prompt}" and "${this.continuationPrompt}"`)) : this.writeln(`Primary prompt set to "${this.prompt}"`);
2868
+ }
2869
+ // ==================== TerminalInterface Implementation ====================
2870
+ /**
2871
+ * Writes text to the terminal without a trailing newline.
2872
+ *
2873
+ * Use this method for inline output where you don't want to start a new line.
2874
+ * The text is written directly to the terminal without any processing.
2875
+ *
2876
+ * @param text - The text to write to the terminal
2877
+ *
2878
+ * @example
2879
+ * ```typescript
2880
+ * terminal.write('Loading');
2881
+ * terminal.write('...');
2882
+ * terminal.write(' Done!\n');
2883
+ * ```
2884
+ */
2885
+ write(t) {
2886
+ this.terminalAdapter.write(t);
2887
+ }
2888
+ /**
2889
+ * Writes text to the terminal followed by a newline.
2890
+ *
2891
+ * The text is processed for clickable URLs (if link detection is enabled)
2892
+ * and newlines are normalized to CRLF for proper terminal display.
2893
+ *
2894
+ * @param text - The text to write to the terminal
2895
+ *
2896
+ * @example
2897
+ * ```typescript
2898
+ * terminal.writeln('Query completed successfully!');
2899
+ * terminal.writeln('Visit https://duckdb.org for documentation');
2900
+ * ```
2901
+ */
2902
+ writeln(t) {
2903
+ const s = this.linkProvider.process(t).replace(/\r?\n/g, `\r
2904
+ `);
2905
+ this.terminalAdapter.writeln(s);
2906
+ }
2907
+ /**
2908
+ * Clears the terminal screen.
2909
+ *
2910
+ * This removes all content from the terminal display and moves the cursor
2911
+ * to the top-left corner.
2912
+ *
2913
+ * @example
2914
+ * ```typescript
2915
+ * terminal.clear();
2916
+ * terminal.writeln('Screen cleared!');
2917
+ * ```
2918
+ */
2919
+ clear() {
2920
+ this.terminalAdapter.clear();
2921
+ }
2922
+ /**
2923
+ * Sets the terminal color theme.
2924
+ *
2925
+ * You can set a built-in theme ('dark' or 'light') or provide a custom
2926
+ * theme object with your own colors. Built-in theme preferences are
2927
+ * persisted to localStorage.
2928
+ *
2929
+ * @param theme - The theme to apply: 'dark', 'light', or a custom Theme object
2930
+ *
2931
+ * @fires themeChange - Emitted after the theme is changed
2932
+ *
2933
+ * @example Set a built-in theme
2934
+ * ```typescript
2935
+ * terminal.setTheme('light');
2936
+ * ```
2937
+ *
2938
+ * @example Set a custom theme
2939
+ * ```typescript
2940
+ * terminal.setTheme({
2941
+ * name: 'my-theme',
2942
+ * colors: {
2943
+ * background: '#1a1b26',
2944
+ * foreground: '#a9b1d6',
2945
+ * cursor: '#c0caf5',
2946
+ * // ... other color properties
2947
+ * },
2948
+ * });
2949
+ * ```
2950
+ */
2951
+ setTheme(t) {
2952
+ const e = this.getCurrentThemeObject();
2953
+ typeof t == "object" ? (this.customTheme = t, this.currentThemeName = "custom", this.terminalAdapter.setTheme(t), this.writeln(`Theme set to ${t.name}`)) : (this.customTheme = null, this.currentThemeName = t, this.terminalAdapter.setTheme(G(t)), ie(t), this.writeln(`Theme set to ${t}`));
2954
+ const s = this.getCurrentThemeObject();
2955
+ this.emit("themeChange", { theme: s, previous: e });
2956
+ const n = this.currentThemeName === "light" || this.customTheme?.name === "light";
2957
+ document.body.classList.toggle("light", n), document.body.classList.toggle("dark", !n);
2958
+ }
2959
+ /**
2960
+ * Gets the current theme mode.
2961
+ *
2962
+ * Returns 'dark' or 'light' based on the current theme. For custom themes,
2963
+ * this returns 'light' if the theme name is 'light', otherwise 'dark'.
2964
+ *
2965
+ * @returns The current theme mode: 'dark' or 'light'
2966
+ *
2967
+ * @example
2968
+ * ```typescript
2969
+ * const mode = terminal.getTheme();
2970
+ * console.log(`Current theme mode: ${mode}`);
2971
+ * ```
2972
+ */
2973
+ getTheme() {
2974
+ return this.currentThemeName === "custom" && this.customTheme ? this.customTheme.name === "light" ? "light" : "dark" : this.currentThemeName;
2975
+ }
2976
+ }
2977
+ function Ce(i) {
2978
+ const t = /* @__PURE__ */ new Map();
2979
+ return t.set(".help", {
2980
+ name: ".help",
2981
+ description: "Show available commands",
2982
+ handler: () => re(t, i)
2983
+ }), t.set(".clear", {
2984
+ name: ".clear",
2985
+ description: "Clear the terminal",
2986
+ handler: () => i.clear()
2987
+ }), t.set(".tables", {
2988
+ name: ".tables",
2989
+ description: "List all tables",
2990
+ handler: () => ne(i)
2991
+ }), t.set(".schema", {
2992
+ name: ".schema",
2993
+ description: "Show table schema",
2994
+ usage: ".schema <table_name>",
2995
+ handler: (e) => oe(e, i)
2996
+ }), t.set(".timer", {
2997
+ name: ".timer",
2998
+ description: "Toggle query timing",
2999
+ usage: ".timer on|off",
3000
+ handler: (e) => ae(e, i)
3001
+ }), t.set(".mode", {
3002
+ name: ".mode",
3003
+ description: "Set output mode",
3004
+ usage: ".mode table|csv|tsv|json",
3005
+ handler: (e) => le(e, i)
3006
+ }), t.set(".theme", {
3007
+ name: ".theme",
3008
+ description: "Set color theme (clears screen)",
3009
+ usage: ".theme dark|light",
3010
+ handler: (e) => he(e, i)
3011
+ }), t.set(".examples", {
3012
+ name: ".examples",
3013
+ description: "Show example queries",
3014
+ handler: () => ce(i)
3015
+ }), t.set(".files", {
3016
+ name: ".files",
3017
+ description: "Manage loaded files",
3018
+ usage: ".files [list|add|remove <name|index>]",
3019
+ handler: (e) => ue(e, i)
3020
+ }), t.set(".open", {
3021
+ name: ".open",
3022
+ description: "Open a file picker to load files",
3023
+ handler: () => tt(i)
3024
+ }), t.set(".copy", {
3025
+ name: ".copy",
3026
+ description: "Copy last result to clipboard",
3027
+ handler: () => we(i)
3028
+ }), t.set(".highlight", {
3029
+ name: ".highlight",
3030
+ description: "Toggle syntax highlighting",
3031
+ usage: ".highlight on|off",
3032
+ handler: (e) => de(e, i)
3033
+ }), t.set(".links", {
3034
+ name: ".links",
3035
+ description: "Toggle clickable URL detection",
3036
+ usage: ".links on|off",
3037
+ handler: (e) => fe(e, i)
3038
+ }), t.set(".pagesize", {
3039
+ name: ".pagesize",
3040
+ description: "Enable pagination for large results (default: off)",
3041
+ usage: ".pagesize <number> (0 = disabled)",
3042
+ handler: (e) => me(e, i)
3043
+ }), t.set(".reset", {
3044
+ name: ".reset",
3045
+ description: "Reset database and all settings to defaults",
3046
+ handler: () => ge(i)
3047
+ }), t.set(".prompt", {
3048
+ name: ".prompt",
3049
+ description: "Get or set the command prompt",
3050
+ usage: ".prompt [primary [continuation]]",
3051
+ handler: (e) => pe(e, i)
3052
+ }), t;
3053
+ }
3054
+ function re(i, t) {
3055
+ t.writeln(y("Available commands:")), t.writeln("");
3056
+ for (const e of i.values()) {
3057
+ const s = e.usage ? ` ${u(e.usage)}` : "";
3058
+ t.writeln(` ${c(e.name, S)} ${e.description}${s}`);
3059
+ }
3060
+ t.writeln(""), t.writeln("SQL statements must end with a semicolon (;)");
3061
+ }
3062
+ async function ne(i) {
3063
+ const t = await i.getDatabase().getTables();
3064
+ if (t.length === 0) {
3065
+ i.writeln(u("No tables found"));
3066
+ return;
3067
+ }
3068
+ for (const e of t)
3069
+ i.writeln(e);
3070
+ }
3071
+ async function oe(i, t) {
3072
+ if (i.length === 0) {
3073
+ t.writeln("Usage: .schema <table_name>");
3074
+ return;
3075
+ }
3076
+ const e = await t.getDatabase().getTableSchema(i[0]);
3077
+ if (e.length === 0) {
3078
+ t.writeln(u(`Table not found: ${i[0]}`));
3079
+ return;
3080
+ }
3081
+ for (const s of e)
3082
+ t.writeln(` ${s.name} ${u(s.type)}`);
3083
+ }
3084
+ function ae(i, t) {
3085
+ if (i.length === 0) {
3086
+ t.writeln(`Timer is ${t.getShowTimer() ? "on" : "off"}`);
3087
+ return;
3088
+ }
3089
+ const e = i[0].toLowerCase();
3090
+ e === "on" ? (t.setShowTimer(!0), t.writeln("Timer is now on")) : e === "off" ? (t.setShowTimer(!1), t.writeln("Timer is now off")) : t.writeln("Usage: .timer on|off");
3091
+ }
3092
+ function le(i, t) {
3093
+ if (i.length === 0) {
3094
+ t.writeln(`Output mode: ${t.getOutputMode()}`);
3095
+ return;
3096
+ }
3097
+ const e = i[0].toLowerCase();
3098
+ e === "table" || e === "csv" || e === "tsv" || e === "json" ? (t.setOutputMode(e), t.writeln(`Output mode set to ${e}`)) : t.writeln("Usage: .mode table|csv|tsv|json");
3099
+ }
3100
+ function he(i, t) {
3101
+ if (i.length === 0) {
3102
+ t.writeln(`Theme: ${t.getThemeName()}`);
3103
+ return;
3104
+ }
3105
+ const e = i[0].toLowerCase();
3106
+ e === "dark" || e === "light" ? t.setTheme(e) : t.writeln("Usage: .theme dark|light");
3107
+ }
3108
+ function ce(i) {
3109
+ i.writeln(y("Example queries:")), i.writeln(""), i.writeln(c(" -- Create a table", w)), i.writeln(" " + i.getHighlightedSQL("CREATE TABLE users (id INTEGER, name VARCHAR);")), i.writeln(""), i.writeln(c(" -- Insert data", w)), i.writeln(" " + i.getHighlightedSQL("INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');")), i.writeln(""), i.writeln(c(" -- Query data", w)), i.writeln(" " + i.getHighlightedSQL("SELECT * FROM users WHERE name LIKE 'A%';")), i.writeln(""), i.writeln(c(" -- Use built-in functions", w)), i.writeln(" " + i.getHighlightedSQL("SELECT range(10), current_timestamp;"));
3110
+ }
3111
+ async function ue(i, t) {
3112
+ const e = i[0]?.toLowerCase() ?? "list", s = t.getLoadedFiles();
3113
+ if (e === "list") {
3114
+ if (s.size === 0) {
3115
+ t.writeln(u("No files loaded")), t.writeln(u("Use .open or drag-and-drop to add files"));
3116
+ return;
3117
+ }
3118
+ t.writeln(y("Loaded files:"));
3119
+ let n = 1;
3120
+ for (const [r, o] of s)
3121
+ t.writeln(` ${u(`${n}.`)} ${r} ${u(`(${I(o.size)})`)}`), n++;
3122
+ } else if (e === "add")
3123
+ await tt(t);
3124
+ else if (e === "remove" || e === "rm") {
3125
+ const n = i.slice(1).join(" ");
3126
+ if (!n) {
3127
+ t.writeln("Usage: .files remove <filename|index>");
3128
+ return;
3129
+ }
3130
+ const r = parseInt(n, 10);
3131
+ let o = null;
3132
+ if (!isNaN(r) && r >= 1) {
3133
+ const l = Array.from(s.keys());
3134
+ if (r <= l.length)
3135
+ o = l[r - 1];
3136
+ else {
3137
+ t.writeln(c(`Invalid index: ${r}. Use .files list to see available files.`, "red"));
3138
+ return;
3139
+ }
3140
+ } else if (s.has(n))
3141
+ o = n;
3142
+ else {
3143
+ t.writeln(c(`File not found: ${n}`, "red"));
3144
+ return;
3145
+ }
3146
+ if (o)
3147
+ try {
3148
+ await t.removeFile(o), t.writeln(c(`Removed: ${o}`, "green"));
3149
+ } catch (l) {
3150
+ t.writeln(c(`Error removing file: ${l}`, "red"));
3151
+ }
3152
+ } else
3153
+ t.writeln("Usage: .files [list|add|remove <name|index>]");
3154
+ }
3155
+ async function tt(i) {
3156
+ i.writeln(u("Opening file picker..."));
3157
+ const t = await K({
3158
+ multiple: !0,
3159
+ accept: ".csv,.parquet,.json,.db,.duckdb"
3160
+ });
3161
+ if (t.length === 0) {
3162
+ i.writeln(u("No files selected"));
3163
+ return;
3164
+ }
3165
+ for (const e of t)
3166
+ await i.loadFile(e);
3167
+ }
3168
+ function de(i, t) {
3169
+ if (i.length === 0) {
3170
+ t.writeln(`Syntax highlighting is ${t.getSyntaxHighlighting() ? "on" : "off"}`);
3171
+ return;
3172
+ }
3173
+ const e = i[0].toLowerCase();
3174
+ e === "on" ? (t.setSyntaxHighlighting(!0), t.writeln("Syntax highlighting is now on")) : e === "off" ? (t.setSyntaxHighlighting(!1), t.writeln("Syntax highlighting is now off")) : t.writeln("Usage: .highlight on|off");
3175
+ }
3176
+ function fe(i, t) {
3177
+ const e = t.getLinkProvider();
3178
+ if (i.length === 0) {
3179
+ t.writeln(`URL link detection is ${e.isEnabled() ? "on" : "off"}`);
3180
+ return;
3181
+ }
3182
+ const s = i[0].toLowerCase();
3183
+ s === "on" ? (e.setEnabled(!0), t.writeln("URL link detection is now on")) : s === "off" ? (e.setEnabled(!1), t.writeln("URL link detection is now off")) : t.writeln("Usage: .links on|off");
3184
+ }
3185
+ function me(i, t) {
3186
+ if (i.length === 0) {
3187
+ const s = t.getPageSize();
3188
+ s === 0 ? t.writeln("Pagination is disabled (showing all rows)") : t.writeln(`Page size: ${s} rows`);
3189
+ return;
3190
+ }
3191
+ const e = parseInt(i[0], 10);
3192
+ if (isNaN(e) || e < 0) {
3193
+ t.writeln("Usage: .pagesize <number> (0 = no pagination)");
3194
+ return;
3195
+ }
3196
+ t.setPageSize(e), e === 0 ? t.writeln("Pagination disabled (will show all rows)") : t.writeln(`Page size set to ${e} rows`);
3197
+ }
3198
+ async function ge(i) {
3199
+ await i.resetState();
3200
+ }
3201
+ function pe(i, t) {
3202
+ if (i.length === 0) {
3203
+ t.writeln(`Primary prompt: "${t.getPrompt()}"`), t.writeln(`Continuation prompt: "${t.getContinuationPrompt()}"`);
3204
+ return;
3205
+ }
3206
+ const e = i[0];
3207
+ i.length >= 2 ? (t.setPrompts(e, i[1]), t.writeln(`Prompts set to "${e}" and "${i[1]}"`)) : (t.setPrompts(e), t.writeln(`Primary prompt set to "${e}"`));
3208
+ }
3209
+ async function we(i) {
3210
+ const t = i.getLastQueryResult();
3211
+ if (!t) {
3212
+ i.writeln(u("No query result to copy"));
3213
+ return;
3214
+ }
3215
+ const e = i.getOutputMode();
3216
+ let s;
3217
+ switch (e) {
3218
+ case "csv":
3219
+ s = O(t.columns, t.rows);
3220
+ break;
3221
+ case "tsv":
3222
+ s = $(t.columns, t.rows);
3223
+ break;
3224
+ case "json":
3225
+ s = D(t.columns, t.rows);
3226
+ break;
3227
+ default:
3228
+ s = k(t.columns, t.rows);
3229
+ }
3230
+ await J(s) ? i.writeln(c(`Copied ${t.rowCount} rows to clipboard (${e} format)`, b)) : i.writeln(c("Failed to copy to clipboard", g));
3231
+ }
3232
+ class Ie {
3233
+ constructor(t) {
3234
+ a(this, "state", {
3235
+ query: null,
3236
+ currentPage: 0,
3237
+ totalRows: 0,
3238
+ pageSize: 0,
3239
+ isActive: !1
3240
+ });
3241
+ a(this, "ctx");
3242
+ this.ctx = t;
3243
+ }
3244
+ /**
3245
+ * Initialize pagination for a query
3246
+ */
3247
+ start(t, e, s) {
3248
+ this.state = {
3249
+ query: t,
3250
+ currentPage: 0,
3251
+ totalRows: e,
3252
+ pageSize: s,
3253
+ isActive: !0
3254
+ };
3255
+ }
3256
+ /**
3257
+ * Exit pagination mode
3258
+ */
3259
+ exit() {
3260
+ this.state = {
3261
+ query: null,
3262
+ currentPage: 0,
3263
+ totalRows: 0,
3264
+ pageSize: 0,
3265
+ isActive: !1
3266
+ };
3267
+ }
3268
+ /**
3269
+ * Check if pagination is currently active
3270
+ */
3271
+ isActive() {
3272
+ return this.state.isActive;
3273
+ }
3274
+ /**
3275
+ * Get current state
3276
+ */
3277
+ getState() {
3278
+ return this.state;
3279
+ }
3280
+ /**
3281
+ * Get the total number of pages
3282
+ */
3283
+ getTotalPages() {
3284
+ return this.state.pageSize === 0 ? 0 : Math.ceil(this.state.totalRows / this.state.pageSize);
3285
+ }
3286
+ /**
3287
+ * Get current page (1-indexed for display)
3288
+ */
3289
+ getCurrentPageDisplay() {
3290
+ return this.state.currentPage + 1;
3291
+ }
3292
+ /**
3293
+ * Execute the paginated query for the current page
3294
+ */
3295
+ async executeCurrentPage() {
3296
+ if (!this.state.query) return;
3297
+ const t = this.state.currentPage * this.state.pageSize, e = `${this.state.query} LIMIT ${this.state.pageSize} OFFSET ${t}`;
3298
+ try {
3299
+ const s = await this.ctx.getDatabase().executeQuery(e);
3300
+ this.ctx.displayResult(s, !1), this.showNavigationHint();
3301
+ } catch (s) {
3302
+ const n = s instanceof Error ? s.message : String(s);
3303
+ this.ctx.writeln(c(`Error: ${n}`, g));
3304
+ }
3305
+ }
3306
+ /**
3307
+ * Show navigation hint
3308
+ */
3309
+ showNavigationHint() {
3310
+ const t = this.getTotalPages();
3311
+ this.ctx.writeln(""), this.ctx.writeln(
3312
+ u(
3313
+ `Page ${this.getCurrentPageDisplay()}/${t} (${this.state.totalRows} rows) - n:next p:prev 1-${t}:goto q:quit`
3314
+ )
3315
+ );
3316
+ }
3317
+ /**
3318
+ * Handle user input during pagination
3319
+ * @returns true if the input was handled, false otherwise
3320
+ */
3321
+ async handleInput(t) {
3322
+ if (!this.state.isActive) return !1;
3323
+ const e = this.getTotalPages(), s = t.toLowerCase();
3324
+ if (s === "n" || t === "\x1B[B")
3325
+ return this.state.currentPage < e - 1 ? (this.state.currentPage++, await this.executeCurrentPage()) : this.ctx.writeln(u("Already on last page")), !0;
3326
+ if (s === "p" || t === "\x1B[A")
3327
+ return this.state.currentPage > 0 ? (this.state.currentPage--, await this.executeCurrentPage()) : this.ctx.writeln(u("Already on first page")), !0;
3328
+ if (s === "q" || s === "\x1B" || s === "")
3329
+ return this.ctx.writeln(""), this.exit(), !0;
3330
+ if (s === "\r" || s === `
3331
+ `) {
3332
+ const n = this.ctx.getInputContent().trim();
3333
+ if (n) {
3334
+ const r = parseInt(n, 10);
3335
+ !isNaN(r) && r >= 1 && r <= e ? (this.state.currentPage = r - 1, this.ctx.clearInput(), await this.executeCurrentPage()) : (this.ctx.writeln(c(`Invalid page number. Enter 1-${e}`, g)), this.ctx.clearInput());
3336
+ }
3337
+ return !0;
3338
+ }
3339
+ return /^\d$/.test(s) ? (this.ctx.write(this.ctx.insertChar(s)), !0) : s === "" || s === "\b" ? (this.ctx.write(this.ctx.backspace()), !0) : !1;
3340
+ }
3341
+ /**
3342
+ * Check if a query should use pagination
3343
+ * @param sql The SQL query
3344
+ * @param rowCount The total row count
3345
+ * @param pageSize The configured page size (0 = disabled)
3346
+ * @returns Whether pagination should be enabled
3347
+ */
3348
+ static shouldPaginate(t, e, s) {
3349
+ return !(s === 0 || e <= s || !t.trim().toUpperCase().startsWith("SELECT") || /\b(LIMIT|OFFSET)\b/i.test(t));
3350
+ }
3351
+ /**
3352
+ * Strip trailing semicolon from SQL for pagination
3353
+ */
3354
+ static prepareQuery(t) {
3355
+ return t.replace(/;\s*$/, "");
3356
+ }
3357
+ }
3358
+ async function be(i) {
3359
+ const t = new se(i);
3360
+ return await t.start(), t;
3361
+ }
3362
+ async function Pe(i) {
3363
+ return be(i);
3364
+ }
3365
+ export {
3366
+ dt as BOLD,
3367
+ ft as DIM,
3368
+ ut as Database,
3369
+ se as DuckDBTerminal,
3370
+ U as FG_BLUE,
3371
+ S as FG_CYAN,
3372
+ b as FG_GREEN,
3373
+ g as FG_RED,
3374
+ F as FG_WHITE,
3375
+ N as FG_YELLOW,
3376
+ Pt as HistoryStore,
3377
+ Ct as InputBuffer,
3378
+ Xt as LinkProvider,
3379
+ Ie as PaginationHandler,
3380
+ v as RESET,
3381
+ ct as TerminalAdapter,
3382
+ y as bold,
3383
+ c as colorize,
3384
+ Le as containsURL,
3385
+ J as copyToClipboard,
3386
+ Ce as createCommands,
3387
+ be as createTerminal,
3388
+ qt as darkTheme,
3389
+ u as dim,
3390
+ Pe as embed,
3391
+ Re as extractURLs,
3392
+ O as formatCSV,
3393
+ D as formatJSON,
3394
+ $ as formatTSV,
3395
+ k as formatTable,
3396
+ ee as getSavedTheme,
3397
+ G as getTheme,
3398
+ H as highlightSQL,
3399
+ ye as isClipboardAvailable,
3400
+ Se as isSQLComplete,
3401
+ Ae as isValidURL,
3402
+ te as lightTheme,
3403
+ Vt as linkifyText,
3404
+ Ft as readFromClipboard,
3405
+ ie as saveTheme,
3406
+ Z as tokenize
3407
+ };