@dbstudio/cli 0.1.7

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,206 @@
1
+ import { Database } from "bun:sqlite";
2
+ import fs from "node:fs";
3
+ import type {
4
+ ColumnInfo,
5
+ DatabaseConfig,
6
+ QueryResult,
7
+ TableInfo,
8
+ } from "@dbstudio/types";
9
+ import type { DatabaseDriver } from "./index";
10
+
11
+ export class SQLiteDriver implements DatabaseDriver {
12
+ private db: Database | null = null;
13
+ private config: DatabaseConfig;
14
+
15
+ constructor(config: DatabaseConfig) {
16
+ this.config = config;
17
+ }
18
+
19
+ async connect(): Promise<void> {
20
+ if (!this.config.filepath) {
21
+ throw new Error("SQLite requires filepath");
22
+ }
23
+
24
+ if (!fs.existsSync(this.config.filepath)) {
25
+ throw new Error(`Database file not found: ${this.config.filepath}`);
26
+ }
27
+
28
+ this.db = new Database(this.config.filepath);
29
+ }
30
+
31
+ async disconnect(): Promise<void> {
32
+ if (this.db) {
33
+ this.db.close();
34
+ this.db = null;
35
+ }
36
+ }
37
+
38
+ async query(sql: string, params?: unknown[]): Promise<QueryResult> {
39
+ if (!this.db) throw new Error("Not connected");
40
+
41
+ const start = Date.now();
42
+ const isSelect = sql.trim().toUpperCase().startsWith("SELECT");
43
+
44
+ let result: QueryResult;
45
+
46
+ if (isSelect) {
47
+ const stmt = this.db.prepare(sql);
48
+ const rows = params ? stmt.all(...(params as any[])) : stmt.all();
49
+ result = {
50
+ columns: rows.length > 0 ? Object.keys(rows[0] as object) : [],
51
+ rows: rows as Record<string, unknown>[],
52
+ rowCount: rows.length,
53
+ executionTimeMs: Date.now() - start,
54
+ };
55
+ } else {
56
+ const stmt = this.db.prepare(sql);
57
+ const info = params ? stmt.run(...(params as any[])) : stmt.run();
58
+ result = {
59
+ columns: [],
60
+ rows: [],
61
+ rowCount: 0,
62
+ affectedRows: info.changes,
63
+ executionTimeMs: Date.now() - start,
64
+ };
65
+ }
66
+
67
+ return result;
68
+ }
69
+
70
+ async getTables(): Promise<TableInfo[]> {
71
+ if (!this.db) throw new Error("Not connected");
72
+
73
+ const rows = this.db
74
+ .prepare(
75
+ `SELECT name FROM sqlite_master
76
+ WHERE type = 'table' AND name NOT LIKE 'sqlite_%'
77
+ ORDER BY name`,
78
+ )
79
+ .all() as { name: string }[];
80
+
81
+ const tables: TableInfo[] = [];
82
+ for (const row of rows) {
83
+ try {
84
+ const tableInfo = await this.getTableSchema(row.name);
85
+ tables.push(tableInfo);
86
+ } catch (error) {
87
+ console.error(`Failed to get schema for table ${row.name}:`, error);
88
+ // Continue with other tables
89
+ }
90
+ }
91
+
92
+ return tables;
93
+ }
94
+
95
+ async getTableSchema(table: string): Promise<TableInfo> {
96
+ if (!this.db) throw new Error("Not connected");
97
+
98
+ const columnsRows = this.db
99
+ .prepare(`PRAGMA table_info("${table}")`)
100
+ .all() as {
101
+ name: string;
102
+ type: string;
103
+ notnull: number;
104
+ dflt_value: string | null;
105
+ pk: number;
106
+ }[];
107
+
108
+ const columns: ColumnInfo[] = columnsRows.map((row) => ({
109
+ name: row.name,
110
+ type: row.type,
111
+ nullable: row.notnull === 0,
112
+ defaultValue: row.dflt_value ?? undefined,
113
+ isPrimaryKey: row.pk === 1,
114
+ isForeignKey: false,
115
+ }));
116
+
117
+ const countRow = this.db
118
+ .prepare(`SELECT COUNT(*) as count FROM "${table}"`)
119
+ .get() as {
120
+ count: number;
121
+ };
122
+
123
+ // Get indexes
124
+ const indexesRows = this.db
125
+ .prepare(`PRAGMA index_list("${table}")`)
126
+ .all() as {
127
+ name: string;
128
+ unique: number;
129
+ }[];
130
+
131
+ const indexes = await Promise.all(
132
+ indexesRows.map(async (idx) => {
133
+ const info = this.db!.prepare(
134
+ `PRAGMA index_info("${idx.name}")`,
135
+ ).all() as {
136
+ name: string;
137
+ }[];
138
+ return {
139
+ name: idx.name,
140
+ unique: idx.unique === 1,
141
+ columns: info.map((i) => i.name),
142
+ };
143
+ }),
144
+ );
145
+
146
+ // Get foreign keys for relations
147
+ const fkRows = this.db
148
+ .prepare(`PRAGMA foreign_key_list("${table}")`)
149
+ .all() as {
150
+ table: string;
151
+ from: string;
152
+ to: string;
153
+ }[];
154
+
155
+ const relations = fkRows.map((fk) => ({
156
+ name: `${table}_${fk.from}_fk`,
157
+ type: "belongsTo" as const,
158
+ fromTable: table,
159
+ fromColumn: fk.from,
160
+ toTable: fk.table,
161
+ toColumn: fk.to,
162
+ }));
163
+
164
+ return {
165
+ name: table,
166
+ schema: "main",
167
+ columns,
168
+ rowCount: countRow.count,
169
+ indexes,
170
+ relations,
171
+ };
172
+ }
173
+
174
+ async insertRow(
175
+ table: string,
176
+ data: Record<string, unknown>,
177
+ ): Promise<QueryResult> {
178
+ if (!this.db) throw new Error("Not connected");
179
+
180
+ const columns = Object.keys(data);
181
+ const values = Object.values(data);
182
+ const placeholders = values.map(() => "?").join(", ");
183
+ const sql = `INSERT INTO "${table}" (${columns.map((c) => `"${c}"`).join(", ")}) VALUES (${placeholders})`;
184
+
185
+ return this.query(sql, values);
186
+ }
187
+
188
+ async updateRow(
189
+ table: string,
190
+ data: Record<string, unknown>,
191
+ where: Record<string, unknown>,
192
+ ): Promise<QueryResult> {
193
+ if (!this.db) throw new Error("Not connected");
194
+
195
+ const setClauses = Object.keys(data)
196
+ .map((c) => `"${c}" = ?`)
197
+ .join(", ");
198
+ const whereClauses = Object.keys(where)
199
+ .map((c) => `"${c}" = ?`)
200
+ .join(" AND ");
201
+ const values = [...Object.values(data), ...Object.values(where)];
202
+ const sql = `UPDATE "${table}" SET ${setClauses} WHERE ${whereClauses}`;
203
+
204
+ return this.query(sql, values);
205
+ }
206
+ }
package/src/index.ts ADDED
@@ -0,0 +1,28 @@
1
+ import "dotenv/config";
2
+ import chalk from "chalk";
3
+ import { program } from "commander";
4
+ import { connectCommand } from "./commands/connect";
5
+ import { disconnectCommand } from "./commands/disconnect";
6
+ import { statusCommand } from "./commands/status";
7
+
8
+ const logo = `
9
+ ${chalk.green("╔══════════════════════════════════════╗")}
10
+ ${chalk.green("║")} ${chalk.bold.white("DBStudio")} ${chalk.gray("CLI Agent")} ${chalk.green("║")}
11
+ ${chalk.green("║")} ${chalk.gray("Connect your database to the cloud")} ${chalk.green("║")}
12
+ ${chalk.green("╚══════════════════════════════════════╝")}
13
+ `;
14
+
15
+ program
16
+ .name("dbstudio")
17
+ .description("DBStudio CLI - Connect local databases to DBStudio cloud")
18
+ .version("0.1.0")
19
+ .hook("preAction", () => {
20
+ console.log(logo);
21
+ });
22
+
23
+ // Register commands
24
+ program.addCommand(connectCommand);
25
+ program.addCommand(statusCommand);
26
+ program.addCommand(disconnectCommand);
27
+
28
+ program.parse(process.argv);
package/tsconfig.json ADDED
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "./dist",
10
+ "rootDir": "./src",
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*"],
14
+ "exclude": ["node_modules", "dist"]
15
+ }
package/tsup.config.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from "tsup";
2
+
3
+ export default defineConfig({
4
+ entry: ["src/index.ts"],
5
+ format: ["esm"],
6
+ clean: true,
7
+ dts: true,
8
+ target: "node20",
9
+ banner: { js: "#!/usr/bin/env bun" },
10
+ external: ["bun:sqlite"],
11
+ });