voltjs-framework 1.0.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 (60) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1265 -0
  3. package/bin/volt.js +139 -0
  4. package/package.json +56 -0
  5. package/src/api/graphql.js +399 -0
  6. package/src/api/rest.js +204 -0
  7. package/src/api/websocket.js +285 -0
  8. package/src/cli/build.js +111 -0
  9. package/src/cli/create.js +371 -0
  10. package/src/cli/db.js +106 -0
  11. package/src/cli/dev.js +114 -0
  12. package/src/cli/generate.js +278 -0
  13. package/src/cli/lint.js +172 -0
  14. package/src/cli/routes.js +118 -0
  15. package/src/cli/start.js +42 -0
  16. package/src/cli/test.js +138 -0
  17. package/src/core/app.js +701 -0
  18. package/src/core/config.js +232 -0
  19. package/src/core/middleware.js +133 -0
  20. package/src/core/plugins.js +88 -0
  21. package/src/core/react-renderer.js +244 -0
  22. package/src/core/renderer.js +337 -0
  23. package/src/core/router.js +183 -0
  24. package/src/database/index.js +461 -0
  25. package/src/database/migration.js +192 -0
  26. package/src/database/model.js +285 -0
  27. package/src/database/query.js +394 -0
  28. package/src/database/seeder.js +89 -0
  29. package/src/index.js +156 -0
  30. package/src/security/auth.js +425 -0
  31. package/src/security/cors.js +80 -0
  32. package/src/security/csrf.js +125 -0
  33. package/src/security/encryption.js +110 -0
  34. package/src/security/helmet.js +103 -0
  35. package/src/security/index.js +75 -0
  36. package/src/security/rateLimit.js +119 -0
  37. package/src/security/sanitizer.js +113 -0
  38. package/src/security/xss.js +110 -0
  39. package/src/ui/component.js +224 -0
  40. package/src/ui/reactive.js +503 -0
  41. package/src/ui/template.js +448 -0
  42. package/src/utils/cache.js +216 -0
  43. package/src/utils/collection.js +772 -0
  44. package/src/utils/cron.js +213 -0
  45. package/src/utils/date.js +223 -0
  46. package/src/utils/events.js +181 -0
  47. package/src/utils/excel.js +482 -0
  48. package/src/utils/form.js +547 -0
  49. package/src/utils/hash.js +121 -0
  50. package/src/utils/http.js +461 -0
  51. package/src/utils/logger.js +186 -0
  52. package/src/utils/mail.js +347 -0
  53. package/src/utils/paginator.js +179 -0
  54. package/src/utils/pdf.js +417 -0
  55. package/src/utils/queue.js +199 -0
  56. package/src/utils/schema.js +985 -0
  57. package/src/utils/sms.js +243 -0
  58. package/src/utils/storage.js +348 -0
  59. package/src/utils/string.js +236 -0
  60. package/src/utils/validation.js +318 -0
