@nitronjs/framework 0.2.3 → 0.2.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.
Files changed (68) hide show
  1. package/README.md +3 -1
  2. package/cli/create.js +88 -72
  3. package/cli/njs.js +14 -7
  4. package/lib/Auth/Auth.js +167 -0
  5. package/lib/Build/CssBuilder.js +9 -0
  6. package/lib/Build/FileAnalyzer.js +16 -0
  7. package/lib/Build/HydrationBuilder.js +17 -0
  8. package/lib/Build/Manager.js +15 -0
  9. package/lib/Build/colors.js +4 -0
  10. package/lib/Build/plugins.js +84 -20
  11. package/lib/Console/Commands/DevCommand.js +13 -9
  12. package/lib/Console/Commands/MakeCommand.js +24 -10
  13. package/lib/Console/Commands/MigrateCommand.js +0 -1
  14. package/lib/Console/Commands/MigrateFreshCommand.js +17 -25
  15. package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
  16. package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
  17. package/lib/Console/Commands/SeedCommand.js +4 -2
  18. package/lib/Console/Commands/StorageLinkCommand.js +20 -5
  19. package/lib/Console/Output.js +142 -0
  20. package/lib/Core/Config.js +2 -1
  21. package/lib/Core/Paths.js +8 -0
  22. package/lib/Database/DB.js +141 -51
  23. package/lib/Database/Drivers/MySQLDriver.js +102 -157
  24. package/lib/Database/Migration/Checksum.js +3 -8
  25. package/lib/Database/Migration/MigrationRepository.js +25 -35
  26. package/lib/Database/Migration/MigrationRunner.js +56 -61
  27. package/lib/Database/Model.js +157 -83
  28. package/lib/Database/QueryBuilder.js +31 -0
  29. package/lib/Database/QueryValidation.js +36 -44
  30. package/lib/Database/Schema/Blueprint.js +25 -36
  31. package/lib/Database/Schema/Manager.js +31 -68
  32. package/lib/Database/Seeder/SeederRunner.js +12 -31
  33. package/lib/Date/DateTime.js +9 -0
  34. package/lib/Encryption/Encryption.js +52 -0
  35. package/lib/Faker/Faker.js +11 -0
  36. package/lib/Filesystem/Storage.js +120 -0
  37. package/lib/HMR/Server.js +81 -10
  38. package/lib/Hashing/Hash.js +41 -0
  39. package/lib/Http/Server.js +177 -152
  40. package/lib/Logging/{Manager.js → Log.js} +68 -80
  41. package/lib/Mail/Mail.js +187 -0
  42. package/lib/Route/Router.js +416 -0
  43. package/lib/Session/File.js +135 -233
  44. package/lib/Session/Manager.js +117 -171
  45. package/lib/Session/Memory.js +28 -38
  46. package/lib/Session/Session.js +71 -107
  47. package/lib/Support/Str.js +103 -0
  48. package/lib/Translation/Lang.js +54 -0
  49. package/lib/View/Client/hmr-client.js +94 -51
  50. package/lib/View/Client/nitronjs-icon.png +0 -0
  51. package/lib/View/{Manager.js → View.js} +44 -29
  52. package/lib/index.d.ts +42 -8
  53. package/lib/index.js +19 -12
  54. package/package.json +1 -1
  55. package/skeleton/app/Controllers/HomeController.js +7 -1
  56. package/skeleton/resources/css/global.css +1 -0
  57. package/skeleton/resources/views/Site/Home.tsx +456 -79
  58. package/skeleton/tsconfig.json +6 -1
  59. package/lib/Auth/Manager.js +0 -111
  60. package/lib/Database/Connection.js +0 -61
  61. package/lib/Database/Manager.js +0 -162
  62. package/lib/Encryption/Manager.js +0 -47
  63. package/lib/Filesystem/Manager.js +0 -74
  64. package/lib/Hashing/Manager.js +0 -25
  65. package/lib/Mail/Manager.js +0 -120
  66. package/lib/Route/Loader.js +0 -80
  67. package/lib/Route/Manager.js +0 -286
  68. package/lib/Translation/Manager.js +0 -49
