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.
Files changed (170) hide show
  1. package/LICENSE +20 -20
  2. package/dist/app.js +48 -25
  3. package/dist/core/context.d.ts +1 -0
  4. package/dist/core/context.js +2 -0
  5. package/dist/core/diff/data-differ.d.ts +1 -0
  6. package/dist/core/diff/data-differ.js +243 -0
  7. package/dist/core/diff/patch-generator.d.ts +1 -0
  8. package/dist/core/diff/patch-generator.js +77 -0
  9. package/dist/core/diff/schema-differ.d.ts +1 -0
  10. package/dist/core/diff/schema-differ.js +229 -0
  11. package/dist/core/errors.d.ts +1 -0
  12. package/dist/core/errors.js +39 -0
  13. package/dist/core/index.d.ts +1 -0
  14. package/dist/core/index.js +20 -0
  15. package/dist/core/result.d.ts +1 -0
  16. package/dist/core/result.js +10 -0
  17. package/dist/core/router.d.ts +1 -0
  18. package/dist/core/router.js +37 -0
  19. package/dist/core/transfer/dialect-mapper.d.ts +1 -0
  20. package/dist/core/transfer/dialect-mapper.js +303 -0
  21. package/dist/core/transfer/masking.d.ts +1 -0
  22. package/dist/core/transfer/masking.js +49 -0
  23. package/dist/core/transfer/sync-engine.d.ts +1 -0
  24. package/dist/core/transfer/sync-engine.js +234 -0
  25. package/dist/core/transfer/topological-sort.d.ts +1 -0
  26. package/dist/core/transfer/topological-sort.js +55 -0
  27. package/dist/data/chain.js +1 -1
  28. package/dist/data/engine.js +1 -1
  29. package/dist/db/database.d.ts +1 -1
  30. package/dist/db/database.js +9 -31
  31. package/dist/db/dialects/index.d.ts +1 -0
  32. package/dist/db/dialects/index.js +58 -0
  33. package/dist/db/dialects/postgres/ddl.d.ts +1 -0
  34. package/dist/db/dialects/postgres/ddl.js +107 -0
  35. package/dist/db/dialects/postgres/dialect.d.ts +1 -0
  36. package/dist/db/dialects/postgres/dialect.js +28 -0
  37. package/dist/db/dialects/postgres/introspector.d.ts +1 -0
  38. package/dist/db/dialects/postgres/introspector.js +265 -0
  39. package/dist/db/dialects/postgres/types.d.ts +1 -0
  40. package/dist/db/dialects/postgres/types.js +83 -0
  41. package/dist/db/dialects/sqlite/ddl.d.ts +1 -0
  42. package/dist/db/dialects/sqlite/ddl.js +244 -0
  43. package/dist/db/dialects/sqlite/dialect.d.ts +1 -0
  44. package/dist/db/dialects/sqlite/dialect.js +25 -0
  45. package/dist/db/dialects/sqlite/introspector.d.ts +1 -0
  46. package/dist/db/dialects/sqlite/introspector.js +217 -0
  47. package/dist/db/dialects/sqlite/types.d.ts +1 -0
  48. package/dist/db/dialects/sqlite/types.js +69 -0
  49. package/dist/db/dialects/types.d.ts +1 -0
  50. package/dist/db/dialects/types.js +15 -0
  51. package/dist/db/export.js +1 -1
  52. package/dist/db/index.d.ts +1 -1
  53. package/dist/db/index.js +1 -1
  54. package/dist/db/manager.d.ts +1 -1
  55. package/dist/db/manager.js +101 -21
  56. package/dist/db/postgres.d.ts +1 -1
  57. package/dist/db/postgres.js +20 -284
  58. package/dist/db/types.d.ts +1 -1
  59. package/dist/db/types.js +0 -4
  60. package/dist/index.d.ts +1 -1
  61. package/dist/index.js +4 -1
  62. package/dist/modules/databases/databases.routes.d.ts +1 -0
  63. package/dist/modules/databases/databases.routes.js +150 -0
  64. package/dist/modules/databases/databases.service.d.ts +1 -0
  65. package/dist/modules/databases/databases.service.js +134 -0
  66. package/dist/modules/databases/index.d.ts +1 -0
  67. package/dist/{routes → modules/databases}/index.js +2 -3
  68. package/dist/modules/erd/erd.routes.d.ts +1 -0
  69. package/dist/modules/erd/erd.routes.js +32 -0
  70. package/dist/modules/erd/erd.service.d.ts +1 -0
  71. package/dist/modules/erd/erd.service.js +39 -0
  72. package/dist/modules/erd/index.d.ts +1 -0
  73. package/dist/modules/erd/index.js +18 -0
  74. package/dist/modules/index.d.ts +1 -0
  75. package/dist/modules/index.js +70 -0
  76. package/dist/modules/query/index.d.ts +1 -0
  77. package/dist/modules/query/index.js +18 -0
  78. package/dist/modules/query/query.routes.d.ts +1 -0
  79. package/dist/modules/query/query.routes.js +54 -0
  80. package/dist/modules/query/query.service.d.ts +1 -0
  81. package/dist/modules/query/query.service.js +95 -0
  82. package/dist/modules/rows/blob.helper.d.ts +1 -0
  83. package/dist/modules/rows/blob.helper.js +40 -0
  84. package/dist/modules/rows/display.helper.d.ts +1 -0
  85. package/dist/modules/rows/display.helper.js +73 -0
  86. package/dist/modules/rows/index.d.ts +1 -0
  87. package/dist/modules/rows/index.js +20 -0
  88. package/dist/modules/rows/rows.routes.d.ts +1 -0
  89. package/dist/modules/rows/rows.routes.js +555 -0
  90. package/dist/modules/rows/rows.service.d.ts +1 -0
  91. package/dist/modules/rows/rows.service.js +433 -0
  92. package/dist/modules/schema/index.d.ts +1 -0
  93. package/dist/modules/schema/index.js +18 -0
  94. package/dist/modules/schema/schema.routes.d.ts +1 -0
  95. package/dist/modules/schema/schema.routes.js +171 -0
  96. package/dist/modules/schema/schema.service.d.ts +1 -0
  97. package/dist/modules/schema/schema.service.js +58 -0
  98. package/dist/modules/search/index.d.ts +1 -0
  99. package/dist/modules/search/index.js +19 -0
  100. package/dist/modules/search/search.routes.d.ts +1 -0
  101. package/dist/modules/search/search.routes.js +38 -0
  102. package/dist/modules/search/search.service.d.ts +1 -0
  103. package/dist/modules/search/search.service.js +458 -0
  104. package/dist/modules/search/search.types.d.ts +1 -0
  105. package/dist/modules/search/search.types.js +2 -0
  106. package/dist/modules/seed/index.d.ts +1 -0
  107. package/dist/modules/seed/index.js +18 -0
  108. package/dist/modules/seed/seed.routes.d.ts +1 -0
  109. package/dist/modules/seed/seed.routes.js +212 -0
  110. package/dist/modules/seed/seed.service.d.ts +1 -0
  111. package/dist/{routes/api/helpers.js → modules/seed/seed.service.js} +86 -109
  112. package/dist/modules/sync/index.d.ts +1 -0
  113. package/dist/modules/sync/index.js +18 -0
  114. package/dist/modules/sync/sync.routes.d.ts +1 -0
  115. package/dist/modules/sync/sync.routes.js +135 -0
  116. package/dist/modules/sync/sync.service.d.ts +1 -0
  117. package/dist/modules/sync/sync.service.js +41 -0
  118. package/dist/modules/tables/index.d.ts +1 -0
  119. package/dist/modules/tables/index.js +18 -0
  120. package/dist/modules/tables/tables.routes.d.ts +1 -0
  121. package/dist/modules/tables/tables.routes.js +194 -0
  122. package/dist/modules/tables/tables.service.d.ts +1 -0
  123. package/dist/modules/tables/tables.service.js +218 -0
  124. package/dist/public/css/app.css +1 -1
  125. package/dist/public/js/browse.js +1 -1
  126. package/dist/public/js/query.js +1 -1
  127. package/dist/public/js/search.js +1 -0
  128. package/dist/public/js/sync.js +1 -0
  129. package/dist/types/api.d.ts +1 -1
  130. package/dist/utils/common.d.ts +1 -1
  131. package/dist/utils/common.js +1 -3
  132. package/dist/utils/icons.js +3 -0
  133. package/dist/utils/stream.d.ts +1 -0
  134. package/dist/utils/stream.js +131 -0
  135. package/dist/views/layouts/main.hbs +1 -1
  136. package/dist/views/pages/error.hbs +10 -1
  137. package/dist/views/pages/home.hbs +1 -1
  138. package/dist/views/pages/query.hbs +1 -1
  139. package/dist/views/pages/seed-select.hbs +1 -252
  140. package/dist/views/pages/seed.hbs +1 -307
  141. package/dist/views/pages/sync.hbs +409 -0
  142. package/dist/views/pages/table.hbs +1 -1
  143. package/dist/views/partials/navbar.hbs +1 -1
  144. package/dist/views/partials/sidebar.hbs +1 -1
  145. package/docs/API.md +4 -17
  146. package/docs/EXAMPLES.md +579 -579
  147. package/docs/SECURITY.md +265 -265
  148. package/package.json +10 -9
  149. package/dist/db/introspection.d.ts +0 -1
  150. package/dist/db/introspection.js +0 -164
  151. package/dist/routes/api/erd.d.ts +0 -1
  152. package/dist/routes/api/erd.js +0 -45
  153. package/dist/routes/api/helpers.d.ts +0 -1
  154. package/dist/routes/api/import-export.d.ts +0 -1
  155. package/dist/routes/api/import-export.js +0 -84
  156. package/dist/routes/api/index.d.ts +0 -1
  157. package/dist/routes/api/index.js +0 -55
  158. package/dist/routes/api/query.d.ts +0 -1
  159. package/dist/routes/api/query.js +0 -77
  160. package/dist/routes/api/rows.d.ts +0 -1
  161. package/dist/routes/api/rows.js +0 -373
  162. package/dist/routes/api/seed.d.ts +0 -1
  163. package/dist/routes/api/seed.js +0 -180
  164. package/dist/routes/api/tables.d.ts +0 -1
  165. package/dist/routes/api/tables.js +0 -230
  166. package/dist/routes/databases.d.ts +0 -1
  167. package/dist/routes/databases.js +0 -212
  168. package/dist/routes/index.d.ts +0 -1
  169. package/dist/routes/pages.d.ts +0 -1
  170. package/dist/routes/pages.js +0 -540
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 Muhammad Bin Khalid
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Muhammad Bin Khalid
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
21
  SOFTWARE.
