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,134 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DatabasesService = void 0;
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const common_1 = require("../../utils/common");
|
|
10
|
+
const BLOCKED_NAMES = new Set([
|
|
11
|
+
'node_modules',
|
|
12
|
+
'.git',
|
|
13
|
+
'.svn',
|
|
14
|
+
'.hg',
|
|
15
|
+
'System Volume Information',
|
|
16
|
+
'$RECYCLE.BIN',
|
|
17
|
+
'.env',
|
|
18
|
+
'.aws',
|
|
19
|
+
'.ssh',
|
|
20
|
+
]);
|
|
21
|
+
function formatDate(iso) {
|
|
22
|
+
try {
|
|
23
|
+
return new Date(iso).toLocaleString();
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return iso;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
class DatabasesService {
|
|
30
|
+
static async getDatabaseRows(manager) {
|
|
31
|
+
const entries = manager.list();
|
|
32
|
+
return Promise.all(entries.map(async (e) => ({
|
|
33
|
+
...e,
|
|
34
|
+
isPostgres: e.dialect === 'postgres',
|
|
35
|
+
isSqlite: e.dialect === 'sqlite',
|
|
36
|
+
sizeLabel: e.dialect === 'postgres' ? 'Remote' : (0, common_1.formatBytes)(e.size),
|
|
37
|
+
modifiedLabel: e.dialect === 'postgres' ? 'Connected' : (e.modified ? formatDate(e.modified) : '—'),
|
|
38
|
+
tables: await manager.countTables(e.id),
|
|
39
|
+
})));
|
|
40
|
+
}
|
|
41
|
+
static listDirectory(base, manager, browseRoot) {
|
|
42
|
+
if (base.includes('\0'))
|
|
43
|
+
throw new Error('Invalid path.');
|
|
44
|
+
if (browseRoot && !(0, common_1.isPathWithinRoot)(browseRoot, base)) {
|
|
45
|
+
throw new Error(`Path is outside the allowed folder: ${browseRoot}`);
|
|
46
|
+
}
|
|
47
|
+
if (!(0, node_fs_1.existsSync)(base))
|
|
48
|
+
throw new Error(`Path does not exist: ${base}`);
|
|
49
|
+
if (!(0, node_fs_1.statSync)(base).isDirectory())
|
|
50
|
+
throw new Error(`Not a directory: ${base}`);
|
|
51
|
+
const entries = [];
|
|
52
|
+
for (const name of (0, node_fs_1.readdirSync)(base)) {
|
|
53
|
+
if (name.startsWith('.') || BLOCKED_NAMES.has(name) || name.toLowerCase().startsWith('.env'))
|
|
54
|
+
continue;
|
|
55
|
+
const full = node_path_1.default.join(base, name);
|
|
56
|
+
let isDir = false;
|
|
57
|
+
let size = 0;
|
|
58
|
+
try {
|
|
59
|
+
const st = (0, node_fs_1.statSync)(full);
|
|
60
|
+
isDir = st.isDirectory();
|
|
61
|
+
size = isDir ? 0 : st.size;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
if (!isDir && !manager.isDbFile(name))
|
|
67
|
+
continue;
|
|
68
|
+
if (browseRoot && !(0, common_1.isPathWithinRoot)(browseRoot, full))
|
|
69
|
+
continue;
|
|
70
|
+
entries.push({ name, path: full, isDir, isDb: !isDir, size });
|
|
71
|
+
}
|
|
72
|
+
entries.sort((a, b) => a.isDir === b.isDir ? a.name.localeCompare(b.name, undefined, { sensitivity: 'base' }) : a.isDir ? -1 : 1);
|
|
73
|
+
const parent = node_path_1.default.dirname(base) === base ? null : node_path_1.default.dirname(base);
|
|
74
|
+
const effectiveParent = parent && (!browseRoot || (0, common_1.isPathWithinRoot)(browseRoot, parent)) ? parent : null;
|
|
75
|
+
return {
|
|
76
|
+
path: base,
|
|
77
|
+
name: node_path_1.default.basename(base) || base,
|
|
78
|
+
parent: effectiveParent,
|
|
79
|
+
entries,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
static openDatabaseFile(manager, filePath, isRo, browseRoot) {
|
|
83
|
+
const p = String(filePath ?? '').trim();
|
|
84
|
+
if (!p || p.includes('\0'))
|
|
85
|
+
throw new Error('Valid database path is required.');
|
|
86
|
+
const abs = node_path_1.default.resolve(p);
|
|
87
|
+
if (browseRoot && !(0, common_1.isPathWithinRoot)(browseRoot, abs)) {
|
|
88
|
+
throw new Error(`Cannot open a database outside the allowed folder: ${browseRoot}`);
|
|
89
|
+
}
|
|
90
|
+
return manager.openFile(p, isRo);
|
|
91
|
+
}
|
|
92
|
+
static async connectPostgres(manager, name, connectionString, isRo) {
|
|
93
|
+
const conn = String(connectionString ?? '').trim();
|
|
94
|
+
if (!conn) {
|
|
95
|
+
throw new Error('PostgreSQL connection string is required (e.g. postgresql://user:password@localhost:5432/dbname).');
|
|
96
|
+
}
|
|
97
|
+
if (!(0, common_1.isPostgresConnectionString)(conn)) {
|
|
98
|
+
throw new Error('Invalid connection protocol. Connection string must start with postgres:// or postgresql://');
|
|
99
|
+
}
|
|
100
|
+
let id;
|
|
101
|
+
try {
|
|
102
|
+
id = manager.addConnection(name, conn, isRo);
|
|
103
|
+
const db = manager.open(id);
|
|
104
|
+
await db.listTables();
|
|
105
|
+
return id;
|
|
106
|
+
}
|
|
107
|
+
catch (err) {
|
|
108
|
+
if (id)
|
|
109
|
+
manager.remove(id);
|
|
110
|
+
throw new Error(`Failed to connect to PostgreSQL: ${(0, common_1.errorMessage)(err)}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
static createDatabase(manager, name) {
|
|
114
|
+
const trimmed = String(name ?? '').trim();
|
|
115
|
+
if (!trimmed)
|
|
116
|
+
throw new Error('Database name is required.');
|
|
117
|
+
return manager.create(trimmed);
|
|
118
|
+
}
|
|
119
|
+
static setDatabaseMode(manager, id, readonly, invalidate) {
|
|
120
|
+
if (!manager.has(id))
|
|
121
|
+
throw new Error(`Database "${id}" does not exist.`);
|
|
122
|
+
manager.setReadonly(id, readonly);
|
|
123
|
+
if (invalidate)
|
|
124
|
+
invalidate(id);
|
|
125
|
+
}
|
|
126
|
+
static removeDatabase(manager, id, invalidate) {
|
|
127
|
+
if (!manager.has(id))
|
|
128
|
+
throw new Error(`Database "${id}" does not exist.`);
|
|
129
|
+
if (invalidate)
|
|
130
|
+
invalidate(id);
|
|
131
|
+
manager.remove(id);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
exports.DatabasesService = DatabasesService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './databases.service';export * from './databases.routes';
|
|
@@ -14,6 +14,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
14
14
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./
|
|
18
|
-
__exportStar(require("./databases"), exports);
|
|
19
|
-
__exportStar(require("./api/index"), exports);
|
|
17
|
+
__exportStar(require("./databases.service"), exports);
|
|
18
|
+
__exportStar(require("./databases.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerErdRoutes(router:Router,ctx:RouteContext):void;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerErdRoutes = registerErdRoutes;
|
|
4
|
+
const router_1 = require("../../core/router");
|
|
5
|
+
const tables_service_1 = require("../tables/tables.service");
|
|
6
|
+
const erd_service_1 = require("./erd.service");
|
|
7
|
+
function registerErdRoutes(router, ctx) {
|
|
8
|
+
router.get('/erd/:db', async (req, res, next) => {
|
|
9
|
+
try {
|
|
10
|
+
const db = ctx.getContext(req);
|
|
11
|
+
const dbId = req.params.db;
|
|
12
|
+
res.locals.dbId = dbId;
|
|
13
|
+
const tablesList = await tables_service_1.TableService.listTables(db);
|
|
14
|
+
const allNames = tablesList.map((t) => t.name);
|
|
15
|
+
(0, router_1.initPageLocals)(res, db, dbId, allNames);
|
|
16
|
+
res.render('pages/erd', { title: 'ER Diagram', dbId });
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
next(err);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
router.get('/api/erd/:db', async (req, res) => {
|
|
23
|
+
try {
|
|
24
|
+
const db = ctx.getContext(req);
|
|
25
|
+
const graph = await erd_service_1.ErdService.getErdGraph(db);
|
|
26
|
+
(0, router_1.ok)(res, graph);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
(0, router_1.fail)(res, err.message ?? 'Unknown error.', 500);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {DatabaseContext} from '../../core/context';export interface ErdColumn{name:string;type:string;pk:number|boolean;notnull:number|boolean;dflt_value:unknown;}export interface ErdForeignKey{ from :string;table:string;to:string;on_delete:string;on_update:string;}export interface ErdTable{name:string;columns:ErdColumn[];foreignKeys:ErdForeignKey[];primaryKey:string[];}export interface ErdGraph{tables:ErdTable[];}export declare class ErdService{static getErdGraph(ctx:DatabaseContext):Promise<ErdGraph>;}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ErdService = void 0;
|
|
4
|
+
class ErdService {
|
|
5
|
+
static async getErdGraph(ctx) {
|
|
6
|
+
const tablesResult = await ctx.db.listTables();
|
|
7
|
+
if (!tablesResult.success || !tablesResult.data) {
|
|
8
|
+
throw new Error(tablesResult.error ?? 'Failed to list tables.');
|
|
9
|
+
}
|
|
10
|
+
const tableNames = tablesResult.data.map((t) => t.name);
|
|
11
|
+
const tables = await Promise.all(tableNames.map(async (name) => {
|
|
12
|
+
const infoResult = await ctx.db.getTableInfo(name);
|
|
13
|
+
if (!infoResult.success || !infoResult.data) {
|
|
14
|
+
return { name, columns: [], foreignKeys: [], primaryKey: [] };
|
|
15
|
+
}
|
|
16
|
+
const { columns, foreignKeys, primaryKey } = infoResult.data;
|
|
17
|
+
return {
|
|
18
|
+
name,
|
|
19
|
+
columns: columns.map((c) => ({
|
|
20
|
+
name: c.name,
|
|
21
|
+
type: c.type,
|
|
22
|
+
pk: c.pk,
|
|
23
|
+
notnull: c.notnull,
|
|
24
|
+
dflt_value: c.dflt_value,
|
|
25
|
+
})),
|
|
26
|
+
foreignKeys: foreignKeys.map((fk) => ({
|
|
27
|
+
from: fk.from,
|
|
28
|
+
table: fk.table,
|
|
29
|
+
to: fk.to ?? '',
|
|
30
|
+
on_delete: fk.on_delete,
|
|
31
|
+
on_update: fk.on_update,
|
|
32
|
+
})),
|
|
33
|
+
primaryKey,
|
|
34
|
+
};
|
|
35
|
+
}));
|
|
36
|
+
return { tables };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.ErdService = ErdService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './erd.service';export * from './erd.routes';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./erd.service"), exports);
|
|
18
|
+
__exportStar(require("./erd.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import type {RouteContext} from '../core/router';export * from './databases/index';export * from './tables/index';export * from './rows/index';export * from './query/index';export * from './schema/index';export * from './seed/index';export * from './erd/index';export declare function registerModules(router:Router,ctx:RouteContext):void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.registerModules = registerModules;
|
|
18
|
+
const tables_routes_1 = require("./tables/tables.routes");
|
|
19
|
+
const rows_routes_1 = require("./rows/rows.routes");
|
|
20
|
+
const schema_routes_1 = require("./schema/schema.routes");
|
|
21
|
+
const query_routes_1 = require("./query/query.routes");
|
|
22
|
+
const seed_routes_1 = require("./seed/seed.routes");
|
|
23
|
+
const erd_routes_1 = require("./erd/erd.routes");
|
|
24
|
+
__exportStar(require("./databases/index"), exports);
|
|
25
|
+
__exportStar(require("./tables/index"), exports);
|
|
26
|
+
__exportStar(require("./rows/index"), exports);
|
|
27
|
+
__exportStar(require("./query/index"), exports);
|
|
28
|
+
__exportStar(require("./schema/index"), exports);
|
|
29
|
+
__exportStar(require("./seed/index"), exports);
|
|
30
|
+
__exportStar(require("./erd/index"), exports);
|
|
31
|
+
function registerModules(router, ctx) {
|
|
32
|
+
router.use((req, res, next) => {
|
|
33
|
+
if (req.method === 'GET')
|
|
34
|
+
return next();
|
|
35
|
+
const p = req.path;
|
|
36
|
+
if (p.endsWith('/generate') ||
|
|
37
|
+
p.endsWith('/preview') ||
|
|
38
|
+
p.includes('/api/query/') ||
|
|
39
|
+
p.endsWith('/rows/bulk-impact') ||
|
|
40
|
+
p.endsWith('/rows/bulk-export')) {
|
|
41
|
+
return next();
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const dbCtx = ctx.getContext(req);
|
|
45
|
+
if (dbCtx && (dbCtx.isReadOnly || dbCtx.db.isReadOnly)) {
|
|
46
|
+
return res.status(403).json({
|
|
47
|
+
success: false,
|
|
48
|
+
error: 'Database is open in read-only mode — write operations are disabled.',
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
}
|
|
54
|
+
return next();
|
|
55
|
+
});
|
|
56
|
+
(0, tables_routes_1.registerTableRoutes)(router, ctx);
|
|
57
|
+
(0, rows_routes_1.registerRowRoutes)(router, ctx);
|
|
58
|
+
(0, schema_routes_1.registerSchemaRoutes)(router, ctx);
|
|
59
|
+
(0, query_routes_1.registerQueryRoutes)(router, ctx);
|
|
60
|
+
(0, seed_routes_1.registerSeedRoutes)(router, ctx);
|
|
61
|
+
(0, erd_routes_1.registerErdRoutes)(router, ctx);
|
|
62
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './query.service';export * from './query.routes';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./query.service"), exports);
|
|
18
|
+
__exportStar(require("./query.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerQueryRoutes(router:Router,ctx:RouteContext):void;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerQueryRoutes = registerQueryRoutes;
|
|
4
|
+
const router_1 = require("../../core/router");
|
|
5
|
+
const tables_service_1 = require("../tables/tables.service");
|
|
6
|
+
const query_service_1 = require("./query.service");
|
|
7
|
+
function registerQueryRoutes(router, ctx) {
|
|
8
|
+
router.get('/query/:db', async (req, res, next) => {
|
|
9
|
+
try {
|
|
10
|
+
const db = ctx.getContext(req);
|
|
11
|
+
const dbId = req.params.db;
|
|
12
|
+
res.locals.dbId = dbId;
|
|
13
|
+
const tablesList = await tables_service_1.TableService.listTables(db);
|
|
14
|
+
const allNames = tablesList.map((t) => t.name);
|
|
15
|
+
(0, router_1.initPageLocals)(res, db, dbId, allNames);
|
|
16
|
+
res.render('pages/query', { title: 'Query editor', dbId });
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
next(err);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
router.post('/api/query/:db', (0, router_1.wrap)(async (req, res) => {
|
|
23
|
+
const db = ctx.getContext(req);
|
|
24
|
+
const rawSql = String(req.body?.sql ?? '').trim();
|
|
25
|
+
if (!rawSql)
|
|
26
|
+
return (0, router_1.fail)(res, 'SQL query is required.');
|
|
27
|
+
try {
|
|
28
|
+
const result = await query_service_1.QueryService.execute(db, rawSql);
|
|
29
|
+
(0, router_1.ok)(res, result);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (err instanceof query_service_1.QueryExecutionError) {
|
|
33
|
+
return (0, router_1.fail)(res, err.message, err.statusCode, err.details);
|
|
34
|
+
}
|
|
35
|
+
(0, router_1.fail)(res, err.message ?? 'Query failed.', 500);
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
router.post('/api/query/:db/export', (0, router_1.wrap)(async (req, res) => {
|
|
39
|
+
const db = ctx.getContext(req);
|
|
40
|
+
const sql = String(req.body?.sql ?? '').trim();
|
|
41
|
+
const format = String(req.body?.format ?? 'csv').toLowerCase() === 'json' ? 'json' : 'csv';
|
|
42
|
+
if (!sql)
|
|
43
|
+
return (0, router_1.fail)(res, 'SQL query is required.');
|
|
44
|
+
try {
|
|
45
|
+
const exp = await query_service_1.QueryService.streamExportQuery(db, sql, format);
|
|
46
|
+
res.setHeader('Content-Type', exp.contentType);
|
|
47
|
+
res.setHeader('Content-Disposition', `attachment; filename="${exp.filename}"`);
|
|
48
|
+
exp.stream.pipe(res);
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
(0, router_1.fail)(res, err.message ?? 'Query failed.', 400);
|
|
52
|
+
}
|
|
53
|
+
}));
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {DatabaseContext} from '../../core/context';import{type SqlErrorDetails} from '../../sql/error-analyzer';export type QueryExecutionResult={kind:'script';message:string;}|{kind:'select';columns:string[];rows:Record<string,unknown>[];}|{kind:'write';changes:number|null;message:string;};export declare class QueryExecutionError extends Error{readonly statusCode:number;readonly details?:(SqlErrorDetails|Record<string,unknown>|null)|undefined;constructor(message:string,statusCode?:number,details?:(SqlErrorDetails|Record<string,unknown>|null)|undefined);}export declare class QueryService{static execute(ctx:DatabaseContext,rawSql:string):Promise<QueryExecutionResult>;static exportQuery(ctx:DatabaseContext,sql:string,format:'csv'|'json'):Promise<{data:string;contentType:string;filename:string;}>;static streamExportQuery(ctx:DatabaseContext,sql:string,format:'csv'|'json'):Promise<{stream:NodeJS.ReadableStream;contentType:string;filename:string;}>;}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.QueryService = exports.QueryExecutionError = void 0;
|
|
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 csv_1 = require("../../utils/csv");
|
|
8
|
+
const stream_1 = require("../../utils/stream");
|
|
9
|
+
class QueryExecutionError extends Error {
|
|
10
|
+
statusCode;
|
|
11
|
+
details;
|
|
12
|
+
constructor(message, statusCode = 400, details) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.statusCode = statusCode;
|
|
15
|
+
this.details = details;
|
|
16
|
+
this.name = 'QueryExecutionError';
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
exports.QueryExecutionError = QueryExecutionError;
|
|
20
|
+
class QueryService {
|
|
21
|
+
static async execute(ctx, rawSql) {
|
|
22
|
+
const { db } = ctx;
|
|
23
|
+
const trimmed = String(rawSql ?? '').trim();
|
|
24
|
+
if (!trimmed) {
|
|
25
|
+
throw new QueryExecutionError('SQL query is required.', 400);
|
|
26
|
+
}
|
|
27
|
+
const { kind } = (0, classifier_1.classifySql)(trimmed);
|
|
28
|
+
if (db.hasMultipleStatements(trimmed)) {
|
|
29
|
+
if (db.isReadOnly) {
|
|
30
|
+
throw new QueryExecutionError('Database is open in read-only mode — write statements are disabled.', 403);
|
|
31
|
+
}
|
|
32
|
+
const r = await db.execResult(trimmed);
|
|
33
|
+
if (!r.success) {
|
|
34
|
+
const details = await (0, error_analyzer_1.analyzeSqlError)(trimmed, r.error ?? 'Script execution failed.', db);
|
|
35
|
+
throw new QueryExecutionError(r.error ?? 'Query failed.', 400, details);
|
|
36
|
+
}
|
|
37
|
+
return { kind: 'script', message: 'Script executed successfully.' };
|
|
38
|
+
}
|
|
39
|
+
if (kind === 'select' || kind === 'read' || kind === 'count') {
|
|
40
|
+
const r = await db.all(trimmed);
|
|
41
|
+
if (!r.success) {
|
|
42
|
+
const details = await (0, error_analyzer_1.analyzeSqlError)(trimmed, r.error ?? 'Query execution failed.', db);
|
|
43
|
+
throw new QueryExecutionError(r.error ?? 'Query failed.', 400, details);
|
|
44
|
+
}
|
|
45
|
+
const rows = (r.data ?? []).map(common_1.normalizeRow);
|
|
46
|
+
return { kind: 'select', columns: rows.length ? Object.keys(rows[0]) : [], rows };
|
|
47
|
+
}
|
|
48
|
+
if (db.isReadOnly) {
|
|
49
|
+
throw new QueryExecutionError('Database is open in read-only mode — write statements are disabled.', 403);
|
|
50
|
+
}
|
|
51
|
+
const r = await db.runWrite(trimmed);
|
|
52
|
+
if (!r.success) {
|
|
53
|
+
const details = await (0, error_analyzer_1.analyzeSqlError)(trimmed, r.error ?? 'Statement execution failed.', db);
|
|
54
|
+
throw new QueryExecutionError(r.error ?? 'Query failed.', 400, details);
|
|
55
|
+
}
|
|
56
|
+
const changes = r.data?.changes;
|
|
57
|
+
return {
|
|
58
|
+
kind: 'write',
|
|
59
|
+
changes: changes ?? null,
|
|
60
|
+
message: changes != null ? `${changes} row(s) affected.` : 'Statement executed successfully.',
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
static async exportQuery(ctx, sql, format) {
|
|
64
|
+
const trimmed = String(sql ?? '').trim();
|
|
65
|
+
if (!trimmed)
|
|
66
|
+
throw new QueryExecutionError('SQL query is required.', 400);
|
|
67
|
+
const r = await ctx.db.all(trimmed);
|
|
68
|
+
if (!r.success)
|
|
69
|
+
throw new QueryExecutionError(r.error ?? 'Query failed.', 400);
|
|
70
|
+
const rows = (r.data ?? []).map(common_1.normalizeRow);
|
|
71
|
+
const columns = rows.length ? Object.keys(rows[0]) : [];
|
|
72
|
+
if (format === 'json') {
|
|
73
|
+
return { data: (0, csv_1.toJson)(rows), contentType: 'application/json; charset=utf-8', filename: 'query_results.json' };
|
|
74
|
+
}
|
|
75
|
+
return { data: (0, csv_1.toCsv)(rows, columns), contentType: 'text/csv; charset=utf-8', filename: 'query_results.csv' };
|
|
76
|
+
}
|
|
77
|
+
static async streamExportQuery(ctx, sql, format) {
|
|
78
|
+
const result = await QueryService.execute(ctx, sql);
|
|
79
|
+
if (result.kind !== 'select') {
|
|
80
|
+
throw new QueryExecutionError('Query must be a SELECT statement to export.', 400);
|
|
81
|
+
}
|
|
82
|
+
const columns = result.columns;
|
|
83
|
+
const transform = format === 'json' ? (0, stream_1.createJsonStream)() : (0, stream_1.createCsvStream)(columns);
|
|
84
|
+
process.nextTick(() => {
|
|
85
|
+
for (const row of result.rows) {
|
|
86
|
+
transform.write(row);
|
|
87
|
+
}
|
|
88
|
+
transform.end();
|
|
89
|
+
});
|
|
90
|
+
const contentType = format === 'json' ? 'application/json; charset=utf-8' : 'text/csv; charset=utf-8';
|
|
91
|
+
const filename = `query_results.${format}`;
|
|
92
|
+
return { stream: transform, contentType, filename };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
exports.QueryService = QueryService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{type MimeAnalysis,type BlobMetadata} from '../../utils/datatype';export declare function resolveBlobBuffer(rawVal:unknown):Buffer;export declare function parseBlobPayload(data:unknown,format?:string):Buffer;export declare function getBlobMetadata(rawVal:unknown):{isNull:boolean;}&(Partial<BlobMetadata>);export declare function sniffBlobMime(buf:Buffer):MimeAnalysis;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveBlobBuffer = resolveBlobBuffer;
|
|
4
|
+
exports.parseBlobPayload = parseBlobPayload;
|
|
5
|
+
exports.getBlobMetadata = getBlobMetadata;
|
|
6
|
+
exports.sniffBlobMime = sniffBlobMime;
|
|
7
|
+
const datatype_1 = require("../../utils/datatype");
|
|
8
|
+
function resolveBlobBuffer(rawVal) {
|
|
9
|
+
if (Buffer.isBuffer(rawVal) || rawVal instanceof Uint8Array) {
|
|
10
|
+
return Buffer.isBuffer(rawVal) ? rawVal : Buffer.from(rawVal);
|
|
11
|
+
}
|
|
12
|
+
if (typeof rawVal === 'string' && /^0x[0-9a-f]*$/i.test(rawVal)) {
|
|
13
|
+
return Buffer.from(rawVal.slice(2), 'hex');
|
|
14
|
+
}
|
|
15
|
+
return Buffer.from(String(rawVal ?? ''), 'utf8');
|
|
16
|
+
}
|
|
17
|
+
function parseBlobPayload(data, format = 'base64') {
|
|
18
|
+
if (data === undefined || data === null) {
|
|
19
|
+
throw new Error('Payload data is required.');
|
|
20
|
+
}
|
|
21
|
+
if (format === 'base64') {
|
|
22
|
+
const base64Str = String(data).replace(/^data:[^;]+;base64,/, '');
|
|
23
|
+
return Buffer.from(base64Str, 'base64');
|
|
24
|
+
}
|
|
25
|
+
if (format === 'hex') {
|
|
26
|
+
const hexStr = String(data).replace(/^0x/i, '');
|
|
27
|
+
return Buffer.from(hexStr, 'hex');
|
|
28
|
+
}
|
|
29
|
+
return Buffer.from(String(data), 'utf8');
|
|
30
|
+
}
|
|
31
|
+
function getBlobMetadata(rawVal) {
|
|
32
|
+
if (rawVal === null || rawVal === undefined) {
|
|
33
|
+
return { isNull: true, size: 0, sizeFormatted: '0 B' };
|
|
34
|
+
}
|
|
35
|
+
const meta = (0, datatype_1.analyzeBlob)(rawVal);
|
|
36
|
+
return { isNull: false, ...meta };
|
|
37
|
+
}
|
|
38
|
+
function sniffBlobMime(buf) {
|
|
39
|
+
return (0, datatype_1.sniffMimeType)(buf);
|
|
40
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {TableInfoData} from '../../db/index';export interface DisplayCell{name:string;value:unknown;isNull:boolean;display:string;type?:string;isBlob?:boolean;isJson?:boolean;isImage?:boolean;isUrl?:boolean;isColor?:boolean;blobSize?:string;blobMime?:string;blobExt?:string;blobUrl?:string;}export interface DisplayRow{cells:DisplayCell[];pkEncoded:string|null;refs?:{table:string; from :string;to:string;value:string;count:number;}[];}export declare function buildDisplayRows(rawRows:Record<string,unknown>[],info:TableInfoData,dbId?:string,table?:string,basePath?:string):DisplayRow[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildDisplayRows = buildDisplayRows;
|
|
4
|
+
const common_1 = require("../../utils/common");
|
|
5
|
+
const datatype_1 = require("../../utils/datatype");
|
|
6
|
+
const blob_helper_1 = require("./blob.helper");
|
|
7
|
+
function buildDisplayRows(rawRows, info, dbId, table, basePath = '') {
|
|
8
|
+
const pkCols = info.primaryKey.length ? info.primaryKey : ['_rowid_'];
|
|
9
|
+
return rawRows.map((row) => {
|
|
10
|
+
const pkEncoded = (0, common_1.encodePk)(pkCols.map((c) => row[c]));
|
|
11
|
+
return {
|
|
12
|
+
cells: info.columns.map((c) => {
|
|
13
|
+
const raw = row[c.name];
|
|
14
|
+
const v = (0, common_1.normalizeCell)(raw);
|
|
15
|
+
const isNull = v == null;
|
|
16
|
+
const typeUpper = (c.type || '').toUpperCase();
|
|
17
|
+
const isBlob = typeUpper.includes('BLOB') ||
|
|
18
|
+
typeUpper.includes('BYTEA') ||
|
|
19
|
+
Buffer.isBuffer(raw) ||
|
|
20
|
+
raw instanceof Uint8Array ||
|
|
21
|
+
(typeof v === 'string' && (/^0x[0-9a-f]{8,}$/i.test(v) || /^\\x[0-9a-f]{8,}$/i.test(v)));
|
|
22
|
+
let isJson = false;
|
|
23
|
+
let isImage = false;
|
|
24
|
+
let isUrl = false;
|
|
25
|
+
let isColor = false;
|
|
26
|
+
let blobSize = '';
|
|
27
|
+
let blobMime = '';
|
|
28
|
+
let blobExt = '';
|
|
29
|
+
let blobUrl = '';
|
|
30
|
+
if (!isNull) {
|
|
31
|
+
const str = String(v);
|
|
32
|
+
if (isBlob) {
|
|
33
|
+
const buf = (0, blob_helper_1.resolveBlobBuffer)(raw ?? v);
|
|
34
|
+
const mimeInfo = (0, blob_helper_1.sniffBlobMime)(buf);
|
|
35
|
+
blobSize = (0, common_1.formatBytes)(buf.length);
|
|
36
|
+
blobMime = mimeInfo.mime;
|
|
37
|
+
blobExt = mimeInfo.ext;
|
|
38
|
+
isImage = mimeInfo.isImage;
|
|
39
|
+
if (table && pkEncoded && dbId) {
|
|
40
|
+
blobUrl = `${basePath}/api/tables/${encodeURIComponent(dbId)}/${encodeURIComponent(table)}/row/${encodeURIComponent(pkEncoded)}/blob/${encodeURIComponent(c.name)}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
else if ((0, datatype_1.isJsonString)(v) || typeUpper.includes('JSON')) {
|
|
44
|
+
isJson = (0, datatype_1.isJsonString)(v);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
if (/^https?:\/\/[^\s$.?#].[^\s]*$/i.test(str))
|
|
48
|
+
isUrl = true;
|
|
49
|
+
else if (/^#(?:[0-9a-fA-F]{3}){1,2}$|^rgba?\(\s*\d+\s*,\s*\d+\s*,\s*\d+/i.test(str))
|
|
50
|
+
isColor = true;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
name: c.name,
|
|
55
|
+
value: v,
|
|
56
|
+
isNull,
|
|
57
|
+
display: isNull ? '' : String(v),
|
|
58
|
+
type: c.type,
|
|
59
|
+
isBlob,
|
|
60
|
+
isJson,
|
|
61
|
+
isImage,
|
|
62
|
+
isUrl,
|
|
63
|
+
isColor,
|
|
64
|
+
blobSize,
|
|
65
|
+
blobMime,
|
|
66
|
+
blobExt,
|
|
67
|
+
blobUrl,
|
|
68
|
+
};
|
|
69
|
+
}),
|
|
70
|
+
pkEncoded,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './blob.helper';export * from './display.helper';export * from './rows.service';export * from './rows.routes';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./blob.helper"), exports);
|
|
18
|
+
__exportStar(require("./display.helper"), exports);
|
|
19
|
+
__exportStar(require("./rows.service"), exports);
|
|
20
|
+
__exportStar(require("./rows.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerRowRoutes(router:Router,ctx:RouteContext):void;
|