@@ -1,9 +1,12 @@
1
1
  import DB from "../DB.js";
2
2
 
3
3
  class MigrationRepository {
4
-
5
4
  static table = 'migrations';
6
5
 
6
+ // ─────────────────────────────────────────────────────────────────────────
7
+ // Public Methods
8
+ // ─────────────────────────────────────────────────────────────────────────
9
+
7
10
  static async tableExists() {
8
11
  const [rows] = await DB.rawQuery(`SHOW TABLES LIKE '${this.table}'`);
9
12
  return rows.length > 0;
@@ -11,67 +14,46 @@ class MigrationRepository {
11
14
 
12
15
  static async getExecuted() {
13
16
  if (!await this.tableExists()) return [];
14
- return await DB.table(this.table)
15
- .orderBy("batch", "asc")
16
- .orderBy("id", "asc")
17
- .get();
17
+ return await DB.table(this.table).orderBy('batch', 'asc').orderBy('id', 'asc').get();
18
18
  }
19
19
 
20
20
  static async getExecutedNames() {
21
- const migrations = await this.getExecuted();
22
- return new Set(migrations.map(m => m.name));
21
+ return new Set((await this.getExecuted()).map(m => m.name));
23
22
  }
24
23
 
25
24
  static async getNextBatchNumber() {
26
- if (!await this.tableExists()) return 1;
27
- const result = await DB.table(this.table)
28
- .select(DB.rawExpr("MAX(batch) as max_batch"))
29
- .first();
30
- return (result?.max_batch || 0) + 1;
25
+ return (await this.#getMaxBatch()) + 1;
31
26
  }
32
27
 
33
28
  static async getLastBatchNumber() {
34
- if (!await this.tableExists()) return 0;
35
- const result = await DB.table(this.table)
36
- .select(DB.rawExpr("MAX(batch) as max_batch"))
37
- .first();
38
- return result?.max_batch || 0;
29
+ return await this.#getMaxBatch();
39
30
  }
40
31
 
41
32
  static async getByBatch(batch) {
42
- return await DB.table(this.table)
43
- .where("batch", batch)
44
- .orderBy("id", "desc")
45
- .get();
33
+ return await DB.table(this.table).where('batch', batch).orderBy('id', 'desc').get();
46
34
  }
47
35
 
48
36
  static async getLastBatches(steps = 1) {
49
37
  const lastBatch = await this.getLastBatchNumber();
50
38
  if (lastBatch === 0) return [];
51
39
 
52
- const minBatch = Math.max(1, lastBatch - steps + 1);
53
40
  return await DB.table(this.table)
54
- .where("batch", ">=", minBatch)
55
- .orderBy("batch", "desc")
56
- .orderBy("id", "desc")
41
+ .where('batch', '>=', Math.max(1, lastBatch - steps + 1))
42
+ .orderBy('batch', 'desc')
43
+ .orderBy('id', 'desc')
57
44
  .get();
58
45
  }
59
46
 
60
47
  static async log(name, batch, checksum) {
61
- await DB.table(this.table).insert({
62
- name,
63
- batch,
64
- checksum,
65
- executed_at: new Date()
66
- });
48
+ await DB.table(this.table).insert({ name, batch, checksum, executed_at: new Date() });
67
49
  }
68
50
 
69
51
  static async delete(name) {
70
- await DB.table(this.table).where("name", name).delete();
52
+ await DB.table(this.table).where('name', name).delete();
71
53
  }
72
54
 
73
55
  static async find(name) {
74
- return await DB.table(this.table).where("name", name).first();
56
+ return await DB.table(this.table).where('name', name).first();
75
57
  }
76
58
 
77
59
  static async exists(name) {
@@ -79,10 +61,18 @@ class MigrationRepository {
79
61
  }
80
62
 
81
63
  static async getChecksum(name) {
82
- const migration = await this.find(name);
83
- return migration?.checksum || null;
64
+ return (await this.find(name))?.checksum || null;
84
65
  }
85
66
 
67
+ // ─────────────────────────────────────────────────────────────────────────
68
+ // Private Methods
69
+ // ─────────────────────────────────────────────────────────────────────────
70
+
71
+ static async #getMaxBatch() {
72
+ if (!await this.tableExists()) return 0;
73
+ const result = await DB.table(this.table).select(DB.rawExpr('MAX(batch) as max_batch')).first();
74
+ return result?.max_batch || 0;
75
+ }
86
76
  }
87
77
 
88
78
  export default MigrationRepository;
@@ -4,20 +4,11 @@ import { pathToFileURL, fileURLToPath } from 'url';
4
4
  import Checksum from './Checksum.js';
5
5
  import MigrationRepository from './MigrationRepository.js';
6
6
  import Paths from '../../Core/Paths.js';
7
+ import Output from '../../Console/Output.js';
7
8
 
8
9
  const __filename = fileURLToPath(import.meta.url);
9
10
  const __dirname = path.dirname(__filename);
10
11
 
11
- const COLORS = {
12
- reset: '\x1b[0m',
13
- red: '\x1b[31m',
14
- green: '\x1b[32m',
15
- yellow: '\x1b[33m',
16
- cyan: '\x1b[36m',
17
- dim: '\x1b[2m',
18
- bold: '\x1b[1m'
19
- };
20
-
21
12
  class MigrationRunner {
22
13
 
23
14
  static get frameworkMigrationsDir() {
@@ -45,7 +36,7 @@ class MigrationRunner {
45
36
  const filePath = path.join(frameworkDir, file);
46
37
  const fileUrl = pathToFileURL(filePath).href;
47
38
 
48
- console.log(`${COLORS.dim}Migrating:${COLORS.reset} ${COLORS.cyan}[framework] ${file}${COLORS.reset}`);
39
+ Output.frameworkMigration('pending', file);
49
40
 
50
41
  const { default: migration } = await import(fileUrl);
51
42
 
@@ -56,7 +47,7 @@ class MigrationRunner {
56
47
  await migration.up();
57
48
  ran.push(file);
58
49
 
59
- console.log(`${COLORS.green}✅ Migrated:${COLORS.reset} ${COLORS.cyan}[framework] ${file}${COLORS.reset}\n`);
50
+ Output.frameworkMigration('done', file);
60
51
  }
61
52
 
62
53
  return { success: true, ran };
@@ -71,7 +62,7 @@ class MigrationRunner {
71
62
  const migrationsDir = Paths.migrations;
72
63
 
73
64
  if (!fs.existsSync(migrationsDir)) {
74
- console.log(`${COLORS.yellow}⚠️ No migrations directory found${COLORS.reset}`);
65
+ Output.warn("No migrations directory found");
75
66
  return { success: true, ran: [] };
76
67
  }
77
68
 
@@ -80,7 +71,7 @@ class MigrationRunner {
80
71
  .sort();
81
72
 
82
73
  if (files.length === 0) {
83
- console.log(`${COLORS.yellow}⚠️ No migration files found${COLORS.reset}`);
74
+ Output.warn("No migration files found");
84
75
  return { success: true, ran: [] };
85
76
  }
86
77
 
@@ -93,11 +84,11 @@ class MigrationRunner {
93
84
  const storedChecksum = await MigrationRepository.getChecksum(file);
94
85
 
95
86
  if (currentChecksum !== storedChecksum) {
96
- console.error(`${COLORS.red}❌ CHECKSUM MISMATCH: ${file}${COLORS.reset}`);
97
- console.error(`${COLORS.dim} Stored: ${storedChecksum}${COLORS.reset}`);
98
- console.error(`${COLORS.dim} Current: ${currentChecksum}${COLORS.reset}`);
99
- console.error(`${COLORS.red} Migration files must NEVER be modified after execution.${COLORS.reset}`);
100
- console.error(`${COLORS.red} Create a NEW migration for any schema changes.${COLORS.reset}`);
87
+ Output.error(`Checksum mismatch: ${file}`);
88
+ Output.errorDetail(`Stored: ${storedChecksum}`);
89
+ Output.errorDetail(`Current: ${currentChecksum}`);
90
+ Output.error("Migration files must NEVER be modified after execution.");
91
+ Output.error("Create a NEW migration for any schema changes.");
101
92
  return {
102
93
  success: false,
103
94
  ran: [],
@@ -110,12 +101,13 @@ class MigrationRunner {
110
101
  const pending = files.filter(f => !executedNames.has(f));
111
102
 
112
103
  if (pending.length === 0) {
113
- console.log(`${COLORS.green}Nothing to migrate. All migrations are up to date.${COLORS.reset}`);
104
+ console.log(` ${Output.COLORS.green}${Output.ICONS.success}${Output.COLORS.reset} ${Output.COLORS.dim}Nothing to migrate. All migrations are up to date.${Output.COLORS.reset}`);
105
+ console.log();
114
106
  return { success: true, ran: [] };
115
107
  }
116
108
 
117
109
  const batch = await MigrationRepository.getNextBatchNumber();
118
- console.log(`${COLORS.cyan}📦 Running migrations (batch ${batch})${COLORS.reset}\n`);
110
+ Output.migrationHeader(batch);
119
111
 
120
112
  const executedInBatch = [];
121
113
 
@@ -125,7 +117,7 @@ class MigrationRunner {
125
117
  const fileUrl = pathToFileURL(filePath).href;
126
118
  const checksum = Checksum.fromFile(filePath);
127
119
 
128
- console.log(`${COLORS.dim}Migrating:${COLORS.reset} ${COLORS.cyan}${file}${COLORS.reset}`);
120
+ Output.pending("Migrating", file);
129
121
 
130
122
  const { default: migration } = await import(fileUrl);
131
123
 
@@ -137,32 +129,37 @@ class MigrationRunner {
137
129
  await MigrationRepository.log(file, batch, checksum);
138
130
  executedInBatch.push({ file, migration });
139
131
 
140
- console.log(`${COLORS.green}✅ Migrated:${COLORS.reset} ${COLORS.cyan}${file}${COLORS.reset}\n`);
132
+ Output.done("Migrated", file);
141
133
  }
142
134
 
143
- console.log(`${COLORS.green}${COLORS.bold}✅ All migrations completed successfully.${COLORS.reset}`);
135
+ Output.migrationSuccess();
144
136
  return { success: true, ran: executedInBatch.map(e => e.file) };
145
137
 
146
138
  }
147
139
  catch (error) {
148
- console.error(`\n${COLORS.red}❌ Migration failed: ${error.message}${COLORS.reset}`);
140
+ Output.newline();
141
+ Output.error(`Migration failed: ${error.message}`);
149
142
 
150
143
  if (executedInBatch.length > 0) {
151
- console.log(`\n${COLORS.yellow}⚠️ Rolling back ${executedInBatch.length} migration(s) from this batch...${COLORS.reset}\n`);
144
+ Output.newline();
145
+ Output.warn(`Rolling back ${executedInBatch.length} migration(s) from this batch...`);
146
+ Output.newline();
152
147
 
153
148
  for (const { file, migration } of executedInBatch.reverse()) {
154
149
  try {
155
- console.log(`${COLORS.dim}Rolling back:${COLORS.reset} ${COLORS.cyan}${file}${COLORS.reset}`);
150
+ Output.pending("Rolling back", file);
156
151
 
157
152
  if (typeof migration.down === 'function') {
158
153
  await migration.down();
159
154
  }
160
155
 
161
156
  await MigrationRepository.delete(file);
162
- console.log(`${COLORS.yellow}↩️ Rolled back:${COLORS.reset} ${COLORS.cyan}${file}${COLORS.reset}\n`);
163
- } catch (rollbackError) {
164
- console.error(`${COLORS.red}❌ Rollback failed for ${file}: ${rollbackError.message}${COLORS.reset}`);
165
- console.error(`${COLORS.red} Manual intervention may be required.${COLORS.reset}`);
157
+ Output.rollback("Rolled back", file);
158
+ Output.newline();
159
+ }
160
+ catch (rollbackError) {
161
+ Output.error(`Rollback failed for ${file}: ${rollbackError.message}`);
162
+ Output.error("Manual intervention may be required.");
166
163
  }
167
164
  }
168
165
  }
@@ -174,68 +171,70 @@ class MigrationRunner {
174
171
  static async rollback(steps = 1) {
175
172
  const tableExists = await MigrationRepository.tableExists();
176
173
  if (!tableExists) {
177
- console.log(`${COLORS.yellow}⚠️ No migrations have been run yet.${COLORS.reset}`);
174
+ Output.warn("No migrations have been run yet.");
178
175
  return { success: true, rolledBack: [] };
179
176
  }
180
177
 
181
178
  const lastBatch = await MigrationRepository.getLastBatchNumber();
182
179
  if (lastBatch === 0) {
183
- console.log(`${COLORS.yellow}⚠️ Nothing to rollback.${COLORS.reset}`);
180
+ Output.warn("Nothing to rollback.");
184
181
  return { success: true, rolledBack: [] };
185
182
  }
186
183
 
187
184
  const toRollback = await MigrationRepository.getLastBatches(steps);
188
185
 
189
186
  if (toRollback.length === 0) {
190
- console.log(`${COLORS.yellow}⚠️ Nothing to rollback.${COLORS.reset}`);
187
+ Output.warn("Nothing to rollback.");
191
188
  return { success: true, rolledBack: [] };
192
189
  }
193
190
 
194
191
  const migrationsDir = Paths.migrations;
195
192
  const rolledBack = [];
196
193
 
197
- console.log(`${COLORS.yellow}⚠️ Rolling back ${toRollback.length} migration(s)...${COLORS.reset}\n`);
194
+ Output.rollbackHeader(toRollback.length);
198
195
 
199
196
  try {
200
197
  for (const record of toRollback) {
201
198
  const filePath = path.join(migrationsDir, record.name);
202
199
 
203
200
  if (!fs.existsSync(filePath)) {
204
- console.error(`${COLORS.red}❌ Migration file not found: ${record.name}${COLORS.reset}`);
205
- console.error(`${COLORS.red} Cannot rollback without the migration file.${COLORS.reset}`);
201
+ Output.error(`Migration file not found: ${record.name}`);
202
+ Output.errorDetail("Cannot rollback without the migration file.");
206
203
  throw new Error(`Migration file not found: ${record.name}`);
207
204
  }
208
205
 
209
206
  const currentChecksum = Checksum.fromFile(filePath);
210
207
  if (currentChecksum !== record.checksum) {
211
- console.error(`${COLORS.red}❌ CHECKSUM MISMATCH: ${record.name}${COLORS.reset}`);
212
- console.error(`${COLORS.red} Migration file was modified after execution.${COLORS.reset}`);
213
- console.error(`${COLORS.red} Rollback cannot proceed safely.${COLORS.reset}`);
208
+ Output.error(`Checksum mismatch: ${record.name}`);
209
+ Output.errorDetail("Migration file was modified after execution.");
210
+ Output.errorDetail("Rollback cannot proceed safely.");
214
211
  throw new Error(`Checksum mismatch for migration: ${record.name}`);
215
212
  }
216
213
 
217
214
  const fileUrl = pathToFileURL(filePath).href;
218
215
  const { default: migration } = await import(fileUrl);
219
216
 
220
- console.log(`${COLORS.dim}Rolling back:${COLORS.reset} ${COLORS.cyan}${record.name}${COLORS.reset} ${COLORS.dim}(batch ${record.batch})${COLORS.reset}`);
217
+ Output.pending("Rolling back", `${record.name} (batch ${record.batch})`);
221
218
 
222
219
  if (typeof migration.down === 'function') {
223
220
  await migration.down();
224
221
  } else {
225
- console.warn(`${COLORS.yellow} ⚠️ No down() method, skipping schema rollback${COLORS.reset}`);
222
+ Output.warn("No down() method, skipping schema rollback");
226
223
  }
227
224
 
228
225
  await MigrationRepository.delete(record.name);
229
226
  rolledBack.push(record.name);
230
227
 
231
- console.log(`${COLORS.yellow}↩️ Rolled back:${COLORS.reset} ${COLORS.cyan}${record.name}${COLORS.reset}\n`);
228
+ Output.rollbackDone(record.name, record.batch);
232
229
  }
233
230
 
234
- console.log(`${COLORS.green}${COLORS.bold}✅ Rollback completed successfully.${COLORS.reset}`);
231
+ Output.rollbackSuccess();
235
232
  return { success: true, rolledBack };
236
233
 
237
- } catch (error) {
238
- console.error(`\n${COLORS.red}❌ Rollback failed: ${error.message}${COLORS.reset}`);
234
+ }
235
+ catch (error) {
236
+ Output.newline();
237
+ Output.error(`Rollback failed: ${error.message}`);
239
238
  return { success: false, rolledBack, error };
240
239
  }
241
240
  }
@@ -243,17 +242,18 @@ class MigrationRunner {
243
242
  static async reset() {
244
243
  const tableExists = await MigrationRepository.tableExists();
245
244
  if (!tableExists) {
246
- console.log(`${COLORS.yellow}⚠️ No migrations have been run yet.${COLORS.reset}`);
245
+ Output.warn("No migrations have been run yet.");
247
246
  return { success: true, rolledBack: [] };
248
247
  }
249
248
 
250
249
  const lastBatch = await MigrationRepository.getLastBatchNumber();
251
250
  if (lastBatch === 0) {
252
- console.log(`${COLORS.yellow}⚠️ Nothing to reset.${COLORS.reset}`);
251
+ Output.warn("Nothing to reset.");
253
252
  return { success: true, rolledBack: [] };
254
253
  }
255
254
 
256
- console.log(`${COLORS.yellow}⚠️ Resetting all migrations...${COLORS.reset}\n`);
255
+ Output.warn("Resetting all migrations...");
256
+ Output.newline();
257
257
  return await this.rollback(lastBatch);
258
258
  }
259
259
 
@@ -280,7 +280,8 @@ class MigrationRunner {
280
280
  batch: record.batch,
281
281
  executedAt: record.executed_at
282
282
  };
283
- } else {
283
+ }
284
+ else {
284
285
  return {
285
286
  name: file,
286
287
  status: 'Pending',
@@ -297,26 +298,20 @@ class MigrationRunner {
297
298
  const status = await this.status();
298
299
 
299
300
  if (status.length === 0) {
300
- console.log(`${COLORS.yellow}⚠️ No migrations found.${COLORS.reset}`);
301
+ console.log(` ${Output.COLORS.green}${Output.ICONS.success}${Output.COLORS.reset} ${Output.COLORS.dim}No migrations found${Output.COLORS.reset}`);
302
+ Output.newline();
301
303
  return;
302
304
  }
303
305
 
304
- console.log(`\n${COLORS.bold}Migration Status${COLORS.reset}\n`);
305
- console.log(`${COLORS.dim}${'─'.repeat(80)}${COLORS.reset}`);
306
+ Output.statusHeader();
306
307
 
307
308
  for (const migration of status) {
308
- const statusColor = migration.status === 'Ran' ? COLORS.green : COLORS.yellow;
309
- const statusIcon = migration.status === 'Ran' ? '✅' : '⏳';
310
- const batchInfo = migration.batch ? ` ${COLORS.dim}(batch ${migration.batch})${COLORS.reset}` : '';
311
-
312
- console.log(`${statusIcon} ${statusColor}${migration.status.padEnd(7)}${COLORS.reset} ${migration.name}${batchInfo}`);
309
+ Output.statusRow(migration.status, migration.name, migration.batch);
313
310
  }
314
311
 
315
- console.log(`${COLORS.dim}${'─'.repeat(80)}${COLORS.reset}\n`);
316
-
317
312
  const ran = status.filter(m => m.status === 'Ran').length;
318
313
  const pending = status.filter(m => m.status === 'Pending').length;
319
- console.log(`${COLORS.dim}Total: ${status.length} | Ran: ${ran} | Pending: ${pending}${COLORS.reset}\n`);
314
+ Output.statusFooter(status.length, ran, pending);
320
315
  }
321
316
 
322
317
  }