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,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerSeedRoutes = registerSeedRoutes;
|
|
4
|
+
const router_1 = require("../../core/router");
|
|
5
|
+
const tables_service_1 = require("../tables/tables.service");
|
|
6
|
+
const rows_service_1 = require("../rows/rows.service");
|
|
7
|
+
const index_1 = require("../../data/index");
|
|
8
|
+
const seed_service_1 = require("./seed.service");
|
|
9
|
+
function registerSeedRoutes(router, ctx) {
|
|
10
|
+
router.get('/seed/:db', async (req, res, next) => {
|
|
11
|
+
try {
|
|
12
|
+
const db = ctx.getContext(req);
|
|
13
|
+
const dbId = req.params.db;
|
|
14
|
+
res.locals.dbId = dbId;
|
|
15
|
+
const tablesList = await tables_service_1.TableService.listTables(db);
|
|
16
|
+
const allNames = tablesList.map((t) => t.name);
|
|
17
|
+
const { tables: tableNames } = (0, router_1.initPageLocals)(res, db, dbId, allNames);
|
|
18
|
+
if (tableNames.length === 0) {
|
|
19
|
+
return res.render('pages/seed-select', {
|
|
20
|
+
title: 'Seed Data Generator',
|
|
21
|
+
dbId,
|
|
22
|
+
tables: tableNames,
|
|
23
|
+
seedTables: [],
|
|
24
|
+
hasTables: false,
|
|
25
|
+
dialect: db.dialect.name,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const tableStats = await Promise.all(tableNames.map(async (name) => {
|
|
29
|
+
const [info, rowCount] = await Promise.all([
|
|
30
|
+
tables_service_1.TableService.getTableInfo(db, name),
|
|
31
|
+
rows_service_1.RowsService.getRowCount(db, name),
|
|
32
|
+
]);
|
|
33
|
+
const cols = info ? info.columns.length : 0;
|
|
34
|
+
const fks = info ? info.foreignKeys.length : 0;
|
|
35
|
+
return {
|
|
36
|
+
name,
|
|
37
|
+
cols,
|
|
38
|
+
fks,
|
|
39
|
+
rowCount,
|
|
40
|
+
};
|
|
41
|
+
}));
|
|
42
|
+
const defaultTable = req.query.table ? String(req.query.table) : tableStats[0]?.name;
|
|
43
|
+
const defaultMode = req.query.mode === 'chain' ? 'chain' : 'single';
|
|
44
|
+
res.render('pages/seed-select', {
|
|
45
|
+
title: 'Seed Data Generator · Choose Table & Mode',
|
|
46
|
+
dbId,
|
|
47
|
+
tables: tableNames,
|
|
48
|
+
seedTables: tableStats,
|
|
49
|
+
hasTables: true,
|
|
50
|
+
defaultTable,
|
|
51
|
+
defaultMode,
|
|
52
|
+
dialect: db.dialect.name,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
next(err);
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
router.get('/seed/:db/:table', async (req, res, next) => {
|
|
60
|
+
try {
|
|
61
|
+
const db = ctx.getContext(req);
|
|
62
|
+
const dbId = req.params.db;
|
|
63
|
+
const table = req.params.table;
|
|
64
|
+
res.locals.dbId = dbId;
|
|
65
|
+
const tablesList = await tables_service_1.TableService.listTables(db);
|
|
66
|
+
const allNames = tablesList.map((t) => t.name);
|
|
67
|
+
const { tables: allTables } = (0, router_1.initPageLocals)(res, db, dbId, allNames);
|
|
68
|
+
const initialMode = req.query.mode === 'chain' ? 'chain' : 'single';
|
|
69
|
+
const [info, rowCount] = await Promise.all([
|
|
70
|
+
tables_service_1.TableService.getTableInfo(db, table),
|
|
71
|
+
rows_service_1.RowsService.getRowCount(db, table),
|
|
72
|
+
]);
|
|
73
|
+
if (!info) {
|
|
74
|
+
return (0, router_1.notFound)(res, `Table "${table}" does not exist.`);
|
|
75
|
+
}
|
|
76
|
+
const columns = (0, index_1.buildColumnConfigs)(info);
|
|
77
|
+
res.locals.currentTable = table;
|
|
78
|
+
res.render('pages/seed', {
|
|
79
|
+
title: `Seed data · ${table}`,
|
|
80
|
+
dbId,
|
|
81
|
+
table,
|
|
82
|
+
allTables,
|
|
83
|
+
mode: initialMode,
|
|
84
|
+
isChainMode: initialMode === 'chain',
|
|
85
|
+
rowCount,
|
|
86
|
+
colCount: info.columns.length,
|
|
87
|
+
maxRows: index_1.MAX_SEED_ROWS,
|
|
88
|
+
quickCounts: [10, 50, 100, 500, 1000, index_1.MAX_SEED_ROWS],
|
|
89
|
+
seedConfig: { dbId, table, columns, rowCount, maxRows: index_1.MAX_SEED_ROWS, mode: initialMode },
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
catch (err) {
|
|
93
|
+
next(err);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
router.post('/api/seed/:db/validate', (0, router_1.wrap)(async (req, res) => {
|
|
97
|
+
const db = ctx.getContext(req);
|
|
98
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.validatePlan(db, (0, seed_service_1.parseUnifiedGenerationPlan)(req.body)));
|
|
99
|
+
}));
|
|
100
|
+
router.post('/api/seed/:db/preview', (0, router_1.wrap)(async (req, res) => {
|
|
101
|
+
const db = ctx.getContext(req);
|
|
102
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.previewPlan(db, (0, seed_service_1.parseUnifiedGenerationPlan)(req.body)));
|
|
103
|
+
}));
|
|
104
|
+
router.post('/api/seed/:db/generate', (0, router_1.wrap)(async (req, res) => {
|
|
105
|
+
const db = ctx.getContext(req);
|
|
106
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.generatePlan(db, (0, seed_service_1.parseUnifiedGenerationPlan)(req.body)));
|
|
107
|
+
}));
|
|
108
|
+
router.post('/api/seed/:db/execute', (0, router_1.wrap)(async (req, res) => {
|
|
109
|
+
const db = ctx.getContext(req);
|
|
110
|
+
const plan = (0, seed_service_1.parseUnifiedGenerationPlan)(req.body);
|
|
111
|
+
const truncate = Boolean(req.body?.truncate || plan.options?.truncateAll);
|
|
112
|
+
const result = await seed_service_1.SeedService.executePlan(db, plan, truncate);
|
|
113
|
+
(0, router_1.ok)(res, result, 201);
|
|
114
|
+
}));
|
|
115
|
+
const getSeedConfigHandler = (0, router_1.wrap)(async (req, res) => {
|
|
116
|
+
const db = ctx.getContext(req);
|
|
117
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
118
|
+
if (!info)
|
|
119
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
120
|
+
(0, router_1.ok)(res, seed_service_1.SeedService.getSingleTableSeedConfig(info));
|
|
121
|
+
});
|
|
122
|
+
router.get('/api/tables/:db/:table/seed/config', getSeedConfigHandler);
|
|
123
|
+
router.get('/api/tables/:db/:table/seed/plan', getSeedConfigHandler);
|
|
124
|
+
router.post('/api/tables/:db/:table/seed/generate', (0, router_1.wrap)(async (req, res) => {
|
|
125
|
+
const db = ctx.getContext(req);
|
|
126
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
127
|
+
if (!info)
|
|
128
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
129
|
+
const { count, plan } = (0, seed_service_1.parseSeedRequest)(req.body);
|
|
130
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.generateSingleTable(db, info, count, plan));
|
|
131
|
+
}));
|
|
132
|
+
router.post('/api/tables/:db/:table/seed/preview', (0, router_1.wrap)(async (req, res) => {
|
|
133
|
+
const db = ctx.getContext(req);
|
|
134
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
135
|
+
if (!info)
|
|
136
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
137
|
+
const { count, plan } = (0, seed_service_1.parseSeedRequest)(req.body);
|
|
138
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.previewSingleTable(db, info, count, plan));
|
|
139
|
+
}));
|
|
140
|
+
router.post('/api/tables/:db/:table/seed', (0, router_1.wrap)(async (req, res) => {
|
|
141
|
+
const db = ctx.getContext(req);
|
|
142
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
143
|
+
if (!info)
|
|
144
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
145
|
+
const { count, plan, truncate } = (0, seed_service_1.parseSeedRequest)(req.body);
|
|
146
|
+
try {
|
|
147
|
+
const result = await seed_service_1.SeedService.seedSingleTable(db, req.params.table, info, count, plan, truncate);
|
|
148
|
+
(0, router_1.ok)(res, result, 201);
|
|
149
|
+
}
|
|
150
|
+
catch (err) {
|
|
151
|
+
(0, router_1.fail)(res, err.message ?? 'Seed failed.', 400);
|
|
152
|
+
}
|
|
153
|
+
}));
|
|
154
|
+
router.get('/api/tables/:db/:table/seed/chain', (0, router_1.wrap)(async (req, res) => {
|
|
155
|
+
const db = ctx.getContext(req);
|
|
156
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
157
|
+
if (!info)
|
|
158
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
159
|
+
const rawScope = String(req.query.scope ?? 'chain');
|
|
160
|
+
const scope = rawScope === 'ancestors' || rawScope === 'descendants' || rawScope === 'all' || rawScope === 'single'
|
|
161
|
+
? rawScope
|
|
162
|
+
: 'chain';
|
|
163
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.getChainConfig(db, req.params.table, scope));
|
|
164
|
+
}));
|
|
165
|
+
router.post('/api/tables/:db/:table/seed/chain/preview', (0, router_1.wrap)(async (req, res) => {
|
|
166
|
+
const db = ctx.getContext(req);
|
|
167
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
168
|
+
if (!info)
|
|
169
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
170
|
+
const { scope, counts, plans, modes, seed } = (0, seed_service_1.parseChainSeedRequest)(req.body);
|
|
171
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.previewChain(db, req.params.table, scope, counts, plans, modes, seed));
|
|
172
|
+
}));
|
|
173
|
+
router.post('/api/tables/:db/:table/seed/chain/generate', (0, router_1.wrap)(async (req, res) => {
|
|
174
|
+
const db = ctx.getContext(req);
|
|
175
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
176
|
+
if (!info)
|
|
177
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
178
|
+
const { scope, counts, plans, modes, seed } = (0, seed_service_1.parseChainSeedRequest)(req.body);
|
|
179
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.generateChain(db, req.params.table, scope, counts, plans, modes, seed));
|
|
180
|
+
}));
|
|
181
|
+
router.post('/api/tables/:db/:table/seed/chain', (0, router_1.wrap)(async (req, res) => {
|
|
182
|
+
const db = ctx.getContext(req);
|
|
183
|
+
const info = await tables_service_1.TableService.getTableInfo(db, req.params.table);
|
|
184
|
+
if (!info)
|
|
185
|
+
return (0, router_1.fail)(res, `Table "${req.params.table}" does not exist.`, 404);
|
|
186
|
+
const { scope, counts, plans, truncate, modes, seed } = (0, seed_service_1.parseChainSeedRequest)(req.body);
|
|
187
|
+
const result = await seed_service_1.SeedService.executeChain(db, req.params.table, scope, counts, plans, truncate, modes, seed);
|
|
188
|
+
(0, router_1.ok)(res, result, 201);
|
|
189
|
+
}));
|
|
190
|
+
router.get('/api/seed/:db/chain', (0, router_1.wrap)(async (req, res) => {
|
|
191
|
+
const db = ctx.getContext(req);
|
|
192
|
+
const rawRoot = String(req.query.table ?? '');
|
|
193
|
+
const rawScope = String(req.query.scope ?? (rawRoot ? 'chain' : 'all'));
|
|
194
|
+
const scope = rawScope === 'ancestors' || rawScope === 'descendants' || rawScope === 'all' || rawScope === 'single'
|
|
195
|
+
? rawScope
|
|
196
|
+
: 'all';
|
|
197
|
+
const rootTable = rawRoot || (await db.db.listTables()).data?.[0]?.name || '';
|
|
198
|
+
if (!rootTable)
|
|
199
|
+
return (0, router_1.fail)(res, 'No tables found in database.', 404);
|
|
200
|
+
(0, router_1.ok)(res, await seed_service_1.SeedService.getChainConfig(db, rootTable, scope));
|
|
201
|
+
}));
|
|
202
|
+
router.post('/api/seed/:db/chain', (0, router_1.wrap)(async (req, res) => {
|
|
203
|
+
const db = ctx.getContext(req);
|
|
204
|
+
const { scope, counts, plans, truncate, modes, seed } = (0, seed_service_1.parseChainSeedRequest)(req.body);
|
|
205
|
+
const rawRoot = String(req.body?.rootTable ?? '');
|
|
206
|
+
const rootTable = rawRoot || (await db.db.listTables()).data?.[0]?.name || '';
|
|
207
|
+
if (!rootTable)
|
|
208
|
+
return (0, router_1.fail)(res, 'No tables found in database.', 404);
|
|
209
|
+
const result = await seed_service_1.SeedService.executeChain(db, rootTable, scope, counts, plans, truncate, modes, seed);
|
|
210
|
+
(0, router_1.ok)(res, result, 201);
|
|
211
|
+
}));
|
|
212
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {TableInfoData} from '../../db/index';import type {DatabaseContext} from '../../core/context';import{type ColumnPlan,type ErChainScope,type GenerationPlan,type TableGenerationMode} from '../../data/index';export declare function parseSeedRequest(body:unknown):{count:number;plan:Record<string,ColumnPlan>;truncate:boolean;seed?:number|null;};export declare function parseChainSeedRequest(body:unknown):{scope:ErChainScope;counts:Record<string,number>;plans:Record<string,Record<string,ColumnPlan>>;truncate:boolean;modes?:Record<string,TableGenerationMode>;seed?:number|null;};export declare function parseUnifiedGenerationPlan(body:unknown):GenerationPlan;export declare class SeedService{static validatePlan(ctx:DatabaseContext,plan:GenerationPlan):Promise<import("../../data/types").ValidationReport>;static previewPlan(ctx:DatabaseContext,plan:GenerationPlan):Promise<import("../../data/types").ErChainResult>;static generatePlan(ctx:DatabaseContext,plan:GenerationPlan):Promise<import("../../data/types").ErChainResult>;static executePlan(ctx:DatabaseContext,plan:GenerationPlan,truncate?:boolean):Promise<{message:string;inserted:Record<string,number>;totalInserted:number;elapsedMs:number;warnings:string[];}>;static getSingleTableSeedConfig(info:TableInfoData):{table:string;columns:import("../../data/types").ColumnGeneratorConfig[];maxRows:number;};static previewSingleTable(ctx:DatabaseContext,info:TableInfoData,count:number,plan:Record<string,ColumnPlan>):Promise<{table:string;previewRows:Record<string,unknown>[];warnings:string[];}>;static generateSingleTable(ctx:DatabaseContext,info:TableInfoData,count:number,plan:Record<string,ColumnPlan>):Promise<{table:string;count:number;sql:string;previewRows:Record<string,unknown>[];warnings:string[];}>;static seedSingleTable(ctx:DatabaseContext,table:string,info:TableInfoData,count:number,plan:Record<string,ColumnPlan>,truncate:boolean):Promise<{message:string;inserted:number;skipped:number;warnings:string[];elapsedMs:number;}>;static getChainConfig(ctx:DatabaseContext,table:string,scope:ErChainScope):Promise<import("../../data/types").ErChainConfig>;static previewChain(ctx:DatabaseContext,table:string,scope:ErChainScope,counts:Record<string,number>,plans:Record<string,Record<string,ColumnPlan>>,modes?:Record<string,TableGenerationMode>,seed?:number|null):Promise<import("../../data/types").ErChainResult>;static generateChain(ctx:DatabaseContext,table:string,scope:ErChainScope,counts:Record<string,number>,plans:Record<string,Record<string,ColumnPlan>>,modes?:Record<string,TableGenerationMode>,seed?:number|null):Promise<import("../../data/types").ErChainResult>;static executeChain(ctx:DatabaseContext,table:string,scope:ErChainScope,counts:Record<string,number>,plans:Record<string,Record<string,ColumnPlan>>,truncate:boolean,modes?:Record<string,TableGenerationMode>,seed?:number|null):Promise<{message:string;inserted:Record<string,number>;totalInserted:number;elapsedMs:number;warnings:string[];}>;}
|
|
@@ -1,74 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.requireTable = requireTable;
|
|
5
|
-
exports.buildFields = buildFields;
|
|
6
|
-
exports.buildUpdateFields = buildUpdateFields;
|
|
7
|
-
exports.pkWhere = pkWhere;
|
|
3
|
+
exports.SeedService = void 0;
|
|
8
4
|
exports.parseSeedRequest = parseSeedRequest;
|
|
9
5
|
exports.parseChainSeedRequest = parseChainSeedRequest;
|
|
10
6
|
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
7
|
const generator_1 = require("../../sql/generator");
|
|
16
|
-
const
|
|
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;
|
|
8
|
+
const index_1 = require("../../data/index");
|
|
72
9
|
function parseSeedRequest(body) {
|
|
73
10
|
const b = (body ?? {});
|
|
74
11
|
const count = Math.max(1, Math.min(index_1.MAX_SEED_ROWS, Number.parseInt(String(b.count ?? '10'), 10) || 10));
|
|
@@ -144,12 +81,7 @@ function parseUnifiedGenerationPlan(body) {
|
|
|
144
81
|
columns[colName] = sanitized;
|
|
145
82
|
}
|
|
146
83
|
}
|
|
147
|
-
tables[tableName] = {
|
|
148
|
-
mode,
|
|
149
|
-
rows,
|
|
150
|
-
columns,
|
|
151
|
-
truncate: Boolean(spec.truncate),
|
|
152
|
-
};
|
|
84
|
+
tables[tableName] = { mode, rows, columns, truncate: Boolean(spec.truncate) };
|
|
153
85
|
}
|
|
154
86
|
}
|
|
155
87
|
const rawSeed = b.seed !== undefined ? b.seed : rawPlan.options?.seed;
|
|
@@ -170,44 +102,89 @@ function parseUnifiedGenerationPlan(body) {
|
|
|
170
102
|
},
|
|
171
103
|
};
|
|
172
104
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
}
|
|
183
|
-
async
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
105
|
+
class SeedService {
|
|
106
|
+
static async validatePlan(ctx, plan) {
|
|
107
|
+
return (0, index_1.validateGenerationPlan)(ctx.db, plan);
|
|
108
|
+
}
|
|
109
|
+
static async previewPlan(ctx, plan) {
|
|
110
|
+
return index_1.SeedEngine.executePlan(ctx.db, plan, { previewLimit: 50 });
|
|
111
|
+
}
|
|
112
|
+
static async generatePlan(ctx, plan) {
|
|
113
|
+
return index_1.SeedEngine.executePlan(ctx.db, plan);
|
|
114
|
+
}
|
|
115
|
+
static async executePlan(ctx, plan, truncate = false) {
|
|
116
|
+
const gen = await index_1.SeedEngine.executePlan(ctx.db, plan, { truncateAll: truncate });
|
|
117
|
+
const exec = await (0, index_1.executeChainInsert)(ctx.db, gen, truncate);
|
|
118
|
+
return {
|
|
119
|
+
message: `${truncate ? 'Cleared tables and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
|
|
120
|
+
inserted: exec.inserted,
|
|
121
|
+
totalInserted: exec.totalInserted,
|
|
122
|
+
elapsedMs: exec.elapsedMs,
|
|
123
|
+
warnings: exec.warnings,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
static getSingleTableSeedConfig(info) {
|
|
127
|
+
return { table: info.table, columns: (0, index_1.buildColumnConfigs)(info), maxRows: index_1.MAX_SEED_ROWS };
|
|
128
|
+
}
|
|
129
|
+
static async previewSingleTable(ctx, info, count, plan) {
|
|
130
|
+
const configs = (0, index_1.buildColumnConfigs)(info);
|
|
131
|
+
const gen = await (0, index_1.generateRows)(ctx.db, info, configs, Math.min(50, count), plan);
|
|
132
|
+
return { table: info.table, previewRows: gen.previewRows, warnings: gen.warnings };
|
|
133
|
+
}
|
|
134
|
+
static async generateSingleTable(ctx, info, count, plan) {
|
|
135
|
+
const configs = (0, index_1.buildColumnConfigs)(info);
|
|
136
|
+
const gen = await (0, index_1.generateRows)(ctx.db, info, configs, count, plan);
|
|
137
|
+
return { table: info.table, count: gen.rows.length, sql: (0, index_1.buildSeedInsertSql)(info.table, gen.rows), previewRows: gen.previewRows, warnings: gen.warnings };
|
|
138
|
+
}
|
|
139
|
+
static async seedSingleTable(ctx, table, info, count, plan, truncate) {
|
|
140
|
+
const { db } = ctx;
|
|
141
|
+
const configs = (0, index_1.buildColumnConfigs)(info);
|
|
142
|
+
const t0 = Date.now();
|
|
143
|
+
if (truncate) {
|
|
144
|
+
const clearRes = await db.run(`DELETE FROM ${(0, generator_1.quoteIdentifier)(table)}`);
|
|
145
|
+
if (!clearRes.success) {
|
|
146
|
+
throw new Error(clearRes.error ?? 'Failed to clear table before seeding.');
|
|
147
|
+
}
|
|
205
148
|
}
|
|
206
|
-
|
|
207
|
-
|
|
149
|
+
const gen = await (0, index_1.generateRows)(db, info, configs, count, plan, truncate);
|
|
150
|
+
const result = await db.insertRows(table, gen.rows);
|
|
151
|
+
if (!result.success)
|
|
152
|
+
throw new Error(result.error ?? 'Seed failed.');
|
|
153
|
+
const elapsedMs = Date.now() - t0;
|
|
154
|
+
return {
|
|
155
|
+
message: `${truncate ? 'Cleared table and inserted' : 'Inserted'} ${result.data?.inserted ?? 0} row(s) in ${elapsedMs}ms.`,
|
|
156
|
+
inserted: result.data?.inserted ?? 0,
|
|
157
|
+
skipped: result.data?.skipped ?? 0,
|
|
158
|
+
warnings: gen.warnings,
|
|
159
|
+
elapsedMs,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
static async getChainConfig(ctx, table, scope) {
|
|
163
|
+
return (0, index_1.getErChainConfig)(ctx.db, table, scope);
|
|
164
|
+
}
|
|
165
|
+
static async previewChain(ctx, table, scope, counts, plans, modes, seed) {
|
|
166
|
+
const config = await (0, index_1.getErChainConfig)(ctx.db, table, scope);
|
|
167
|
+
const previewCounts = {};
|
|
168
|
+
for (const t of config.tables) {
|
|
169
|
+
previewCounts[t.name] = Math.min(50, counts[t.name] ?? t.suggestedCount ?? 10);
|
|
208
170
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
171
|
+
return (0, index_1.generateChainRows)(ctx.db, config, plans, previewCounts, false, modes, seed);
|
|
172
|
+
}
|
|
173
|
+
static async generateChain(ctx, table, scope, counts, plans, modes, seed) {
|
|
174
|
+
const config = await (0, index_1.getErChainConfig)(ctx.db, table, scope);
|
|
175
|
+
return (0, index_1.generateChainRows)(ctx.db, config, plans, counts, false, modes, seed);
|
|
176
|
+
}
|
|
177
|
+
static async executeChain(ctx, table, scope, counts, plans, truncate, modes, seed) {
|
|
178
|
+
const config = await (0, index_1.getErChainConfig)(ctx.db, table, scope);
|
|
179
|
+
const gen = await (0, index_1.generateChainRows)(ctx.db, config, plans, counts, truncate, modes, seed);
|
|
180
|
+
const exec = await (0, index_1.executeChainInsert)(ctx.db, gen, truncate);
|
|
181
|
+
return {
|
|
182
|
+
message: `${truncate ? 'Cleared tables in reverse order and seeded' : 'Seeded'} ${exec.totalInserted} row(s) across ${Object.keys(exec.inserted).length} table(s) in ${exec.elapsedMs}ms.`,
|
|
183
|
+
inserted: exec.inserted,
|
|
184
|
+
totalInserted: exec.totalInserted,
|
|
185
|
+
elapsedMs: exec.elapsedMs,
|
|
186
|
+
warnings: exec.warnings,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
213
189
|
}
|
|
190
|
+
exports.SeedService = SeedService;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './sync.service';export * from './sync.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("./sync.service"), exports);
|
|
18
|
+
__exportStar(require("./sync.routes"), exports);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import type {Router} from 'express';import{type RouteContext} from '../../core/router';export declare function registerSyncRoutes(router:Router,ctx:RouteContext):void;
|
|
@@ -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;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './tables.service';export * from './tables.routes';
|