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
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
import type { DatabaseAdapter } from '../adapter/base.ts';
|
|
2
|
+
import type { ViewState, TablesViewState, TableDetailViewState, SchemaViewState, RowDetailViewState } from '../types.ts';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Navigation state manager
|
|
6
|
+
* Handles view hierarchy and cursor position
|
|
7
|
+
*/
|
|
8
|
+
export class Navigator {
|
|
9
|
+
private adapter: DatabaseAdapter;
|
|
10
|
+
private states: ViewState[] = [];
|
|
11
|
+
private currentState: ViewState | null = null;
|
|
12
|
+
|
|
13
|
+
constructor(adapter: DatabaseAdapter) {
|
|
14
|
+
this.adapter = adapter;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Initialize navigator with tables list view
|
|
19
|
+
*/
|
|
20
|
+
init(): void {
|
|
21
|
+
const tables = this.adapter.getTables();
|
|
22
|
+
this.currentState = {
|
|
23
|
+
type: 'tables',
|
|
24
|
+
tables: tables,
|
|
25
|
+
cursor: 0,
|
|
26
|
+
scrollOffset: 0
|
|
27
|
+
} as TablesViewState;
|
|
28
|
+
this.states = [this.currentState];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get current view state
|
|
33
|
+
* @returns Current view state
|
|
34
|
+
*/
|
|
35
|
+
getState(): ViewState {
|
|
36
|
+
if (!this.currentState) {
|
|
37
|
+
throw new Error('Navigator not initialized');
|
|
38
|
+
}
|
|
39
|
+
return this.currentState;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Move cursor up
|
|
44
|
+
*/
|
|
45
|
+
moveUp(): void {
|
|
46
|
+
const state = this.currentState;
|
|
47
|
+
if (!state) return;
|
|
48
|
+
|
|
49
|
+
if (state.type === 'tables') {
|
|
50
|
+
if (state.cursor > 0) {
|
|
51
|
+
state.cursor--;
|
|
52
|
+
}
|
|
53
|
+
} else if (state.type === 'table-detail') {
|
|
54
|
+
if (state.dataCursor > 0) {
|
|
55
|
+
state.dataCursor--;
|
|
56
|
+
} else if (state.dataOffset > 0) {
|
|
57
|
+
state.dataOffset--;
|
|
58
|
+
// Reload data if needed
|
|
59
|
+
this.reload();
|
|
60
|
+
}
|
|
61
|
+
} else if (state.type === 'schema-view') {
|
|
62
|
+
if (state.cursor > 0) {
|
|
63
|
+
state.cursor--;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Move cursor down
|
|
70
|
+
*/
|
|
71
|
+
moveDown(): void {
|
|
72
|
+
const state = this.currentState;
|
|
73
|
+
if (!state) return;
|
|
74
|
+
|
|
75
|
+
if (state.type === 'tables') {
|
|
76
|
+
if (state.cursor < state.tables.length - 1) {
|
|
77
|
+
state.cursor++;
|
|
78
|
+
}
|
|
79
|
+
} else if (state.type === 'table-detail') {
|
|
80
|
+
const maxCursor = Math.min(state.data.length - 1, state.visibleRows - 1);
|
|
81
|
+
if (state.dataCursor < maxCursor) {
|
|
82
|
+
state.dataCursor++;
|
|
83
|
+
} else if (state.dataOffset + state.dataCursor < state.totalRows - 1) {
|
|
84
|
+
state.dataOffset++;
|
|
85
|
+
// Reload data if needed
|
|
86
|
+
this.reload();
|
|
87
|
+
}
|
|
88
|
+
} else if (state.type === 'schema-view') {
|
|
89
|
+
if (state.cursor < state.schema.length - 1) {
|
|
90
|
+
state.cursor++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Jump to top
|
|
97
|
+
*/
|
|
98
|
+
jumpToTop(): void {
|
|
99
|
+
const state = this.currentState;
|
|
100
|
+
if (!state) return;
|
|
101
|
+
|
|
102
|
+
if (state.type === 'tables') {
|
|
103
|
+
state.cursor = 0;
|
|
104
|
+
} else if (state.type === 'table-detail') {
|
|
105
|
+
state.dataOffset = 0;
|
|
106
|
+
state.dataCursor = 0;
|
|
107
|
+
this.reload();
|
|
108
|
+
} else if (state.type === 'schema-view') {
|
|
109
|
+
state.cursor = 0;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Jump to bottom
|
|
115
|
+
*/
|
|
116
|
+
jumpToBottom(): void {
|
|
117
|
+
const state = this.currentState;
|
|
118
|
+
if (!state) return;
|
|
119
|
+
|
|
120
|
+
if (state.type === 'tables') {
|
|
121
|
+
state.cursor = state.tables.length - 1;
|
|
122
|
+
} else if (state.type === 'table-detail') {
|
|
123
|
+
const lastPageOffset = Math.max(0, state.totalRows - state.visibleRows);
|
|
124
|
+
state.dataOffset = lastPageOffset;
|
|
125
|
+
state.dataCursor = Math.min(state.visibleRows - 1, state.totalRows - 1 - lastPageOffset);
|
|
126
|
+
this.reload();
|
|
127
|
+
} else if (state.type === 'schema-view') {
|
|
128
|
+
state.cursor = state.schema.length - 1;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Toggle schema display in table detail view
|
|
134
|
+
*/
|
|
135
|
+
toggleSchema(): void {
|
|
136
|
+
const state = this.currentState;
|
|
137
|
+
if (state && state.type === 'table-detail') {
|
|
138
|
+
// Toggle showSchema flag
|
|
139
|
+
state.showSchema = !state.showSchema;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* View schema in full screen mode
|
|
145
|
+
*/
|
|
146
|
+
viewSchema(): void {
|
|
147
|
+
const state = this.currentState;
|
|
148
|
+
if (state && state.type === 'table-detail') {
|
|
149
|
+
// Create full screen schema view
|
|
150
|
+
const newState: SchemaViewState = {
|
|
151
|
+
type: 'schema-view',
|
|
152
|
+
tableName: state.tableName,
|
|
153
|
+
schema: state.schema,
|
|
154
|
+
cursor: 0,
|
|
155
|
+
scrollOffset: 0
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
this.states.push(newState);
|
|
159
|
+
this.currentState = newState;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Enter the selected item (go deeper in hierarchy)
|
|
165
|
+
*/
|
|
166
|
+
enter(): void {
|
|
167
|
+
const state = this.currentState;
|
|
168
|
+
if (!state) return;
|
|
169
|
+
|
|
170
|
+
if (state.type === 'tables') {
|
|
171
|
+
const selectedTable = state.tables[state.cursor];
|
|
172
|
+
if (!selectedTable) return;
|
|
173
|
+
|
|
174
|
+
// Load table details
|
|
175
|
+
const schema = this.adapter.getTableSchema(selectedTable.name);
|
|
176
|
+
const totalRows = selectedTable.row_count;
|
|
177
|
+
const data = this.adapter.getTableData(selectedTable.name, { limit: 100, offset: 0 });
|
|
178
|
+
|
|
179
|
+
const newState: TableDetailViewState = {
|
|
180
|
+
type: 'table-detail',
|
|
181
|
+
tableName: selectedTable.name,
|
|
182
|
+
schema: schema,
|
|
183
|
+
data: data,
|
|
184
|
+
totalRows: totalRows,
|
|
185
|
+
dataOffset: 0,
|
|
186
|
+
dataCursor: 0,
|
|
187
|
+
visibleRows: 20, // Will be updated by renderer
|
|
188
|
+
showSchema: false // Schema display toggle
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
this.states.push(newState);
|
|
192
|
+
this.currentState = newState;
|
|
193
|
+
} else if (state.type === 'table-detail') {
|
|
194
|
+
// Enter row detail view
|
|
195
|
+
if (state.data.length > 0) {
|
|
196
|
+
const selectedRow = state.data[state.dataCursor];
|
|
197
|
+
if (!selectedRow) return;
|
|
198
|
+
|
|
199
|
+
const newState: RowDetailViewState = {
|
|
200
|
+
type: 'row-detail',
|
|
201
|
+
tableName: state.tableName,
|
|
202
|
+
row: selectedRow,
|
|
203
|
+
rowIndex: state.dataOffset + state.dataCursor,
|
|
204
|
+
schema: state.schema
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
this.states.push(newState);
|
|
208
|
+
this.currentState = newState;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Go back to previous view
|
|
215
|
+
*/
|
|
216
|
+
back(): void {
|
|
217
|
+
if (this.states.length > 1) {
|
|
218
|
+
this.states.pop();
|
|
219
|
+
this.currentState = this.states[this.states.length - 1];
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Reload current view data
|
|
225
|
+
*/
|
|
226
|
+
reload(): void {
|
|
227
|
+
const state = this.currentState;
|
|
228
|
+
|
|
229
|
+
if (state && state.type === 'table-detail') {
|
|
230
|
+
// Reload table data with current offset
|
|
231
|
+
const loadOffset = Math.max(0, state.dataOffset);
|
|
232
|
+
state.data = this.adapter.getTableData(state.tableName, {
|
|
233
|
+
limit: 100,
|
|
234
|
+
offset: loadOffset
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get breadcrumb path
|
|
241
|
+
* @returns Breadcrumb path string
|
|
242
|
+
*/
|
|
243
|
+
getPath(): string {
|
|
244
|
+
const parts: string[] = [];
|
|
245
|
+
for (const state of this.states) {
|
|
246
|
+
if (state.type === 'tables') {
|
|
247
|
+
// Root level, no need to add
|
|
248
|
+
} else if (state.type === 'table-detail') {
|
|
249
|
+
parts.push(state.tableName);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return parts.length > 0 ? parts.join(' > ') : '';
|
|
253
|
+
}
|
|
254
|
+
}
|