dbn-cli 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +41 -0
- package/bin/dbn.ts +9 -0
- package/package.json +40 -0
- package/src/adapter/base.ts +46 -0
- package/src/adapter/sqlite.ts +110 -0
- package/src/index.ts +250 -0
- package/src/types.ts +109 -0
- package/src/ui/navigator.ts +254 -0
- package/src/ui/renderer.ts +499 -0
- package/src/ui/screen.ts +101 -0
- package/src/ui/theme.ts +58 -0
- package/src/utils/format.ts +137 -0
package/README.md
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# dbn
|
|
2
|
+
|
|
3
|
+
A lightweight terminal SQLite browser with an ncdu-style interface.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Minimal dependencies** - Uses Node.js 24+ built-in `node:sqlite` module
|
|
8
|
+
- **Full-screen TUI** - ncdu-inspired keyboard navigation
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g dbn-cli
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Usage
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
dbn <path-to-sqlite-db-file>
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Keyboard Shortcuts
|
|
23
|
+
|
|
24
|
+
- `j/k` or `↓/↑` - Navigate
|
|
25
|
+
- `Enter` or `l` - Open/view details
|
|
26
|
+
- `h` or `Esc` - Go back
|
|
27
|
+
- `s` - Toggle schema view
|
|
28
|
+
- `r` - Reload data
|
|
29
|
+
- `g/G` - Jump to top/bottom
|
|
30
|
+
- `q` - Quit
|
|
31
|
+
|
|
32
|
+
## Development
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
npm run dev path/to/database.db # Run in dev mode
|
|
36
|
+
npm test # Run tests
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## License
|
|
40
|
+
|
|
41
|
+
MIT
|
package/bin/dbn.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --experimental-strip-types --disable-warning=ExperimentalWarning
|
|
2
|
+
|
|
3
|
+
import { main } from '../src/index.ts';
|
|
4
|
+
|
|
5
|
+
// Get command line arguments (skip node and script path)
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
// Run the application
|
|
9
|
+
main(args);
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dbn-cli",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "A lightweight terminal-based database browser",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"dbn": "./bin/dbn.ts"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "node --test --experimental-strip-types test/*.test.ts",
|
|
12
|
+
"test:watch": "node --test --watch --experimental-strip-types test/*.test.ts",
|
|
13
|
+
"test:coverage": "node --test --experimental-test-coverage --experimental-strip-types test/*.test.ts",
|
|
14
|
+
"dev": "node bin/dbp.ts"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"database",
|
|
18
|
+
"sqlite",
|
|
19
|
+
"cli",
|
|
20
|
+
"terminal",
|
|
21
|
+
"browser",
|
|
22
|
+
"tui"
|
|
23
|
+
],
|
|
24
|
+
"author": "Amio <amio.cn@gmail.com>",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=24"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"bin/",
|
|
31
|
+
"src/"
|
|
32
|
+
],
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/node": "^24.10.9"
|
|
35
|
+
}
|
|
36
|
+
,
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"string-width": "^5.0.1"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { TableInfo, ColumnSchema, QueryOptions } from '../types.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Base class for database adapters
|
|
5
|
+
* Defines the interface that all database adapters must implement
|
|
6
|
+
*/
|
|
7
|
+
export abstract class DatabaseAdapter {
|
|
8
|
+
/**
|
|
9
|
+
* Connect to the database
|
|
10
|
+
* @param path - Path to the database file or connection string
|
|
11
|
+
*/
|
|
12
|
+
abstract connect(path: string): void;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Get all tables in the database
|
|
16
|
+
* @returns Array of table information
|
|
17
|
+
*/
|
|
18
|
+
abstract getTables(): TableInfo[];
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Get schema information for a specific table
|
|
22
|
+
* @param tableName - Name of the table
|
|
23
|
+
* @returns Array of column schema information
|
|
24
|
+
*/
|
|
25
|
+
abstract getTableSchema(tableName: string): ColumnSchema[];
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get data from a specific table with pagination
|
|
29
|
+
* @param tableName - Name of the table
|
|
30
|
+
* @param options - Query options
|
|
31
|
+
* @returns Array of row objects
|
|
32
|
+
*/
|
|
33
|
+
abstract getTableData(tableName: string, options?: QueryOptions): Record<string, any>[];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Get the total row count for a specific table
|
|
37
|
+
* @param tableName - Name of the table
|
|
38
|
+
* @returns Total number of rows
|
|
39
|
+
*/
|
|
40
|
+
abstract getRowCount(tableName: string): number;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Close the database connection
|
|
44
|
+
*/
|
|
45
|
+
abstract close(): void;
|
|
46
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { DatabaseAdapter } from './base.ts';
|
|
3
|
+
import type { TableInfo, ColumnSchema, QueryOptions } from '../types.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SQLite adapter using Node.js 22+ built-in node:sqlite
|
|
7
|
+
*/
|
|
8
|
+
export class SQLiteAdapter extends DatabaseAdapter {
|
|
9
|
+
private db: DatabaseSync | null = null;
|
|
10
|
+
private path: string | null = null;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Connect to SQLite database file
|
|
14
|
+
* @param path - Path to the .db or .sqlite file
|
|
15
|
+
*/
|
|
16
|
+
connect(path: string): void {
|
|
17
|
+
try {
|
|
18
|
+
this.db = new DatabaseSync(path);
|
|
19
|
+
this.path = path;
|
|
20
|
+
// Enable WAL mode for better performance
|
|
21
|
+
this.db.exec('PRAGMA journal_mode = WAL');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
throw new Error(`Failed to open database: ${(error as Error).message}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Get all tables in the database
|
|
29
|
+
* @returns Array of table information
|
|
30
|
+
*/
|
|
31
|
+
getTables(): TableInfo[] {
|
|
32
|
+
if (!this.db) {
|
|
33
|
+
throw new Error('Database not connected');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const query = `
|
|
37
|
+
SELECT name
|
|
38
|
+
FROM sqlite_master
|
|
39
|
+
WHERE type='table' AND name NOT LIKE 'sqlite_%'
|
|
40
|
+
ORDER BY name
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
const stmt = this.db.prepare(query);
|
|
44
|
+
const tables = stmt.all() as { name: string }[];
|
|
45
|
+
|
|
46
|
+
// Get row count for each table
|
|
47
|
+
return tables.map(table => {
|
|
48
|
+
const countStmt = this.db!.prepare(`SELECT COUNT(*) as count FROM "${table.name}"`);
|
|
49
|
+
const result = countStmt.get() as { count: number };
|
|
50
|
+
return {
|
|
51
|
+
name: table.name,
|
|
52
|
+
row_count: result.count
|
|
53
|
+
};
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Get schema information for a specific table
|
|
59
|
+
* @param tableName - Name of the table
|
|
60
|
+
* @returns Array of column schema information
|
|
61
|
+
*/
|
|
62
|
+
getTableSchema(tableName: string): ColumnSchema[] {
|
|
63
|
+
if (!this.db) {
|
|
64
|
+
throw new Error('Database not connected');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const stmt = this.db.prepare(`PRAGMA table_info("${tableName}")`);
|
|
68
|
+
return stmt.all() as unknown as ColumnSchema[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Get data from a specific table with pagination
|
|
73
|
+
* @param tableName - Name of the table
|
|
74
|
+
* @param options - Query options
|
|
75
|
+
* @returns Array of row objects
|
|
76
|
+
*/
|
|
77
|
+
getTableData(tableName: string, { limit = 50, offset = 0 }: QueryOptions = {}): Record<string, any>[] {
|
|
78
|
+
if (!this.db) {
|
|
79
|
+
throw new Error('Database not connected');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const stmt = this.db.prepare(`SELECT * FROM "${tableName}" LIMIT ? OFFSET ?`);
|
|
83
|
+
return stmt.all(limit, offset) as Record<string, any>[];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get the total row count for a specific table
|
|
88
|
+
* @param tableName - Name of the table
|
|
89
|
+
* @returns Total number of rows
|
|
90
|
+
*/
|
|
91
|
+
getRowCount(tableName: string): number {
|
|
92
|
+
if (!this.db) {
|
|
93
|
+
throw new Error('Database not connected');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const stmt = this.db.prepare(`SELECT COUNT(*) as count FROM "${tableName}"`);
|
|
97
|
+
const result = stmt.get() as { count: number };
|
|
98
|
+
return result.count;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Close the database connection
|
|
103
|
+
*/
|
|
104
|
+
close(): void {
|
|
105
|
+
if (this.db) {
|
|
106
|
+
this.db.close();
|
|
107
|
+
this.db = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import { stdin, stdout, exit } from 'node:process';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import * as readline from 'node:readline';
|
|
4
|
+
import { SQLiteAdapter } from './adapter/sqlite.ts';
|
|
5
|
+
import { Screen } from './ui/screen.ts';
|
|
6
|
+
import { Renderer } from './ui/renderer.ts';
|
|
7
|
+
import { Navigator } from './ui/navigator.ts';
|
|
8
|
+
import type { KeyPress } from './types.ts';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Main application class
|
|
12
|
+
*/
|
|
13
|
+
export class DBPeek {
|
|
14
|
+
private dbPath: string;
|
|
15
|
+
private adapter: SQLiteAdapter | null = null;
|
|
16
|
+
private screen: Screen | null = null;
|
|
17
|
+
private renderer: Renderer | null = null;
|
|
18
|
+
private navigator: Navigator | null = null;
|
|
19
|
+
private isRunning: boolean = false;
|
|
20
|
+
|
|
21
|
+
constructor(dbPath: string) {
|
|
22
|
+
this.dbPath = dbPath;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Initialize the application
|
|
27
|
+
*/
|
|
28
|
+
init(): void {
|
|
29
|
+
// Validate database file
|
|
30
|
+
if (!existsSync(this.dbPath)) {
|
|
31
|
+
console.error(`Error: Database file not found: ${this.dbPath}`);
|
|
32
|
+
exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Create adapter and connect
|
|
36
|
+
try {
|
|
37
|
+
this.adapter = new SQLiteAdapter();
|
|
38
|
+
this.adapter.connect(this.dbPath);
|
|
39
|
+
} catch (error) {
|
|
40
|
+
console.error(`Error: ${(error as Error).message}`);
|
|
41
|
+
exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Initialize UI components
|
|
45
|
+
this.screen = new Screen();
|
|
46
|
+
this.renderer = new Renderer(this.screen);
|
|
47
|
+
this.navigator = new Navigator(this.adapter);
|
|
48
|
+
|
|
49
|
+
// Initialize navigator with tables
|
|
50
|
+
try {
|
|
51
|
+
this.navigator.init();
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error(`Error loading tables: ${(error as Error).message}`);
|
|
54
|
+
this.cleanup();
|
|
55
|
+
exit(1);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Start the application
|
|
61
|
+
*/
|
|
62
|
+
start(): void {
|
|
63
|
+
if (!this.screen || !this.navigator) {
|
|
64
|
+
throw new Error('Application not initialized');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
this.isRunning = true;
|
|
68
|
+
|
|
69
|
+
// Enter full screen mode
|
|
70
|
+
this.screen.enter();
|
|
71
|
+
|
|
72
|
+
// Set up keyboard input
|
|
73
|
+
this.setupInput();
|
|
74
|
+
|
|
75
|
+
// Handle screen resize
|
|
76
|
+
this.screen.on('resize', () => {
|
|
77
|
+
this.render();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Initial render
|
|
81
|
+
this.render();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Set up keyboard input handling
|
|
86
|
+
*/
|
|
87
|
+
private setupInput(): void {
|
|
88
|
+
// Enable raw mode for key-by-key input
|
|
89
|
+
stdin.setRawMode(true);
|
|
90
|
+
stdin.resume();
|
|
91
|
+
stdin.setEncoding('utf8');
|
|
92
|
+
|
|
93
|
+
// Enable keypress events
|
|
94
|
+
readline.emitKeypressEvents(stdin);
|
|
95
|
+
|
|
96
|
+
stdin.on('keypress', (str: string, key: KeyPress) => {
|
|
97
|
+
if (!this.isRunning) return;
|
|
98
|
+
|
|
99
|
+
this.handleKeypress(str, key);
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Handle keypress events
|
|
105
|
+
*/
|
|
106
|
+
private handleKeypress(str: string, key: KeyPress): void {
|
|
107
|
+
if (!this.navigator) return;
|
|
108
|
+
|
|
109
|
+
// Handle Ctrl+C
|
|
110
|
+
if (key.ctrl && key.name === 'c') {
|
|
111
|
+
this.quit();
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Handle different keys
|
|
116
|
+
switch (key.name) {
|
|
117
|
+
case 'q':
|
|
118
|
+
this.quit();
|
|
119
|
+
break;
|
|
120
|
+
|
|
121
|
+
case 'j':
|
|
122
|
+
case 'down':
|
|
123
|
+
this.navigator.moveDown();
|
|
124
|
+
this.render();
|
|
125
|
+
break;
|
|
126
|
+
|
|
127
|
+
case 'k':
|
|
128
|
+
case 'up':
|
|
129
|
+
this.navigator.moveUp();
|
|
130
|
+
this.render();
|
|
131
|
+
break;
|
|
132
|
+
|
|
133
|
+
case 'return':
|
|
134
|
+
case 'l':
|
|
135
|
+
case 'right':
|
|
136
|
+
this.navigator.enter();
|
|
137
|
+
this.render();
|
|
138
|
+
break;
|
|
139
|
+
|
|
140
|
+
case 'h':
|
|
141
|
+
case 'left':
|
|
142
|
+
case 'escape':
|
|
143
|
+
this.navigator.back();
|
|
144
|
+
this.render();
|
|
145
|
+
break;
|
|
146
|
+
|
|
147
|
+
case 'g':
|
|
148
|
+
if (key.shift) {
|
|
149
|
+
// G - jump to bottom
|
|
150
|
+
this.navigator.jumpToBottom();
|
|
151
|
+
} else {
|
|
152
|
+
// g - jump to top
|
|
153
|
+
this.navigator.jumpToTop();
|
|
154
|
+
}
|
|
155
|
+
this.render();
|
|
156
|
+
break;
|
|
157
|
+
|
|
158
|
+
case 'r':
|
|
159
|
+
// Reload current view
|
|
160
|
+
this.navigator.reload();
|
|
161
|
+
this.render();
|
|
162
|
+
break;
|
|
163
|
+
|
|
164
|
+
case 's':
|
|
165
|
+
// Toggle schema view in full screen mode
|
|
166
|
+
const currentState = this.navigator.getState();
|
|
167
|
+
if (currentState.type === 'table-detail') {
|
|
168
|
+
this.navigator.viewSchema();
|
|
169
|
+
this.render();
|
|
170
|
+
} else if (currentState.type === 'schema-view') {
|
|
171
|
+
this.navigator.back();
|
|
172
|
+
this.render();
|
|
173
|
+
}
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Render the current state
|
|
180
|
+
*/
|
|
181
|
+
private render(): void {
|
|
182
|
+
if (!this.renderer || !this.navigator) return;
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const state = this.navigator.getState();
|
|
186
|
+
this.renderer.render(state, this.dbPath);
|
|
187
|
+
} catch (error) {
|
|
188
|
+
// If rendering fails, try to show error and quit gracefully
|
|
189
|
+
this.cleanup();
|
|
190
|
+
console.error(`Render error: ${(error as Error).message}`);
|
|
191
|
+
exit(1);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Quit the application
|
|
197
|
+
*/
|
|
198
|
+
quit(): void {
|
|
199
|
+
this.isRunning = false;
|
|
200
|
+
this.cleanup();
|
|
201
|
+
exit(0);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Clean up resources
|
|
206
|
+
*/
|
|
207
|
+
cleanup(): void {
|
|
208
|
+
// Exit full screen mode
|
|
209
|
+
if (this.screen) {
|
|
210
|
+
this.screen.exit();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Close database connection
|
|
214
|
+
if (this.adapter) {
|
|
215
|
+
this.adapter.close();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Restore terminal
|
|
219
|
+
if (stdin.isTTY) {
|
|
220
|
+
stdin.setRawMode(false);
|
|
221
|
+
stdin.pause();
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Main entry point
|
|
228
|
+
*/
|
|
229
|
+
export function main(args: string[]): void {
|
|
230
|
+
const dbPath = args[0];
|
|
231
|
+
|
|
232
|
+
if (!dbPath) {
|
|
233
|
+
console.error('Usage: dbn <path-to-sqlite-db-file>');
|
|
234
|
+
console.error('');
|
|
235
|
+
console.error('Example:');
|
|
236
|
+
console.error(' dbn ./mydatabase.db');
|
|
237
|
+
exit(1);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const app = new DBPeek(dbPath);
|
|
241
|
+
|
|
242
|
+
// Handle process termination
|
|
243
|
+
process.on('SIGINT', () => app.quit());
|
|
244
|
+
process.on('SIGTERM', () => app.quit());
|
|
245
|
+
process.on('exit', () => app.cleanup());
|
|
246
|
+
|
|
247
|
+
// Initialize and start
|
|
248
|
+
app.init();
|
|
249
|
+
app.start();
|
|
250
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Type definitions for DBPeek
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Table metadata
|
|
7
|
+
*/
|
|
8
|
+
export interface TableInfo {
|
|
9
|
+
name: string;
|
|
10
|
+
row_count: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Column schema information
|
|
15
|
+
*/
|
|
16
|
+
export interface ColumnSchema {
|
|
17
|
+
cid: number;
|
|
18
|
+
name: string;
|
|
19
|
+
type: string;
|
|
20
|
+
notnull: number;
|
|
21
|
+
dflt_value: any;
|
|
22
|
+
pk: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Query options for fetching table data
|
|
27
|
+
*/
|
|
28
|
+
export interface QueryOptions {
|
|
29
|
+
limit?: number;
|
|
30
|
+
offset?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Base view state properties
|
|
35
|
+
*/
|
|
36
|
+
interface BaseViewState {
|
|
37
|
+
type: string;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Tables list view state
|
|
42
|
+
*/
|
|
43
|
+
export interface TablesViewState extends BaseViewState {
|
|
44
|
+
type: 'tables';
|
|
45
|
+
tables: TableInfo[];
|
|
46
|
+
cursor: number;
|
|
47
|
+
scrollOffset: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Table detail view state
|
|
52
|
+
*/
|
|
53
|
+
export interface TableDetailViewState extends BaseViewState {
|
|
54
|
+
type: 'table-detail';
|
|
55
|
+
tableName: string;
|
|
56
|
+
schema: ColumnSchema[];
|
|
57
|
+
data: Record<string, any>[];
|
|
58
|
+
totalRows: number;
|
|
59
|
+
dataOffset: number;
|
|
60
|
+
dataCursor: number;
|
|
61
|
+
visibleRows: number;
|
|
62
|
+
showSchema?: boolean;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Schema view state (full screen schema display)
|
|
67
|
+
*/
|
|
68
|
+
export interface SchemaViewState extends BaseViewState {
|
|
69
|
+
type: 'schema-view';
|
|
70
|
+
tableName: string;
|
|
71
|
+
schema: ColumnSchema[];
|
|
72
|
+
cursor: number;
|
|
73
|
+
scrollOffset: number;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Row detail view state
|
|
78
|
+
*/
|
|
79
|
+
export interface RowDetailViewState extends BaseViewState {
|
|
80
|
+
type: 'row-detail';
|
|
81
|
+
tableName: string;
|
|
82
|
+
row: Record<string, any>;
|
|
83
|
+
rowIndex: number;
|
|
84
|
+
schema: ColumnSchema[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Union type for all possible view states
|
|
89
|
+
*/
|
|
90
|
+
export type ViewState = TablesViewState | TableDetailViewState | SchemaViewState | RowDetailViewState;
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Screen dimensions
|
|
94
|
+
*/
|
|
95
|
+
export interface ScreenDimensions {
|
|
96
|
+
width: number;
|
|
97
|
+
height: number;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Keypress event from readline
|
|
102
|
+
*/
|
|
103
|
+
export interface KeyPress {
|
|
104
|
+
name: string;
|
|
105
|
+
ctrl: boolean;
|
|
106
|
+
shift: boolean;
|
|
107
|
+
meta: boolean;
|
|
108
|
+
sequence: string;
|
|
109
|
+
}
|