millas 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +137 -0
- package/bin/millas.js +6 -0
- package/package.json +56 -0
- package/src/admin/Admin.js +617 -0
- package/src/admin/index.js +13 -0
- package/src/admin/resources/AdminResource.js +317 -0
- package/src/auth/Auth.js +254 -0
- package/src/auth/AuthController.js +188 -0
- package/src/auth/AuthMiddleware.js +67 -0
- package/src/auth/Hasher.js +51 -0
- package/src/auth/JwtDriver.js +74 -0
- package/src/auth/RoleMiddleware.js +44 -0
- package/src/cache/Cache.js +231 -0
- package/src/cache/drivers/FileDriver.js +152 -0
- package/src/cache/drivers/MemoryDriver.js +158 -0
- package/src/cache/drivers/NullDriver.js +27 -0
- package/src/cache/index.js +8 -0
- package/src/cli.js +27 -0
- package/src/commands/make.js +61 -0
- package/src/commands/migrate.js +174 -0
- package/src/commands/new.js +50 -0
- package/src/commands/queue.js +92 -0
- package/src/commands/route.js +93 -0
- package/src/commands/serve.js +50 -0
- package/src/container/Application.js +177 -0
- package/src/container/Container.js +281 -0
- package/src/container/index.js +13 -0
- package/src/controller/Controller.js +367 -0
- package/src/errors/HttpError.js +29 -0
- package/src/events/Event.js +39 -0
- package/src/events/EventEmitter.js +151 -0
- package/src/events/Listener.js +46 -0
- package/src/events/index.js +15 -0
- package/src/index.js +93 -0
- package/src/mail/Mail.js +210 -0
- package/src/mail/MailMessage.js +196 -0
- package/src/mail/TemplateEngine.js +150 -0
- package/src/mail/drivers/LogDriver.js +36 -0
- package/src/mail/drivers/MailgunDriver.js +84 -0
- package/src/mail/drivers/SendGridDriver.js +97 -0
- package/src/mail/drivers/SmtpDriver.js +67 -0
- package/src/mail/index.js +19 -0
- package/src/middleware/AuthMiddleware.js +46 -0
- package/src/middleware/CorsMiddleware.js +59 -0
- package/src/middleware/LogMiddleware.js +61 -0
- package/src/middleware/Middleware.js +36 -0
- package/src/middleware/MiddlewarePipeline.js +94 -0
- package/src/middleware/ThrottleMiddleware.js +61 -0
- package/src/orm/drivers/DatabaseManager.js +135 -0
- package/src/orm/fields/index.js +132 -0
- package/src/orm/index.js +19 -0
- package/src/orm/migration/MigrationRunner.js +216 -0
- package/src/orm/migration/ModelInspector.js +338 -0
- package/src/orm/migration/SchemaBuilder.js +173 -0
- package/src/orm/model/Model.js +371 -0
- package/src/orm/query/QueryBuilder.js +197 -0
- package/src/providers/AdminServiceProvider.js +40 -0
- package/src/providers/AuthServiceProvider.js +53 -0
- package/src/providers/CacheStorageServiceProvider.js +71 -0
- package/src/providers/DatabaseServiceProvider.js +45 -0
- package/src/providers/EventServiceProvider.js +34 -0
- package/src/providers/MailServiceProvider.js +51 -0
- package/src/providers/ProviderRegistry.js +82 -0
- package/src/providers/QueueServiceProvider.js +52 -0
- package/src/providers/ServiceProvider.js +45 -0
- package/src/queue/Job.js +135 -0
- package/src/queue/Queue.js +147 -0
- package/src/queue/drivers/DatabaseDriver.js +194 -0
- package/src/queue/drivers/SyncDriver.js +72 -0
- package/src/queue/index.js +16 -0
- package/src/queue/workers/QueueWorker.js +140 -0
- package/src/router/MiddlewareRegistry.js +82 -0
- package/src/router/Route.js +255 -0
- package/src/router/RouteGroup.js +19 -0
- package/src/router/RouteRegistry.js +55 -0
- package/src/router/Router.js +138 -0
- package/src/router/index.js +15 -0
- package/src/scaffold/generator.js +34 -0
- package/src/scaffold/maker.js +272 -0
- package/src/scaffold/templates.js +350 -0
- package/src/storage/Storage.js +170 -0
- package/src/storage/drivers/LocalDriver.js +215 -0
- package/src/storage/index.js +6 -0
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs-extra');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* ModelInspector
|
|
8
|
+
*
|
|
9
|
+
* Implements `millas makemigrations` — Django-style migration generation.
|
|
10
|
+
*
|
|
11
|
+
* Workflow:
|
|
12
|
+
* 1. Scan app/models/ for Model subclasses with static fields
|
|
13
|
+
* 2. Load the last known schema snapshot (.millas/schema.json)
|
|
14
|
+
* 3. Diff current fields vs snapshot
|
|
15
|
+
* 4. Generate timestamped migration file(s) for each change
|
|
16
|
+
* 5. Update the snapshot
|
|
17
|
+
*
|
|
18
|
+
* Detects:
|
|
19
|
+
* - New tables (model added)
|
|
20
|
+
* - Dropped tables (model removed)
|
|
21
|
+
* - Added columns
|
|
22
|
+
* - Removed columns
|
|
23
|
+
* - Changed column types
|
|
24
|
+
*/
|
|
25
|
+
class ModelInspector {
|
|
26
|
+
constructor(modelsPath, migrationsPath, snapshotPath) {
|
|
27
|
+
this._modelsPath = modelsPath;
|
|
28
|
+
this._migrationsPath = migrationsPath;
|
|
29
|
+
this._snapshotPath = snapshotPath || path.join(process.cwd(), '.millas', 'schema.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Detect changes and generate migration files.
|
|
34
|
+
* Returns an array of generated file paths.
|
|
35
|
+
*/
|
|
36
|
+
async makeMigrations() {
|
|
37
|
+
const current = this._scanModels();
|
|
38
|
+
const snapshot = this._loadSnapshot();
|
|
39
|
+
const diffs = this._diff(current, snapshot);
|
|
40
|
+
|
|
41
|
+
if (diffs.length === 0) {
|
|
42
|
+
return { files: [], message: 'No changes detected.' };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const files = [];
|
|
46
|
+
for (const diff of diffs) {
|
|
47
|
+
const file = await this._generateMigration(diff);
|
|
48
|
+
if (file) files.push(file);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
this._saveSnapshot(current);
|
|
52
|
+
return { files, message: `Generated ${files.length} migration file(s).` };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ─── Model scanning ───────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
_scanModels() {
|
|
58
|
+
const schema = {};
|
|
59
|
+
if (!fs.existsSync(this._modelsPath)) return schema;
|
|
60
|
+
|
|
61
|
+
const files = fs.readdirSync(this._modelsPath)
|
|
62
|
+
.filter(f => f.endsWith('.js') && !f.startsWith('.'));
|
|
63
|
+
|
|
64
|
+
for (const file of files) {
|
|
65
|
+
try {
|
|
66
|
+
const ModelClass = require(path.join(this._modelsPath, file));
|
|
67
|
+
if (!ModelClass || !ModelClass.fields) continue;
|
|
68
|
+
|
|
69
|
+
const table = ModelClass.table || file.replace('.js', '').toLowerCase() + 's';
|
|
70
|
+
schema[table] = this._extractFields(ModelClass.fields);
|
|
71
|
+
} catch {
|
|
72
|
+
// Skip unloadable models
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return schema;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
_extractFields(fields) {
|
|
80
|
+
const result = {};
|
|
81
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
82
|
+
result[name] = {
|
|
83
|
+
type: field.type,
|
|
84
|
+
nullable: field.nullable ?? false,
|
|
85
|
+
unique: field.unique ?? false,
|
|
86
|
+
default: field.default !== undefined ? field.default : null,
|
|
87
|
+
max: field.max ?? null,
|
|
88
|
+
unsigned: field.unsigned ?? false,
|
|
89
|
+
enumValues: field.enumValues ?? null,
|
|
90
|
+
references: field.references ?? null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── Diffing ──────────────────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
_diff(current, snapshot) {
|
|
99
|
+
const diffs = [];
|
|
100
|
+
|
|
101
|
+
// New tables
|
|
102
|
+
for (const table of Object.keys(current)) {
|
|
103
|
+
if (!snapshot[table]) {
|
|
104
|
+
diffs.push({ type: 'create_table', table, fields: current[table] });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Dropped tables
|
|
109
|
+
for (const table of Object.keys(snapshot)) {
|
|
110
|
+
if (!current[table]) {
|
|
111
|
+
diffs.push({ type: 'drop_table', table });
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Column changes on existing tables
|
|
116
|
+
for (const table of Object.keys(current)) {
|
|
117
|
+
if (!snapshot[table]) continue;
|
|
118
|
+
|
|
119
|
+
const curr = current[table];
|
|
120
|
+
const prev = snapshot[table];
|
|
121
|
+
|
|
122
|
+
// Added columns
|
|
123
|
+
for (const col of Object.keys(curr)) {
|
|
124
|
+
if (!prev[col]) {
|
|
125
|
+
diffs.push({ type: 'add_column', table, column: col, field: curr[col] });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Removed columns
|
|
130
|
+
for (const col of Object.keys(prev)) {
|
|
131
|
+
if (!curr[col]) {
|
|
132
|
+
diffs.push({ type: 'drop_column', table, column: col });
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Changed columns
|
|
137
|
+
for (const col of Object.keys(curr)) {
|
|
138
|
+
if (!prev[col]) continue;
|
|
139
|
+
if (JSON.stringify(curr[col]) !== JSON.stringify(prev[col])) {
|
|
140
|
+
diffs.push({ type: 'alter_column', table, column: col, field: curr[col], previous: prev[col] });
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return diffs;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// ─── Migration generation ─────────────────────────────────────────────────
|
|
149
|
+
|
|
150
|
+
async _generateMigration(diff) {
|
|
151
|
+
const name = this._diffToName(diff);
|
|
152
|
+
const ts = new Date().toISOString().replace(/[-T:.Z]/g, '').slice(0, 14);
|
|
153
|
+
const fileName = `${ts}_${name}.js`;
|
|
154
|
+
const filePath = path.join(this._migrationsPath, fileName);
|
|
155
|
+
|
|
156
|
+
await fs.ensureDir(this._migrationsPath);
|
|
157
|
+
|
|
158
|
+
const content = this._renderMigration(diff, name);
|
|
159
|
+
await fs.writeFile(filePath, content, 'utf8');
|
|
160
|
+
return fileName;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
_diffToName(diff) {
|
|
164
|
+
switch (diff.type) {
|
|
165
|
+
case 'create_table': return `create_${diff.table}_table`;
|
|
166
|
+
case 'drop_table': return `drop_${diff.table}_table`;
|
|
167
|
+
case 'add_column': return `add_${diff.column}_to_${diff.table}`;
|
|
168
|
+
case 'drop_column': return `remove_${diff.column}_from_${diff.table}`;
|
|
169
|
+
case 'alter_column': return `alter_${diff.column}_on_${diff.table}`;
|
|
170
|
+
default: return `migration_${diff.type}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
_renderMigration(diff, name) {
|
|
175
|
+
switch (diff.type) {
|
|
176
|
+
|
|
177
|
+
case 'create_table':
|
|
178
|
+
return `'use strict';
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Migration: ${name}
|
|
182
|
+
* Auto-generated by: millas makemigrations
|
|
183
|
+
*/
|
|
184
|
+
module.exports = {
|
|
185
|
+
async up(db) {
|
|
186
|
+
await db.schema.createTable('${diff.table}', (t) => {
|
|
187
|
+
${this._renderColumns(diff.fields)} });
|
|
188
|
+
},
|
|
189
|
+
|
|
190
|
+
async down(db) {
|
|
191
|
+
await db.schema.dropTableIfExists('${diff.table}');
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
`;
|
|
195
|
+
|
|
196
|
+
case 'drop_table':
|
|
197
|
+
return `'use strict';
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Migration: ${name}
|
|
201
|
+
*/
|
|
202
|
+
module.exports = {
|
|
203
|
+
async up(db) {
|
|
204
|
+
await db.schema.dropTableIfExists('${diff.table}');
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
async down(db) {
|
|
208
|
+
// Recreate if needed — add column definitions here
|
|
209
|
+
await db.schema.createTable('${diff.table}', (t) => {
|
|
210
|
+
t.increments('id');
|
|
211
|
+
t.timestamps();
|
|
212
|
+
});
|
|
213
|
+
},
|
|
214
|
+
};
|
|
215
|
+
`;
|
|
216
|
+
|
|
217
|
+
case 'add_column':
|
|
218
|
+
return `'use strict';
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Migration: ${name}
|
|
222
|
+
*/
|
|
223
|
+
module.exports = {
|
|
224
|
+
async up(db) {
|
|
225
|
+
await db.schema.table('${diff.table}', (t) => {
|
|
226
|
+
${this._renderColumn(' ', diff.column, diff.field)}
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
async down(db) {
|
|
231
|
+
await db.schema.table('${diff.table}', (t) => {
|
|
232
|
+
t.dropColumn('${diff.column}');
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
};
|
|
236
|
+
`;
|
|
237
|
+
|
|
238
|
+
case 'drop_column':
|
|
239
|
+
return `'use strict';
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Migration: ${name}
|
|
243
|
+
*/
|
|
244
|
+
module.exports = {
|
|
245
|
+
async up(db) {
|
|
246
|
+
await db.schema.table('${diff.table}', (t) => {
|
|
247
|
+
t.dropColumn('${diff.column}');
|
|
248
|
+
});
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
async down(db) {
|
|
252
|
+
await db.schema.table('${diff.table}', (t) => {
|
|
253
|
+
t.string('${diff.column}').nullable();
|
|
254
|
+
});
|
|
255
|
+
},
|
|
256
|
+
};
|
|
257
|
+
`;
|
|
258
|
+
|
|
259
|
+
case 'alter_column':
|
|
260
|
+
return `'use strict';
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Migration: ${name}
|
|
264
|
+
* Changed: ${JSON.stringify(diff.previous)} → ${JSON.stringify(diff.field)}
|
|
265
|
+
*/
|
|
266
|
+
module.exports = {
|
|
267
|
+
async up(db) {
|
|
268
|
+
await db.schema.table('${diff.table}', (t) => {
|
|
269
|
+
${this._renderColumn(' ', diff.column, diff.field, '.alter()')}
|
|
270
|
+
});
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
async down(db) {
|
|
274
|
+
await db.schema.table('${diff.table}', (t) => {
|
|
275
|
+
${this._renderColumn(' ', diff.column, diff.previous, '.alter()')}
|
|
276
|
+
});
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
default:
|
|
282
|
+
return `'use strict';\nmodule.exports = { async up(db) {}, async down(db) {} };\n`;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
_renderColumns(fields) {
|
|
287
|
+
return Object.entries(fields)
|
|
288
|
+
.map(([name, field]) => this._renderColumn(' ', name, field))
|
|
289
|
+
.join('\n') + '\n';
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
_renderColumn(indent, name, field, suffix = '') {
|
|
293
|
+
let line;
|
|
294
|
+
switch (field.type) {
|
|
295
|
+
case 'id': line = `t.increments('${name}')`; break;
|
|
296
|
+
case 'string': line = `t.string('${name}', ${field.max || 255})`; break;
|
|
297
|
+
case 'text': line = `t.text('${name}')`; break;
|
|
298
|
+
case 'integer': line = field.unsigned ? `t.integer('${name}').unsigned()` : `t.integer('${name}')`; break;
|
|
299
|
+
case 'bigInteger': line = `t.bigInteger('${name}')`; break;
|
|
300
|
+
case 'float': line = `t.float('${name}')`; break;
|
|
301
|
+
case 'decimal': line = `t.decimal('${name}', ${field.precision||8}, ${field.scale||2})`; break;
|
|
302
|
+
case 'boolean': line = `t.boolean('${name}')`; break;
|
|
303
|
+
case 'json': line = `t.json('${name}')`; break;
|
|
304
|
+
case 'date': line = `t.date('${name}')`; break;
|
|
305
|
+
case 'timestamp': line = `t.timestamp('${name}', { useTz: false })`; break;
|
|
306
|
+
case 'enum': line = `t.enum('${name}', ${JSON.stringify(field.enumValues||[])})`;break;
|
|
307
|
+
case 'uuid': line = `t.uuid('${name}')`; break;
|
|
308
|
+
default: line = `t.string('${name}')`;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (field.nullable) line += '.nullable()';
|
|
312
|
+
else if (field.type !== 'id') line += '.notNullable()';
|
|
313
|
+
if (field.unique) line += '.unique()';
|
|
314
|
+
if (field.default !== null && field.default !== undefined)
|
|
315
|
+
line += `.defaultTo(${JSON.stringify(field.default)})`;
|
|
316
|
+
if (field.references)
|
|
317
|
+
line += `.references('${field.references.column}').inTable('${field.references.table}').onDelete('CASCADE')`;
|
|
318
|
+
|
|
319
|
+
return `${indent}${line}${suffix};`;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// ─── Snapshot ─────────────────────────────────────────────────────────────
|
|
323
|
+
|
|
324
|
+
_loadSnapshot() {
|
|
325
|
+
try {
|
|
326
|
+
return fs.readJsonSync(this._snapshotPath);
|
|
327
|
+
} catch {
|
|
328
|
+
return {};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
_saveSnapshot(schema) {
|
|
333
|
+
fs.ensureDirSync(path.dirname(this._snapshotPath));
|
|
334
|
+
fs.writeJsonSync(this._snapshotPath, schema, { spaces: 2 });
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
module.exports = ModelInspector;
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { FieldDefinition } = require('../fields');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* SchemaBuilder
|
|
7
|
+
*
|
|
8
|
+
* Converts Model.fields definitions into knex schema operations.
|
|
9
|
+
* Used by the migration system (Phase 6) and DatabaseServiceProvider.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* const sb = new SchemaBuilder(db);
|
|
13
|
+
* await sb.createFromModel(User);
|
|
14
|
+
* await sb.dropTable('users');
|
|
15
|
+
* await sb.tableExists('users');
|
|
16
|
+
*/
|
|
17
|
+
class SchemaBuilder {
|
|
18
|
+
constructor(knexConnection) {
|
|
19
|
+
this._db = knexConnection;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Create a table from a Model class's static fields definition.
|
|
24
|
+
*/
|
|
25
|
+
async createFromModel(ModelClass) {
|
|
26
|
+
const table = ModelClass.table;
|
|
27
|
+
const fields = ModelClass.fields;
|
|
28
|
+
|
|
29
|
+
await this._db.schema.createTable(table, (t) => {
|
|
30
|
+
this._applyFields(t, fields, ModelClass);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Create table only if it doesn't exist.
|
|
36
|
+
*/
|
|
37
|
+
async createFromModelIfNotExists(ModelClass) {
|
|
38
|
+
const exists = await this.tableExists(ModelClass.table);
|
|
39
|
+
if (!exists) await this.createFromModel(ModelClass);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Drop a table.
|
|
44
|
+
*/
|
|
45
|
+
async dropTable(tableName) {
|
|
46
|
+
await this._db.schema.dropTableIfExists(tableName);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Check whether a table exists.
|
|
51
|
+
*/
|
|
52
|
+
async tableExists(tableName) {
|
|
53
|
+
return this._db.schema.hasTable(tableName);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Add a column to an existing table.
|
|
58
|
+
*/
|
|
59
|
+
async addColumn(tableName, columnName, fieldDef) {
|
|
60
|
+
await this._db.schema.table(tableName, (t) => {
|
|
61
|
+
this._applyField(t, columnName, fieldDef);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Drop a column from a table.
|
|
67
|
+
*/
|
|
68
|
+
async dropColumn(tableName, columnName) {
|
|
69
|
+
await this._db.schema.table(tableName, (t) => {
|
|
70
|
+
t.dropColumn(columnName);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Rename a table.
|
|
76
|
+
*/
|
|
77
|
+
async renameTable(from, to) {
|
|
78
|
+
await this._db.schema.renameTable(from, to);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Return the raw knex schema builder for advanced use.
|
|
83
|
+
*/
|
|
84
|
+
get schema() {
|
|
85
|
+
return this._db.schema;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ─── Internal ─────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
_applyFields(tableBuilder, fields, ModelClass) {
|
|
91
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
92
|
+
this._applyField(tableBuilder, name, field, ModelClass);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
_applyField(t, name, field) {
|
|
97
|
+
let col;
|
|
98
|
+
|
|
99
|
+
switch (field.type) {
|
|
100
|
+
case 'id':
|
|
101
|
+
t.increments(name);
|
|
102
|
+
return;
|
|
103
|
+
|
|
104
|
+
case 'string':
|
|
105
|
+
col = t.string(name, field.max || 255);
|
|
106
|
+
break;
|
|
107
|
+
|
|
108
|
+
case 'text':
|
|
109
|
+
col = t.text(name);
|
|
110
|
+
break;
|
|
111
|
+
|
|
112
|
+
case 'integer':
|
|
113
|
+
col = field.unsigned ? t.integer(name).unsigned() : t.integer(name);
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'bigInteger':
|
|
117
|
+
col = field.unsigned ? t.bigInteger(name).unsigned() : t.bigInteger(name);
|
|
118
|
+
break;
|
|
119
|
+
|
|
120
|
+
case 'float':
|
|
121
|
+
col = t.float(name);
|
|
122
|
+
break;
|
|
123
|
+
|
|
124
|
+
case 'decimal':
|
|
125
|
+
col = t.decimal(name, field.precision || 8, field.scale || 2);
|
|
126
|
+
break;
|
|
127
|
+
|
|
128
|
+
case 'boolean':
|
|
129
|
+
col = t.boolean(name);
|
|
130
|
+
break;
|
|
131
|
+
|
|
132
|
+
case 'json':
|
|
133
|
+
col = t.json(name);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 'date':
|
|
137
|
+
col = t.date(name);
|
|
138
|
+
break;
|
|
139
|
+
|
|
140
|
+
case 'timestamp':
|
|
141
|
+
col = t.timestamp(name, { useTz: false });
|
|
142
|
+
break;
|
|
143
|
+
|
|
144
|
+
case 'enum':
|
|
145
|
+
col = t.enum(name, field.enumValues || []);
|
|
146
|
+
break;
|
|
147
|
+
|
|
148
|
+
case 'uuid':
|
|
149
|
+
col = t.uuid(name);
|
|
150
|
+
break;
|
|
151
|
+
|
|
152
|
+
default:
|
|
153
|
+
col = t.string(name);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (!col) return;
|
|
157
|
+
|
|
158
|
+
if (field.nullable) col = col.nullable();
|
|
159
|
+
else if (field.type !== 'id') col = col.notNullable();
|
|
160
|
+
|
|
161
|
+
if (field.unique) col = col.unique();
|
|
162
|
+
|
|
163
|
+
if (field.default !== undefined) col = col.defaultTo(field.default);
|
|
164
|
+
|
|
165
|
+
if (field.references) {
|
|
166
|
+
col = col.references(field.references.column)
|
|
167
|
+
.inTable(field.references.table)
|
|
168
|
+
.onDelete('CASCADE');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
module.exports = SchemaBuilder;
|