package/dist/app.js CHANGED
@@ -10,14 +10,13 @@ const express_handlebars_1 = require("express-handlebars");
10
10
  const database_1 = require("./db/database");
11
11
  const postgres_1 = require("./db/postgres");
12
12
  const logger_1 = require("./utils/logger");
13
- const pages_1 = require("./routes/pages");
14
- const index_1 = require("./routes/api/index");
15
- const databases_1 = require("./routes/databases");
13
+ const index_1 = require("./modules/index");
16
14
  const args_1 = require("./cli/args");
17
15
  const common_1 = require("./utils/common");
18
16
  const icons_1 = require("./utils/icons");
19
17
  const serverless_1 = require("./serverless");
20
18
  const auth_1 = require("./auth");
19
+ const index_2 = require("./db/dialects/index");
21
20
  const APP_VERSION = (0, args_1.getPackageVersion)();
22
21
  function createRouter(options = {}) {
23
22
  const isServerless = options.serverless !== undefined ? options.serverless : (0, serverless_1.isServerlessEnvironment)();
@@ -60,10 +59,15 @@ function createRouter(options = {}) {
60
59
  else {
61
60
  singleDb = new database_1.SqliteDatabase(options.dbPath ?? 'admindb.db', logger, { readonly: globalReadonly });
62
61
  }
63
- if (!defaultDbId) {
62
+ if (!defaultDbId && singleDb) {
64
63
  defaultDbId = singleDb.dialect === 'postgres' ? 'PostgreSQL' : (node_path_1.default.basename(singleDb.path) || 'SQLite');
65
64
  }
66
65
  }
66
+ let singleDialect;
67
+ if (singleDb) {
68
+ const pgSchema = singleDb.schema || 'public';
69
+ singleDialect = (0, index_2.getDialect)(singleDb.dialect, singleDb.dialect === 'postgres' ? pgSchema : undefined);
70
+ }
67
71
  const app = (0, express_1.default)();
68
72
  app.disable('x-powered-by');
69
73
  app.use(express_1.default.static(node_path_1.default.join(__dirname, 'public')));
@@ -88,14 +92,28 @@ function createRouter(options = {}) {
88
92
  res.locals.isDefaultPassword = authConfig.isDefaultPassword;
89
93
  res.locals.icons = icons_1.ICONS;
90
94
  res.locals.tables = [];
91
- res.locals.internalTables = [];
92
95
  next();
93
96
  }
94
97
  catch (err) {
95
98
  next(err);
96
99
  }
97
100
  });
