admindb 2.5.0 → 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/dist/app.js +13 -1
- 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/router.d.ts +1 -1
- 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/db/dialects/postgres/introspector.js +1 -1
- package/dist/db/dialects/sqlite/introspector.js +6 -3
- package/dist/modules/index.d.ts +1 -1
- package/dist/modules/index.js +9 -1
- 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/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/public/css/app.css +1 -1
- package/dist/public/js/browse.js +1 -1
- package/dist/public/js/search.js +1 -0
- package/dist/public/js/sync.js +1 -0
- package/dist/utils/icons.js +3 -0
- package/dist/views/layouts/main.hbs +1 -1
- 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/package.json +7 -8
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSyncRoutes = registerSyncRoutes;
|
|
4
|
+
const router_1 = require("../../core/router");
|
|
5
|
+
const tables_service_1 = require("../tables/tables.service");
|
|
6
|
+
const sync_service_1 = require("./sync.service");
|
|
7
|
+
function registerSyncRoutes(router, ctx) {
|
|
8
|
+
const getDb = (dbId, readonly = false) => {
|
|
9
|
+
if (ctx.getNamedContext) {
|
|
10
|
+
return ctx.getNamedContext(dbId, readonly);
|
|
11
|
+
}
|
|
12
|
+
return ctx.getContext({ params: { db: dbId } });
|
|
13
|
+
};
|
|
14
|
+
const renderSyncPage = async (req, res, next) => {
|
|
15
|
+
try {
|
|
16
|
+
const activeDbId = req.params.db || res.locals.dbId || '';
|
|
17
|
+
let currentCtx = null;
|
|
18
|
+
let allNames = [];
|
|
19
|
+
try {
|
|
20
|
+
if (activeDbId) {
|
|
21
|
+
currentCtx = getDb(activeDbId);
|
|
22
|
+
const tablesList = await tables_service_1.TableService.listTables(currentCtx);
|
|
23
|
+
allNames = tablesList.map((t) => t.name);
|
|
24
|
+
(0, router_1.initPageLocals)(res, currentCtx, activeDbId, allNames);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
catch { }
|
|
28
|
+
const databases = sync_service_1.SyncService.listAvailableDatabases(ctx.manager, currentCtx
|
|
29
|
+
? {
|
|
30
|
+
id: currentCtx.id,
|
|
31
|
+
name: currentCtx.name,
|
|
32
|
+
dialect: currentCtx.dialect.name,
|
|
33
|
+
path: currentCtx.path,
|
|
34
|
+
readonly: currentCtx.isReadOnly,
|
|
35
|
+
}
|
|
36
|
+
: undefined);
|
|
37
|
+
res.render('pages/sync', {
|
|
38
|
+
title: 'Diff & Replication Studio',
|
|
39
|
+
dbId: activeDbId,
|
|
40
|
+
databases,
|
|
41
|
+
tables: allNames,
|
|
42
|
+
isSqlite: currentCtx?.dialect.name === 'sqlite',
|
|
43
|
+
isPostgres: currentCtx?.dialect.name === 'postgres',
|
|
44
|
+
readonly: currentCtx ? currentCtx.isReadOnly : false,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
next(err);
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
router.get('/diff', renderSyncPage);
|
|
52
|
+
router.get('/diff/:db', renderSyncPage);
|
|
53
|
+
router.get('/sync', renderSyncPage);
|
|
54
|
+
router.get('/sync/:db', renderSyncPage);
|
|
55
|
+
router.get('/api/sync/databases', (0, router_1.wrap)(async (_req, res) => {
|
|
56
|
+
const databases = sync_service_1.SyncService.listAvailableDatabases(ctx.manager);
|
|
57
|
+
(0, router_1.ok)(res, databases);
|
|
58
|
+
}));
|
|
59
|
+
router.post('/api/sync/diff/schema', (0, router_1.wrap)(async (req, res) => {
|
|
60
|
+
const { sourceDbId, targetDbId } = req.body || {};
|
|
61
|
+
if (!sourceDbId || !targetDbId) {
|
|
62
|
+
return (0, router_1.fail)(res, 'Both sourceDbId and targetDbId are required.');
|
|
63
|
+
}
|
|
64
|
+
const srcCtx = getDb(sourceDbId, true);
|
|
65
|
+
const tgtCtx = getDb(targetDbId, true);
|
|
66
|
+
const diff = await sync_service_1.SyncService.diffSchema(srcCtx.db, sourceDbId, tgtCtx.db, targetDbId);
|
|
67
|
+
(0, router_1.ok)(res, diff);
|
|
68
|
+
}));
|
|
69
|
+
router.post('/api/sync/diff/data', (0, router_1.wrap)(async (req, res) => {
|
|
70
|
+
const { sourceDbId, targetDbId, table, limit } = req.body || {};
|
|
71
|
+
if (!sourceDbId || !targetDbId || !table) {
|
|
72
|
+
return (0, router_1.fail)(res, 'sourceDbId, targetDbId, and table are required.');
|
|
73
|
+
}
|
|
74
|
+
const srcCtx = getDb(sourceDbId, true);
|
|
75
|
+
const tgtCtx = getDb(targetDbId, true);
|
|
76
|
+
const rowLimit = typeof limit === 'number' && limit > 0 ? Math.min(limit, 500) : 100;
|
|
77
|
+
const diff = await sync_service_1.SyncService.diffTableData(srcCtx.db, tgtCtx.db, table, rowLimit);
|
|
78
|
+
(0, router_1.ok)(res, diff);
|
|
79
|
+
}));
|
|
80
|
+
router.post('/api/sync/patch', (0, router_1.wrap)(async (req, res) => {
|
|
81
|
+
const { sourceDbId, targetDbId } = req.body || {};
|
|
82
|
+
if (!sourceDbId || !targetDbId) {
|
|
83
|
+
return (0, router_1.fail)(res, 'Both sourceDbId and targetDbId are required.');
|
|
84
|
+
}
|
|
85
|
+
const srcCtx = getDb(sourceDbId, true);
|
|
86
|
+
const tgtCtx = getDb(targetDbId, true);
|
|
87
|
+
const diff = await sync_service_1.SyncService.diffSchema(srcCtx.db, sourceDbId, tgtCtx.db, targetDbId);
|
|
88
|
+
const targetDialect = tgtCtx.dialect.name === 'postgres' ? 'postgres' : 'sqlite';
|
|
89
|
+
const patch = sync_service_1.SyncService.generatePatch(diff, targetDialect);
|
|
90
|
+
(0, router_1.ok)(res, patch);
|
|
91
|
+
}));
|
|
92
|
+
router.post('/api/sync/dry-run', (0, router_1.wrap)(async (req, res) => {
|
|
93
|
+
const { sourceDbId, targetDbId, mode, tables, maskData } = req.body || {};
|
|
94
|
+
if (!sourceDbId || !targetDbId) {
|
|
95
|
+
return (0, router_1.fail)(res, 'Both sourceDbId and targetDbId are required.');
|
|
96
|
+
}
|
|
97
|
+
const srcCtx = getDb(sourceDbId, true);
|
|
98
|
+
const tgtCtx = getDb(targetDbId, true);
|
|
99
|
+
const result = await sync_service_1.SyncService.replicate(srcCtx.db, sourceDbId, tgtCtx.db, targetDbId, {
|
|
100
|
+
mode,
|
|
101
|
+
tables,
|
|
102
|
+
maskData,
|
|
103
|
+
dryRun: true,
|
|
104
|
+
});
|
|
105
|
+
(0, router_1.ok)(res, result);
|
|
106
|
+
}));
|
|
107
|
+
router.post('/api/sync/execute', (0, router_1.wrap)(async (req, res) => {
|
|
108
|
+
const { sourceDbId, targetDbId, mode, tables, maskData, confirmTargetName } = req.body || {};
|
|
109
|
+
if (!sourceDbId || !targetDbId) {
|
|
110
|
+
return (0, router_1.fail)(res, 'Both sourceDbId and targetDbId are required.');
|
|
111
|
+
}
|
|
112
|
+
if (sourceDbId === targetDbId) {
|
|
113
|
+
return (0, router_1.fail)(res, 'Source and target database cannot be the same.');
|
|
114
|
+
}
|
|
115
|
+
const srcCtx = getDb(sourceDbId, true);
|
|
116
|
+
const tgtCtx = getDb(targetDbId, false);
|
|
117
|
+
if (tgtCtx.isReadOnly) {
|
|
118
|
+
return (0, router_1.fail)(res, `Target database "${targetDbId}" is in read-only mode and cannot be modified.`);
|
|
119
|
+
}
|
|
120
|
+
const isTargetProd = targetDbId.toLowerCase().includes('prod') || targetDbId.toLowerCase().includes('production');
|
|
121
|
+
if (isTargetProd && confirmTargetName !== targetDbId) {
|
|
122
|
+
return (0, router_1.fail)(res, `Target database appears to be PRODUCTION (${targetDbId}). You must type the target database name to confirm.`, 400, { requiresConfirmation: true, targetDbId });
|
|
123
|
+
}
|
|
124
|
+
const result = await sync_service_1.SyncService.replicate(srcCtx.db, sourceDbId, tgtCtx.db, targetDbId, {
|
|
125
|
+
mode,
|
|
126
|
+
tables,
|
|
127
|
+
maskData,
|
|
128
|
+
dryRun: false,
|
|
129
|
+
});
|
|
130
|
+
if (!result.success) {
|
|
131
|
+
return (0, router_1.fail)(res, result.error || 'Replication failed.', 500, { result });
|
|
132
|
+
}
|
|
133
|
+
(0, router_1.ok)(res, result);
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {IDatabase} from '../../db/types';import type {DbManager,DatabaseEntry} from '../../db/manager';import{type SchemaDiffResult} from '../../core/diff/schema-differ';import{type TableDataDiffResult} from '../../core/diff/data-differ';import{type GeneratedPatch} from '../../core/diff/patch-generator';import{type SyncOptions,type SyncResult} from '../../core/transfer/sync-engine';import type {SupportedDialect} from '../../core/transfer/dialect-mapper';export declare class SyncService{static listAvailableDatabases(manager?:DbManager,defaultDb?:{id:string;name:string;dialect:string;path:string;readonly :boolean;}):DatabaseEntry[];static diffSchema(sourceDb:IDatabase,sourceDbId:string,targetDb:IDatabase,targetDbId:string):Promise<SchemaDiffResult>;static diffTableData(sourceDb:IDatabase,targetDb:IDatabase,table:string,limit?:number):Promise<TableDataDiffResult>;static generatePatch(diff:SchemaDiffResult,targetDialect:SupportedDialect):GeneratedPatch;static replicate(sourceDb:IDatabase,sourceDbId:string,targetDb:IDatabase,targetDbId:string,options:SyncOptions):Promise<SyncResult>;}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SyncService = void 0;
|
|
4
|
+
const schema_differ_1 = require("../../core/diff/schema-differ");
|
|
5
|
+
const data_differ_1 = require("../../core/diff/data-differ");
|
|
6
|
+
const patch_generator_1 = require("../../core/diff/patch-generator");
|
|
7
|
+
const sync_engine_1 = require("../../core/transfer/sync-engine");
|
|
8
|
+
class SyncService {
|
|
9
|
+
static listAvailableDatabases(manager, defaultDb) {
|
|
10
|
+
if (manager) {
|
|
11
|
+
return manager.list();
|
|
12
|
+
}
|
|
13
|
+
if (defaultDb) {
|
|
14
|
+
return [
|
|
15
|
+
{
|
|
16
|
+
id: defaultDb.id,
|
|
17
|
+
name: defaultDb.name,
|
|
18
|
+
path: defaultDb.path,
|
|
19
|
+
dialect: defaultDb.dialect === 'postgres' ? 'postgres' : 'sqlite',
|
|
20
|
+
size: 0,
|
|
21
|
+
modified: '',
|
|
22
|
+
readonly: defaultDb.readonly,
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
}
|
|
26
|
+
return [];
|
|
27
|
+
}
|
|
28
|
+
static async diffSchema(sourceDb, sourceDbId, targetDb, targetDbId) {
|
|
29
|
+
return (0, schema_differ_1.compareSchemas)(sourceDb, sourceDbId, targetDb, targetDbId);
|
|
30
|
+
}
|
|
31
|
+
static async diffTableData(sourceDb, targetDb, table, limit = 200) {
|
|
32
|
+
return (0, data_differ_1.compareTableData)(sourceDb, targetDb, table, limit);
|
|
33
|
+
}
|
|
34
|
+
static generatePatch(diff, targetDialect) {
|
|
35
|
+
return (0, patch_generator_1.generateSchemaPatch)(diff, targetDialect);
|
|
36
|
+
}
|
|
37
|
+
static async replicate(sourceDb, sourceDbId, targetDb, targetDbId, options) {
|
|
38
|
+
return (0, sync_engine_1.executeSync)(sourceDb, sourceDbId, targetDb, targetDbId, options);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
exports.SyncService = SyncService;
|