admindb 2.4.0 → 2.5.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/LICENSE +20 -20
- package/dist/app.js +36 -25
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.js +2 -0
- package/dist/core/errors.d.ts +1 -0
- package/dist/core/errors.js +39 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +20 -0
- package/dist/core/result.d.ts +1 -0
- package/dist/core/result.js +10 -0
- package/dist/core/router.d.ts +1 -0
- package/dist/core/router.js +37 -0
- package/dist/data/chain.js +1 -1
- package/dist/data/engine.js +1 -1
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.js +9 -31
- package/dist/db/dialects/index.d.ts +1 -0
- package/dist/db/dialects/index.js +58 -0
- package/dist/db/dialects/postgres/ddl.d.ts +1 -0
- package/dist/db/dialects/postgres/ddl.js +107 -0
- package/dist/db/dialects/postgres/dialect.d.ts +1 -0
- package/dist/db/dialects/postgres/dialect.js +28 -0
- package/dist/db/dialects/postgres/introspector.d.ts +1 -0
- package/dist/db/dialects/postgres/introspector.js +265 -0
- package/dist/db/dialects/postgres/types.d.ts +1 -0
- package/dist/db/dialects/postgres/types.js +83 -0
- package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
- package/dist/db/dialects/sqlite/ddl.js +244 -0
- package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
- package/dist/db/dialects/sqlite/dialect.js +25 -0
- package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
- package/dist/db/dialects/sqlite/introspector.js +214 -0
- package/dist/db/dialects/sqlite/types.d.ts +1 -0
- package/dist/db/dialects/sqlite/types.js +69 -0
- package/dist/db/dialects/types.d.ts +1 -0
- package/dist/db/dialects/types.js +15 -0
- package/dist/db/export.js +1 -1
- package/dist/db/index.d.ts +1 -1
- package/dist/db/index.js +1 -1
- package/dist/db/manager.d.ts +1 -1
- package/dist/db/manager.js +101 -21
- package/dist/db/postgres.d.ts +1 -1
- package/dist/db/postgres.js +20 -284
- package/dist/db/types.d.ts +1 -1
- package/dist/db/types.js +0 -4
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -1
- package/dist/modules/databases/databases.routes.d.ts +1 -0
- package/dist/modules/databases/databases.routes.js +150 -0
- package/dist/modules/databases/databases.service.d.ts +1 -0
- package/dist/modules/databases/databases.service.js +134 -0
- package/dist/modules/databases/index.d.ts +1 -0
- package/dist/{routes → modules/databases}/index.js +2 -3
- package/dist/modules/erd/erd.routes.d.ts +1 -0
- package/dist/modules/erd/erd.routes.js +32 -0
- package/dist/modules/erd/erd.service.d.ts +1 -0
- package/dist/modules/erd/erd.service.js +39 -0
- package/dist/modules/erd/index.d.ts +1 -0
- package/dist/modules/erd/index.js +18 -0
- package/dist/modules/index.d.ts +1 -0
- package/dist/modules/index.js +62 -0
- package/dist/modules/query/index.d.ts +1 -0
- package/dist/modules/query/index.js +18 -0
- package/dist/modules/query/query.routes.d.ts +1 -0
- package/dist/modules/query/query.routes.js +54 -0
- package/dist/modules/query/query.service.d.ts +1 -0
- package/dist/modules/query/query.service.js +95 -0
- package/dist/modules/rows/blob.helper.d.ts +1 -0
- package/dist/modules/rows/blob.helper.js +40 -0
- package/dist/modules/rows/display.helper.d.ts +1 -0
- package/dist/modules/rows/display.helper.js +73 -0
- package/dist/modules/rows/index.d.ts +1 -0
- package/dist/modules/rows/index.js +20 -0
- package/dist/modules/rows/rows.routes.d.ts +1 -0
- package/dist/modules/rows/rows.routes.js +555 -0
- package/dist/modules/rows/rows.service.d.ts +1 -0
- package/dist/modules/rows/rows.service.js +433 -0
- package/dist/modules/schema/index.d.ts +1 -0
- package/dist/modules/schema/index.js +18 -0
- package/dist/modules/schema/schema.routes.d.ts +1 -0
- package/dist/modules/schema/schema.routes.js +171 -0
- package/dist/modules/schema/schema.service.d.ts +1 -0
- package/dist/modules/schema/schema.service.js +58 -0
- package/dist/modules/seed/index.d.ts +1 -0
- package/dist/modules/seed/index.js +18 -0
- package/dist/modules/seed/seed.routes.d.ts +1 -0
- package/dist/modules/seed/seed.routes.js +212 -0
- package/dist/modules/seed/seed.service.d.ts +1 -0
- package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
- package/dist/modules/tables/index.d.ts +1 -0
- package/dist/modules/tables/index.js +18 -0
- package/dist/modules/tables/tables.routes.d.ts +1 -0
- package/dist/modules/tables/tables.routes.js +194 -0
- package/dist/modules/tables/tables.service.d.ts +1 -0
- package/dist/modules/tables/tables.service.js +218 -0
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +1 -3
- package/dist/utils/stream.d.ts +1 -0
- package/dist/utils/stream.js +131 -0
- package/dist/views/pages/error.hbs +10 -1
- package/dist/views/pages/home.hbs +1 -1
- package/dist/views/pages/query.hbs +1 -1
- package/dist/views/pages/seed-select.hbs +1 -252
- package/dist/views/pages/seed.hbs +1 -307
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/sidebar.hbs +1 -1
- package/docs/API.md +4 -17
- package/docs/EXAMPLES.md +579 -579
- package/docs/SECURITY.md +265 -265
- package/package.json +4 -2
- package/dist/db/introspection.d.ts +0 -1
- package/dist/db/introspection.js +0 -164
- package/dist/routes/api/erd.d.ts +0 -1
- package/dist/routes/api/erd.js +0 -45
- package/dist/routes/api/helpers.d.ts +0 -1
- package/dist/routes/api/import-export.d.ts +0 -1
- package/dist/routes/api/import-export.js +0 -84
- package/dist/routes/api/index.d.ts +0 -1
- package/dist/routes/api/index.js +0 -55
- package/dist/routes/api/query.d.ts +0 -1
- package/dist/routes/api/query.js +0 -77
- package/dist/routes/api/rows.d.ts +0 -1
- package/dist/routes/api/rows.js +0 -373
- package/dist/routes/api/seed.d.ts +0 -1
- package/dist/routes/api/seed.js +0 -180
- package/dist/routes/api/tables.d.ts +0 -1
- package/dist/routes/api/tables.js +0 -230
- package/dist/routes/databases.d.ts +0 -1
- package/dist/routes/databases.js +0 -212
- package/dist/routes/index.d.ts +0 -1
- package/dist/routes/pages.d.ts +0 -1
- package/dist/routes/pages.js +0 -540
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerTableRoutes = registerTableRoutes;
|
|
4
|
+
const router_1 = require("../../core/router");
|
|
5
|
+
const tables_service_1 = require("./tables.service");
|
|
6
|
+
function registerTableRoutes(router, ctx) {
|
|
7
|
+
router.get('/home/:db', async (req, res, next) => {
|
|
8
|
+
try {
|
|
9
|
+
const db = ctx.getContext(req);
|
|
10
|
+
const dbId = req.params.db;
|
|
11
|
+
const tablesList = await tables_service_1.TableService.listTables(db);
|
|
12
|
+
const allNames = tablesList.map((t) => t.name);
|
|
13
|
+
const { tables: names } = (0, router_1.initPageLocals)(res, db, dbId, allNames);
|
|
14
|
+
const homeData = await tables_service_1.TableService.getHomeStats(db, names);
|
|
15
|
+
res.render('pages/home', {
|
|
16
|
+
title: 'Home',
|
|
17
|
+
dbId,
|
|
18
|
+
tables: names,
|
|
19
|
+
counts: homeData.counts,
|
|
20
|
+
cols: homeData.cols,
|
|
21
|
+
totalRows: homeData.totalRows,
|
|
22
|
+
dbPath: db.path,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
next(err);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
router.get('/info/:db', async (req, res, next) => {
|
|
30
|
+
try {
|
|
31
|
+
const db = ctx.getContext(req);
|
|
32
|
+
const dbId = req.params.db;
|
|
33
|
+
res.locals.dbId = dbId;
|
|
34
|
+
const dbInfo = await tables_service_1.TableService.getDatabaseInfo(db);
|
|
35
|
+
(0, router_1.initPageLocals)(res, db, dbId, dbInfo.tables);
|
|
36
|
+
res.render('pages/info', {
|
|
37
|
+
title: 'Database Info',
|
|
38
|
+
dbId,
|
|
39
|
+
settings: dbInfo.settings,
|
|
40
|
+
dialect: dbInfo.dialect,
|
|
41
|
+
dbPath: dbInfo.dbPath,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
next(err);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
router.get('/export/:db', async (req, res, next) => {
|
|
49
|
+
try {
|
|
50
|
+
const db = ctx.getContext(req);
|
|
51
|
+
const dbId = req.params.db;
|
|
52
|
+
const data = await tables_service_1.TableService.getSqlDump(db);
|
|
53
|
+
const stamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
54
|
+
res.setHeader('Content-Type', 'application/sql; charset=utf-8');
|
|
55
|
+
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(dbId)}-${stamp}.sql"`);
|
|
56
|
+
res.send(data);
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
next(err);
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
router.get('/api/tables/:db', (0, router_1.wrap)(async (req, res) => {
|
|
63
|
+
const db = ctx.getContext(req);
|
|
64
|
+
try {
|
|
65
|
+
const tables = await tables_service_1.TableService.listTables(db);
|
|
66
|
+
(0, router_1.ok)(res, tables);
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to list tables.', 500);
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
router.get('/api/tables/:db/:table/info', (0, router_1.wrap)(async (req, res) => {
|
|
73
|
+
const db = ctx.getContext(req);
|
|
74
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
75
|
+
if (!info)
|
|
76
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
77
|
+
(0, router_1.ok)(res, info);
|
|
78
|
+
}));
|
|
79
|
+
router.get('/api/tables/:db/:table/fk-options', (0, router_1.wrap)(async (req, res) => {
|
|
80
|
+
const db = ctx.getContext(req);
|
|
81
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
82
|
+
if (!info)
|
|
83
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
84
|
+
const options = await tables_service_1.TableService.getFkOptions(db, req.params.table, info);
|
|
85
|
+
(0, router_1.ok)(res, options);
|
|
86
|
+
}));
|
|
87
|
+
router.get('/api/tables/:db/:table/ddl', (0, router_1.wrap)(async (req, res) => {
|
|
88
|
+
const db = ctx.getContext(req);
|
|
89
|
+
try {
|
|
90
|
+
const ddl = await tables_service_1.TableService.getTableDdl(db, req.params.table);
|
|
91
|
+
if (!ddl)
|
|
92
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
93
|
+
(0, router_1.ok)(res, { sql: ddl });
|
|
94
|
+
}
|
|
95
|
+
catch (err) {
|
|
96
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to fetch DDL.', 500);
|
|
97
|
+
}
|
|
98
|
+
}));
|
|
99
|
+
router.get('/api/info/:db/ddl', (0, router_1.wrap)(async (req, res) => {
|
|
100
|
+
const db = ctx.getContext(req);
|
|
101
|
+
try {
|
|
102
|
+
const dump = await tables_service_1.TableService.getSchemaDump(db);
|
|
103
|
+
(0, router_1.ok)(res, dump);
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to export schema DDL.', 500);
|
|
107
|
+
}
|
|
108
|
+
}));
|
|
109
|
+
router.post('/api/tables/:db/:table/rename', (0, router_1.wrap)(async (req, res) => {
|
|
110
|
+
const db = ctx.getContext(req);
|
|
111
|
+
const newName = String(req.body?.name ?? '').trim();
|
|
112
|
+
if (!newName)
|
|
113
|
+
return (0, router_1.fail)(res, 'New table name is required.');
|
|
114
|
+
try {
|
|
115
|
+
await tables_service_1.TableService.renameTable(db, req.params.table, newName);
|
|
116
|
+
(0, router_1.ok)(res, { message: `Table renamed to "${newName}".`, name: newName });
|
|
117
|
+
}
|
|
118
|
+
catch (err) {
|
|
119
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to rename table.');
|
|
120
|
+
}
|
|
121
|
+
}));
|
|
122
|
+
router.delete('/api/tables/:db/:table', (0, router_1.wrap)(async (req, res) => {
|
|
123
|
+
const db = ctx.getContext(req);
|
|
124
|
+
try {
|
|
125
|
+
await tables_service_1.TableService.dropTable(db, req.params.table);
|
|
126
|
+
(0, router_1.ok)(res, { message: `Table "${req.params.table}" dropped.` });
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to drop table.');
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
router.post('/api/tables/:db/bulk-drop', (0, router_1.wrap)(async (req, res) => {
|
|
133
|
+
const db = ctx.getContext(req);
|
|
134
|
+
const tables = Array.isArray(req.body?.tables)
|
|
135
|
+
? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
|
136
|
+
: [];
|
|
137
|
+
const force = Boolean(req.body?.force);
|
|
138
|
+
if (tables.length === 0)
|
|
139
|
+
return (0, router_1.fail)(res, 'No tables specified.');
|
|
140
|
+
const { dropped, failed } = await tables_service_1.TableService.bulkDropTables(db, tables, force);
|
|
141
|
+
(0, router_1.ok)(res, {
|
|
142
|
+
dropped,
|
|
143
|
+
failed,
|
|
144
|
+
message: `Dropped ${dropped.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
|
|
145
|
+
});
|
|
146
|
+
}));
|
|
147
|
+
router.post('/api/tables/:db/bulk-truncate', (0, router_1.wrap)(async (req, res) => {
|
|
148
|
+
const db = ctx.getContext(req);
|
|
149
|
+
const tables = Array.isArray(req.body?.tables)
|
|
150
|
+
? req.body.tables.filter((t) => typeof t === 'string' && t.trim().length > 0)
|
|
151
|
+
: [];
|
|
152
|
+
const force = Boolean(req.body?.force);
|
|
153
|
+
if (tables.length === 0)
|
|
154
|
+
return (0, router_1.fail)(res, 'No tables specified.');
|
|
155
|
+
const { cleared, failed } = await tables_service_1.TableService.bulkTruncateTables(db, tables, force);
|
|
156
|
+
(0, router_1.ok)(res, {
|
|
157
|
+
cleared,
|
|
158
|
+
failed,
|
|
159
|
+
message: `Cleared ${cleared.length} table(s)${failed.length ? `, ${failed.length} failed` : ''}.`,
|
|
160
|
+
});
|
|
161
|
+
}));
|
|
162
|
+
router.post('/api/tables/:db/generate', (0, router_1.wrap)(async (req, res) => {
|
|
163
|
+
const name = String(req.body?.name ?? '').trim();
|
|
164
|
+
const columns = (req.body?.columns ?? []);
|
|
165
|
+
if (!name)
|
|
166
|
+
return (0, router_1.fail)(res, 'Table name is required.');
|
|
167
|
+
if (!Array.isArray(columns) || columns.length === 0)
|
|
168
|
+
return (0, router_1.fail)(res, 'At least one column is required.');
|
|
169
|
+
try {
|
|
170
|
+
(0, router_1.ok)(res, { sql: tables_service_1.TableService.generateCreateTableSql(name, columns) });
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
(0, router_1.fail)(res, err.message);
|
|
174
|
+
}
|
|
175
|
+
}));
|
|
176
|
+
const createTableHandler = (0, router_1.wrap)(async (req, res) => {
|
|
177
|
+
const db = ctx.getContext(req);
|
|
178
|
+
const name = String(req.body?.name ?? '').trim();
|
|
179
|
+
const columns = (req.body?.columns ?? []);
|
|
180
|
+
if (!name)
|
|
181
|
+
return (0, router_1.fail)(res, 'Table name is required.');
|
|
182
|
+
if (!Array.isArray(columns) || columns.length === 0)
|
|
183
|
+
return (0, router_1.fail)(res, 'At least one column is required.');
|
|
184
|
+
try {
|
|
185
|
+
await tables_service_1.TableService.createTable(db, name, columns);
|
|
186
|
+
(0, router_1.ok)(res, { message: `Table "${name}" created.`, name }, 201);
|
|
187
|
+
}
|
|
188
|
+
catch (err) {
|
|
189
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to create table.');
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
router.post('/api/tables/:db', createTableHandler);
|
|
193
|
+
router.post('/api/tables/:db/create', createTableHandler);
|
|
194
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {TableInfoData} from '../../db/index';import type {DatabaseContext} from '../../core/context';import{type ColumnDef} from '../../sql/generator';export interface FkOption{value:unknown;label:string;}export interface BulkOperationResult{succeeded:string[];failed:{table:string;error:string;}[];message:string;}export interface TableHomeStat{name:string;count:number;cols:number;}export interface HomeSummaryData{stats:TableHomeStat[];counts:Record<string,number>;cols:Record<string,number>;totalRows:number;savedQueriesCount:number;}export interface DatabaseInfoResult{settings:Record<string,unknown>;tables:string[];dialect:string;dbPath:string;}export declare class TableService{static listTables(ctx:DatabaseContext):Promise<import("../../db/types").TableListItem[]>;static getTableInfo(ctx:DatabaseContext,table:string):Promise<TableInfoData|null>;static requireTable(ctx:DatabaseContext,table:string):Promise<TableInfoData>;static getFkOptions(ctx:DatabaseContext,table:string,info?:TableInfoData):Promise<Record<string,FkOption[]>>;static getTableDdl(ctx:DatabaseContext,table:string):Promise<string|null>;static renameTable(ctx:DatabaseContext,table:string,newName:string):Promise<void>;static dropTable(ctx:DatabaseContext,table:string):Promise<void>;static bulkDropTables(ctx:DatabaseContext,tables:string[],force?:boolean):Promise<{dropped:string[];failed:{table:string;error:string;}[];}>;static bulkTruncateTables(ctx:DatabaseContext,tables:string[],force?:boolean):Promise<{cleared:string[];failed:{table:string;error:string;}[];}>;static getIncomingForeignKeyCounts(ctx:DatabaseContext,rawRows:Record<string,unknown>[],refColumns:{table:string; from :string;to:string;}[]):Promise<Map<string,Map<string,number>>>;static getDatabaseInfo(ctx:DatabaseContext):Promise<DatabaseInfoResult>;static getSchemaDump(ctx:DatabaseContext):Promise<string>;static getSqlDump(ctx:DatabaseContext):Promise<string>;static getHomeStats(ctx:DatabaseContext,tableNames:string[]):Promise<HomeSummaryData>;static generateCreateTableSql(name:string,columns:ColumnDef[]):string;static createTable(ctx:DatabaseContext,name:string,columns:ColumnDef[]):Promise<void>;}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TableService = void 0;
|
|
4
|
+
const generator_1 = require("../../sql/generator");
|
|
5
|
+
const export_1 = require("../../db/export");
|
|
6
|
+
class TableService {
|
|
7
|
+
static async listTables(ctx) {
|
|
8
|
+
const r = await ctx.db.listTables();
|
|
9
|
+
if (!r.success) {
|
|
10
|
+
throw new Error(r.error ?? 'Failed to list tables.');
|
|
11
|
+
}
|
|
12
|
+
return r.data ?? [];
|
|
13
|
+
}
|
|
14
|
+
static async getTableInfo(ctx, table) {
|
|
15
|
+
const info = await ctx.db.getTableInfo(table);
|
|
16
|
+
if (!info.success || !info.data?.columns?.length) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return info.data;
|
|
20
|
+
}
|
|
21
|
+
static async requireTable(ctx, table) {
|
|
22
|
+
const info = await TableService.getTableInfo(ctx, table);
|
|
23
|
+
if (!info) {
|
|
24
|
+
throw new Error(`Table "${table}" does not exist.`);
|
|
25
|
+
}
|
|
26
|
+
return info;
|
|
27
|
+
}
|
|
28
|
+
static async getFkOptions(ctx, table, info) {
|
|
29
|
+
const tableInfo = info ?? (await TableService.requireTable(ctx, table));
|
|
30
|
+
const options = {};
|
|
31
|
+
for (const fk of tableInfo.foreignKeys) {
|
|
32
|
+
try {
|
|
33
|
+
const refInfo = await TableService.getTableInfo(ctx, fk.table);
|
|
34
|
+
if (!refInfo)
|
|
35
|
+
continue;
|
|
36
|
+
const refCol = fk.to || refInfo.primaryKey[0] || refInfo.columns[0]?.name;
|
|
37
|
+
const pk = refInfo.primaryKey[0] ?? refInfo.columns[0]?.name;
|
|
38
|
+
const labelCol = refInfo.columns.find((c) => /name|title|label|username|email/i.test(c.name))?.name ?? pk;
|
|
39
|
+
const rows = await ctx.db.getRows(fk.table, { limit: 100, orderBy: labelCol });
|
|
40
|
+
if (rows.success && rows.data) {
|
|
41
|
+
options[fk.from] = rows.data.map((r) => ({
|
|
42
|
+
value: r[refCol],
|
|
43
|
+
label: r[labelCol] != null ? `${r[labelCol]} (${r[refCol]})` : String(r[refCol]),
|
|
44
|
+
}));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return options;
|
|
51
|
+
}
|
|
52
|
+
static async getTableDdl(ctx, table) {
|
|
53
|
+
const r = await ctx.db.getCreateStatement(table);
|
|
54
|
+
if (!r.success) {
|
|
55
|
+
throw new Error(r.error ?? 'Failed to fetch DDL.');
|
|
56
|
+
}
|
|
57
|
+
return r.data ?? null;
|
|
58
|
+
}
|
|
59
|
+
static async renameTable(ctx, table, newName) {
|
|
60
|
+
const trimmed = String(newName ?? '').trim();
|
|
61
|
+
if (!trimmed)
|
|
62
|
+
throw new Error('New table name is required.');
|
|
63
|
+
const r = await ctx.db.renameTable(table, trimmed);
|
|
64
|
+
if (!r.success)
|
|
65
|
+
throw new Error(r.error ?? 'Failed to rename table.');
|
|
66
|
+
}
|
|
67
|
+
static async dropTable(ctx, table) {
|
|
68
|
+
const r = await ctx.db.dropTable(table);
|
|
69
|
+
if (!r.success)
|
|
70
|
+
throw new Error(r.error ?? 'Failed to drop table.');
|
|
71
|
+
}
|
|
72
|
+
static async bulkDropTables(ctx, tables, force = false) {
|
|
73
|
+
if (tables.length === 0)
|
|
74
|
+
throw new Error('No tables specified.');
|
|
75
|
+
const dropped = [];
|
|
76
|
+
const failed = [];
|
|
77
|
+
const { db, dialect } = ctx;
|
|
78
|
+
if (force && dialect.ddl.setForeignKeys) {
|
|
79
|
+
await db.run(dialect.ddl.setForeignKeys(false));
|
|
80
|
+
}
|
|
81
|
+
for (const table of tables) {
|
|
82
|
+
try {
|
|
83
|
+
const dropSql = dialect.ddl.dropTable(table, { cascade: force, ifExists: force });
|
|
84
|
+
const r = await db.run(dropSql);
|
|
85
|
+
if (!r.success)
|
|
86
|
+
throw new Error(r.error ?? 'Failed to drop table.');
|
|
87
|
+
dropped.push(table);
|
|
88
|
+
}
|
|
89
|
+
catch (err) {
|
|
90
|
+
failed.push({ table, error: err.message });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (force && dialect.ddl.setForeignKeys) {
|
|
94
|
+
await db.run(dialect.ddl.setForeignKeys(true));
|
|
95
|
+
}
|
|
96
|
+
return { dropped, failed };
|
|
97
|
+
}
|
|
98
|
+
static async bulkTruncateTables(ctx, tables, force = false) {
|
|
99
|
+
if (tables.length === 0)
|
|
100
|
+
throw new Error('No tables specified.');
|
|
101
|
+
const cleared = [];
|
|
102
|
+
const failed = [];
|
|
103
|
+
const { db, dialect } = ctx;
|
|
104
|
+
if (force && dialect.ddl.setForeignKeys) {
|
|
105
|
+
await db.run(dialect.ddl.setForeignKeys(false));
|
|
106
|
+
}
|
|
107
|
+
for (const table of tables) {
|
|
108
|
+
try {
|
|
109
|
+
const truncateSql = dialect.ddl.truncateTable(table, { cascade: force, restartIdentity: force });
|
|
110
|
+
const r = await db.run(truncateSql);
|
|
111
|
+
if (!r.success)
|
|
112
|
+
throw new Error(r.error ?? 'Failed to clear table.');
|
|
113
|
+
cleared.push(table);
|
|
114
|
+
}
|
|
115
|
+
catch (err) {
|
|
116
|
+
failed.push({ table, error: err.message });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (force && dialect.ddl.setForeignKeys) {
|
|
120
|
+
await db.run(dialect.ddl.setForeignKeys(true));
|
|
121
|
+
}
|
|
122
|
+
return { cleared, failed };
|
|
123
|
+
}
|
|
124
|
+
static async getIncomingForeignKeyCounts(ctx, rawRows, refColumns) {
|
|
125
|
+
const { db } = ctx;
|
|
126
|
+
const countEntries = await Promise.all(refColumns.map(async (col) => {
|
|
127
|
+
const values = rawRows
|
|
128
|
+
.map((r) => r[col.to])
|
|
129
|
+
.filter((v) => v !== null && v !== undefined);
|
|
130
|
+
if (!values.length)
|
|
131
|
+
return [col.table, new Map()];
|
|
132
|
+
const placeholders = values.map(() => '?').join(', ');
|
|
133
|
+
const cR = await db.all(`SELECT ${(0, generator_1.quoteIdentifier)(col.from)} AS fk, COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(col.table)} WHERE ${(0, generator_1.quoteIdentifier)(col.from)} IN (${placeholders}) GROUP BY ${(0, generator_1.quoteIdentifier)(col.from)}`, values);
|
|
134
|
+
const map = new Map();
|
|
135
|
+
for (const row of (cR.data ?? [])) {
|
|
136
|
+
map.set(String(row.fk), Number(row.c));
|
|
137
|
+
}
|
|
138
|
+
return [col.table, map];
|
|
139
|
+
}));
|
|
140
|
+
return new Map(countEntries);
|
|
141
|
+
}
|
|
142
|
+
static async getDatabaseInfo(ctx) {
|
|
143
|
+
const all = await ctx.db.listTables();
|
|
144
|
+
const tables = (all.data ?? []).map((t) => t.name);
|
|
145
|
+
const settingsRes = await ctx.db.getSettings();
|
|
146
|
+
return {
|
|
147
|
+
settings: settingsRes.success && settingsRes.data ? settingsRes.data : {},
|
|
148
|
+
tables,
|
|
149
|
+
dialect: ctx.dialect.name,
|
|
150
|
+
dbPath: ctx.path,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
static async getSchemaDump(ctx) {
|
|
154
|
+
const dump = await (0, export_1.generateSchemaDump)(ctx.db);
|
|
155
|
+
if (!dump.success || !dump.data) {
|
|
156
|
+
throw new Error(dump.error ?? 'Failed to generate schema dump.');
|
|
157
|
+
}
|
|
158
|
+
return dump.data;
|
|
159
|
+
}
|
|
160
|
+
static async getSqlDump(ctx) {
|
|
161
|
+
const dump = await (0, export_1.generateSqlDump)(ctx.db);
|
|
162
|
+
if (!dump.success || !dump.data) {
|
|
163
|
+
throw new Error(dump.error ?? 'Failed to generate SQL dump.');
|
|
164
|
+
}
|
|
165
|
+
return dump.data;
|
|
166
|
+
}
|
|
167
|
+
static async getHomeStats(ctx, tableNames) {
|
|
168
|
+
const { db, dialect } = ctx;
|
|
169
|
+
let colCountsMap = {};
|
|
170
|
+
if (dialect.introspector.getColumnCountMap) {
|
|
171
|
+
const colCountRes = await dialect.introspector.getColumnCountMap(db);
|
|
172
|
+
if (colCountRes.success && colCountRes.data) {
|
|
173
|
+
colCountsMap = colCountRes.data;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const stats = await Promise.all(tableNames.map(async (name) => {
|
|
177
|
+
const countR = await db.getRowCount(name);
|
|
178
|
+
const cols = colCountsMap[name] !== undefined
|
|
179
|
+
? colCountsMap[name]
|
|
180
|
+
: (await db.getTableInfo(name)).data?.columns.length ?? 0;
|
|
181
|
+
return {
|
|
182
|
+
name,
|
|
183
|
+
count: countR.success ? countR.data : 0,
|
|
184
|
+
cols,
|
|
185
|
+
};
|
|
186
|
+
}));
|
|
187
|
+
const counts = {};
|
|
188
|
+
const cols = {};
|
|
189
|
+
let totalRows = 0;
|
|
190
|
+
for (const s of stats) {
|
|
191
|
+
counts[s.name] = s.count;
|
|
192
|
+
cols[s.name] = s.cols;
|
|
193
|
+
totalRows += s.count;
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
stats,
|
|
197
|
+
counts,
|
|
198
|
+
cols,
|
|
199
|
+
totalRows,
|
|
200
|
+
savedQueriesCount: 0,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
static generateCreateTableSql(name, columns) {
|
|
204
|
+
const trimmed = String(name ?? '').trim();
|
|
205
|
+
if (!trimmed)
|
|
206
|
+
throw new Error('Table name is required.');
|
|
207
|
+
if (!Array.isArray(columns) || columns.length === 0)
|
|
208
|
+
throw new Error('At least one column is required.');
|
|
209
|
+
return (0, generator_1.generateCreateTable)(trimmed, columns);
|
|
210
|
+
}
|
|
211
|
+
static async createTable(ctx, name, columns) {
|
|
212
|
+
const sql = TableService.generateCreateTableSql(name, columns);
|
|
213
|
+
const r = await ctx.db.execResult(sql);
|
|
214
|
+
if (!r.success)
|
|
215
|
+
throw new Error(r.error ?? 'Failed to create table.');
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
exports.TableService = TableService;
|