@@ -0,0 +1,461 @@
1
+ /**
2
+ * VoltJS Database Module
3
+ *
4
+ * Universal database abstraction supporting SQLite, MySQL, PostgreSQL.
5
+ * Includes ORM, query builder, migrations, and seeding.
6
+ * Uses native Node.js features — zero dependencies.
7
+ */
8
+
9
+ 'use strict';
10
+
11
+ const { Model } = require('./model');
12
+ const { QueryBuilder } = require('./query');
13
+ const { Migration } = require('./migration');
14
+ const { Seeder } = require('./seeder');
15
+
16
+ class Database {
17
+ constructor(config = {}) {
18
+ this._config = {
19
+ driver: config.driver || 'memory',
20
+ host: config.host || 'localhost',
21
+ port: config.port || null,
22
+ name: config.name || 'volt_db',
23
+ user: config.user || null,
24
+ password: config.password || null,
25
+ filename: config.filename || ':memory:',
26
+ pool: config.pool || { min: 2, max: 10 },
27
+ ...config,
28
+ };
29
+
30
+ this._connection = null;
31
+ this._tables = new Map(); // In-memory store
32
+ this._connected = false;
33
+ this._queryLog = [];
34
+ }
35
+
36
+ /** Connect to database */
37
+ async connect() {
38
+ const driver = this._config.driver;
39
+
40
+ switch (driver) {
41
+ case 'memory':
42
+ this._connection = this._tables;
43
+ this._connected = true;
44
+ break;
45
+ case 'sqlite':
46
+ await this._connectSQLite();
47
+ break;
48
+ case 'mysql':
49
+ await this._connectMySQL();
50
+ break;
51
+ case 'postgres':
52
+ case 'postgresql':
53
+ await this._connectPostgres();
54
+ break;
55
+ default:
56
+ // Default to in-memory
57
+ this._connection = this._tables;
58
+ this._connected = true;
59
+ }
60
+
61
+ return this;
62
+ }
63
+
64
+ /** Get a query builder for a table */
65
+ table(tableName) {
66
+ return new QueryBuilder(tableName, this);
67
+ }
68
+
69
+ /** Create a model class bound to this database */
70
+ model(tableName, schema = {}) {
71
+ return Model.create(tableName, schema, this);
72
+ }
73
+
74
+ /** Run a raw query */
75
+ async query(sql, params = []) {
76
+ this._logQuery(sql, params);
77
+
78
+ if (this._config.driver === 'memory') {
79
+ return this._memoryQuery(sql, params);
80
+ }
81
+
82
+ // For real database drivers
83
+ if (this._connection && typeof this._connection.query === 'function') {
84
+ return this._connection.query(sql, params);
85
+ }
86
+
87
+ throw new Error('Database not connected');
88
+ }
89
+
90
+ /** Create table (in-memory) */
91
+ async createTable(tableName, schema) {
92
+ if (this._config.driver === 'memory') {
93
+ this._tables.set(tableName, {
94
+ schema,
95
+ data: [],
96
+ autoIncrement: 1,
97
+ });
98
+ return true;
99
+ }
100
+
101
+ // Generate CREATE TABLE SQL
102
+ const columns = Object.entries(schema).map(([name, def]) => {
103
+ return this._columnToSQL(name, def);
104
+ });
105
+
106
+ const sql = `CREATE TABLE IF NOT EXISTS ${tableName} (${columns.join(', ')})`;
107
+ return this.query(sql);
108
+ }
109
+
110
+ /** Drop table */
111
+ async dropTable(tableName) {
112
+ if (this._config.driver === 'memory') {
113
+ this._tables.delete(tableName);
114
+ return true;
115
+ }
116
+ return this.query(`DROP TABLE IF EXISTS ${tableName}`);
117
+ }
118
+
119
+ /** Check if table exists */
120
+ async tableExists(tableName) {
121
+ if (this._config.driver === 'memory') {
122
+ return this._tables.has(tableName);
123
+ }
124
+ try {
125
+ await this.query(`SELECT 1 FROM ${tableName} LIMIT 1`);
126
+ return true;
127
+ } catch {
128
+ return false;
129
+ }
130
+ }
131
+
132
+ /** Run migrations */
133
+ async migrate(migrationsDir) {
134
+ const migration = new Migration(this, migrationsDir);
135
+ return migration.run();
136
+ }
137
+
138
+ /** Rollback migrations */
139
+ async rollback(migrationsDir) {
140
+ const migration = new Migration(this, migrationsDir);
141
+ return migration.rollback();
142
+ }
143
+
144
+ /** Run seeders */
145
+ async seed(seedersDir) {
146
+ const seeder = new Seeder(this, seedersDir);
147
+ return seeder.run();
148
+ }
149
+
150
+ /** Close connection */
151
+ async close() {
152
+ if (this._connection && typeof this._connection.end === 'function') {
153
+ await this._connection.end();
154
+ }
155
+ this._connected = false;
156
+ }
157
+
158
+ /** Get query log */
159
+ getQueryLog() {
160
+ return [...this._queryLog];
161
+ }
162
+
163
+ /** Clear query log */
164
+ clearQueryLog() {
165
+ this._queryLog = [];
166
+ }
167
+
168
+ /** Transaction support */
169
+ async transaction(callback) {
170
+ if (this._config.driver === 'memory') {
171
+ // Simple transaction for in-memory: snapshot and restore on error
172
+ const snapshot = new Map();
173
+ for (const [name, table] of this._tables) {
174
+ snapshot.set(name, { ...table, data: [...table.data.map(r => ({ ...r }))] });
175
+ }
176
+ try {
177
+ const result = await callback(this);
178
+ return result;
179
+ } catch (err) {
180
+ // Rollback
181
+ for (const [name, table] of snapshot) {
182
+ this._tables.set(name, table);
183
+ }
184
+ throw err;
185
+ }
186
+ }
187
+
188
+ await this.query('BEGIN');
189
+ try {
190
+ const result = await callback(this);
191
+ await this.query('COMMIT');
192
+ return result;
193
+ } catch (err) {
194
+ await this.query('ROLLBACK');
195
+ throw err;
196
+ }
197
+ }
198
+
199
+ // ===== IN-MEMORY DATABASE ENGINE =====
200
+
201
+ _memoryQuery(sql, params) {
202
+ // Basic SQL parser for in-memory database
203
+ const normalized = sql.trim();
204
+
205
+ if (normalized.toUpperCase().startsWith('INSERT')) {
206
+ return this._memoryInsert(normalized, params);
207
+ }
208
+ if (normalized.toUpperCase().startsWith('SELECT')) {
209
+ return this._memorySelect(normalized, params);
210
+ }
211
+ if (normalized.toUpperCase().startsWith('UPDATE')) {
212
+ return this._memoryUpdate(normalized, params);
213
+ }
214
+ if (normalized.toUpperCase().startsWith('DELETE')) {
215
+ return this._memoryDelete(normalized, params);
216
+ }
217
+
218
+ return { success: true };
219
+ }
220
+
221
+ _memoryInsert(sql, params) {
222
+ const match = sql.match(/INSERT\s+INTO\s+(\w+)\s*\(([^)]+)\)\s*VALUES\s*\(([^)]+)\)/i);
223
+ if (!match) throw new Error('Invalid INSERT syntax');
224
+
225
+ const tableName = match[1];
226
+ const table = this._tables.get(tableName);
227
+ if (!table) throw new Error(`Table "${tableName}" not found`);
228
+
229
+ const columns = match[2].split(',').map(c => c.trim());
230
+ const values = match[3].split(',').map((v, i) => {
231
+ if (v.trim() === '?') return params[i];
232
+ return v.trim().replace(/^'|'$/g, '');
233
+ });
234
+
235
+ const row = { id: table.autoIncrement++ };
236
+ columns.forEach((col, i) => {
237
+ row[col] = values[i];
238
+ });
239
+
240
+ // Set timestamps
241
+ if (table.schema.created_at || table.schema.createdAt) {
242
+ row.created_at = row.created_at || new Date().toISOString();
243
+ }
244
+ if (table.schema.updated_at || table.schema.updatedAt) {
245
+ row.updated_at = new Date().toISOString();
246
+ }
247
+
248
+ table.data.push(row);
249
+ return { insertId: row.id, affectedRows: 1 };
250
+ }
251
+
252
+ _memorySelect(sql, params) {
253
+ const match = sql.match(/SELECT\s+(.+?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+?))?(?:\s+ORDER\s+BY\s+(.+?))?(?:\s+LIMIT\s+(\d+))?(?:\s+OFFSET\s+(\d+))?$/i);
254
+ if (!match) return [];
255
+
256
+ const tableName = match[2];
257
+ const table = this._tables.get(tableName);
258
+ if (!table) return [];
259
+
260
+ let results = [...table.data];
261
+
262
+ // Apply WHERE
263
+ if (match[3]) {
264
+ results = this._applyWhere(results, match[3], params);
265
+ }
266
+
267
+ // Apply ORDER BY
268
+ if (match[4]) {
269
+ const [field, direction] = match[4].trim().split(/\s+/);
270
+ results.sort((a, b) => {
271
+ if (direction?.toUpperCase() === 'DESC') return a[field] > b[field] ? -1 : 1;
272
+ return a[field] > b[field] ? 1 : -1;
273
+ });
274
+ }
275
+
276
+ // Apply LIMIT & OFFSET
277
+ const offset = match[6] ? parseInt(match[6]) : 0;
278
+ const limit = match[5] ? parseInt(match[5]) : results.length;
279
+ results = results.slice(offset, offset + limit);
280
+
281
+ // Apply column selection
282
+ if (match[1].trim() !== '*') {
283
+ const columns = match[1].split(',').map(c => c.trim());
284
+ results = results.map(row => {
285
+ const filtered = {};
286
+ columns.forEach(col => { filtered[col] = row[col]; });
287
+ return filtered;
288
+ });
289
+ }
290
+
291
+ return results;
292
+ }
293
+
294
+ _memoryUpdate(sql, params) {
295
+ const match = sql.match(/UPDATE\s+(\w+)\s+SET\s+(.+?)(?:\s+WHERE\s+(.+))?$/i);
296
+ if (!match) throw new Error('Invalid UPDATE syntax');
297
+
298
+ const tableName = match[1];
299
+ const table = this._tables.get(tableName);
300
+ if (!table) throw new Error(`Table "${tableName}" not found`);
301
+
302
+ const setParts = match[2].split(',').map(s => s.trim());
303
+ let paramIndex = 0;
304
+ const updates = {};
305
+ for (const part of setParts) {
306
+ const [col, val] = part.split('=').map(s => s.trim());
307
+ updates[col] = val === '?' ? params[paramIndex++] : val.replace(/^'|'$/g, '');
308
+ }
309
+
310
+ let affected = 0;
311
+ for (const row of table.data) {
312
+ if (!match[3] || this._matchesWhere(row, match[3], params.slice(paramIndex))) {
313
+ Object.assign(row, updates);
314
+ row.updated_at = new Date().toISOString();
315
+ affected++;
316
+ }
317
+ }
318
+
319
+ return { affectedRows: affected };
320
+ }
321
+
322
+ _memoryDelete(sql, params) {
323
+ const match = sql.match(/DELETE\s+FROM\s+(\w+)(?:\s+WHERE\s+(.+))?$/i);
324
+ if (!match) throw new Error('Invalid DELETE syntax');
325
+
326
+ const tableName = match[1];
327
+ const table = this._tables.get(tableName);
328
+ if (!table) throw new Error(`Table "${tableName}" not found`);
329
+
330
+ const before = table.data.length;
331
+ if (match[2]) {
332
+ table.data = table.data.filter(row => !this._matchesWhere(row, match[2], params));
333
+ } else {
334
+ table.data = [];
335
+ }
336
+
337
+ return { affectedRows: before - table.data.length };
338
+ }
339
+
340
+ _applyWhere(data, whereClause, params) {
341
+ return data.filter(row => this._matchesWhere(row, whereClause, params));
342
+ }
343
+
344
+ _matchesWhere(row, whereClause, params) {
345
+ // Simple WHERE parser: supports AND, OR, =, !=, >, <, >=, <=, LIKE
346
+ const conditions = whereClause.split(/\s+AND\s+/i);
347
+ let paramIdx = 0;
348
+
349
+ return conditions.every(condition => {
350
+ const match = condition.match(/(\w+)\s*(=|!=|<>|>=|<=|>|<|LIKE)\s*(.+)/i);
351
+ if (!match) return true;
352
+
353
+ const field = match[1].trim();
354
+ const op = match[2].toUpperCase();
355
+ let value = match[3].trim();
356
+
357
+ if (value === '?') {
358
+ value = params[paramIdx++];
359
+ } else {
360
+ value = value.replace(/^'|'$/g, '');
361
+ }
362
+
363
+ const rowVal = row[field];
364
+
365
+ switch (op) {
366
+ case '=': return rowVal == value;
367
+ case '!=': case '<>': return rowVal != value;
368
+ case '>': return rowVal > value;
369
+ case '<': return rowVal < value;
370
+ case '>=': return rowVal >= value;
371
+ case '<=': return rowVal <= value;
372
+ case 'LIKE':
373
+ const pattern = value.replace(/%/g, '.*').replace(/_/g, '.');
374
+ return new RegExp(`^${pattern}$`, 'i').test(String(rowVal));
375
+ default: return true;
376
+ }
377
+ });
378
+ }
379
+
380
+ _columnToSQL(name, def) {
381
+ if (typeof def === 'string') {
382
+ return `${name} ${def}`;
383
+ }
384
+
385
+ let sql = `${name} ${def.type || 'TEXT'}`;
386
+ if (def.primary) sql += ' PRIMARY KEY';
387
+ if (def.autoIncrement) sql += ' AUTOINCREMENT';
388
+ if (def.notNull || def.required) sql += ' NOT NULL';
389
+ if (def.unique) sql += ' UNIQUE';
390
+ if (def.default !== undefined) {
391
+ sql += ` DEFAULT ${typeof def.default === 'string' ? `'${def.default}'` : def.default}`;
392
+ }
393
+ if (def.references) {
394
+ sql += ` REFERENCES ${def.references.table}(${def.references.column || 'id'})`;
395
+ }
396
+
397
+ return sql;
398
+ }
399
+
400
+ _logQuery(sql, params) {
401
+ if (this._queryLog.length < 1000) { // Prevent memory leak
402
+ this._queryLog.push({ sql, params, timestamp: new Date() });
403
+ }
404
+ }
405
+
406
+ // ===== DRIVER CONNECTIONS =====
407
+
408
+ async _connectSQLite() {
409
+ try {
410
+ // Try to use better-sqlite3 or sqlite3
411
+ const sqlite3 = require('better-sqlite3');
412
+ this._connection = sqlite3(this._config.filename);
413
+ this._connected = true;
414
+ } catch {
415
+ console.warn('\x1b[33mSQLite driver not found. Install: npm install better-sqlite3\x1b[0m');
416
+ console.warn('\x1b[33mFalling back to in-memory database.\x1b[0m');
417
+ this._connection = this._tables;
418
+ this._connected = true;
419
+ }
420
+ }
421
+
422
+ async _connectMySQL() {
423
+ try {
424
+ const mysql = require('mysql2/promise');
425
+ this._connection = await mysql.createPool({
426
+ host: this._config.host,
427
+ port: this._config.port || 3306,
428
+ database: this._config.name,
429
+ user: this._config.user,
430
+ password: this._config.password,
431
+ ...this._config.pool,
432
+ });
433
+ this._connected = true;
434
+ } catch {
435
+ console.warn('\x1b[33mMySQL driver not found. Install: npm install mysql2\x1b[0m');
436
+ this._connection = this._tables;
437
+ this._connected = true;
438
+ }
439
+ }
440
+
441
+ async _connectPostgres() {
442
+ try {
443
+ const { Pool } = require('pg');
444
+ this._connection = new Pool({
445
+ host: this._config.host,
446
+ port: this._config.port || 5432,
447
+ database: this._config.name,
448
+ user: this._config.user,
449
+ password: this._config.password,
450
+ ...this._config.pool,
451
+ });
452
+ this._connected = true;
453
+ } catch {
454
+ console.warn('\x1b[33mPostgreSQL driver not found. Install: npm install pg\x1b[0m');
455
+ this._connection = this._tables;
456
+ this._connected = true;
457
+ }
458
+ }
459
+ }
460
+
461
+ module.exports = { Database };
@@ -0,0 +1,192 @@
1
+ /**
2
+ * VoltJS Migration System
3
+ *
4
+ * @example
5
+ * // migrations/001_create_users.js
6
+ * module.exports = {
7
+ * up: async (db) => {
8
+ * await db.createTable('users', {
9
+ * id: { type: 'INTEGER', primary: true, autoIncrement: true },
10
+ * name: { type: 'TEXT', required: true },
11
+ * email: { type: 'TEXT', unique: true },
12
+ * created_at: 'DATETIME',
13
+ * updated_at: 'DATETIME',
14
+ * });
15
+ * },
16
+ * down: async (db) => {
17
+ * await db.dropTable('users');
18
+ * }
19
+ * };
20
+ */
21
+
22
+ 'use strict';
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ class Migration {
28
+ constructor(db, migrationsDir) {
29
+ this._db = db;
30
+ this._dir = migrationsDir || path.join(process.cwd(), 'migrations');
31
+ this._tableName = '_volt_migrations';
32
+ }
33
+
34
+ /** Run all pending migrations */
35
+ async run() {
36
+ await this._ensureMigrationTable();
37
+ const completed = await this._getCompleted();
38
+ const files = this._getMigrationFiles();
39
+ const pending = files.filter(f => !completed.includes(f));
40
+
41
+ if (pending.length === 0) {
42
+ console.log('\x1b[33m No pending migrations.\x1b[0m');
43
+ return [];
44
+ }
45
+
46
+ const results = [];
47
+ for (const file of pending) {
48
+ try {
49
+ const migration = require(path.join(this._dir, file));
50
+ console.log(`\x1b[36m Migrating: ${file}\x1b[0m`);
51
+ await migration.up(this._db);
52
+ await this._markCompleted(file);
53
+ console.log(`\x1b[32m ✓ Migrated: ${file}\x1b[0m`);
54
+ results.push({ file, status: 'success' });
55
+ } catch (err) {
56
+ console.error(`\x1b[31m ✗ Migration failed: ${file} — ${err.message}\x1b[0m`);
57
+ results.push({ file, status: 'error', error: err.message });
58
+ break; // Stop on first failure
59
+ }
60
+ }
61
+
62
+ return results;
63
+ }
64
+
65
+ /** Rollback last batch of migrations */
66
+ async rollback() {
67
+ await this._ensureMigrationTable();
68
+ const completed = await this._getCompleted();
69
+
70
+ if (completed.length === 0) {
71
+ console.log('\x1b[33m Nothing to rollback.\x1b[0m');
72
+ return [];
73
+ }
74
+
75
+ // Rollback last migration
76
+ const lastFile = completed[completed.length - 1];
77
+ try {
78
+ const migration = require(path.join(this._dir, lastFile));
79
+ if (migration.down) {
80
+ console.log(`\x1b[36m Rolling back: ${lastFile}\x1b[0m`);
81
+ await migration.down(this._db);
82
+ await this._markRolledBack(lastFile);
83
+ console.log(`\x1b[32m ✓ Rolled back: ${lastFile}\x1b[0m`);
84
+ }
85
+ return [{ file: lastFile, status: 'rolled_back' }];
86
+ } catch (err) {
87
+ console.error(`\x1b[31m ✗ Rollback failed: ${lastFile} — ${err.message}\x1b[0m`);
88
+ return [{ file: lastFile, status: 'error', error: err.message }];
89
+ }
90
+ }
91
+
92
+ /** Get migration files sorted by name */
93
+ _getMigrationFiles() {
94
+ try {
95
+ if (!fs.existsSync(this._dir)) {
96
+ fs.mkdirSync(this._dir, { recursive: true });
97
+ return [];
98
+ }
99
+ return fs.readdirSync(this._dir)
100
+ .filter(f => f.endsWith('.js'))
101
+ .sort();
102
+ } catch {
103
+ return [];
104
+ }
105
+ }
106
+
107
+ /** Ensure migration tracking table exists */
108
+ async _ensureMigrationTable() {
109
+ if (this._db._config.driver === 'memory') {
110
+ if (!this._db._tables.has(this._tableName)) {
111
+ this._db._tables.set(this._tableName, {
112
+ schema: { id: 'INTEGER', name: 'TEXT', ran_at: 'DATETIME' },
113
+ data: [],
114
+ autoIncrement: 1,
115
+ });
116
+ }
117
+ } else {
118
+ await this._db.query(`CREATE TABLE IF NOT EXISTS ${this._tableName} (
119
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
120
+ name TEXT NOT NULL,
121
+ ran_at DATETIME DEFAULT CURRENT_TIMESTAMP
122
+ )`);
123
+ }
124
+ }
125
+
126
+ /** Get list of completed migrations */
127
+ async _getCompleted() {
128
+ if (this._db._config.driver === 'memory') {
129
+ const table = this._db._tables.get(this._tableName);
130
+ return table ? table.data.map(r => r.name) : [];
131
+ }
132
+ const results = await this._db.query(`SELECT name FROM ${this._tableName} ORDER BY id`);
133
+ return results.map(r => r.name);
134
+ }
135
+
136
+ /** Mark a migration as completed */
137
+ async _markCompleted(name) {
138
+ if (this._db._config.driver === 'memory') {
139
+ const table = this._db._tables.get(this._tableName);
140
+ table.data.push({ id: table.autoIncrement++, name, ran_at: new Date().toISOString() });
141
+ } else {
142
+ await this._db.query(`INSERT INTO ${this._tableName} (name) VALUES (?)`, [name]);
143
+ }
144
+ }
145
+
146
+ /** Mark a migration as rolled back */
147
+ async _markRolledBack(name) {
148
+ if (this._db._config.driver === 'memory') {
149
+ const table = this._db._tables.get(this._tableName);
150
+ table.data = table.data.filter(r => r.name !== name);
151
+ } else {
152
+ await this._db.query(`DELETE FROM ${this._tableName} WHERE name = ?`, [name]);
153
+ }
154
+ }
155
+
156
+ /** Generate a new migration file */
157
+ static generate(name, migrationsDir) {
158
+ const dir = migrationsDir || path.join(process.cwd(), 'migrations');
159
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
160
+
161
+ const timestamp = new Date().toISOString().replace(/[-:T]/g, '').slice(0, 14);
162
+ const filename = `${timestamp}_${name}.js`;
163
+ const filepath = path.join(dir, filename);
164
+
165
+ const template = `/**
166
+ * Migration: ${name}
167
+ * Created: ${new Date().toISOString()}
168
+ */
169
+
170
+ module.exports = {
171
+ async up(db) {
172
+ await db.createTable('${name}', {
173
+ id: { type: 'INTEGER', primary: true, autoIncrement: true },
174
+ // Add your columns here
175
+ created_at: 'DATETIME',
176
+ updated_at: 'DATETIME',
177
+ });
178
+ },
179
+
180
+ async down(db) {
181
+ await db.dropTable('${name}');
182
+ }
183
+ };
184
+ `;
185
+
186
+ fs.writeFileSync(filepath, template);
187
+ console.log(`\x1b[32m ✓ Created migration: ${filename}\x1b[0m`);
188
+ return filepath;
189
+ }
190
+ }
191
+
192
+ module.exports = { Migration };