nanodb-orm 0.0.3 → 0.0.5
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/README.md +506 -333
- package/dist/cli.d.ts +96 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +348 -0
- package/dist/cli.js.map +1 -0
- package/dist/constants/index.d.ts +7 -54
- package/dist/constants/index.d.ts.map +1 -1
- package/dist/constants/index.js +9 -61
- package/dist/constants/index.js.map +1 -1
- package/dist/core/config.d.ts +3 -13
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +5 -27
- package/dist/core/config.js.map +1 -1
- package/dist/core/connection.d.ts +16 -31
- package/dist/core/connection.d.ts.map +1 -1
- package/dist/core/connection.js +42 -78
- package/dist/core/connection.js.map +1 -1
- package/dist/core/index.d.ts +2 -3
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +3 -18
- package/dist/core/index.js.map +1 -1
- package/dist/index.d.ts +235 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +252 -35
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +127 -18
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +280 -47
- package/dist/init.js.map +1 -1
- package/dist/jest.setup.d.ts +4 -0
- package/dist/jest.setup.d.ts.map +1 -0
- package/dist/jest.setup.js +40 -0
- package/dist/jest.setup.js.map +1 -0
- package/dist/types/errors.d.ts +30 -12
- package/dist/types/errors.d.ts.map +1 -1
- package/dist/types/errors.js +98 -23
- package/dist/types/errors.js.map +1 -1
- package/dist/types/index.d.ts +268 -4
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +5 -4
- package/dist/types/index.js.map +1 -1
- package/dist/utils/error-handler.d.ts +6 -31
- package/dist/utils/error-handler.d.ts.map +1 -1
- package/dist/utils/error-handler.js +24 -81
- package/dist/utils/error-handler.js.map +1 -1
- package/dist/utils/index.d.ts +1 -3
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +4 -6
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/logger.d.ts +6 -25
- package/dist/utils/logger.d.ts.map +1 -1
- package/dist/utils/logger.js +20 -38
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/migrations.d.ts +16 -90
- package/dist/utils/migrations.d.ts.map +1 -1
- package/dist/utils/migrations.js +220 -422
- package/dist/utils/migrations.js.map +1 -1
- package/dist/utils/schema-introspection.d.ts +30 -169
- package/dist/utils/schema-introspection.d.ts.map +1 -1
- package/dist/utils/schema-introspection.js +125 -462
- package/dist/utils/schema-introspection.js.map +1 -1
- package/dist/utils/seeds.d.ts +15 -48
- package/dist/utils/seeds.d.ts.map +1 -1
- package/dist/utils/seeds.js +108 -186
- package/dist/utils/seeds.js.map +1 -1
- package/dist/utils/sync.d.ts +16 -41
- package/dist/utils/sync.d.ts.map +1 -1
- package/dist/utils/sync.js +78 -172
- package/dist/utils/sync.js.map +1 -1
- package/dist/utils/transactions.d.ts +61 -44
- package/dist/utils/transactions.d.ts.map +1 -1
- package/dist/utils/transactions.js +103 -137
- package/dist/utils/transactions.js.map +1 -1
- package/package.json +29 -10
- package/dist/example.d.ts +0 -67
- package/dist/example.d.ts.map +0 -1
- package/dist/example.js +0 -86
- package/dist/example.js.map +0 -1
- package/dist/types/database.d.ts +0 -74
- package/dist/types/database.d.ts.map +0 -1
- package/dist/types/database.js +0 -6
- package/dist/types/database.js.map +0 -1
- package/dist/types/types.d.ts +0 -30
- package/dist/types/types.d.ts.map +0 -1
- package/dist/types/types.js +0 -6
- package/dist/types/types.js.map +0 -1
- package/llm.txt +0 -336
package/dist/utils/migrations.js
CHANGED
|
@@ -1,457 +1,255 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Completely Generic Database Migration Utilities
|
|
4
|
-
* Uses Drizzle to auto-create tables from schema
|
|
5
|
-
* No hardcoded table names or structures - completely dynamic!
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.DatabaseMigrations = void 0;
|
|
9
4
|
const drizzle_orm_1 = require("drizzle-orm");
|
|
10
5
|
const connection_1 = require("../core/connection");
|
|
11
6
|
const logger_1 = require("./logger");
|
|
12
|
-
const errors_1 = require("../types/errors");
|
|
13
7
|
const error_handler_1 = require("./error-handler");
|
|
14
8
|
const transactions_1 = require("./transactions");
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
// Get all tables from the provided schema
|
|
37
|
-
const tableNames = Object.keys(this.tables);
|
|
38
|
-
logger_1.logger.info(`Found ${tableNames.length} tables in schema: ${tableNames.join(', ')}`);
|
|
39
|
-
// Use Drizzle to auto-create tables
|
|
40
|
-
for (const tableName of tableNames) {
|
|
41
|
-
await this.createTableWithDrizzle(tableName, db);
|
|
42
|
-
}
|
|
43
|
-
logger_1.logger.info('Database schema initialization completed!');
|
|
44
|
-
}, {
|
|
45
|
-
operation: 'init-schema',
|
|
46
|
-
context: 'Schema initialization failed'
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
/**
|
|
50
|
-
* Create a table using Drizzle's table definition
|
|
51
|
-
* Uses the actual Drizzle table object to ensure correct SQL generation
|
|
52
|
-
*/
|
|
53
|
-
static async createTableWithDrizzle(tableName, db) {
|
|
54
|
-
try {
|
|
55
|
-
const table = this.tables[tableName];
|
|
56
|
-
if (!table) {
|
|
57
|
-
throw new Error(`Table ${tableName} not found in provided schema`);
|
|
58
|
-
}
|
|
59
|
-
logger_1.logger.info(`Creating table ${tableName} using Drizzle definition`);
|
|
60
|
-
// Check if table exists and has correct schema
|
|
61
|
-
try {
|
|
62
|
-
const hasCorrectSchema = await this.validateTableSchema(tableName, table, db);
|
|
63
|
-
if (!hasCorrectSchema) {
|
|
64
|
-
if (!this.migrationConfig.autoMigrate) {
|
|
65
|
-
logger_1.logger.warn(`Table ${tableName} needs schema update but auto-migration is disabled`);
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
const hasData = await this.tableHasData(tableName, db);
|
|
69
|
-
if (hasData && this.migrationConfig.preserveData && !this.migrationConfig.dropTables) {
|
|
70
|
-
logger_1.logger.warn(`Table ${tableName} has data and preserveData is enabled. Skipping schema update.`);
|
|
71
|
-
logger_1.logger.warn(`To update schema, set dropTables: true or preserveData: false in migration config`);
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
if (hasData) {
|
|
75
|
-
logger_1.logger.info(`Table ${tableName} has data but will be recreated due to configuration`);
|
|
76
|
-
}
|
|
77
|
-
logger_1.logger.info(`Table ${tableName} needs schema update, recreating...`);
|
|
78
|
-
// Use transaction to ensure atomic table recreation
|
|
79
|
-
const result = await transactions_1.TransactionManager.recreateTable(tableName, async (db) => {
|
|
80
|
-
const basicCreateSql = this.generateBasicCreateSql(tableName, table);
|
|
81
|
-
await db.run(basicCreateSql);
|
|
82
|
-
});
|
|
83
|
-
if (!result.success) {
|
|
84
|
-
throw new errors_1.DatabaseError(`Failed to recreate table ${tableName}: ${result.error?.message}`, 'recreate-table', result.error);
|
|
85
|
-
}
|
|
86
|
-
logger_1.logger.info(`Table ${tableName} recreated with correct schema`);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
logger_1.logger.info(`Table ${tableName} already has correct schema`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
error_handler_1.ErrorHandler.handleError(error, {
|
|
94
|
-
operation: 'create-table',
|
|
95
|
-
context: `Error ensuring table ${tableName}`
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
catch (error) {
|
|
100
|
-
error_handler_1.ErrorHandler.handleError(error, {
|
|
101
|
-
operation: 'create-table',
|
|
102
|
-
context: `Failed to create table ${tableName}`
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Validate if table exists and has the correct schema
|
|
108
|
-
*/
|
|
109
|
-
static async validateTableSchema(tableName, table, db) {
|
|
110
|
-
try {
|
|
111
|
-
// First check if table exists
|
|
112
|
-
const checkResult = await db.run((0, drizzle_orm_1.sql) `
|
|
113
|
-
SELECT name FROM sqlite_master
|
|
114
|
-
WHERE type='table' AND name=${tableName}
|
|
115
|
-
`);
|
|
116
|
-
if (!checkResult.rows || checkResult.rows.length === 0) {
|
|
117
|
-
logger_1.logger.info(`Table ${tableName} does not exist, needs creation`);
|
|
118
|
-
return false;
|
|
119
|
-
}
|
|
120
|
-
// Get expected columns from the Drizzle table
|
|
121
|
-
const expectedColumns = this.getExpectedColumns(table);
|
|
122
|
-
// Get actual columns from the database
|
|
123
|
-
const actualColumns = await this.getActualColumns(tableName, db);
|
|
124
|
-
logger_1.logger.info(`Expected columns for ${tableName}:`, expectedColumns);
|
|
125
|
-
logger_1.logger.info(`Actual columns for ${tableName}:`, actualColumns);
|
|
126
|
-
// Check if all expected columns exist
|
|
127
|
-
for (const expectedCol of expectedColumns) {
|
|
128
|
-
if (!actualColumns.includes(expectedCol)) {
|
|
129
|
-
logger_1.logger.info(`Table ${tableName} missing column: ${expectedCol}`);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
// Check if there are extra columns that shouldn't exist
|
|
134
|
-
for (const actualCol of actualColumns) {
|
|
135
|
-
if (!expectedColumns.includes(actualCol)) {
|
|
136
|
-
logger_1.logger.info(`Table ${tableName} has extra column that should be removed: ${actualCol}`);
|
|
137
|
-
return false;
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return true;
|
|
141
|
-
}
|
|
142
|
-
catch (error) {
|
|
143
|
-
error_handler_1.ErrorHandler.handleNonThrowingError(error, 'validate-schema', `Error validating schema for table ${tableName}`);
|
|
144
|
-
return false; // If we can't validate, assume it needs recreation
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
/**
|
|
148
|
-
* Get expected column names from Drizzle table definition
|
|
149
|
-
*/
|
|
150
|
-
static getExpectedColumns(table) {
|
|
151
|
-
const columns = {};
|
|
152
|
-
// Use the same logic as generateBasicCreateSql
|
|
153
|
-
if (table._ && table._.columns) {
|
|
154
|
-
Object.assign(columns, table._.columns);
|
|
155
|
-
}
|
|
156
|
-
else if (table.columns) {
|
|
157
|
-
Object.assign(columns, table.columns);
|
|
158
|
-
}
|
|
159
|
-
else {
|
|
160
|
-
const tableKeys = Object.keys(table).filter(key => !key.startsWith('_') &&
|
|
161
|
-
typeof table[key] === 'object' &&
|
|
162
|
-
table[key] !== null &&
|
|
163
|
-
!Array.isArray(table[key]));
|
|
164
|
-
for (const key of tableKeys) {
|
|
165
|
-
columns[key] = table[key];
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
const expectedColumns = [];
|
|
169
|
-
for (const [colName, column] of Object.entries(columns)) {
|
|
170
|
-
const col = column;
|
|
171
|
-
const dbColumnName = col.name || col.columnName || colName;
|
|
172
|
-
expectedColumns.push(dbColumnName);
|
|
173
|
-
}
|
|
174
|
-
return expectedColumns;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* Get actual column names from database table
|
|
178
|
-
*/
|
|
179
|
-
static async getActualColumns(tableName, db) {
|
|
180
|
-
try {
|
|
181
|
-
const result = await db.run((0, drizzle_orm_1.sql) `PRAGMA table_info(${drizzle_orm_1.sql.identifier(tableName)})`);
|
|
182
|
-
if (result.rows) {
|
|
183
|
-
return result.rows.map((row) => row.name);
|
|
184
|
-
}
|
|
185
|
-
return [];
|
|
186
|
-
}
|
|
187
|
-
catch (error) {
|
|
188
|
-
error_handler_1.ErrorHandler.handleNonThrowingError(error, 'get-table-info', `Error getting table info for ${tableName}`);
|
|
189
|
-
return [];
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
/**
|
|
193
|
-
* Check if table has any data
|
|
194
|
-
*/
|
|
195
|
-
static async tableHasData(tableName, db) {
|
|
196
|
-
try {
|
|
197
|
-
const result = await db.run((0, drizzle_orm_1.sql) `SELECT COUNT(*) as count FROM ${drizzle_orm_1.sql.identifier(tableName)}`);
|
|
198
|
-
if (result.rows && result.rows.length > 0) {
|
|
199
|
-
const count = result.rows[0].count;
|
|
200
|
-
return count > 0;
|
|
201
|
-
}
|
|
202
|
-
return false;
|
|
203
|
-
}
|
|
204
|
-
catch (error) {
|
|
205
|
-
error_handler_1.ErrorHandler.handleNonThrowingError(error, 'check-table-data', `Error checking data for table ${tableName}`);
|
|
206
|
-
return false; // If we can't check, assume no data
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Generate basic CREATE TABLE SQL for SQLite
|
|
211
|
-
* This is a simplified approach that works for most common table structures
|
|
212
|
-
*/
|
|
213
|
-
static generateBasicCreateSql(tableName, table) {
|
|
214
|
-
try {
|
|
215
|
-
// Extract column information from Drizzle table with multiple fallback options
|
|
216
|
-
let columns = {};
|
|
217
|
-
if (table._ && table._.columns) {
|
|
218
|
-
columns = table._.columns;
|
|
219
|
-
}
|
|
220
|
-
else if (table.columns) {
|
|
221
|
-
columns = table.columns;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
// Try to extract columns directly from the table object
|
|
225
|
-
const tableKeys = Object.keys(table).filter(key => !key.startsWith('_') &&
|
|
226
|
-
typeof table[key] === 'object' &&
|
|
227
|
-
table[key] !== null &&
|
|
228
|
-
!Array.isArray(table[key]));
|
|
229
|
-
for (const key of tableKeys) {
|
|
230
|
-
columns[key] = table[key];
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const columnDefs = [];
|
|
234
|
-
const foreignKeys = [];
|
|
235
|
-
for (const [colName, column] of Object.entries(columns)) {
|
|
236
|
-
const col = column;
|
|
237
|
-
// Extract the actual database column name from the Drizzle column
|
|
238
|
-
const dbColumnName = col.name || col.columnName || colName;
|
|
239
|
-
let columnDef = `"${dbColumnName}"`;
|
|
240
|
-
// Determine column type
|
|
241
|
-
if (col.dataType === 'integer' || col.columnType === 'SQLiteInteger' || col.type === 'integer') {
|
|
242
|
-
columnDef += ' INTEGER';
|
|
243
|
-
}
|
|
244
|
-
else if (col.dataType === 'real' || col.columnType === 'SQLiteReal' || col.type === 'real') {
|
|
245
|
-
columnDef += ' REAL';
|
|
246
|
-
}
|
|
247
|
-
else if (col.dataType === 'text' || col.columnType === 'SQLiteText' || col.type === 'text') {
|
|
248
|
-
columnDef += ' TEXT';
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
columnDef += ' TEXT';
|
|
252
|
-
}
|
|
253
|
-
// Add constraints
|
|
254
|
-
if (col.primary) {
|
|
255
|
-
columnDef += ' PRIMARY KEY';
|
|
256
|
-
// Add AUTOINCREMENT for primary key if specified
|
|
257
|
-
if (col.autoIncrement) {
|
|
258
|
-
columnDef += ' AUTOINCREMENT';
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
if (col.notNull) {
|
|
262
|
-
columnDef += ' NOT NULL';
|
|
263
|
-
}
|
|
264
|
-
if (col.unique) {
|
|
265
|
-
columnDef += ' UNIQUE';
|
|
266
|
-
}
|
|
267
|
-
if (col.default !== undefined) {
|
|
268
|
-
if (typeof col.default === 'string') {
|
|
269
|
-
columnDef += ` DEFAULT '${col.default}'`;
|
|
270
|
-
}
|
|
271
|
-
else if (col.default?.sql) {
|
|
272
|
-
// Handle SQL expressions like sql`(datetime('now'))`
|
|
273
|
-
columnDef += ` DEFAULT ${col.default.sql}`;
|
|
274
|
-
}
|
|
275
|
-
else if (col.default?.name === 'sql') {
|
|
276
|
-
columnDef += ` DEFAULT ${col.default.value}`;
|
|
277
|
-
}
|
|
278
|
-
else if (typeof col.default === 'number') {
|
|
279
|
-
columnDef += ` DEFAULT ${col.default}`;
|
|
280
|
-
}
|
|
281
|
-
else {
|
|
282
|
-
columnDef += ` DEFAULT ${col.default}`;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
columnDefs.push(columnDef);
|
|
286
|
-
// Handle foreign key references
|
|
287
|
-
if (col.references) {
|
|
288
|
-
const foreignTable = col.references.table || col.references.foreignTable?.tableName || 'unknown';
|
|
289
|
-
const foreignColumn = col.references.column || col.references.columnName || 'id';
|
|
290
|
-
const onDelete = col.references.onDelete || 'CASCADE';
|
|
291
|
-
foreignKeys.push(`FOREIGN KEY ("${dbColumnName}") REFERENCES "${foreignTable}"("${foreignColumn}") ON DELETE ${onDelete}`);
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
// Combine column definitions and foreign keys
|
|
295
|
-
const allConstraints = [...columnDefs, ...foreignKeys];
|
|
296
|
-
return `CREATE TABLE IF NOT EXISTS "${tableName}" (
|
|
297
|
-
${allConstraints.join(',\n ')}
|
|
298
|
-
)`;
|
|
299
|
-
}
|
|
300
|
-
catch (error) {
|
|
301
|
-
error_handler_1.ErrorHandler.handleError(error, {
|
|
302
|
-
operation: 'generate-sql',
|
|
303
|
-
context: `Error generating SQL for table ${tableName}`
|
|
304
|
-
});
|
|
305
|
-
return ''; // This will never be reached due to handleError throwing
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
/**
|
|
309
|
-
* Drop all tables
|
|
310
|
-
* Uses the provided schema tables
|
|
311
|
-
*/
|
|
312
|
-
static async dropAllTables() {
|
|
313
|
-
return error_handler_1.ErrorHandler.wrapAsync(async () => {
|
|
314
|
-
logger_1.logger.warn('Dropping all tables...');
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Module State
|
|
11
|
+
// ============================================================================
|
|
12
|
+
let tables = {};
|
|
13
|
+
let config = {
|
|
14
|
+
preserveData: true,
|
|
15
|
+
autoMigrate: true,
|
|
16
|
+
dropTables: false,
|
|
17
|
+
};
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// Public API
|
|
20
|
+
// ============================================================================
|
|
21
|
+
exports.DatabaseMigrations = {
|
|
22
|
+
/** Initialize with schema data */
|
|
23
|
+
initialize(data) {
|
|
24
|
+
tables = data.tables;
|
|
25
|
+
config = { ...config, ...data.migrationConfig };
|
|
26
|
+
},
|
|
27
|
+
/** Create all tables from schema */
|
|
28
|
+
async initializeSchema() {
|
|
29
|
+
return (0, error_handler_1.withErrorHandling)('init-schema', async () => {
|
|
315
30
|
const db = await connection_1.DatabaseConnection.getInstance();
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
logger_1.logger.info(`Dropped table ${tableName}`);
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
if (!result.success) {
|
|
328
|
-
throw new errors_1.DatabaseError(`Failed to drop tables: ${result.error?.message}`, 'drop-tables', result.error);
|
|
329
|
-
}
|
|
330
|
-
logger_1.logger.info('All tables dropped successfully');
|
|
331
|
-
}, {
|
|
332
|
-
operation: 'drop-tables',
|
|
333
|
-
context: 'Drop tables failed'
|
|
31
|
+
const tableNames = Object.keys(tables);
|
|
32
|
+
logger_1.logger.info(`Initializing ${tableNames.length} tables...`);
|
|
33
|
+
for (const name of tableNames) {
|
|
34
|
+
const table = tables[name];
|
|
35
|
+
if (table)
|
|
36
|
+
await ensureTable(name, table, db);
|
|
37
|
+
}
|
|
38
|
+
logger_1.logger.info('Schema initialization complete');
|
|
334
39
|
});
|
|
335
|
-
}
|
|
336
|
-
/**
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
*/
|
|
340
|
-
static async resetDatabase() {
|
|
341
|
-
try {
|
|
40
|
+
},
|
|
41
|
+
/** Drop all tables and recreate */
|
|
42
|
+
async resetDatabase() {
|
|
43
|
+
return (0, error_handler_1.withErrorHandling)('reset-db', async () => {
|
|
342
44
|
logger_1.logger.warn('Resetting database...');
|
|
343
45
|
await this.dropAllTables();
|
|
344
46
|
await this.initializeSchema();
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
context: 'Database reset failed'
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
/**
|
|
355
|
-
* Check if tables exist in the database
|
|
356
|
-
* Uses the provided schema tables
|
|
357
|
-
*/
|
|
358
|
-
static async checkTableExistence() {
|
|
359
|
-
try {
|
|
47
|
+
});
|
|
48
|
+
},
|
|
49
|
+
/** Drop all tables */
|
|
50
|
+
async dropAllTables() {
|
|
51
|
+
return (0, error_handler_1.withErrorHandling)('drop-tables', async () => {
|
|
360
52
|
const db = await connection_1.DatabaseConnection.getInstance();
|
|
361
|
-
const tableNames = Object.keys(
|
|
362
|
-
const
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
// Check if table exists by querying sqlite_master
|
|
366
|
-
const result = await db.run((0, drizzle_orm_1.sql) `
|
|
367
|
-
SELECT name FROM sqlite_master
|
|
368
|
-
WHERE type='table' AND name=${tableName}
|
|
369
|
-
`);
|
|
370
|
-
existence[tableName] = (result.rows && result.rows.length > 0);
|
|
371
|
-
}
|
|
372
|
-
catch (error) {
|
|
373
|
-
error_handler_1.ErrorHandler.handleOptionalError(error, 'check-table-existence', `Could not check existence of table ${tableName}`);
|
|
374
|
-
existence[tableName] = false;
|
|
375
|
-
}
|
|
53
|
+
const tableNames = Object.keys(tables).reverse();
|
|
54
|
+
for (const name of tableNames) {
|
|
55
|
+
await db.run((0, drizzle_orm_1.sql) `DROP TABLE IF EXISTS ${drizzle_orm_1.sql.identifier(name)}`);
|
|
56
|
+
logger_1.logger.debug(`Dropped: ${name}`);
|
|
376
57
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
/** Check which tables exist */
|
|
61
|
+
async checkTableExistence() {
|
|
62
|
+
const db = await connection_1.DatabaseConnection.getInstance();
|
|
63
|
+
const result = {};
|
|
64
|
+
for (const name of Object.keys(tables)) {
|
|
65
|
+
result[name] = await tableExists(name, db);
|
|
66
|
+
}
|
|
67
|
+
return result;
|
|
68
|
+
},
|
|
69
|
+
/** Validate schema matches database */
|
|
70
|
+
async validateSchema() {
|
|
389
71
|
try {
|
|
390
|
-
const tableNames = Object.keys(this.tables);
|
|
391
72
|
const existence = await this.checkTableExistence();
|
|
392
|
-
const
|
|
393
|
-
const
|
|
394
|
-
const errors = [];
|
|
395
|
-
// Check for missing tables
|
|
396
|
-
for (const tableName of tableNames) {
|
|
397
|
-
if (!existence[tableName]) {
|
|
398
|
-
missingTables.push(tableName);
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
// Check for extra tables (tables in DB but not in schema.ts)
|
|
402
|
-
// This would require querying the database for all tables
|
|
403
|
-
// For now, we'll focus on missing tables
|
|
404
|
-
const isValid = missingTables.length === 0 && errors.length === 0;
|
|
73
|
+
const expected = Object.keys(tables);
|
|
74
|
+
const missingTables = expected.filter((t) => !existence[t]);
|
|
405
75
|
return {
|
|
406
|
-
isValid,
|
|
76
|
+
isValid: missingTables.length === 0,
|
|
407
77
|
missingTables,
|
|
408
|
-
extraTables,
|
|
409
|
-
errors
|
|
78
|
+
extraTables: [],
|
|
79
|
+
errors: [],
|
|
410
80
|
};
|
|
411
81
|
}
|
|
412
82
|
catch (error) {
|
|
413
|
-
error_handler_1.
|
|
83
|
+
(0, error_handler_1.logError)('validate-schema', error);
|
|
414
84
|
return {
|
|
415
85
|
isValid: false,
|
|
416
86
|
missingTables: [],
|
|
417
87
|
extraTables: [],
|
|
418
|
-
errors: [error.message]
|
|
88
|
+
errors: [error instanceof Error ? error.message : String(error)],
|
|
419
89
|
};
|
|
420
90
|
}
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
// ============================================================================
|
|
94
|
+
// Internal Functions
|
|
95
|
+
// ============================================================================
|
|
96
|
+
async function ensureTable(name, table, db) {
|
|
97
|
+
const exists = await tableExists(name, db);
|
|
98
|
+
const expectedCols = getColumnNames(table);
|
|
99
|
+
if (!exists) {
|
|
100
|
+
logger_1.logger.info(`Creating table: ${name}`);
|
|
101
|
+
await db.run(drizzle_orm_1.sql.raw(generateCreateSql(name, table)));
|
|
102
|
+
return;
|
|
421
103
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
104
|
+
const actualCols = await getActualColumns(name, db);
|
|
105
|
+
const schemaMatches = columnsMatch(expectedCols, actualCols);
|
|
106
|
+
if (schemaMatches) {
|
|
107
|
+
logger_1.logger.debug(`Table ${name} is up to date`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (!config.autoMigrate) {
|
|
111
|
+
logger_1.logger.warn(`Table ${name} needs update but auto-migrate is disabled`);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const hasData = await tableHasData(name, db);
|
|
115
|
+
if (hasData && config.preserveData && !config.dropTables) {
|
|
116
|
+
logger_1.logger.warn(`Table ${name} has data, skipping update (set dropTables: true to force)`);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
logger_1.logger.info(`Recreating table: ${name}`);
|
|
120
|
+
const result = await (0, transactions_1.recreateTable)(name, generateCreateSql(name, table));
|
|
121
|
+
if (!result.success) {
|
|
122
|
+
throw result.error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
async function tableExists(name, db) {
|
|
126
|
+
try {
|
|
127
|
+
const result = await db.run((0, drizzle_orm_1.sql) `
|
|
128
|
+
SELECT 1 FROM sqlite_master WHERE type='table' AND name=${name}
|
|
129
|
+
`);
|
|
130
|
+
return (result.rows?.length ?? 0) > 0;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
async function tableHasData(name, db) {
|
|
137
|
+
try {
|
|
138
|
+
const result = await db.run((0, drizzle_orm_1.sql) `SELECT COUNT(*) as c FROM ${drizzle_orm_1.sql.identifier(name)}`);
|
|
139
|
+
const row = result.rows?.[0];
|
|
140
|
+
const count = row ? row.c : 0;
|
|
141
|
+
return (count ?? 0) > 0;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
async function getActualColumns(name, db) {
|
|
148
|
+
try {
|
|
149
|
+
const result = await db.run((0, drizzle_orm_1.sql) `PRAGMA table_info(${drizzle_orm_1.sql.identifier(name)})`);
|
|
150
|
+
return result.rows?.map((r) => r.name) ?? [];
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function getColumnNames(table) {
|
|
157
|
+
const cols = extractColumns(table);
|
|
158
|
+
return Object.keys(cols);
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Extract column information from a Drizzle table.
|
|
162
|
+
* Handles both real Drizzle tables and simple objects.
|
|
163
|
+
*/
|
|
164
|
+
function extractColumns(table) {
|
|
165
|
+
const cols = {};
|
|
166
|
+
for (const [key, val] of Object.entries(table)) {
|
|
167
|
+
if (key.startsWith('_') || key === 'Symbol(drizzle:Name)')
|
|
168
|
+
continue;
|
|
169
|
+
if (typeof val !== 'object' || val === null)
|
|
170
|
+
continue;
|
|
171
|
+
const column = val;
|
|
172
|
+
// Check if it's a Drizzle column (has .name property that's a string)
|
|
173
|
+
const columnName = typeof column.name === 'string' ? column.name : key;
|
|
174
|
+
if (!column.name && !column.dataType)
|
|
175
|
+
continue;
|
|
176
|
+
// Determine SQL type
|
|
177
|
+
let dataType = 'TEXT';
|
|
178
|
+
const rawType = column.dataType ?? column.columnType ?? '';
|
|
179
|
+
const sqliteType = column.getSQLType?.() ?? '';
|
|
180
|
+
if (rawType.includes('integer') || sqliteType.includes('integer')) {
|
|
181
|
+
dataType = 'INTEGER';
|
|
182
|
+
}
|
|
183
|
+
else if (rawType.includes('real') || sqliteType.includes('real')) {
|
|
184
|
+
dataType = 'REAL';
|
|
185
|
+
}
|
|
186
|
+
else if (rawType.includes('blob') || sqliteType.includes('blob')) {
|
|
187
|
+
dataType = 'BLOB';
|
|
188
|
+
}
|
|
189
|
+
const info = {
|
|
190
|
+
name: columnName,
|
|
191
|
+
dataType,
|
|
192
|
+
isPrimary: column.primary === true || column.primaryKey === true,
|
|
193
|
+
hasAutoIncrement: column.autoIncrement === true || column.hasAutoIncrement === true,
|
|
194
|
+
isNotNull: column.notNull === true || column.isNotNull === true,
|
|
195
|
+
isUnique: column.unique === true || column.isUnique === true,
|
|
196
|
+
defaultValue: column.default ?? column.defaultValue,
|
|
197
|
+
};
|
|
198
|
+
if (column.references) {
|
|
199
|
+
const ref = column.references;
|
|
200
|
+
info.references = {
|
|
201
|
+
table: ref.table ?? ref.foreignTable?.tableName ?? 'unknown',
|
|
202
|
+
column: ref.column ?? ref.columnName ?? 'id',
|
|
203
|
+
onDelete: ref.onDelete ?? 'CASCADE',
|
|
437
204
|
};
|
|
438
205
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
206
|
+
cols[columnName] = info;
|
|
207
|
+
}
|
|
208
|
+
return cols;
|
|
209
|
+
}
|
|
210
|
+
function columnsMatch(expected, actual) {
|
|
211
|
+
if (expected.length !== actual.length)
|
|
212
|
+
return false;
|
|
213
|
+
const sortedExpected = [...expected].sort();
|
|
214
|
+
const sortedActual = [...actual].sort();
|
|
215
|
+
return sortedExpected.every((col, i) => col === sortedActual[i]);
|
|
216
|
+
}
|
|
217
|
+
function generateCreateSql(tableName, table) {
|
|
218
|
+
const cols = extractColumns(table);
|
|
219
|
+
const columnDefs = [];
|
|
220
|
+
const foreignKeys = [];
|
|
221
|
+
for (const [colName, info] of Object.entries(cols)) {
|
|
222
|
+
const parts = [`"${colName}"`, info.dataType];
|
|
223
|
+
if (info.isPrimary) {
|
|
224
|
+
parts.push('PRIMARY KEY');
|
|
225
|
+
if (info.hasAutoIncrement)
|
|
226
|
+
parts.push('AUTOINCREMENT');
|
|
227
|
+
}
|
|
228
|
+
if (info.isNotNull && !info.isPrimary)
|
|
229
|
+
parts.push('NOT NULL');
|
|
230
|
+
if (info.isUnique)
|
|
231
|
+
parts.push('UNIQUE');
|
|
232
|
+
// Default value
|
|
233
|
+
if (info.defaultValue !== undefined) {
|
|
234
|
+
const defaultVal = info.defaultValue;
|
|
235
|
+
if (typeof defaultVal === 'string') {
|
|
236
|
+
parts.push(`DEFAULT '${defaultVal}'`);
|
|
237
|
+
}
|
|
238
|
+
else if (typeof defaultVal === 'number') {
|
|
239
|
+
parts.push(`DEFAULT ${defaultVal}`);
|
|
240
|
+
}
|
|
241
|
+
else if (typeof defaultVal === 'object' && defaultVal !== null && 'sql' in defaultVal) {
|
|
242
|
+
parts.push(`DEFAULT ${defaultVal.sql}`);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
columnDefs.push(parts.join(' '));
|
|
246
|
+
// Foreign key
|
|
247
|
+
if (info.references) {
|
|
248
|
+
const { table: refTable, column: refCol, onDelete } = info.references;
|
|
249
|
+
foreignKeys.push(`FOREIGN KEY ("${colName}") REFERENCES "${refTable}"("${refCol}") ON DELETE ${onDelete}`);
|
|
447
250
|
}
|
|
448
251
|
}
|
|
252
|
+
const allDefs = [...columnDefs, ...foreignKeys].join(',\n ');
|
|
253
|
+
return `CREATE TABLE IF NOT EXISTS "${tableName}" (\n ${allDefs}\n)`;
|
|
449
254
|
}
|
|
450
|
-
exports.DatabaseMigrations = DatabaseMigrations;
|
|
451
|
-
DatabaseMigrations.tables = {};
|
|
452
|
-
DatabaseMigrations.migrationConfig = {
|
|
453
|
-
preserveData: true,
|
|
454
|
-
autoMigrate: true,
|
|
455
|
-
dropTables: false,
|
|
456
|
-
};
|
|
457
255
|
//# sourceMappingURL=migrations.js.map
|