admindb 2.4.1 → 2.6.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 +48 -25
- package/dist/core/context.d.ts +1 -0
- package/dist/core/context.js +2 -0
- package/dist/core/diff/data-differ.d.ts +1 -0
- package/dist/core/diff/data-differ.js +243 -0
- package/dist/core/diff/patch-generator.d.ts +1 -0
- package/dist/core/diff/patch-generator.js +77 -0
- package/dist/core/diff/schema-differ.d.ts +1 -0
- package/dist/core/diff/schema-differ.js +229 -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/core/transfer/dialect-mapper.d.ts +1 -0
- package/dist/core/transfer/dialect-mapper.js +303 -0
- package/dist/core/transfer/masking.d.ts +1 -0
- package/dist/core/transfer/masking.js +49 -0
- package/dist/core/transfer/sync-engine.d.ts +1 -0
- package/dist/core/transfer/sync-engine.js +234 -0
- package/dist/core/transfer/topological-sort.d.ts +1 -0
- package/dist/core/transfer/topological-sort.js +55 -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 +217 -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 +70 -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/search/index.d.ts +1 -0
- package/dist/modules/search/index.js +19 -0
- package/dist/modules/search/search.routes.d.ts +1 -0
- package/dist/modules/search/search.routes.js +38 -0
- package/dist/modules/search/search.service.d.ts +1 -0
- package/dist/modules/search/search.service.js +458 -0
- package/dist/modules/search/search.types.d.ts +1 -0
- package/dist/modules/search/search.types.js +2 -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/sync/index.d.ts +1 -0
- package/dist/modules/sync/index.js +18 -0
- package/dist/modules/sync/sync.routes.d.ts +1 -0
- package/dist/modules/sync/sync.routes.js +135 -0
- package/dist/modules/sync/sync.service.d.ts +1 -0
- package/dist/modules/sync/sync.service.js +41 -0
- 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/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/query.js +1 -1
- package/dist/public/js/search.js +1 -0
- package/dist/public/js/sync.js +1 -0
- 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/icons.js +3 -0
- package/dist/utils/stream.d.ts +1 -0
- package/dist/utils/stream.js +131 -0
- package/dist/views/layouts/main.hbs +1 -1
- 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/sync.hbs +409 -0
- package/dist/views/pages/table.hbs +1 -1
- package/dist/views/partials/navbar.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 +10 -9
- 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,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SchemaService = void 0;
|
|
4
|
+
class SchemaService {
|
|
5
|
+
static async getSchema(ctx, table) {
|
|
6
|
+
const r = await ctx.db.getSchema(table);
|
|
7
|
+
if (!r.success || !r.data)
|
|
8
|
+
throw new Error(r.error ?? 'Failed to load schema.');
|
|
9
|
+
return r.data;
|
|
10
|
+
}
|
|
11
|
+
static async getReferencingTables(ctx, table) {
|
|
12
|
+
const r = await ctx.db.getReferencingTables(table);
|
|
13
|
+
if (!r.success || !r.data)
|
|
14
|
+
throw new Error(r.error ?? 'Failed to get referencing tables.');
|
|
15
|
+
return r.data;
|
|
16
|
+
}
|
|
17
|
+
static async addColumn(ctx, table, col) {
|
|
18
|
+
if (!col?.name)
|
|
19
|
+
throw new Error('Column name is required.');
|
|
20
|
+
const r = await ctx.db.addColumn(table, col);
|
|
21
|
+
if (!r.success)
|
|
22
|
+
throw new Error(r.error ?? 'Failed to add column.');
|
|
23
|
+
}
|
|
24
|
+
static async modifyColumn(ctx, table, colName, col) {
|
|
25
|
+
if (!col)
|
|
26
|
+
throw new Error('Column definition is required.');
|
|
27
|
+
const r = await ctx.db.modifyColumn(table, colName, col);
|
|
28
|
+
if (!r.success)
|
|
29
|
+
throw new Error(r.error ?? 'Failed to modify column.');
|
|
30
|
+
}
|
|
31
|
+
static async renameColumn(ctx, table, colName, newName) {
|
|
32
|
+
const trimmed = String(newName ?? '').trim();
|
|
33
|
+
if (!trimmed)
|
|
34
|
+
throw new Error('New column name is required.');
|
|
35
|
+
const r = await ctx.db.renameColumn(table, colName, trimmed);
|
|
36
|
+
if (!r.success)
|
|
37
|
+
throw new Error(r.error ?? 'Failed to rename column.');
|
|
38
|
+
}
|
|
39
|
+
static async dropColumn(ctx, table, colName) {
|
|
40
|
+
const r = await ctx.db.dropColumn(table, colName);
|
|
41
|
+
if (!r.success)
|
|
42
|
+
throw new Error(r.error ?? 'Failed to drop column.');
|
|
43
|
+
}
|
|
44
|
+
static async createIndex(ctx, table, def) {
|
|
45
|
+
if (!def || !Array.isArray(def.columns) || def.columns.length === 0) {
|
|
46
|
+
throw new Error('At least one column is required for an index.');
|
|
47
|
+
}
|
|
48
|
+
const r = await ctx.db.createIndex(table, def);
|
|
49
|
+
if (!r.success)
|
|
50
|
+
throw new Error(r.error ?? 'Failed to create index.');
|
|
51
|
+
}
|
|
52
|
+
static async dropIndex(ctx, indexName) {
|
|
53
|
+
const r = await ctx.db.dropIndex(indexName);
|
|
54
|
+
if (!r.success)
|
|
55
|
+
throw new Error(r.error ?? 'Failed to drop index.');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.SchemaService = SchemaService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './search.types';export * from './search.service';export * from './search.routes';
|
|
@@ -0,0 +1,19 @@
|
|
|
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("./search.types"), exports);
|
|
18
|
+
__exportStar(require("./search.service"), exports);
|
|
19
|
+
__exportStar(require("./search.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerSearchRoutes(router:Router,ctx:RouteContext):void;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSearchRoutes = registerSearchRoutes;
|
|
4
|
+
const router_1 = require("../../core/router");
|
|
5
|
+
const search_service_1 = require("./search.service");
|
|
6
|
+
function registerSearchRoutes(router, ctx) {
|
|
7
|
+
router.get('/api/search/:db', (0, router_1.wrap)(async (req, res) => {
|
|
8
|
+
const db = ctx.getContext(req);
|
|
9
|
+
const query = String(req.query.q ?? '');
|
|
10
|
+
const type = req.query.type ? String(req.query.type) : undefined;
|
|
11
|
+
const limit = req.query.limit ? parseInt(String(req.query.limit), 10) : 50;
|
|
12
|
+
const includeRows = req.query.includeRows !== 'false';
|
|
13
|
+
const basePath = String(res.locals.basePath ?? '');
|
|
14
|
+
try {
|
|
15
|
+
const results = await search_service_1.SearchService.search(db, query, {
|
|
16
|
+
type,
|
|
17
|
+
limit,
|
|
18
|
+
includeRows,
|
|
19
|
+
basePath,
|
|
20
|
+
});
|
|
21
|
+
(0, router_1.ok)(res, results);
|
|
22
|
+
}
|
|
23
|
+
catch (err) {
|
|
24
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to execute global search.', 500);
|
|
25
|
+
}
|
|
26
|
+
}));
|
|
27
|
+
router.get('/api/search/:db/metadata', (0, router_1.wrap)(async (req, res) => {
|
|
28
|
+
const db = ctx.getContext(req);
|
|
29
|
+
const basePath = String(res.locals.basePath ?? '');
|
|
30
|
+
try {
|
|
31
|
+
const index = await search_service_1.SearchService.getMetadataIndex(db, basePath);
|
|
32
|
+
(0, router_1.ok)(res, index);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
(0, router_1.fail)(res, err.message ?? 'Failed to fetch search metadata.', 500);
|
|
36
|
+
}
|
|
37
|
+
}));
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {DatabaseContext} from '../../core/context';import type {SearchOptions,SearchResponse,SearchMetadataIndex,CommandItem} from './search.types';export declare class SearchService{static getCommands(dbId:string,basePath?:string,isReadOnly?:boolean):CommandItem[];static getMetadataIndex(ctx:DatabaseContext,basePath?:string):Promise<SearchMetadataIndex>;private static calculateScore;static search(ctx:DatabaseContext,rawQuery:string,options?:SearchOptions):Promise<SearchResponse>;private static searchRowData;}
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SearchService = void 0;
|
|
4
|
+
const generator_1 = require("../../sql/generator");
|
|
5
|
+
class SearchService {
|
|
6
|
+
static getCommands(dbId, basePath = '', isReadOnly = false) {
|
|
7
|
+
const encDb = encodeURIComponent(dbId);
|
|
8
|
+
const commands = [
|
|
9
|
+
{
|
|
10
|
+
id: 'cmd-query',
|
|
11
|
+
title: 'New SQL Query',
|
|
12
|
+
description: 'Open the SQL query editor to run queries and scripts',
|
|
13
|
+
icon: 'query',
|
|
14
|
+
url: `${basePath}/query/${encDb}`,
|
|
15
|
+
keywords: ['sql', 'query', 'editor', 'execute', 'run', 'select', 'terminal'],
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
id: 'cmd-erd',
|
|
19
|
+
title: 'Entity Relationship Diagram',
|
|
20
|
+
description: 'Visualize database tables, relationships, and schema graph',
|
|
21
|
+
icon: 'diagram',
|
|
22
|
+
url: `${basePath}/erd/${encDb}`,
|
|
23
|
+
keywords: ['erd', 'diagram', 'graph', 'schema', 'relations', 'visualize', 'foreign keys'],
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
id: 'cmd-seed',
|
|
27
|
+
title: 'Seed Data Generator',
|
|
28
|
+
description: 'Generate relational mock and seed data with intelligent heuristics',
|
|
29
|
+
icon: 'seed',
|
|
30
|
+
url: `${basePath}/seed/${encDb}`,
|
|
31
|
+
keywords: ['seed', 'mock', 'fake data', 'generator', 'faker', 'synthetic data'],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
id: 'cmd-diff',
|
|
35
|
+
title: 'Diff & Replicate Database',
|
|
36
|
+
description: 'Compare schemas, generate migration patches, or clone data',
|
|
37
|
+
icon: 'diff',
|
|
38
|
+
url: `${basePath}/diff/${encDb}`,
|
|
39
|
+
keywords: ['diff', 'sync', 'replicate', 'migrate', 'migration', 'patch', 'clone', 'compare'],
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
id: 'cmd-info',
|
|
43
|
+
title: 'Database Info & Settings',
|
|
44
|
+
description: 'View database pragma, engine configuration, and metadata',
|
|
45
|
+
icon: 'settings',
|
|
46
|
+
url: `${basePath}/info/${encDb}`,
|
|
47
|
+
keywords: ['info', 'settings', 'config', 'pragma', 'driver', 'version', 'database info'],
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'cmd-export',
|
|
51
|
+
title: 'Export Database (SQL Dump)',
|
|
52
|
+
description: 'Download full database schema and data as an SQL script',
|
|
53
|
+
icon: 'export',
|
|
54
|
+
url: `${basePath}/export/${encDb}`,
|
|
55
|
+
keywords: ['export', 'dump', 'backup', 'download', 'sql dump', 'save'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
id: 'cmd-home',
|
|
59
|
+
title: 'Database Dashboard',
|
|
60
|
+
description: 'Overview of all tables, total rows, and statistics',
|
|
61
|
+
icon: 'home',
|
|
62
|
+
url: `${basePath}/home/${encDb}`,
|
|
63
|
+
keywords: ['home', 'dashboard', 'overview', 'tables', 'stats'],
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
if (!isReadOnly) {
|
|
67
|
+
commands.push({
|
|
68
|
+
id: 'cmd-designer',
|
|
69
|
+
title: 'Create New Table',
|
|
70
|
+
description: 'Design and create a new database table with custom columns and keys',
|
|
71
|
+
icon: 'plus',
|
|
72
|
+
url: `${basePath}/designer/${encDb}`,
|
|
73
|
+
keywords: ['create table', 'designer', 'new table', 'add table', 'schema designer'],
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return commands;
|
|
77
|
+
}
|
|
78
|
+
static async getMetadataIndex(ctx, basePath = '') {
|
|
79
|
+
const listRes = await ctx.db.listTables();
|
|
80
|
+
const tableItems = listRes.success && listRes.data ? listRes.data : [];
|
|
81
|
+
const tables = [];
|
|
82
|
+
const columns = [];
|
|
83
|
+
const indexes = [];
|
|
84
|
+
const foreignKeys = [];
|
|
85
|
+
await Promise.all(tableItems.map(async (t) => {
|
|
86
|
+
const tableName = t.name;
|
|
87
|
+
try {
|
|
88
|
+
const infoRes = await ctx.db.getTableInfo(tableName);
|
|
89
|
+
if (!infoRes.success || !infoRes.data)
|
|
90
|
+
return;
|
|
91
|
+
const info = infoRes.data;
|
|
92
|
+
tables.push({
|
|
93
|
+
name: tableName,
|
|
94
|
+
columnsCount: info.columns.length,
|
|
95
|
+
primaryKey: info.primaryKey || [],
|
|
96
|
+
});
|
|
97
|
+
for (const col of info.columns) {
|
|
98
|
+
const fk = (info.foreignKeys || []).find((f) => f.from === col.name);
|
|
99
|
+
columns.push({
|
|
100
|
+
table: tableName,
|
|
101
|
+
name: col.name,
|
|
102
|
+
type: col.type || 'TEXT',
|
|
103
|
+
isPk: Boolean(col.pk),
|
|
104
|
+
isFk: Boolean(fk),
|
|
105
|
+
fkTarget: fk ? `${fk.table}.${fk.to || 'id'}` : null,
|
|
106
|
+
notnull: Boolean(col.notnull),
|
|
107
|
+
dflt_value: col.dflt_value,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
for (const idx of info.indexes || []) {
|
|
111
|
+
indexes.push({
|
|
112
|
+
table: tableName,
|
|
113
|
+
name: idx.name,
|
|
114
|
+
unique: Boolean(idx.unique),
|
|
115
|
+
columns: idx.columns || [],
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
for (const fk of info.foreignKeys || []) {
|
|
119
|
+
foreignKeys.push({
|
|
120
|
+
fromTable: tableName,
|
|
121
|
+
fromColumn: fk.from,
|
|
122
|
+
toTable: fk.table,
|
|
123
|
+
toColumn: fk.to || null,
|
|
124
|
+
onUpdate: fk.on_update || 'NO ACTION',
|
|
125
|
+
onDelete: fk.on_delete || 'NO ACTION',
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
}
|
|
131
|
+
}));
|
|
132
|
+
const commands = this.getCommands(ctx.name, basePath, ctx.isReadOnly || ctx.db.isReadOnly);
|
|
133
|
+
return {
|
|
134
|
+
tables,
|
|
135
|
+
columns,
|
|
136
|
+
indexes,
|
|
137
|
+
foreignKeys,
|
|
138
|
+
commands,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
static calculateScore(target, query) {
|
|
142
|
+
if (!target || !query)
|
|
143
|
+
return 0;
|
|
144
|
+
const t = target.toLowerCase();
|
|
145
|
+
const q = query.toLowerCase();
|
|
146
|
+
if (t === q)
|
|
147
|
+
return 100;
|
|
148
|
+
if (t.startsWith(q))
|
|
149
|
+
return 85;
|
|
150
|
+
if (t.includes(`_${q}`) || t.includes(`-${q}`) || t.includes(`.${q}`) || t.includes(` ${q}`))
|
|
151
|
+
return 75;
|
|
152
|
+
if (t.includes(q))
|
|
153
|
+
return 50;
|
|
154
|
+
let qIdx = 0;
|
|
155
|
+
for (let i = 0; i < t.length && qIdx < q.length; i++) {
|
|
156
|
+
if (t[i] === q[qIdx])
|
|
157
|
+
qIdx++;
|
|
158
|
+
}
|
|
159
|
+
if (qIdx === q.length)
|
|
160
|
+
return 25;
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
static async search(ctx, rawQuery, options = {}) {
|
|
164
|
+
const q = String(rawQuery ?? '').trim();
|
|
165
|
+
const basePath = options.basePath ?? '';
|
|
166
|
+
const type = options.type ? options.type.toLowerCase() : 'all';
|
|
167
|
+
const limit = options.limit && options.limit > 0 ? options.limit : 50;
|
|
168
|
+
const includeRows = options.includeRows !== false;
|
|
169
|
+
const encDb = encodeURIComponent(ctx.name);
|
|
170
|
+
const metadata = await this.getMetadataIndex(ctx, basePath);
|
|
171
|
+
const results = [];
|
|
172
|
+
const shouldSearch = (category) => {
|
|
173
|
+
if (type === 'all')
|
|
174
|
+
return true;
|
|
175
|
+
if (type === 'tables' || type === 'table')
|
|
176
|
+
return category === 'table';
|
|
177
|
+
if (type === 'columns' || type === 'column')
|
|
178
|
+
return category === 'column';
|
|
179
|
+
if (type === 'indexes' || type === 'index')
|
|
180
|
+
return category === 'index';
|
|
181
|
+
if (type === 'relations' || type === 'relation' || type === 'foreignkeys')
|
|
182
|
+
return category === 'relation';
|
|
183
|
+
if (type === 'commands' || type === 'command')
|
|
184
|
+
return category === 'command';
|
|
185
|
+
if (type === 'rows' || type === 'row' || type === 'data')
|
|
186
|
+
return category === 'row';
|
|
187
|
+
return false;
|
|
188
|
+
};
|
|
189
|
+
if (shouldSearch('table')) {
|
|
190
|
+
for (const table of metadata.tables) {
|
|
191
|
+
const score = q ? this.calculateScore(table.name, q) : 50;
|
|
192
|
+
if (score > 0 || !q) {
|
|
193
|
+
const encTable = encodeURIComponent(table.name);
|
|
194
|
+
results.push({
|
|
195
|
+
id: `tbl-${table.name}`,
|
|
196
|
+
category: 'table',
|
|
197
|
+
title: table.name,
|
|
198
|
+
subtitle: `Table · ${table.columnsCount} columns${table.primaryKey.length ? ` · PK (${table.primaryKey.join(', ')})` : ''}`,
|
|
199
|
+
icon: 'table',
|
|
200
|
+
url: `${basePath}/tables/${encDb}/${encTable}`,
|
|
201
|
+
score: score + 15,
|
|
202
|
+
badge: 'Table',
|
|
203
|
+
badgeType: 'primary',
|
|
204
|
+
actions: [
|
|
205
|
+
{ label: 'Browse', url: `${basePath}/tables/${encDb}/${encTable}`, primary: true },
|
|
206
|
+
{ label: 'Schema', url: `${basePath}/schema/${encDb}/${encTable}` },
|
|
207
|
+
{ label: 'Query', url: `${basePath}/query/${encDb}?sql=${encodeURIComponent(`SELECT * FROM ${table.name} LIMIT 100;`)}` },
|
|
208
|
+
],
|
|
209
|
+
metadata: {
|
|
210
|
+
table: table.name,
|
|
211
|
+
columnsCount: table.columnsCount,
|
|
212
|
+
primaryKey: table.primaryKey,
|
|
213
|
+
},
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (shouldSearch('column')) {
|
|
219
|
+
for (const col of metadata.columns) {
|
|
220
|
+
let score = 0;
|
|
221
|
+
if (q) {
|
|
222
|
+
const nameScore = this.calculateScore(col.name, q);
|
|
223
|
+
const fullScore = this.calculateScore(`${col.table}.${col.name}`, q);
|
|
224
|
+
const typeScore = this.calculateScore(col.type, q) > 70 ? 40 : 0;
|
|
225
|
+
score = Math.max(nameScore, fullScore, typeScore);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
score = 30;
|
|
229
|
+
}
|
|
230
|
+
if (score > 0 || !q) {
|
|
231
|
+
const encTable = encodeURIComponent(col.table);
|
|
232
|
+
const pkTag = col.isPk ? ' · Primary Key' : '';
|
|
233
|
+
const fkTag = col.isFk ? ` · FK → ${col.fkTarget}` : '';
|
|
234
|
+
const nullTag = col.notnull ? ' · NOT NULL' : '';
|
|
235
|
+
const dfltTag = col.dflt_value ? ` · default ${col.dflt_value}` : '';
|
|
236
|
+
results.push({
|
|
237
|
+
id: `col-${col.table}-${col.name}`,
|
|
238
|
+
category: 'column',
|
|
239
|
+
title: col.name,
|
|
240
|
+
subtitle: `${col.table} · ${col.type}${pkTag}${fkTag}${nullTag}${dfltTag}`,
|
|
241
|
+
icon: 'columns',
|
|
242
|
+
url: `${basePath}/tables/${encDb}/${encTable}`,
|
|
243
|
+
score: score + 10,
|
|
244
|
+
badge: col.isPk ? 'PK' : col.isFk ? 'FK' : col.type,
|
|
245
|
+
badgeType: col.isPk ? 'accent' : col.isFk ? 'info' : 'neutral',
|
|
246
|
+
actions: [
|
|
247
|
+
{ label: 'Browse Table', url: `${basePath}/tables/${encDb}/${encTable}`, primary: true },
|
|
248
|
+
{ label: 'View Schema', url: `${basePath}/schema/${encDb}/${encTable}` },
|
|
249
|
+
],
|
|
250
|
+
metadata: {
|
|
251
|
+
table: col.table,
|
|
252
|
+
column: col.name,
|
|
253
|
+
type: col.type,
|
|
254
|
+
isPk: col.isPk,
|
|
255
|
+
isFk: col.isFk,
|
|
256
|
+
fkTarget: col.fkTarget,
|
|
257
|
+
},
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (shouldSearch('index')) {
|
|
263
|
+
for (const idx of metadata.indexes) {
|
|
264
|
+
let score = 0;
|
|
265
|
+
if (q) {
|
|
266
|
+
const nameScore = this.calculateScore(idx.name, q);
|
|
267
|
+
const colsScore = idx.columns.some((c) => this.calculateScore(c, q) > 60) ? 60 : 0;
|
|
268
|
+
score = Math.max(nameScore, colsScore);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
score = 25;
|
|
272
|
+
}
|
|
273
|
+
if (score > 0 || !q) {
|
|
274
|
+
const encTable = encodeURIComponent(idx.table);
|
|
275
|
+
results.push({
|
|
276
|
+
id: `idx-${idx.table}-${idx.name}`,
|
|
277
|
+
category: 'index',
|
|
278
|
+
title: idx.name,
|
|
279
|
+
subtitle: `${idx.table} (${idx.columns.join(', ')}) · ${idx.unique ? 'UNIQUE ' : ''}Index`,
|
|
280
|
+
icon: 'key',
|
|
281
|
+
url: `${basePath}/schema/${encDb}/${encTable}`,
|
|
282
|
+
score,
|
|
283
|
+
badge: idx.unique ? 'Unique Index' : 'Index',
|
|
284
|
+
badgeType: idx.unique ? 'warning' : 'neutral',
|
|
285
|
+
actions: [
|
|
286
|
+
{ label: 'View in Schema', url: `${basePath}/schema/${encDb}/${encTable}`, primary: true },
|
|
287
|
+
{ label: 'Browse Table', url: `${basePath}/tables/${encDb}/${encTable}` },
|
|
288
|
+
],
|
|
289
|
+
metadata: {
|
|
290
|
+
table: idx.table,
|
|
291
|
+
index: idx.name,
|
|
292
|
+
unique: idx.unique,
|
|
293
|
+
columns: idx.columns,
|
|
294
|
+
},
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
if (shouldSearch('relation')) {
|
|
300
|
+
for (const fk of metadata.foreignKeys) {
|
|
301
|
+
let score = 0;
|
|
302
|
+
if (q) {
|
|
303
|
+
const fromScore = this.calculateScore(`${fk.fromTable}.${fk.fromColumn}`, q);
|
|
304
|
+
const toScore = this.calculateScore(`${fk.toTable}.${fk.toColumn || 'id'}`, q);
|
|
305
|
+
score = Math.max(fromScore, toScore);
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
score = 25;
|
|
309
|
+
}
|
|
310
|
+
if (score > 0 || !q) {
|
|
311
|
+
const fromEnc = encodeURIComponent(fk.fromTable);
|
|
312
|
+
results.push({
|
|
313
|
+
id: `fk-${fk.fromTable}-${fk.fromColumn}-${fk.toTable}`,
|
|
314
|
+
category: 'relation',
|
|
315
|
+
title: `${fk.fromTable}.${fk.fromColumn} → ${fk.toTable}.${fk.toColumn || 'id'}`,
|
|
316
|
+
subtitle: `Foreign Key · ON UPDATE ${fk.onUpdate}, ON DELETE ${fk.onDelete}`,
|
|
317
|
+
icon: 'diagram',
|
|
318
|
+
url: `${basePath}/erd/${encDb}`,
|
|
319
|
+
score,
|
|
320
|
+
badge: 'Relation',
|
|
321
|
+
badgeType: 'info',
|
|
322
|
+
actions: [
|
|
323
|
+
{ label: 'View ERD', url: `${basePath}/erd/${encDb}`, primary: true },
|
|
324
|
+
{ label: `Browse ${fk.fromTable}`, url: `${basePath}/tables/${encDb}/${fromEnc}` },
|
|
325
|
+
],
|
|
326
|
+
metadata: {
|
|
327
|
+
fromTable: fk.fromTable,
|
|
328
|
+
fromColumn: fk.fromColumn,
|
|
329
|
+
toTable: fk.toTable,
|
|
330
|
+
toColumn: fk.toColumn,
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
if (shouldSearch('command')) {
|
|
337
|
+
for (const cmd of metadata.commands) {
|
|
338
|
+
let score = 0;
|
|
339
|
+
if (q) {
|
|
340
|
+
const titleScore = this.calculateScore(cmd.title, q);
|
|
341
|
+
const descScore = this.calculateScore(cmd.description, q) > 70 ? 40 : 0;
|
|
342
|
+
const kwScore = cmd.keywords.some((k) => this.calculateScore(k, q) > 70) ? 70 : 0;
|
|
343
|
+
score = Math.max(titleScore, descScore, kwScore);
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
score = 45;
|
|
347
|
+
}
|
|
348
|
+
if (score > 0 || !q) {
|
|
349
|
+
results.push({
|
|
350
|
+
id: cmd.id,
|
|
351
|
+
category: 'command',
|
|
352
|
+
title: cmd.title,
|
|
353
|
+
subtitle: cmd.description,
|
|
354
|
+
icon: cmd.icon,
|
|
355
|
+
url: cmd.url,
|
|
356
|
+
score: score + 5,
|
|
357
|
+
badge: 'Command',
|
|
358
|
+
badgeType: 'secondary',
|
|
359
|
+
actions: [{ label: 'Execute', url: cmd.url, primary: true }],
|
|
360
|
+
metadata: {
|
|
361
|
+
keywords: cmd.keywords,
|
|
362
|
+
},
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
if (q.length >= 2 && includeRows && shouldSearch('row')) {
|
|
368
|
+
try {
|
|
369
|
+
const rowResults = await this.searchRowData(ctx, q, metadata.tables, metadata.columns, basePath, limit);
|
|
370
|
+
results.push(...rowResults);
|
|
371
|
+
}
|
|
372
|
+
catch {
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
results.sort((a, b) => b.score - a.score);
|
|
376
|
+
const categoryCounts = {
|
|
377
|
+
table: 0,
|
|
378
|
+
column: 0,
|
|
379
|
+
index: 0,
|
|
380
|
+
relation: 0,
|
|
381
|
+
command: 0,
|
|
382
|
+
row: 0,
|
|
383
|
+
};
|
|
384
|
+
for (const r of results) {
|
|
385
|
+
categoryCounts[r.category] = (categoryCounts[r.category] || 0) + 1;
|
|
386
|
+
}
|
|
387
|
+
const slicedResults = results.slice(0, limit);
|
|
388
|
+
return {
|
|
389
|
+
query: q,
|
|
390
|
+
total: results.length,
|
|
391
|
+
categories: categoryCounts,
|
|
392
|
+
results: slicedResults,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
395
|
+
static async searchRowData(ctx, query, tables, columns, basePath, limit) {
|
|
396
|
+
const rowResults = [];
|
|
397
|
+
const encDb = encodeURIComponent(ctx.name);
|
|
398
|
+
const maxTablesToSearch = 12;
|
|
399
|
+
const maxRowsPerTable = 3;
|
|
400
|
+
const searchableTables = tables.slice(0, maxTablesToSearch);
|
|
401
|
+
for (const table of searchableTables) {
|
|
402
|
+
if (rowResults.length >= limit)
|
|
403
|
+
break;
|
|
404
|
+
const textCols = columns.filter((c) => c.table === table.name &&
|
|
405
|
+
/(text|char|varchar|nvarchar|string|clob|json|blob)/i.test(c.type || 'TEXT'));
|
|
406
|
+
if (textCols.length === 0)
|
|
407
|
+
continue;
|
|
408
|
+
const colNames = textCols.map((c) => c.name);
|
|
409
|
+
const whereClauses = colNames.map((c) => `${(0, generator_1.quoteIdentifier)(c)} LIKE ?`).join(' OR ');
|
|
410
|
+
const params = colNames.map(() => `%${query}%`);
|
|
411
|
+
const selectSql = `SELECT rowid AS _rowid_, * FROM ${(0, generator_1.quoteIdentifier)(table.name)} WHERE ${whereClauses} LIMIT ${maxRowsPerTable}`;
|
|
412
|
+
try {
|
|
413
|
+
const res = await ctx.db.all(selectSql, params);
|
|
414
|
+
if (res.success && Array.isArray(res.data)) {
|
|
415
|
+
for (const row of res.data) {
|
|
416
|
+
let matchedCol = colNames[0];
|
|
417
|
+
let matchedVal = '';
|
|
418
|
+
for (const colName of colNames) {
|
|
419
|
+
const val = String(row[colName] ?? '');
|
|
420
|
+
if (val.toLowerCase().includes(query.toLowerCase())) {
|
|
421
|
+
matchedCol = colName;
|
|
422
|
+
matchedVal = val;
|
|
423
|
+
break;
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const snippet = matchedVal.length > 80 ? `${matchedVal.slice(0, 80)}…` : matchedVal;
|
|
427
|
+
const rowId = row._rowid_ ?? row.id ?? row[table.primaryKey[0]] ?? '';
|
|
428
|
+
const encTable = encodeURIComponent(table.name);
|
|
429
|
+
rowResults.push({
|
|
430
|
+
id: `row-${table.name}-${rowId}-${matchedCol}`,
|
|
431
|
+
category: 'row',
|
|
432
|
+
title: `${table.name}${rowId ? ` #${rowId}` : ''}: ${matchedCol} = "${snippet}"`,
|
|
433
|
+
subtitle: `Row data match in ${table.name}.${matchedCol}`,
|
|
434
|
+
icon: 'inspect',
|
|
435
|
+
url: `${basePath}/tables/${encDb}/${encTable}`,
|
|
436
|
+
score: 35,
|
|
437
|
+
badge: 'Row Data',
|
|
438
|
+
badgeType: 'neutral',
|
|
439
|
+
actions: [
|
|
440
|
+
{ label: 'Browse Table', url: `${basePath}/tables/${encDb}/${encTable}`, primary: true },
|
|
441
|
+
],
|
|
442
|
+
metadata: {
|
|
443
|
+
table: table.name,
|
|
444
|
+
column: matchedCol,
|
|
445
|
+
value: snippet,
|
|
446
|
+
row,
|
|
447
|
+
},
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
catch {
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return rowResults;
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
exports.SearchService = SearchService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type SearchCategory='table'|'column'|'index'|'relation'|'command'|'row';export interface SearchAction{label:string;url:string;icon?:string;primary?:boolean;}export interface SearchItem{id:string;category:SearchCategory;title:string;subtitle:string;icon:string;url:string;score:number;badge?:string;badgeType?:'primary'|'secondary'|'accent'|'info'|'success'|'warning'|'error'|'neutral';actions?:SearchAction[];metadata?:Record<string,unknown>;}export interface SearchResponse{query:string;total:number;categories:Record<SearchCategory,number>;results:SearchItem[];}export interface SearchOptions{query?:string;type?:string;limit?:number;includeRows?:boolean;basePath?:string;}export interface TableSummary{name:string;columnsCount:number;rowCount?:number;primaryKey:string[];}export interface ColumnSummary{table:string;name:string;type:string;isPk:boolean;isFk:boolean;fkTarget?:string|null;notnull:boolean;dflt_value:string|null;}export interface IndexSummary{table:string;name:string;unique:boolean;columns:string[];}export interface ForeignKeySummary{fromTable:string;fromColumn:string;toTable:string;toColumn:string|null;onUpdate:string;onDelete:string;}export interface CommandItem{id:string;title:string;description:string;icon:string;url:string;keywords:string[];shortcut?:string;}export interface SearchMetadataIndex{tables:TableSummary[];columns:ColumnSummary[];indexes:IndexSummary[];foreignKeys:ForeignKeySummary[];commands:CommandItem[];}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './seed.service';export * from './seed.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("./seed.service"), exports);
|
|
18
|
+
__exportStar(require("./seed.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerSeedRoutes(router:Router,ctx:RouteContext):void;
|