98
- const getDb = (req) => {
101
+ if (manager) {
102
+ (0, index_1.registerDatabasesRoutes)(app, {
103
+ manager,
104
+ logger,
105
+ basePath,
106
+ readonly: globalReadonly,
107
+ allowBrowse: isServerless ? false : options.allowBrowse,
108
+ browseRoot: options.browseRoot,
109
+ });
110
+ }
111
+ else {
112
+ app.get('/', (_req, res) => {
113
+ res.redirect(302, `${basePath}/home/${encodeURIComponent(defaultDbId)}`);
114
+ });
115
+ }
116
+ const getContext = (req) => {
99
117
  if (manager) {
100
118
  const dbId = String(req.params.db || req.params.dbId || req.params.id || '');
101
119
  if (!manager.has(dbId)) {
@@ -111,27 +129,32 @@ function createRouter(options = {}) {
111
129
  }
112
130
  }
113
131
  const effectiveRo = globalReadonly ? true : manager.isDbReadOnly(dbId);
114
- return manager.open(dbId, effectiveRo);
132
+ return manager.getContext(dbId, effectiveRo);
115
133
  }
116
- return singleDb;
117
- };
118
- if (manager) {
119
- (0, databases_1.registerDatabasesRoutes)(app, {
120
- manager,
134
+ return {
135
+ id: defaultDbId,
136
+ name: defaultDbId,
137
+ path: singleDb.path,
138
+ db: singleDb,
139
+ dialect: singleDialect,
140
+ capabilities: singleDialect.capabilities,
141
+ isReadOnly: singleDb.isReadOnly,
121
142
  logger,
122
- basePath,
123
- readonly: globalReadonly,
124
- allowBrowse: isServerless ? false : options.allowBrowse,
125
- browseRoot: options.browseRoot,
126
- });
127
- }
128
- else {
129
- app.get('/', (_req, res) => {
130
- res.redirect(302, `${basePath}/home/${encodeURIComponent(defaultDbId)}`);
131
- });
132
- }
133
- (0, pages_1.registerPages)(app, { getDb, logger });
134
- (0, index_1.registerApi)(app, { getDb, logger });
143
+ };
144
+ };
145
+ const getNamedContext = (dbId, readonlyOverride) => {
146
+ if (manager) {
147
+ if (!manager.has(dbId)) {
148
+ const err = new Error(`Database "${dbId}" does not exist.`);
149
+ err.status = 404;
150
+ throw err;
151
+ }
152
+ const effectiveRo = globalReadonly ? true : manager.isDbReadOnly(dbId);
153
+ return manager.getContext(dbId, readonlyOverride !== undefined ? readonlyOverride : effectiveRo);
154
+ }
155
+ return getContext({ params: { db: defaultDbId } });
156
+ };
157
+ (0, index_1.registerModules)(app, { getContext, getNamedContext, manager, logger });
135
158
  addErrorHandlers(app, logger);
