admindb 2.1.1 → 2.3.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 +368 -335
- package/dist/app.js +227 -1
- package/dist/auth/config.d.ts +1 -1
- package/dist/auth/config.js +109 -1
- package/dist/auth/crypto.js +98 -1
- package/dist/auth/index.js +21 -1
- package/dist/auth/middleware.js +78 -1
- package/dist/auth/routes.js +64 -1
- package/dist/auth/types.js +7 -1
- package/dist/cli/args.d.ts +1 -1
- package/dist/cli/args.js +309 -1
- package/dist/cli/config.js +118 -1
- package/dist/cli/index.js +20 -1
- package/dist/cli/runner.js +208 -1
- package/dist/cli.js +4 -1
- package/dist/data/chain.d.ts +1 -0
- package/dist/data/chain.js +130 -0
- package/dist/data/datasets.d.ts +1 -1
- package/dist/data/datasets.js +171 -1
- package/dist/data/detector.d.ts +1 -1
- package/dist/data/detector.js +535 -1
- package/dist/data/engine.d.ts +1 -1
- package/dist/data/engine.js +586 -1
- package/dist/data/index.d.ts +1 -1
- package/dist/data/index.js +30 -1
- package/dist/data/prng.d.ts +1 -0
- package/dist/data/prng.js +73 -0
- package/dist/data/registry.d.ts +1 -0
- package/dist/data/registry.js +805 -0
- package/dist/data/schema-graph.d.ts +1 -0
- package/dist/data/schema-graph.js +210 -0
- package/dist/data/strategies.d.ts +1 -1
- package/dist/data/strategies.js +100 -1
- package/dist/data/templates.d.ts +1 -0
- package/dist/data/templates.js +134 -0
- package/dist/data/types.d.ts +1 -1
- package/dist/data/types.js +5 -1
- package/dist/data/validator.d.ts +1 -0
- package/dist/data/validator.js +160 -0
- package/dist/db/database.d.ts +1 -1
- package/dist/db/database.js +472 -1
- package/dist/db/export.d.ts +1 -1
- package/dist/db/export.js +67 -1
- package/dist/db/filters.d.ts +1 -1
- package/dist/db/filters.js +119 -1
- package/dist/db/index.js +24 -1
- package/dist/db/introspection.js +164 -1
- package/dist/db/manager.js +327 -1
- package/dist/db/migrations.js +100 -1
- package/dist/db/postgres.d.ts +1 -1
- package/dist/db/postgres.js +687 -1
- package/dist/db/types.d.ts +1 -1
- package/dist/db/types.js +6 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +52 -1
- package/dist/public/css/app.css +1 -1
- package/dist/public/js/api.js +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/common.js +1 -0
- package/dist/public/js/databases.js +1 -1
- package/dist/public/js/designer.js +1 -1
- package/dist/public/js/forms.js +1 -1
- package/dist/public/js/home.js +1 -0
- package/dist/public/js/inspector.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/schema.js +1 -1
- package/dist/public/js/seed.js +1 -1
- package/dist/routes/api/erd.d.ts +1 -0
- package/dist/routes/api/erd.js +45 -0
- package/dist/routes/api/helpers.d.ts +1 -1
- package/dist/routes/api/helpers.js +213 -1
- package/dist/routes/api/import-export.js +82 -1
- package/dist/routes/api/index.js +48 -1
- package/dist/routes/api/query.js +74 -1
- package/dist/routes/api/rows.js +358 -1
- package/dist/routes/api/seed.js +167 -1
- package/dist/routes/api/tables.js +216 -1
- package/dist/routes/databases.js +210 -1
- package/dist/routes/index.js +19 -1
- package/dist/routes/pages.js +446 -1
- package/dist/serverless.js +149 -1
- package/dist/sql/classifier.js +24 -1
- package/dist/sql/error-analyzer.d.ts +1 -0
- package/dist/sql/error-analyzer.js +550 -0
- package/dist/sql/generator.d.ts +1 -1
- package/dist/sql/generator.js +245 -1
- package/dist/sql/index.js +18 -1
- package/dist/types/api.d.ts +1 -1
- package/dist/types/api.js +2 -1
- package/dist/utils/colors.js +54 -1
- package/dist/utils/common.d.ts +1 -1
- package/dist/utils/common.js +185 -1
- package/dist/utils/csv.d.ts +1 -1
- package/dist/utils/csv.js +61 -1
- package/dist/utils/datatype.js +167 -1
- package/dist/utils/icons.js +81 -1
- package/dist/utils/index.js +20 -1
- package/dist/utils/logger.js +36 -1
- package/dist/views/layouts/main.hbs +1 -74
- package/dist/views/pages/databases.hbs +1 -233
- package/dist/views/pages/designer.hbs +1 -89
- package/dist/views/pages/erd.hbs +1 -0
- package/dist/views/pages/error.hbs +1 -10
- package/dist/views/pages/form.hbs +1 -59
- package/dist/views/pages/home.hbs +1 -124
- package/dist/views/pages/info.hbs +1 -0
- package/dist/views/pages/login.hbs +1 -98
- package/dist/views/pages/query.hbs +1 -87
- package/dist/views/pages/schema.hbs +1 -298
- package/dist/views/pages/seed-select.hbs +251 -0
- package/dist/views/pages/seed.hbs +273 -167
- package/dist/views/pages/table.hbs +1 -327
- package/dist/views/partials/navbar.hbs +1 -83
- package/dist/views/partials/sidebar.hbs +1 -117
- package/docs/API.md +149 -144
- package/docs/EXAMPLES.md +579 -576
- package/docs/SECURITY.md +265 -265
- package/package.json +79 -78
- package/dist/public/js/icons.js +0 -1
|
@@ -1 +1,213 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MAX_BULK_ROWS = exports.wrap = exports.fail = exports.ok = void 0;
|
|
4
|
+
exports.requireTable = requireTable;
|
|
5
|
+
exports.buildFields = buildFields;
|
|
6
|
+
exports.buildUpdateFields = buildUpdateFields;
|
|
7
|
+
exports.pkWhere = pkWhere;
|
|
8
|
+
exports.parseSeedRequest = parseSeedRequest;
|
|
9
|
+
exports.parseChainSeedRequest = parseChainSeedRequest;
|
|
10
|
+
exports.parseUnifiedGenerationPlan = parseUnifiedGenerationPlan;
|
|
11
|
+
exports.resolvePkRows = resolvePkRows;
|
|
12
|
+
exports.computeBulkImpact = computeBulkImpact;
|
|
13
|
+
const common_1 = require("../../utils/common");
|
|
14
|
+
const index_1 = require("../../data/index");
|
|
15
|
+
const generator_1 = require("../../sql/generator");
|
|
16
|
+
const ok = (res, data, status = 200) => res.status(status).json({ success: true, data });
|
|
17
|
+
exports.ok = ok;
|
|
18
|
+
const fail = (res, error, status = 400, details) => res.status(status).json({ success: false, error, ...(details ? { details } : {}) });
|
|
19
|
+
exports.fail = fail;
|
|
20
|
+
const wrap = (fn) => async (req, res) => {
|
|
21
|
+
try {
|
|
22
|
+
await fn(req, res);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
(0, exports.fail)(res, (0, common_1.errorMessage)(err), 500);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
exports.wrap = wrap;
|
|
29
|
+
async function requireTable(db, table) {
|
|
30
|
+
const info = await db.getTableInfo(table);
|
|
31
|
+
return info.success && info.data?.columns?.length ? info.data : null;
|
|
32
|
+
}
|
|
33
|
+
function buildFields(info, values, opts = {}) {
|
|
34
|
+
const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
|
|
35
|
+
const pkSet = new Set(info.primaryKey);
|
|
36
|
+
const fields = [];
|
|
37
|
+
for (const [col, raw] of Object.entries(values ?? {})) {
|
|
38
|
+
if (!typeMap.has(col) || (opts.excludePk && pkSet.has(col)))
|
|
39
|
+
continue;
|
|
40
|
+
const val = (0, common_1.coerceFormValue)(raw, typeMap.get(col));
|
|
41
|
+
if (val !== null)
|
|
42
|
+
fields.push({ column: col, value: val });
|
|
43
|
+
}
|
|
44
|
+
return fields;
|
|
45
|
+
}
|
|
46
|
+
function buildUpdateFields(info, values, nulls) {
|
|
47
|
+
const fields = buildFields(info, values, { excludePk: true });
|
|
48
|
+
const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
|
|
49
|
+
const pkSet = new Set(info.primaryKey);
|
|
50
|
+
for (const col of nulls) {
|
|
51
|
+
if (!pkSet.has(col) && typeMap.has(col) && !fields.some((f) => f.column === col)) {
|
|
52
|
+
fields.push({ column: col, value: null });
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return fields;
|
|
56
|
+
}
|
|
57
|
+
function pkWhere(info, encodedId) {
|
|
58
|
+
const vals = (0, common_1.decodePk)(encodedId);
|
|
59
|
+
if (!info.primaryKey.length) {
|
|
60
|
+
if (vals.length === 1 && vals[0] !== '') {
|
|
61
|
+
const num = Number(vals[0]);
|
|
62
|
+
return [{ column: '_rowid_', value: Number.isFinite(num) ? num : vals[0] }];
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
if (vals.length !== info.primaryKey.length)
|
|
67
|
+
return null;
|
|
68
|
+
const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
|
|
69
|
+
return info.primaryKey.map((col, i) => ({ column: col, value: (0, common_1.coerceFormValue)(vals[i], typeMap.get(col)) }));
|
|
70
|
+
}
|
|
71
|
+
exports.MAX_BULK_ROWS = 1000;
|
|
72
|
+
function parseSeedRequest(body) {
|
|
73
|
+
const b = (body ?? {});
|
|
74
|
+
const count = Math.max(1, Math.min(index_1.MAX_SEED_ROWS, Number.parseInt(String(b.count ?? '10'), 10) || 10));
|
|
75
|
+
const truncate = Boolean(b.truncate);
|
|
76
|
+
const seed = b.seed != null && b.seed !== '' ? Number(b.seed) : null;
|
|
77
|
+
const plan = {};
|
|
78
|
+
if (b.plan && typeof b.plan === 'object') {
|
|
79
|
+
for (const [k, v] of Object.entries(b.plan)) {
|
|
80
|
+
const p = (0, index_1.sanitizeColumnPlan)(v);
|
|
81
|
+
if (p)
|
|
82
|
+
plan[k] = p;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { count, plan, truncate, seed };
|
|
86
|
+
}
|
|
87
|
+
function parseChainSeedRequest(body) {
|
|
88
|
+
const b = (body ?? {});
|
|
89
|
+
const scope = b.scope === 'ancestors' || b.scope === 'descendants' || b.scope === 'all' || b.scope === 'single'
|
|
90
|
+
? b.scope
|
|
91
|
+
: 'chain';
|
|
92
|
+
const truncate = Boolean(b.truncate);
|
|
93
|
+
const seed = b.seed != null && b.seed !== '' ? Number(b.seed) : null;
|
|
94
|
+
const counts = {};
|
|
95
|
+
if (b.counts && typeof b.counts === 'object') {
|
|
96
|
+
for (const [table, rawN] of Object.entries(b.counts)) {
|
|
97
|
+
const n = Number.parseInt(String(rawN), 10);
|
|
98
|
+
if (Number.isFinite(n) && n > 0)
|
|
99
|
+
counts[table] = Math.min(2000, n);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
const modes = {};
|
|
103
|
+
if (b.modes && typeof b.modes === 'object') {
|
|
104
|
+
for (const [table, rawM] of Object.entries(b.modes)) {
|
|
105
|
+
const m = String(rawM);
|
|
106
|
+
if (m === 'generate' || m === 'use_existing' || m === 'generate_if_empty' || m === 'skip') {
|
|
107
|
+
modes[table] = m;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const plans = {};
|
|
112
|
+
if (b.plans && typeof b.plans === 'object') {
|
|
113
|
+
for (const [table, tablePlans] of Object.entries(b.plans)) {
|
|
114
|
+
if (tablePlans && typeof tablePlans === 'object') {
|
|
115
|
+
const pMap = {};
|
|
116
|
+
for (const [col, v] of Object.entries(tablePlans)) {
|
|
117
|
+
const p = (0, index_1.sanitizeColumnPlan)(v);
|
|
118
|
+
if (p)
|
|
119
|
+
pMap[col] = p;
|
|
120
|
+
}
|
|
121
|
+
plans[table] = pMap;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return { scope, counts, plans, truncate, modes, seed };
|
|
126
|
+
}
|
|
127
|
+
function parseUnifiedGenerationPlan(body) {
|
|
128
|
+
const b = (body ?? {});
|
|
129
|
+
const rawPlan = (b.plan && typeof b.plan === 'object' ? b.plan : b);
|
|
130
|
+
const tables = {};
|
|
131
|
+
if (rawPlan.tables && typeof rawPlan.tables === 'object') {
|
|
132
|
+
for (const [tableName, rawSpec] of Object.entries(rawPlan.tables)) {
|
|
133
|
+
if (!rawSpec || typeof rawSpec !== 'object')
|
|
134
|
+
continue;
|
|
135
|
+
const spec = rawSpec;
|
|
136
|
+
const rawMode = String(spec.mode || 'generate');
|
|
137
|
+
const mode = rawMode === 'use_existing' || rawMode === 'generate_if_empty' || rawMode === 'skip' ? rawMode : 'generate';
|
|
138
|
+
const rows = Math.max(1, Math.min(index_1.MAX_SEED_ROWS, Number.parseInt(String(spec.rows ?? '10'), 10) || 10));
|
|
139
|
+
const columns = {};
|
|
140
|
+
if (spec.columns && typeof spec.columns === 'object') {
|
|
141
|
+
for (const [colName, rawColPlan] of Object.entries(spec.columns)) {
|
|
142
|
+
const sanitized = (0, index_1.sanitizeColumnPlan)(rawColPlan);
|
|
143
|
+
if (sanitized)
|
|
144
|
+
columns[colName] = sanitized;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
tables[tableName] = {
|
|
148
|
+
mode,
|
|
149
|
+
rows,
|
|
150
|
+
columns,
|
|
151
|
+
truncate: Boolean(spec.truncate),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const rawSeed = b.seed !== undefined ? b.seed : rawPlan.options?.seed;
|
|
156
|
+
const seed = rawSeed != null && rawSeed !== '' ? Number(rawSeed) : null;
|
|
157
|
+
return {
|
|
158
|
+
name: typeof rawPlan.name === 'string' ? rawPlan.name : undefined,
|
|
159
|
+
description: typeof rawPlan.description === 'string' ? rawPlan.description : undefined,
|
|
160
|
+
rootTable: typeof rawPlan.rootTable === 'string' ? rawPlan.rootTable : undefined,
|
|
161
|
+
scope: rawPlan.scope || 'chain',
|
|
162
|
+
tables,
|
|
163
|
+
relationships: Array.isArray(rawPlan.relationships) ? rawPlan.relationships : undefined,
|
|
164
|
+
options: {
|
|
165
|
+
seed: Number.isFinite(seed) ? seed : null,
|
|
166
|
+
transaction: rawPlan.options ? Boolean(rawPlan.options.transaction ?? true) : true,
|
|
167
|
+
rollbackOnError: rawPlan.options ? Boolean(rawPlan.options.rollbackOnError ?? true) : true,
|
|
168
|
+
truncateAll: Boolean(b.truncate || rawPlan.options?.truncateAll),
|
|
169
|
+
batchSize: Number(rawPlan.options?.batchSize ?? 500) || 500,
|
|
170
|
+
},
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function resolvePkRows(info, ids) {
|
|
174
|
+
const list = Array.isArray(ids) ? ids : ids == null ? [] : [ids];
|
|
175
|
+
const out = [];
|
|
176
|
+
for (const id of list) {
|
|
177
|
+
const where = pkWhere(info, String(id));
|
|
178
|
+
if (where)
|
|
179
|
+
out.push(where);
|
|
180
|
+
}
|
|
181
|
+
return out.length ? out : null;
|
|
182
|
+
}
|
|
183
|
+
async function computeBulkImpact(db, info, wheres) {
|
|
184
|
+
const [rowsR, refInfoR] = await Promise.all([
|
|
185
|
+
db.getRowsByPks(info.table, wheres),
|
|
186
|
+
db.getReferencingTables(info.table),
|
|
187
|
+
]);
|
|
188
|
+
const rows = (rowsR.data ?? []);
|
|
189
|
+
const referencing = refInfoR.data ?? [];
|
|
190
|
+
if (!rows.length || !referencing.length)
|
|
191
|
+
return { references: [], total: 0 };
|
|
192
|
+
const queries = referencing.flatMap((item) => item.refs.map(async (ref) => {
|
|
193
|
+
const targetCol = ref.to || info.primaryKey[0];
|
|
194
|
+
if (!targetCol)
|
|
195
|
+
return null;
|
|
196
|
+
const values = Array.from(new Set(rows.map((r) => r[targetCol]).filter((v) => v != null)));
|
|
197
|
+
if (!values.length)
|
|
198
|
+
return null;
|
|
199
|
+
try {
|
|
200
|
+
const placeholders = values.map(() => '?').join(', ');
|
|
201
|
+
const sql = `SELECT COUNT(*) AS c FROM ${(0, generator_1.quoteIdentifier)(item.table)} WHERE ${(0, generator_1.quoteIdentifier)(ref.from)} IN (${placeholders})`;
|
|
202
|
+
const countR = await db.all(sql, values);
|
|
203
|
+
const count = countR.success && countR.data?.[0] ? Number(countR.data[0].c ?? 0) : 0;
|
|
204
|
+
return count > 0 ? { table: item.table, from: ref.from, to: targetCol, count } : null;
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}));
|
|
210
|
+
const results = (await Promise.all(queries)).filter((r) => r !== null);
|
|
211
|
+
const total = results.reduce((acc, r) => acc + r.count, 0);
|
|
212
|
+
return { references: results, total };
|
|
213
|
+
}
|
|
@@ -1 +1,82 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerImportExportRoutes = registerImportExportRoutes;
|
|
4
|
+
const common_1 = require("../../utils/common");
|
|
5
|
+
const csv_1 = require("../../utils/csv");
|
|
6
|
+
const helpers_1 = require("./helpers");
|
|
7
|
+
function registerImportExportRoutes(router, ctx) {
|
|
8
|
+
const { db } = ctx;
|
|
9
|
+
router.get('/api/tables/:table/export', (0, helpers_1.wrap)(async (req, res) => {
|
|
10
|
+
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
11
|
+
if (!info)
|
|
12
|
+
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
13
|
+
const format = String(req.query.format ?? 'csv').toLowerCase();
|
|
14
|
+
const filters = (0, common_1.parseFilters)(req.query.f);
|
|
15
|
+
const r = await db.getRows(req.params.table, { limit: 1_000_000, filters });
|
|
16
|
+
if (!r.success)
|
|
17
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Failed to export table.');
|
|
18
|
+
const rows = (r.data ?? []).map(common_1.normalizeRow);
|
|
19
|
+
const columns = info.columns.map((c) => c.name);
|
|
20
|
+
if (format === 'json') {
|
|
21
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
22
|
+
res.setHeader('Content-Disposition', `attachment; filename="${req.params.table}.json"`);
|
|
23
|
+
return res.send((0, csv_1.toJson)(rows));
|
|
24
|
+
}
|
|
25
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
26
|
+
res.setHeader('Content-Disposition', `attachment; filename="${req.params.table}.csv"`);
|
|
27
|
+
res.send((0, csv_1.toCsv)(rows, columns));
|
|
28
|
+
}));
|
|
29
|
+
const importCsvHandler = (0, helpers_1.wrap)(async (req, res) => {
|
|
30
|
+
const info = await (0, helpers_1.requireTable)(db, req.params.table);
|
|
31
|
+
if (!info)
|
|
32
|
+
return (0, helpers_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
33
|
+
const csvText = typeof req.body === 'string' ? req.body : String(req.body?.csv ?? '');
|
|
34
|
+
if (!csvText.trim())
|
|
35
|
+
return (0, helpers_1.fail)(res, 'No CSV data provided.');
|
|
36
|
+
const parsed = (0, csv_1.parseCsv)(csvText);
|
|
37
|
+
if (!parsed.length)
|
|
38
|
+
return (0, helpers_1.fail)(res, 'CSV file is empty.');
|
|
39
|
+
const header = parsed[0].map((h) => h.trim());
|
|
40
|
+
const typeMap = new Map(info.columns.map((c) => [c.name, c.type]));
|
|
41
|
+
const validCols = header.filter((h) => typeMap.has(h));
|
|
42
|
+
if (!validCols.length) {
|
|
43
|
+
return (0, helpers_1.fail)(res, 'None of the CSV columns match columns in this table.');
|
|
44
|
+
}
|
|
45
|
+
const rows = parsed.slice(1).map((line) => {
|
|
46
|
+
const fields = [];
|
|
47
|
+
for (let i = 0; i < header.length; i++) {
|
|
48
|
+
const col = header[i];
|
|
49
|
+
if (!typeMap.has(col))
|
|
50
|
+
continue;
|
|
51
|
+
fields.push({ column: col, value: (0, common_1.coerceFormValue)(line[i] ?? '', typeMap.get(col)) });
|
|
52
|
+
}
|
|
53
|
+
return fields;
|
|
54
|
+
});
|
|
55
|
+
const r = await db.insertRows(req.params.table, rows);
|
|
56
|
+
if (!r.success)
|
|
57
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Failed to import CSV.');
|
|
58
|
+
(0, helpers_1.ok)(res, { message: `${r.data?.inserted ?? 0} row(s) imported.`, inserted: r.data?.inserted });
|
|
59
|
+
});
|
|
60
|
+
router.post('/api/tables/:table/import', importCsvHandler);
|
|
61
|
+
router.post('/api/tables/:table/rows/import', importCsvHandler);
|
|
62
|
+
router.post('/api/tables/:table/import/csv', importCsvHandler);
|
|
63
|
+
router.post('/api/query/export', (0, helpers_1.wrap)(async (req, res) => {
|
|
64
|
+
const sql = String(req.body?.sql ?? '').trim();
|
|
65
|
+
const format = String(req.body?.format ?? 'csv').toLowerCase();
|
|
66
|
+
if (!sql)
|
|
67
|
+
return (0, helpers_1.fail)(res, 'SQL query is required.');
|
|
68
|
+
const r = await db.all(sql);
|
|
69
|
+
if (!r.success)
|
|
70
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400);
|
|
71
|
+
const rows = (r.data ?? []).map(common_1.normalizeRow);
|
|
72
|
+
const columns = rows.length ? Object.keys(rows[0]) : [];
|
|
73
|
+
if (format === 'json') {
|
|
74
|
+
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
75
|
+
res.setHeader('Content-Disposition', 'attachment; filename="query_results.json"');
|
|
76
|
+
return res.send((0, csv_1.toJson)(rows));
|
|
77
|
+
}
|
|
78
|
+
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
|
79
|
+
res.setHeader('Content-Disposition', 'attachment; filename="query_results.csv"');
|
|
80
|
+
res.send((0, csv_1.toCsv)(rows, columns));
|
|
81
|
+
}));
|
|
82
|
+
}
|
package/dist/routes/api/index.js
CHANGED
|
@@ -1 +1,48 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerApi = registerApi;
|
|
4
|
+
const tables_1 = require("./tables");
|
|
5
|
+
const rows_1 = require("./rows");
|
|
6
|
+
const import_export_1 = require("./import-export");
|
|
7
|
+
const seed_1 = require("./seed");
|
|
8
|
+
const query_1 = require("./query");
|
|
9
|
+
const erd_1 = require("./erd");
|
|
10
|
+
const export_1 = require("../../db/export");
|
|
11
|
+
function registerApi(router, ctx) {
|
|
12
|
+
const { db } = ctx;
|
|
13
|
+
router.use((req, res, next) => {
|
|
14
|
+
if (!db.isReadOnly || req.method === 'GET')
|
|
15
|
+
return next();
|
|
16
|
+
const p = req.path;
|
|
17
|
+
if (p.endsWith('/generate') ||
|
|
18
|
+
p.endsWith('/preview') ||
|
|
19
|
+
p.endsWith('/api/query/export') ||
|
|
20
|
+
p.endsWith('/api/query') ||
|
|
21
|
+
p.endsWith('/rows/bulk-impact') ||
|
|
22
|
+
p.endsWith('/rows/bulk-export')) {
|
|
23
|
+
return next();
|
|
24
|
+
}
|
|
25
|
+
return res.status(403).json({
|
|
26
|
+
success: false,
|
|
27
|
+
error: 'Database is open in read-only mode — write operations are disabled.',
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
(0, tables_1.registerTableRoutes)(router, ctx);
|
|
31
|
+
(0, rows_1.registerRowRoutes)(router, ctx);
|
|
32
|
+
(0, import_export_1.registerImportExportRoutes)(router, ctx);
|
|
33
|
+
(0, seed_1.registerSeedRoutes)(router, ctx);
|
|
34
|
+
(0, query_1.registerQueryRoutes)(router, ctx);
|
|
35
|
+
(0, erd_1.registerErdRoutes)(router, ctx);
|
|
36
|
+
router.get('/api/info/ddl', async (_req, res) => {
|
|
37
|
+
try {
|
|
38
|
+
const dump = await (0, export_1.generateSchemaDump)(db);
|
|
39
|
+
if (!dump.success) {
|
|
40
|
+
return res.status(500).json({ success: false, error: dump.error });
|
|
41
|
+
}
|
|
42
|
+
res.json({ success: true, data: dump.data });
|
|
43
|
+
}
|
|
44
|
+
catch (err) {
|
|
45
|
+
res.status(500).json({ success: false, error: err instanceof Error ? err.message : String(err) });
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
package/dist/routes/api/query.js
CHANGED
|
@@ -1 +1,74 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerQueryRoutes = registerQueryRoutes;
|
|
4
|
+
const classifier_1 = require("../../sql/classifier");
|
|
5
|
+
const error_analyzer_1 = require("../../sql/error-analyzer");
|
|
6
|
+
const common_1 = require("../../utils/common");
|
|
7
|
+
const helpers_1 = require("./helpers");
|
|
8
|
+
function registerQueryRoutes(router, ctx) {
|
|
9
|
+
const { db } = ctx;
|
|
10
|
+
router.post('/api/query', (0, helpers_1.wrap)(async (req, res) => {
|
|
11
|
+
const rawSql = String(req.body?.sql ?? '').trim();
|
|
12
|
+
if (!rawSql)
|
|
13
|
+
return (0, helpers_1.fail)(res, 'SQL query is required.');
|
|
14
|
+
const { kind } = (0, classifier_1.classifySql)(rawSql);
|
|
15
|
+
if (db.hasMultipleStatements(rawSql)) {
|
|
16
|
+
if (db.isReadOnly) {
|
|
17
|
+
return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
|
|
18
|
+
}
|
|
19
|
+
const r = await db.execResult(rawSql);
|
|
20
|
+
if (!r.success) {
|
|
21
|
+
const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Script execution failed.', db);
|
|
22
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
|
|
23
|
+
}
|
|
24
|
+
return (0, helpers_1.ok)(res, { kind: 'script', message: 'Script executed successfully.' });
|
|
25
|
+
}
|
|
26
|
+
if (kind === 'select' || kind === 'read' || kind === 'count') {
|
|
27
|
+
const r = await db.all(rawSql);
|
|
28
|
+
if (!r.success) {
|
|
29
|
+
const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Query execution failed.', db);
|
|
30
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
|
|
31
|
+
}
|
|
32
|
+
const rows = (r.data ?? []).map(common_1.normalizeRow);
|
|
33
|
+
return (0, helpers_1.ok)(res, { kind: 'select', columns: rows.length ? Object.keys(rows[0]) : [], rows });
|
|
34
|
+
}
|
|
35
|
+
if (db.isReadOnly) {
|
|
36
|
+
return (0, helpers_1.fail)(res, 'Database is open in read-only mode — write statements are disabled.', 403);
|
|
37
|
+
}
|
|
38
|
+
const r = await db.runWrite(rawSql);
|
|
39
|
+
if (!r.success) {
|
|
40
|
+
const details = await (0, error_analyzer_1.analyzeSqlError)(rawSql, r.error ?? 'Statement execution failed.', db);
|
|
41
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Query failed.', 400, details);
|
|
42
|
+
}
|
|
43
|
+
const changes = r.data?.changes;
|
|
44
|
+
(0, helpers_1.ok)(res, {
|
|
45
|
+
kind: 'write',
|
|
46
|
+
changes: changes ?? null,
|
|
47
|
+
message: changes != null ? `${changes} row(s) affected.` : 'Statement executed successfully.',
|
|
48
|
+
});
|
|
49
|
+
}));
|
|
50
|
+
router.get('/api/queries', (0, helpers_1.wrap)(async (_req, res) => {
|
|
51
|
+
const r = await db.listSavedQueries();
|
|
52
|
+
if (!r.success)
|
|
53
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Failed to load queries.', 500);
|
|
54
|
+
(0, helpers_1.ok)(res, r.data);
|
|
55
|
+
}));
|
|
56
|
+
router.post('/api/queries', (0, helpers_1.wrap)(async (req, res) => {
|
|
57
|
+
const name = String(req.body?.name ?? '').trim();
|
|
58
|
+
const sql = String(req.body?.sql ?? '').trim();
|
|
59
|
+
if (!name)
|
|
60
|
+
return (0, helpers_1.fail)(res, 'Query name is required.');
|
|
61
|
+
if (!sql)
|
|
62
|
+
return (0, helpers_1.fail)(res, 'Query SQL is required.');
|
|
63
|
+
const r = await db.saveQuery(name, sql);
|
|
64
|
+
if (!r.success)
|
|
65
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Failed to save query.', 400);
|
|
66
|
+
(0, helpers_1.ok)(res, { message: 'Query saved.' }, 201);
|
|
67
|
+
}));
|
|
68
|
+
router.delete('/api/queries/:id', (0, helpers_1.wrap)(async (req, res) => {
|
|
69
|
+
const r = await db.deleteSavedQuery(req.params.id);
|
|
70
|
+
if (!r.success)
|
|
71
|
+
return (0, helpers_1.fail)(res, r.error ?? 'Failed to delete query.', 400);
|
|
72
|
+
(0, helpers_1.ok)(res, { message: 'Query deleted.' });
|
|
73
|
+
}));
|
|
74
|
+
}
|