millas 0.1.7 → 0.1.9
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/package.json +32 -19
- package/src/commands/migrate.js +138 -77
- package/src/index.js +10 -2
- package/src/orm/index.js +19 -0
- package/src/orm/migration/MigrationRunner.js +32 -42
- package/src/orm/migration/ModelInspector.js +242 -70
- package/src/orm/migration/dialects/mysql.js +26 -0
- package/src/orm/migration/dialects/postgres.js +18 -0
- package/src/orm/migration/dialects/sqlite.js +24 -0
- package/src/orm/model/Model.js +398 -133
- package/src/orm/query/Aggregates.js +56 -0
- package/src/orm/query/LookupParser.js +308 -0
- package/src/orm/query/Q.js +123 -0
- package/src/orm/query/QueryBuilder.js +266 -82
- package/src/orm/relations/BelongsTo.js +68 -0
- package/src/orm/relations/BelongsToMany.js +188 -0
- package/src/orm/relations/HasMany.js +72 -0
- package/src/orm/relations/HasOne.js +67 -0
- package/src/orm/relations/index.js +8 -0
- package/src/scaffold/maker.js +39 -4
- package/src/scaffold/templates.js +26 -3
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "millas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "A modern batteries-included backend framework for Node.js — built on Express, inspired by Laravel, Django, and FastAPI",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
7
|
-
".":
|
|
8
|
-
"./src":
|
|
9
|
-
"./src/*":
|
|
10
|
-
"./bin/*":
|
|
7
|
+
".": "./src/index.js",
|
|
8
|
+
"./src": "./src/index.js",
|
|
9
|
+
"./src/*": "./src/*.js",
|
|
10
|
+
"./bin/*": "./bin/*.js"
|
|
11
11
|
},
|
|
12
12
|
"bin": {
|
|
13
13
|
"millas": "./bin/millas.js"
|
|
@@ -16,9 +16,20 @@
|
|
|
16
16
|
"test": "echo \"Run phase-specific tests in tests/\""
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
|
-
"framework",
|
|
20
|
-
"
|
|
21
|
-
"
|
|
19
|
+
"framework",
|
|
20
|
+
"express",
|
|
21
|
+
"orm",
|
|
22
|
+
"cli",
|
|
23
|
+
"auth",
|
|
24
|
+
"queue",
|
|
25
|
+
"cache",
|
|
26
|
+
"admin",
|
|
27
|
+
"mail",
|
|
28
|
+
"events",
|
|
29
|
+
"laravel",
|
|
30
|
+
"django",
|
|
31
|
+
"fastapi",
|
|
32
|
+
"millas"
|
|
22
33
|
],
|
|
23
34
|
"author": "Millas Framework",
|
|
24
35
|
"license": "MIT",
|
|
@@ -26,22 +37,24 @@
|
|
|
26
37
|
"node": ">=18.0.0"
|
|
27
38
|
},
|
|
28
39
|
"dependencies": {
|
|
29
|
-
"bcryptjs":
|
|
30
|
-
"chalk":
|
|
31
|
-
"commander":
|
|
32
|
-
"fs-extra":
|
|
33
|
-
"inquirer":
|
|
34
|
-
"jsonwebtoken":"^9.0.3",
|
|
35
|
-
"knex":
|
|
36
|
-
"nodemailer":
|
|
37
|
-
"nunjucks":
|
|
38
|
-
"ora":
|
|
40
|
+
"bcryptjs": "3.0.2",
|
|
41
|
+
"chalk": "4.1.2",
|
|
42
|
+
"commander": "^11.0.0",
|
|
43
|
+
"fs-extra": "^11.0.0",
|
|
44
|
+
"inquirer": "8.2.6",
|
|
45
|
+
"jsonwebtoken": "^9.0.3",
|
|
46
|
+
"knex": "^3.1.0",
|
|
47
|
+
"nodemailer": "^6.9.0",
|
|
48
|
+
"nunjucks": "^3.2.4",
|
|
49
|
+
"ora": "5.4.1"
|
|
39
50
|
},
|
|
40
51
|
"peerDependencies": {
|
|
41
52
|
"express": "^4.18.0"
|
|
42
53
|
},
|
|
43
54
|
"peerDependenciesMeta": {
|
|
44
|
-
"express": {
|
|
55
|
+
"express": {
|
|
56
|
+
"optional": false
|
|
57
|
+
}
|
|
45
58
|
},
|
|
46
59
|
"files": [
|
|
47
60
|
"bin/",
|
package/src/commands/migrate.js
CHANGED
|
@@ -6,133 +6,185 @@ const fs = require('fs-extra');
|
|
|
6
6
|
|
|
7
7
|
module.exports = function (program) {
|
|
8
8
|
|
|
9
|
+
// ── makemigrations ────────────────────────────────────────────────────────
|
|
10
|
+
|
|
9
11
|
program
|
|
10
12
|
.command('makemigrations')
|
|
11
|
-
.description('
|
|
13
|
+
.description('Scan model files, detect schema changes, generate migration files')
|
|
12
14
|
.action(async () => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
try {
|
|
16
|
+
const ctx = getProjectContext();
|
|
17
|
+
// Fixed: was incorrectly destructured as { ModelInspector }
|
|
18
|
+
const ModelInspector = require('../orm/migration/ModelInspector');
|
|
19
|
+
const inspector = new ModelInspector(
|
|
20
|
+
ctx.modelsPath,
|
|
21
|
+
ctx.migrationsPath,
|
|
22
|
+
ctx.snapshotPath,
|
|
23
|
+
);
|
|
24
|
+
const result = await inspector.makeMigrations();
|
|
25
|
+
|
|
26
|
+
if (result.files.length === 0) {
|
|
27
|
+
console.log(chalk.yellow(`\n ${result.message}\n`));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(chalk.green(`\n ✔ ${result.message}`));
|
|
30
|
+
result.files.forEach(f => console.log(chalk.cyan(` + ${f}`)));
|
|
31
|
+
console.log(chalk.gray('\n Run: millas migrate to apply these migrations.\n'));
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
console.error(chalk.red(`\n ✖ makemigrations failed: ${err.message}\n`));
|
|
35
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
36
|
+
process.exit(1);
|
|
27
37
|
}
|
|
28
38
|
});
|
|
29
39
|
|
|
40
|
+
// ── migrate ───────────────────────────────────────────────────────────────
|
|
41
|
+
|
|
30
42
|
program
|
|
31
43
|
.command('migrate')
|
|
32
44
|
.description('Run all pending migrations')
|
|
33
45
|
.action(async () => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
46
|
+
try {
|
|
47
|
+
const runner = await getRunner();
|
|
48
|
+
const result = await runner.migrate();
|
|
49
|
+
printResult(result, 'Ran');
|
|
50
|
+
} catch (err) {
|
|
51
|
+
bail('migrate', err);
|
|
52
|
+
}
|
|
37
53
|
});
|
|
38
54
|
|
|
55
|
+
// ── migrate:fresh ─────────────────────────────────────────────────────────
|
|
56
|
+
|
|
39
57
|
program
|
|
40
58
|
.command('migrate:fresh')
|
|
41
|
-
.description('Drop
|
|
59
|
+
.description('Drop ALL tables then re-run every migration from scratch')
|
|
42
60
|
.action(async () => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
try {
|
|
62
|
+
console.log(chalk.yellow('\n ⚠ Dropping all tables…\n'));
|
|
63
|
+
const runner = await getRunner();
|
|
64
|
+
const result = await runner.fresh();
|
|
65
|
+
printResult(result, 'Ran');
|
|
66
|
+
} catch (err) {
|
|
67
|
+
bail('migrate:fresh', err);
|
|
68
|
+
}
|
|
47
69
|
});
|
|
48
70
|
|
|
71
|
+
// ── migrate:rollback ──────────────────────────────────────────────────────
|
|
72
|
+
|
|
49
73
|
program
|
|
50
74
|
.command('migrate:rollback')
|
|
51
75
|
.description('Rollback the last batch of migrations')
|
|
52
76
|
.option('--steps <n>', 'Number of batches to rollback', '1')
|
|
53
77
|
.action(async (options) => {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
try {
|
|
79
|
+
const runner = await getRunner();
|
|
80
|
+
const result = await runner.rollback(Number(options.steps));
|
|
81
|
+
printResult(result, 'Rolled back');
|
|
82
|
+
} catch (err) {
|
|
83
|
+
bail('migrate:rollback', err);
|
|
84
|
+
}
|
|
57
85
|
});
|
|
58
86
|
|
|
87
|
+
// ── migrate:reset ─────────────────────────────────────────────────────────
|
|
88
|
+
|
|
59
89
|
program
|
|
60
90
|
.command('migrate:reset')
|
|
61
|
-
.description('Rollback
|
|
91
|
+
.description('Rollback ALL migrations')
|
|
62
92
|
.action(async () => {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
93
|
+
try {
|
|
94
|
+
const runner = await getRunner();
|
|
95
|
+
const result = await runner.reset();
|
|
96
|
+
printResult(result, 'Rolled back');
|
|
97
|
+
} catch (err) {
|
|
98
|
+
bail('migrate:reset', err);
|
|
99
|
+
}
|
|
66
100
|
});
|
|
67
101
|
|
|
102
|
+
// ── migrate:refresh ───────────────────────────────────────────────────────
|
|
103
|
+
|
|
68
104
|
program
|
|
69
105
|
.command('migrate:refresh')
|
|
70
|
-
.description('Rollback all
|
|
106
|
+
.description('Rollback all then re-run all migrations')
|
|
71
107
|
.action(async () => {
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
108
|
+
try {
|
|
109
|
+
const runner = await getRunner();
|
|
110
|
+
const result = await runner.refresh();
|
|
111
|
+
printResult(result, 'Ran');
|
|
112
|
+
} catch (err) {
|
|
113
|
+
bail('migrate:refresh', err);
|
|
114
|
+
}
|
|
75
115
|
});
|
|
76
116
|
|
|
117
|
+
// ── migrate:status ────────────────────────────────────────────────────────
|
|
118
|
+
|
|
77
119
|
program
|
|
78
120
|
.command('migrate:status')
|
|
79
|
-
.description('Show the status of all
|
|
121
|
+
.description('Show the status of all migration files')
|
|
80
122
|
.action(async () => {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
123
|
+
try {
|
|
124
|
+
const runner = await getRunner();
|
|
125
|
+
const rows = await runner.status();
|
|
126
|
+
|
|
127
|
+
if (rows.length === 0) {
|
|
128
|
+
console.log(chalk.yellow('\n No migration files found.\n'));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const colW = Math.max(...rows.map(r => r.name.length)) + 2;
|
|
133
|
+
console.log(`\n ${'Migration'.padEnd(colW)} ${'Status'.padEnd(10)} Batch`);
|
|
134
|
+
console.log(chalk.gray(' ' + '─'.repeat(colW + 20)));
|
|
135
|
+
|
|
136
|
+
for (const row of rows) {
|
|
137
|
+
const status = row.status === 'Ran'
|
|
138
|
+
? chalk.green(row.status.padEnd(10))
|
|
139
|
+
: chalk.yellow(row.status.padEnd(10));
|
|
140
|
+
const batch = row.batch ? chalk.gray(String(row.batch)) : chalk.gray('—');
|
|
141
|
+
console.log(` ${chalk.cyan(row.name.padEnd(colW))} ${status} ${batch}`);
|
|
142
|
+
}
|
|
143
|
+
console.log();
|
|
144
|
+
} catch (err) {
|
|
145
|
+
bail('migrate:status', err);
|
|
99
146
|
}
|
|
100
|
-
console.log();
|
|
101
147
|
});
|
|
102
148
|
|
|
149
|
+
// ── db:seed ───────────────────────────────────────────────────────────────
|
|
150
|
+
|
|
103
151
|
program
|
|
104
152
|
.command('db:seed')
|
|
105
153
|
.description('Run all database seeders')
|
|
106
154
|
.action(async () => {
|
|
107
|
-
|
|
108
|
-
|
|
155
|
+
try {
|
|
156
|
+
const ctx = getProjectContext();
|
|
157
|
+
const seedersDir = ctx.seedersPath;
|
|
109
158
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
159
|
+
if (!fs.existsSync(seedersDir)) {
|
|
160
|
+
console.log(chalk.yellow('\n No seeders directory found.\n'));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
114
163
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
164
|
+
const files = fs.readdirSync(seedersDir)
|
|
165
|
+
.filter(f => f.endsWith('.js') && !f.startsWith('.'))
|
|
166
|
+
.sort();
|
|
118
167
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
168
|
+
if (files.length === 0) {
|
|
169
|
+
console.log(chalk.yellow('\n No seeder files found.\n'));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
123
172
|
|
|
124
|
-
console.log();
|
|
125
|
-
for (const file of files) {
|
|
126
|
-
const seeder = require(path.join(seedersDir, file));
|
|
127
173
|
const db = await getDbConnection();
|
|
128
|
-
|
|
129
|
-
|
|
174
|
+
console.log();
|
|
175
|
+
for (const file of files) {
|
|
176
|
+
const seeder = require(path.join(seedersDir, file));
|
|
177
|
+
await seeder.run(db);
|
|
178
|
+
console.log(chalk.green(` ✔ Seeded: ${file}`));
|
|
179
|
+
}
|
|
180
|
+
console.log();
|
|
181
|
+
} catch (err) {
|
|
182
|
+
bail('db:seed', err);
|
|
130
183
|
}
|
|
131
|
-
console.log();
|
|
132
184
|
});
|
|
133
185
|
};
|
|
134
186
|
|
|
135
|
-
// ─── Helpers
|
|
187
|
+
// ─── Helpers ──────────────────────────────────────────────────────────────────
|
|
136
188
|
|
|
137
189
|
function getProjectContext() {
|
|
138
190
|
const cwd = process.cwd();
|
|
@@ -149,26 +201,35 @@ async function getDbConnection() {
|
|
|
149
201
|
if (!fs.existsSync(configPath + '.js')) {
|
|
150
202
|
throw new Error('config/database.js not found. Are you inside a Millas project?');
|
|
151
203
|
}
|
|
152
|
-
const config
|
|
204
|
+
const config = require(configPath);
|
|
153
205
|
const DatabaseManager = require('../orm/drivers/DatabaseManager');
|
|
154
206
|
DatabaseManager.configure(config);
|
|
155
207
|
return DatabaseManager.connection();
|
|
156
208
|
}
|
|
157
209
|
|
|
158
210
|
async function getRunner() {
|
|
211
|
+
// Fixed: was incorrectly destructured as { MigrationRunner }
|
|
159
212
|
const MigrationRunner = require('../orm/migration/MigrationRunner');
|
|
160
213
|
const ctx = getProjectContext();
|
|
161
214
|
const db = await getDbConnection();
|
|
162
215
|
return new MigrationRunner(db, ctx.migrationsPath);
|
|
163
216
|
}
|
|
164
217
|
|
|
165
|
-
function
|
|
218
|
+
function printResult(result, verb) {
|
|
166
219
|
const list = result.ran || result.rolledBack || [];
|
|
167
220
|
if (list.length === 0) {
|
|
168
221
|
console.log(chalk.yellow(`\n ${result.message}\n`));
|
|
169
222
|
return;
|
|
170
223
|
}
|
|
171
224
|
console.log(chalk.green(`\n ✔ ${result.message}`));
|
|
172
|
-
list.forEach(f =>
|
|
225
|
+
list.forEach(f =>
|
|
226
|
+
console.log(chalk.cyan(` ${verb === 'Ran' ? '+' : '-'} ${f}`)),
|
|
227
|
+
);
|
|
173
228
|
console.log();
|
|
174
229
|
}
|
|
230
|
+
|
|
231
|
+
function bail(cmd, err) {
|
|
232
|
+
console.error(chalk.red(`\n ✖ ${cmd} failed: ${err.message}\n`));
|
|
233
|
+
if (process.env.DEBUG) console.error(err.stack);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
package/src/index.js
CHANGED
|
@@ -16,8 +16,13 @@ const ServiceProvider = require('./providers/ServiceProvider');
|
|
|
16
16
|
const ProviderRegistry = require('./providers/ProviderRegistry');
|
|
17
17
|
|
|
18
18
|
// ── ORM ───────────────────────────────────────────────────────────
|
|
19
|
-
const {
|
|
20
|
-
|
|
19
|
+
const {
|
|
20
|
+
Model, fields, QueryBuilder, DatabaseManager,
|
|
21
|
+
SchemaBuilder, MigrationRunner, ModelInspector,
|
|
22
|
+
Q, LookupParser,
|
|
23
|
+
Sum, Avg, Min, Max, Count, AggregateExpression,
|
|
24
|
+
HasOne, HasMany, BelongsTo, BelongsToMany,
|
|
25
|
+
} = require('./orm');
|
|
21
26
|
const DatabaseServiceProvider = require('./providers/DatabaseServiceProvider');
|
|
22
27
|
|
|
23
28
|
// ── Auth ──────────────────────────────────────────────────────────
|
|
@@ -68,6 +73,9 @@ module.exports = {
|
|
|
68
73
|
// ORM
|
|
69
74
|
Model, fields, QueryBuilder, DatabaseManager, SchemaBuilder,
|
|
70
75
|
MigrationRunner, ModelInspector, DatabaseServiceProvider,
|
|
76
|
+
Q, LookupParser,
|
|
77
|
+
Sum, Avg, Min, Max, Count, AggregateExpression,
|
|
78
|
+
HasOne, HasMany, BelongsTo, BelongsToMany,
|
|
71
79
|
// Auth
|
|
72
80
|
Auth, Hasher, JwtDriver, AuthMiddleware, RoleMiddleware,
|
|
73
81
|
AuthController, AuthServiceProvider,
|
package/src/orm/index.js
CHANGED
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
const Model = require('./model/Model');
|
|
4
4
|
const { fields } = require('./fields');
|
|
5
5
|
const QueryBuilder = require('./query/QueryBuilder');
|
|
6
|
+
const Q = require('./query/Q');
|
|
7
|
+
const { AggregateExpression, Sum, Avg, Min, Max, Count } = require('./query/Aggregates');
|
|
8
|
+
const LookupParser = require('./query/LookupParser');
|
|
6
9
|
const DatabaseManager = require('./drivers/DatabaseManager');
|
|
7
10
|
const SchemaBuilder = require('./migration/SchemaBuilder');
|
|
8
11
|
const MigrationRunner = require('./migration/MigrationRunner');
|
|
9
12
|
const ModelInspector = require('./migration/ModelInspector');
|
|
13
|
+
const { HasOne, HasMany, BelongsTo, BelongsToMany } = require('./relations');
|
|
10
14
|
|
|
11
15
|
module.exports = {
|
|
16
|
+
// Core
|
|
12
17
|
Model,
|
|
13
18
|
fields,
|
|
14
19
|
QueryBuilder,
|
|
@@ -16,4 +21,18 @@ module.exports = {
|
|
|
16
21
|
SchemaBuilder,
|
|
17
22
|
MigrationRunner,
|
|
18
23
|
ModelInspector,
|
|
24
|
+
|
|
25
|
+
// Query helpers
|
|
26
|
+
Q,
|
|
27
|
+
LookupParser,
|
|
28
|
+
|
|
29
|
+
// Aggregate expressions
|
|
30
|
+
Sum, Avg, Min, Max, Count,
|
|
31
|
+
AggregateExpression,
|
|
32
|
+
|
|
33
|
+
// Relations
|
|
34
|
+
HasOne,
|
|
35
|
+
HasMany,
|
|
36
|
+
BelongsTo,
|
|
37
|
+
BelongsToMany,
|
|
19
38
|
};
|
|
@@ -7,19 +7,19 @@ const path = require('path');
|
|
|
7
7
|
* MigrationRunner
|
|
8
8
|
*
|
|
9
9
|
* Handles the full migration lifecycle:
|
|
10
|
-
* - run pending migrations
|
|
11
|
-
* - rollback last batch
|
|
12
|
-
* - show status table
|
|
13
|
-
* - drop all + re-run
|
|
14
|
-
* - rollback all
|
|
15
|
-
* - rollback all + re-run
|
|
10
|
+
* - run pending migrations (migrate)
|
|
11
|
+
* - rollback last batch (migrate:rollback)
|
|
12
|
+
* - show status table (migrate:status)
|
|
13
|
+
* - drop all + re-run (migrate:fresh)
|
|
14
|
+
* - rollback all (migrate:reset)
|
|
15
|
+
* - rollback all + re-run (migrate:refresh)
|
|
16
16
|
*
|
|
17
17
|
* Migration history is tracked in the `millas_migrations` table.
|
|
18
18
|
* Each migration file must export { up(db), down(db) }.
|
|
19
19
|
*/
|
|
20
20
|
class MigrationRunner {
|
|
21
21
|
/**
|
|
22
|
-
* @param {object} knexConn
|
|
22
|
+
* @param {object} knexConn — live knex connection
|
|
23
23
|
* @param {string} migrationsPath — absolute path to migrations dir
|
|
24
24
|
*/
|
|
25
25
|
constructor(knexConn, migrationsPath) {
|
|
@@ -29,9 +29,7 @@ class MigrationRunner {
|
|
|
29
29
|
|
|
30
30
|
// ─── Public commands ──────────────────────────────────────────────────────
|
|
31
31
|
|
|
32
|
-
/**
|
|
33
|
-
* Run all pending migrations.
|
|
34
|
-
*/
|
|
32
|
+
/** Run all pending migrations. */
|
|
35
33
|
async migrate() {
|
|
36
34
|
await this._ensureTable();
|
|
37
35
|
const pending = await this._pending();
|
|
@@ -53,9 +51,7 @@ class MigrationRunner {
|
|
|
53
51
|
return { ran, batch, message: `Ran ${ran.length} migration(s).` };
|
|
54
52
|
}
|
|
55
53
|
|
|
56
|
-
/**
|
|
57
|
-
* Rollback the last batch of migrations.
|
|
58
|
-
*/
|
|
54
|
+
/** Rollback the last batch of migrations. */
|
|
59
55
|
async rollback(steps = 1) {
|
|
60
56
|
await this._ensureTable();
|
|
61
57
|
const batches = await this._lastBatches(steps);
|
|
@@ -66,7 +62,6 @@ class MigrationRunner {
|
|
|
66
62
|
|
|
67
63
|
const rolledBack = [];
|
|
68
64
|
|
|
69
|
-
// Run in reverse order
|
|
70
65
|
for (const row of [...batches].reverse()) {
|
|
71
66
|
const migration = this._load(row.name);
|
|
72
67
|
await migration.down(this._db);
|
|
@@ -77,18 +72,14 @@ class MigrationRunner {
|
|
|
77
72
|
return { rolledBack, message: `Rolled back ${rolledBack.length} migration(s).` };
|
|
78
73
|
}
|
|
79
74
|
|
|
80
|
-
/**
|
|
81
|
-
* Drop all tables and re-run every migration.
|
|
82
|
-
*/
|
|
75
|
+
/** Drop all tables and re-run every migration. */
|
|
83
76
|
async fresh() {
|
|
84
77
|
await this._dropAllTables();
|
|
85
78
|
await this._ensureTable();
|
|
86
79
|
return this.migrate();
|
|
87
80
|
}
|
|
88
81
|
|
|
89
|
-
/**
|
|
90
|
-
* Rollback ALL migrations.
|
|
91
|
-
*/
|
|
82
|
+
/** Rollback ALL migrations. */
|
|
92
83
|
async reset() {
|
|
93
84
|
await this._ensureTable();
|
|
94
85
|
const all = await this._db('millas_migrations').orderBy('id', 'desc');
|
|
@@ -108,17 +99,13 @@ class MigrationRunner {
|
|
|
108
99
|
return { rolledBack, message: `Reset ${rolledBack.length} migration(s).` };
|
|
109
100
|
}
|
|
110
101
|
|
|
111
|
-
/**
|
|
112
|
-
* Rollback all then re-run all.
|
|
113
|
-
*/
|
|
102
|
+
/** Rollback all then re-run all. */
|
|
114
103
|
async refresh() {
|
|
115
104
|
await this.reset();
|
|
116
105
|
return this.migrate();
|
|
117
106
|
}
|
|
118
107
|
|
|
119
|
-
/**
|
|
120
|
-
* Return status of all migration files.
|
|
121
|
-
*/
|
|
108
|
+
/** Return status of all migration files. */
|
|
122
109
|
async status() {
|
|
123
110
|
await this._ensureTable();
|
|
124
111
|
const files = this._files();
|
|
@@ -127,7 +114,7 @@ class MigrationRunner {
|
|
|
127
114
|
return files.map(file => ({
|
|
128
115
|
name: file,
|
|
129
116
|
status: ran.has(file) ? 'Ran' : 'Pending',
|
|
130
|
-
batch: ran.get
|
|
117
|
+
batch: ran.get(file) || null,
|
|
131
118
|
}));
|
|
132
119
|
}
|
|
133
120
|
|
|
@@ -153,9 +140,7 @@ class MigrationRunner {
|
|
|
153
140
|
|
|
154
141
|
async _ranNames() {
|
|
155
142
|
const rows = await this._db('millas_migrations').select('name', 'batch');
|
|
156
|
-
|
|
157
|
-
// Map with has() and get()
|
|
158
|
-
return map;
|
|
143
|
+
return new Map(rows.map(r => [r.name, r.batch]));
|
|
159
144
|
}
|
|
160
145
|
|
|
161
146
|
async _nextBatch() {
|
|
@@ -168,34 +153,39 @@ class MigrationRunner {
|
|
|
168
153
|
if (!maxBatch?.max) return [];
|
|
169
154
|
|
|
170
155
|
const fromBatch = maxBatch.max - steps + 1;
|
|
171
|
-
// Fetch all then filter in JS to avoid needing >= support in all drivers
|
|
172
156
|
const all = await this._db('millas_migrations').orderBy('id', 'desc');
|
|
173
157
|
return all.filter(r => r.batch >= fromBatch);
|
|
174
158
|
}
|
|
175
159
|
|
|
160
|
+
/**
|
|
161
|
+
* Drop all user tables — dialect-aware.
|
|
162
|
+
* Resolves the knex client name and delegates to the right helper.
|
|
163
|
+
*/
|
|
176
164
|
async _dropAllTables() {
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
165
|
+
const clientName = this._db.client.config.client || 'sqlite3';
|
|
166
|
+
|
|
167
|
+
let dialect;
|
|
168
|
+
if (clientName.includes('pg') || clientName.includes('postgres')) {
|
|
169
|
+
dialect = require('./dialects/postgres');
|
|
170
|
+
} else if (clientName.includes('mysql') || clientName.includes('maria')) {
|
|
171
|
+
dialect = require('./dialects/mysql');
|
|
172
|
+
} else {
|
|
173
|
+
// Default: sqlite / sqlite3
|
|
174
|
+
dialect = require('./dialects/sqlite');
|
|
186
175
|
}
|
|
176
|
+
|
|
177
|
+
await dialect.dropAllTables(this._db);
|
|
187
178
|
}
|
|
188
179
|
|
|
189
180
|
_files() {
|
|
190
181
|
if (!fs.existsSync(this._path)) return [];
|
|
191
182
|
return fs.readdirSync(this._path)
|
|
192
183
|
.filter(f => f.endsWith('.js') && !f.startsWith('.'))
|
|
193
|
-
.sort();
|
|
184
|
+
.sort();
|
|
194
185
|
}
|
|
195
186
|
|
|
196
187
|
_load(name) {
|
|
197
188
|
const filePath = path.join(this._path, name);
|
|
198
|
-
// Clear require cache so fresh loads work in tests
|
|
199
189
|
delete require.cache[require.resolve(filePath)];
|
|
200
190
|
const migration = require(filePath);
|
|
201
191
|
if (typeof migration.up !== 'function' || typeof migration.down !== 'function') {
|