136
159
  return app;
137
160
  }
@@ -0,0 +1 @@
1
+ import type {Logger} from '../utils/logger';import type {IDatabase} from '../db/types';import type {IDialect,DialectCapabilities} from '../db/dialects/types';export interface DatabaseContext{readonly id:string;readonly name:string;readonly path:string;readonly db:IDatabase;readonly dialect:IDialect;readonly capabilities:DialectCapabilities;readonly isReadOnly:boolean;readonly logger:Logger;}
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1 @@
1
+ import type {IDatabase} from '../../db/types';export interface RowValueChange{column:string;sourceValue:unknown;targetValue:unknown;}export interface RowDiffItem{key:string;action:'added'|'removed'|'modified';sourceRow?:Record<string,unknown>;targetRow?:Record<string,unknown>;changes?:RowValueChange[];}export interface TableDataDiffResult{table:string;primaryKeys:string[];sourceRowCount:number;targetRowCount:number;addedCount:number;removedCount:number;modifiedCount:number;identicalCount:number;rows:RowDiffItem[];unifiedDiff:string;}export declare function areValuesEqual(a:unknown,b:unknown):boolean;export declare function compareTableData(sourceDb:IDatabase,targetDb:IDatabase,table:string,limit?:number):Promise<TableDataDiffResult>;
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.areValuesEqual = areValuesEqual;
4
+ exports.compareTableData = compareTableData;
5
+ function areValuesEqual(a, b) {
6
+ if (a === b)
7
+ return true;
8
+ if ((a === null || a === undefined) && (b === null || b === undefined))
9
+ return true;
10
+ if (a === null || a === undefined || b === null || b === undefined)
11
+ return false;
12
+ const isBoolLikeA = typeof a === 'boolean' || a === 0 || a === 1 || a === '0' || a === '1' || a === 'true' || a === 'false';
13
+ const isBoolLikeB = typeof b === 'boolean' || b === 0 || b === 1 || b === '0' || b === '1' || b === 'true' || b === 'false';
14
+ if ((typeof a === 'boolean' || typeof b === 'boolean') && isBoolLikeA && isBoolLikeB) {
15
+ const boolA = a === true || a === 1 || a === '1' || a === 'true';
16
+ const boolB = b === true || b === 1 || b === '1' || b === 'true';
17
+ return boolA === boolB;
18
+ }
19
+ if (typeof a === 'number' && typeof b === 'number') {
20
+ return Math.abs(a - b) < 0.000001;
21
+ }
22
+ if ((typeof a === 'number' || typeof a === 'bigint') && typeof b === 'string') {
23
+ if (b.trim() !== '' && !isNaN(Number(b)) && Number(a) === Number(b))
24
+ return true;
25
+ }
26
+ if ((typeof b === 'number' || typeof b === 'bigint') && typeof a === 'string') {
27
+ if (a.trim() !== '' && !isNaN(Number(a)) && Number(a) === Number(b))
28
+ return true;
29
+ }
30
+ const isDateA = a instanceof Date;
31
+ const isDateB = b instanceof Date;
32
+ if (isDateA || isDateB) {
33
+ const timeA = isDateA ? a.getTime() : new Date(String(a)).getTime();
34
+ const timeB = isDateB ? b.getTime() : new Date(String(b)).getTime();
35
+ if (!isNaN(timeA) && !isNaN(timeB))
36
+ return timeA === timeB;
37
+ }
38
+ if (typeof a === 'string' && typeof b === 'string') {
39
+ if (a.length >= 10 && b.length >= 10 && /^\d{4}-\d{2}-\d{2}/.test(a) && /^\d{4}-\d{2}-\d{2}/.test(b)) {
40
+ const timeA = new Date(a).getTime();
41
+ const timeB = new Date(b).getTime();
42
+ if (!isNaN(timeA) && !isNaN(timeB))
43
+ return timeA === timeB;
44
+ }
45
+ }
46
+ if (typeof a === 'object' || typeof b === 'object') {
47
+ if (Buffer.isBuffer(a) || a instanceof Uint8Array || Buffer.isBuffer(b) || b instanceof Uint8Array) {
48
+ const bufA = Buffer.isBuffer(a) ? a : Buffer.from(a);
49
+ const bufB = Buffer.isBuffer(b) ? b : Buffer.from(b);
50
+ return bufA.equals(bufB);
51
+ }
52
+ try {
53
+ const strA = typeof a === 'string' ? a : JSON.stringify(a);
54
+ const strB = typeof b === 'string' ? b : JSON.stringify(b);
55
+ return JSON.stringify(JSON.parse(strA)) === JSON.stringify(JSON.parse(strB));
56
+ }
57
+ catch {
58
+ }
59
+ }
60
+ return String(a).trim() === String(b).trim();
61
+ }
62
+ async function compareTableData(sourceDb, targetDb, table, limit = 200) {
63
+ const [srcInfoRes, tgtInfoRes] = await Promise.all([
64
+ sourceDb.getTableInfo(table),
65
+ targetDb.getTableInfo(table),
66
+ ]);
67
+ const srcInfo = srcInfoRes.success && srcInfoRes.data ? srcInfoRes.data : null;
68
+ const tgtInfo = tgtInfoRes.success && tgtInfoRes.data ? tgtInfoRes.data : null;
69
+ if (!srcInfo && !tgtInfo) {
70
+ throw new Error(`Table "${table}" does not exist in either source or target database.`);
71
+ }
72
+ if (srcInfo && (!tgtInfo || tgtInfo.columns.length === 0)) {
73
+ const srcCountRes = await sourceDb.getRowCount(table);
74
+ const srcRowCount = srcCountRes.data || 0;
75
+ const srcRowsRes = await sourceDb.getRows(table, { limit });
76
+ const srcRows = srcRowsRes.data || [];
77
+ const pks = srcInfo.primaryKey.length > 0 ? srcInfo.primaryKey : ['id'];
78
+ const rows = srcRows.map((r, idx) => ({
79
+ key: pks.map((k) => String(r[k] ?? '')).join('::') || `row_${idx + 1}`,
80
+ action: 'added',
81
+ sourceRow: r,
82
+ }));
83
+ const lines = [
84
+ `diff --git a/${table}/data b/${table}/data`,
85
+ `@@ Table: ${table} (MISSING IN TARGET DATABASE) @@`,
86
+ ` Target table "${table}" does not exist. All ${srcRowCount} rows are newly added.`,
87
+ ];
88
+ for (const r of rows.slice(0, 100)) {
89
+ lines.push(`+ [PK: ${r.key}] ${JSON.stringify(r.sourceRow)}`);
90
+ }
91
+ return {
92
+ table,
93
+ primaryKeys: pks,
94
+ sourceRowCount: srcRowCount,
95
+ targetRowCount: 0,
96
+ addedCount: rows.length,
97
+ removedCount: 0,
98
+ modifiedCount: 0,
99
+ identicalCount: 0,
100
+ rows,
101
+ unifiedDiff: lines.join('\n'),
102
+ };
103
+ }
104
+ if (!srcInfo && tgtInfo) {
105
+ const tgtCountRes = await targetDb.getRowCount(table);
106
+ const tgtRowCount = tgtCountRes.data || 0;
107
+ const tgtRowsRes = await targetDb.getRows(table, { limit });
108
+ const tgtRows = tgtRowsRes.data || [];
109
+ const pks = tgtInfo.primaryKey.length > 0 ? tgtInfo.primaryKey : ['id'];
110
+ const rows = tgtRows.map((r, idx) => ({
111
+ key: pks.map((k) => String(r[k] ?? '')).join('::') || `row_${idx + 1}`,
112
+ action: 'removed',
113
+ targetRow: r,
114
+ }));
115
+ const lines = [
116
+ `diff --git a/${table}/data b/${table}/data`,
117
+ `@@ Table: ${table} (MISSING IN SOURCE DATABASE) @@`,
118
+ ` Source table "${table}" does not exist. All ${tgtRowCount} target rows would be removed.`,
119
+ ];
120
+ for (const r of rows.slice(0, 100)) {
121
+ lines.push(`- [PK: ${r.key}] ${JSON.stringify(r.targetRow)}`);
122
+ }
123
+ return {
124
+ table,
125
+ primaryKeys: pks,
126
+ sourceRowCount: 0,
127
+ targetRowCount: tgtRowCount,
128
+ addedCount: 0,
129
+ removedCount: rows.length,
130
+ modifiedCount: 0,
131
+ identicalCount: 0,
132
+ rows,
133
+ unifiedDiff: lines.join('\n'),
134
+ };
135
+ }
136
+ const srcColNames = srcInfo.columns.map((c) => c.name);
137
+ const tgtColNames = tgtInfo.columns.map((c) => c.name);
138
+ let pks = srcInfo.primaryKey.length > 0 ? srcInfo.primaryKey : tgtInfo.primaryKey;
139
+ if (!pks || pks.length === 0) {
140
+ const hasId = srcColNames.some((c) => c.toLowerCase() === 'id');
141
+ if (hasId) {
142
+ pks = ['id'];
143
+ }
144
+ else if (srcColNames.length > 0) {
145
+ pks = srcColNames;
146
+ }
147
+ else {
148
+ pks = ['row_idx'];
149
+ }
150
+ }
151
+ const orderCol = pks[0] && srcColNames.includes(pks[0]) && tgtColNames.includes(pks[0]) ? pks[0] : undefined;
152
+ const [srcCountRes, tgtCountRes, srcRowsRes, tgtRowsRes] = await Promise.all([
153
+ sourceDb.getRowCount(table),
154
+ targetDb.getRowCount(table),
155
+ sourceDb.getRows(table, { limit, orderBy: orderCol, orderDir: 'asc' }),
156
+ targetDb.getRows(table, { limit, orderBy: orderCol, orderDir: 'asc' }),
157
+ ]);
158
+ const srcRows = srcRowsRes.data || [];
159
+ const tgtRows = tgtRowsRes.data || [];
160
+ const makeKey = (row, fallbackIdx) => {
161
+ const keyParts = pks.map((k) => {
162
+ const v = row[k];
163
+ return v === null || v === undefined ? '' : String(v);
164
+ });
165
+ const key = keyParts.join('::');
166
+ return key.trim() ? key : `row_${fallbackIdx}`;
167
+ };
168
+ const srcMap = new Map();
169
+ srcRows.forEach((r, idx) => srcMap.set(makeKey(r, idx + 1), r));
170
+ const tgtMap = new Map();
171
+ tgtRows.forEach((r, idx) => tgtMap.set(makeKey(r, idx + 1), r));
172
+ const allKeys = Array.from(new Set([...srcMap.keys(), ...tgtMap.keys()]));
173
+ const diffItems = [];
174
+ let addedCount = 0;
175
+ let removedCount = 0;
176
+ let modifiedCount = 0;
177
+ let identicalCount = 0;
178
+ for (const key of allKeys) {
179
+ const sr = srcMap.get(key);
180
+ const tr = tgtMap.get(key);
181
+ if (sr && !tr) {
182
+ addedCount++;
183
+ diffItems.push({ key, action: 'added', sourceRow: sr });
184
+ continue;
185
+ }
186
+ if (!sr && tr) {
187
+ removedCount++;
188
+ diffItems.push({ key, action: 'removed', targetRow: tr });
189
+ continue;
190
+ }
191
+ if (sr && tr) {
192
+ const changes = [];
193
+ const cols = Array.from(new Set([...Object.keys(sr), ...Object.keys(tr)]));
194
+ for (const col of cols) {
195
+ const sv = sr[col];
196
+ const tv = tr[col];
197
+ if (!areValuesEqual(sv, tv)) {
198
+ changes.push({ column: col, sourceValue: sv, targetValue: tv });
199
+ }
200
+ }
201
+ if (changes.length > 0) {
202
+ modifiedCount++;
203
+ diffItems.push({ key, action: 'modified', sourceRow: sr, targetRow: tr, changes });
204
+ }
205
+ else {
206
+ identicalCount++;
207
+ }
208
+ }
209
+ }
210
+ const lines = [
211
+ `diff --git a/${table}/data b/${table}/data`,
212
+ `@@ Data Diff for Table: ${table} (Key: ${pks.join(', ')}) @@`,
213
+ ];
214
+ for (const item of diffItems.slice(0, 100)) {
215
+ if (item.action === 'added') {
216
+ lines.push(`+ [PK: ${item.key}] ${JSON.stringify(item.sourceRow)}`);
217
+ }
218
+ else if (item.action === 'removed') {
219
+ lines.push(`- [PK: ${item.key}] ${JSON.stringify(item.targetRow)}`);
220
+ }
221
+ else if (item.action === 'modified') {
222
+ const changesStr = (item.changes || [])
223
+ .map((c) => `${c.column}: ${JSON.stringify(c.targetValue)} -> ${JSON.stringify(c.sourceValue)}`)
224
+ .join(', ');
225
+ lines.push(`~ [PK: ${item.key}] ${changesStr}`);
226
+ }
227
+ }
228
+ if (diffItems.length === 0) {
229
+ lines.push(' Rows are identical (up to preview limit).');
230
+ }
231
+ return {
232
+ table,
233
+ primaryKeys: pks,
234
+ sourceRowCount: srcCountRes.data || 0,
235
+ targetRowCount: tgtCountRes.data || 0,
236
+ addedCount,
237
+ removedCount,
238
+ modifiedCount,
239
+ identicalCount,
240
+ rows: diffItems,
241
+ unifiedDiff: lines.join('\n'),
242
+ };
243
+ }
@@ -0,0 +1 @@
1
+ import type {SchemaDiffResult} from './schema-differ';import{type SupportedDialect} from '../transfer/dialect-mapper';export interface GeneratedPatch{upSql:string;downSql:string;}export declare function generateSchemaPatch(diff:SchemaDiffResult,targetDialect:SupportedDialect):GeneratedPatch;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateSchemaPatch = generateSchemaPatch;
4
+ const dialect_mapper_1 = require("../transfer/dialect-mapper");
5
+ function generateSchemaPatch(diff, targetDialect) {
6
+ const upLines = [`-- UP Migration: Align "${diff.targetDbId}" with "${diff.sourceDbId}" (${targetDialect})`];
7
+ const downLines = [`-- DOWN Rollback: Revert changes on "${diff.targetDbId}"`];
8
+ const q = targetDialect === 'postgres' ? (s) => `"${s}"` : (s) => `\`${s}\``;
9
+ for (const table of diff.tables) {
10
+ if (table.action === 'unchanged')
11
+ continue;
12
+ if (table.action === 'added' && table.sourceTable) {
13
+ const createSql = (0, dialect_mapper_1.generateCreateTableForDialect)(table.sourceTable, targetDialect);
14
+ upLines.push(`\n-- Create table ${table.name}`);
15
+ upLines.push(createSql);
16
+ for (const idx of table.sourceTable.indexes || []) {
17
+ if (idx.name?.startsWith('sqlite_'))
18
+ continue;
19
+ const colsArr = Array.isArray(idx?.columns) ? idx.columns.filter(Boolean) : [];
20
+ if (colsArr.length === 0)
21
+ continue;
22
+ const uq = idx.unique ? 'UNIQUE ' : '';
23
+ const cols = colsArr.map(q).join(', ');
24
+ upLines.push(`CREATE ${uq}INDEX IF NOT EXISTS ${q(idx.name)} ON ${q(table.name)} (${cols});`);
25
+ }
26
+ downLines.push(`\n-- Revert: Drop created table ${table.name}`);
27
+ downLines.push(`DROP TABLE IF EXISTS ${q(table.name)};`);
28
+ continue;
29
+ }
30
+ if (table.action === 'removed' && table.targetTable) {
31
+ upLines.push(`\n-- Drop obsolete table ${table.name}`);
32
+ upLines.push(`DROP TABLE IF EXISTS ${q(table.name)};`);
33
+ const recreateSql = (0, dialect_mapper_1.generateCreateTableForDialect)(table.targetTable, targetDialect);
34
+ downLines.push(`\n-- Revert: Re-create dropped table ${table.name}`);
35
+ downLines.push(recreateSql);
36
+ continue;
37
+ }
38
+ if (table.action === 'modified') {
39
+ upLines.push(`\n-- Modify table ${table.name}`);
40
+ for (const col of table.columnDiffs) {
41
+ if (col.action === 'added' && col.sourceCol) {
42
+ const typeStr = (0, dialect_mapper_1.mapColumnToDialect)(col.sourceCol, targetDialect);
43
+ const notNullStr = col.sourceCol.notNull ? ' NOT NULL' : '';
44
+ const mappedDefault = (0, dialect_mapper_1.mapDefaultValueForDialect)(col.sourceCol.defaultValue, col.sourceCol.genericType, targetDialect);
45
+ const dfltStr = mappedDefault !== null && mappedDefault !== undefined ? ` DEFAULT ${mappedDefault}` : '';
46
+ upLines.push(`ALTER TABLE ${q(table.name)} ADD COLUMN ${q(col.name)} ${typeStr}${notNullStr}${dfltStr};`);
47
+ downLines.push(`ALTER TABLE ${q(table.name)} DROP COLUMN IF EXISTS ${q(col.name)};`);
48
+ }
49
+ else if (col.action === 'removed' && col.targetCol) {
50
+ upLines.push(`ALTER TABLE ${q(table.name)} DROP COLUMN IF EXISTS ${q(col.name)};`);
51
+ const typeStr = (0, dialect_mapper_1.mapColumnToDialect)(col.targetCol, targetDialect);
52
+ downLines.push(`ALTER TABLE ${q(table.name)} ADD COLUMN ${q(col.name)} ${typeStr};`);
53
+ }
54
+ }
55
+ for (const idx of table.sourceTable?.indexes || []) {
56
+ if (idx.name?.startsWith('sqlite_'))
57
+ continue;
58
+ if (table.indexesAdded.includes(idx.name)) {
59
+ const colsArr = Array.isArray(idx?.columns) ? idx.columns.filter(Boolean) : [];
60
+ if (colsArr.length === 0)
61
+ continue;
62
+ const uq = idx.unique ? 'UNIQUE ' : '';
63
+ const cols = colsArr.map(q).join(', ');
64
+ upLines.push(`CREATE ${uq}INDEX IF NOT EXISTS ${q(idx.name)} ON ${q(table.name)} (${cols});`);
65
+ downLines.push(`DROP INDEX IF EXISTS ${q(idx.name)};`);
66
+ }
67
+ }
68
+ for (const idxName of table.indexesRemoved) {
69
+ upLines.push(`DROP INDEX IF EXISTS ${q(idxName)};`);
70
+ }
71
+ }
72
+ }
73
+ return {
74
+ upSql: upLines.join('\n'),
75
+ downSql: downLines.join('\n'),
76
+ };
77
+ }
@@ -0,0 +1 @@
1
+ import type {IDatabase} from '../../db/types';import{type NormalizedColumn,type NormalizedTable,type SupportedDialect} from '../transfer/dialect-mapper';export interface ColumnDiff{name:string;action:'added'|'removed'|'modified';sourceCol?:NormalizedColumn;targetCol?:NormalizedColumn;changes?:string[];}export interface TableDiff{name:string;action:'added'|'removed'|'modified'|'unchanged';sourceTable?:NormalizedTable;targetTable?:NormalizedTable;columnDiffs:ColumnDiff[];pkChanged:boolean;indexesAdded:string[];indexesRemoved:string[];fksAdded:string[];fksRemoved:string[];}export interface SchemaDiffResult{sourceDbId:string;targetDbId:string;sourceDialect:SupportedDialect;targetDialect:SupportedDialect;tables:TableDiff[];stats:{addedTables:number;removedTables:number;modifiedTables:number;identicalTables:number;totalColumnsChanged:number;};unifiedDiff:string;}export declare function compareSchemas(sourceDb:IDatabase,sourceDbId:string,targetDb:IDatabase,targetDbId:string):Promise<SchemaDiffResult>;