@nitronjs/framework 0.2.2 → 0.2.4

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 (71) hide show
  1. package/README.md +3 -1
  2. package/cli/create.js +88 -72
  3. package/cli/njs.js +17 -19
  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 +4 -3
  14. package/lib/Console/Commands/MigrateFreshCommand.js +22 -27
  15. package/lib/Console/Commands/MigrateRollbackCommand.js +8 -4
  16. package/lib/Console/Commands/MigrateStatusCommand.js +8 -4
  17. package/lib/Console/Commands/SeedCommand.js +8 -28
  18. package/lib/Console/Commands/StorageLinkCommand.js +20 -5
  19. package/lib/Console/Output.js +143 -0
  20. package/lib/Core/Config.js +2 -1
  21. package/lib/Core/Paths.js +8 -8
  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 +59 -67
  27. package/lib/Database/Model.js +165 -75
  28. package/lib/Database/QueryBuilder.js +43 -0
  29. package/lib/Database/QueryValidation.js +51 -30
  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 +24 -145
  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 +79 -9
  38. package/lib/Hashing/Hash.js +41 -0
  39. package/lib/Http/Server.js +179 -151
  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 +87 -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 +49 -27
  53. package/lib/index.js +19 -13
  54. package/package.json +1 -1
  55. package/skeleton/app/Controllers/HomeController.js +7 -1
  56. package/skeleton/package.json +2 -0
  57. package/skeleton/resources/css/global.css +1 -0
  58. package/skeleton/resources/views/Site/Home.tsx +456 -79
  59. package/skeleton/tsconfig.json +6 -1
  60. package/lib/Auth/Manager.js +0 -111
  61. package/lib/Database/Connection.js +0 -61
  62. package/lib/Database/Manager.js +0 -162
  63. package/lib/Database/Migration/migrations/0000_00_00_00_01_create_seeders_table.js +0 -20
  64. package/lib/Database/Seeder/SeederRepository.js +0 -45
  65. package/lib/Encryption/Manager.js +0 -47
  66. package/lib/Filesystem/Manager.js +0 -74
  67. package/lib/Hashing/Manager.js +0 -25
  68. package/lib/Mail/Manager.js +0 -120
  69. package/lib/Route/Loader.js +0 -80
  70. package/lib/Route/Manager.js +0 -286
  71. package/lib/Translation/Manager.js +0 -49
@@ -0,0 +1,143 @@
1
+ /**
2
+ * Console output utilities for CLI commands.
3
+ * Provides consistent, professional styling for terminal messages.
4
+ */
5
+
6
+ const C = {
7
+ reset: "\x1b[0m",
8
+ bold: "\x1b[1m",
9
+ dim: "\x1b[2m",
10
+ red: "\x1b[31m",
11
+ green: "\x1b[32m",
12
+ yellow: "\x1b[33m",
13
+ blue: "\x1b[34m",
14
+ magenta: "\x1b[35m",
15
+ cyan: "\x1b[36m",
16
+ white: "\x1b[37m",
17
+ gray: "\x1b[90m"
18
+ };
19
+
20
+ const I = {
21
+ success: "✔",
22
+ error: "✖",
23
+ warn: "⚠",
24
+ info: "◆",
25
+ pending: "◇",
26
+ arrow: "→",
27
+ bullet: "•",
28
+ sparkle: "✨",
29
+ database: "🗃️",
30
+ seed: "🌱"
31
+ };
32
+
33
+ // Common patterns
34
+ const LINE = `${C.gray}${"-".repeat(45)}${C.reset}`;
35
+ const DOTS = `${C.gray}···${C.reset}`;
36
+ const log = console.log.bind(console);
37
+
38
+ // Helper: prints section header
39
+ const header = (icon, color, title, subtitle = "") => {
40
+ log(` ${color}${icon}${C.reset} ${C.bold}${title}${C.reset}${subtitle ? ` ${C.gray}${subtitle}${C.reset}` : ""}`);
41
+ log(` ${LINE}`);
42
+ };
43
+
44
+ // Helper: prints section footer with success
45
+ const footer = (message) => {
46
+ log(` ${LINE}`);
47
+ log(` ${C.green}${I.sparkle}${C.reset} ${C.green}${C.bold}${message}${C.reset}`);
48
+ log();
49
+ };
50
+
51
+ // Helper: prints step row
52
+ const step = (icon, color, action, target, suffix = "") => {
53
+ log(` ${color}${icon}${C.reset} ${action} ${DOTS} ${target}${suffix}`);
54
+ };
55
+
56
+ // Basic messages
57
+ const success = (msg) => log(`${C.green}${I.success}${C.reset} ${msg}`);
58
+ const error = (msg) => log(`${C.red}${I.error}${C.reset} ${msg}`);
59
+ const warn = (msg) => log(`${C.yellow}${I.warn}${C.reset} ${msg}`);
60
+ const info = (msg) => log(`${C.blue}${I.info}${C.reset} ${msg}`);
61
+ const dim = (msg) => log(`${C.dim}${msg}${C.reset}`);
62
+ const newline = () => log();
63
+ const errorDetail = (msg) => log(` ${C.dim}${msg}${C.reset}`);
64
+
65
+ // Task steps
66
+ const done = (action, target) => step(I.success, C.green, action, `${C.white}${target}${C.reset}`);
67
+ const pending = (action, target) => step(I.pending, C.gray, `${C.dim}${action}${C.reset}`, `${C.gray}${target}${C.reset}`);
68
+
69
+ // Migration
70
+ const migrationHeader = (batch) => header(I.database, C.magenta, "Running Migrations", `(batch ${batch})`);
71
+ const migrationSuccess = () => footer("All migrations completed successfully");
72
+
73
+ const frameworkMigration = (action, file) => {
74
+ const tag = `${C.magenta}[framework]${C.reset}`;
75
+ if (action === 'pending') {
76
+ step(I.pending, C.gray, `${C.dim}Migrating${C.reset}`, `${tag} ${C.gray}${file}${C.reset}`);
77
+ } else {
78
+ step(I.success, C.green, "Migrated ", `${tag} ${C.white}${file}${C.reset}`);
79
+ }
80
+ };
81
+
82
+ // Seeder
83
+ const seederHeader = () => header(I.seed, C.cyan, "Running Seeders");
84
+ const seederSuccess = () => footer("All seeders completed successfully");
85
+
86
+ // Drop tables
87
+ const dropTablesHeader = () => header(I.warn, C.yellow, "Dropping Tables");
88
+ const droppingTable = (name) => step(I.pending, C.yellow, `${C.dim}Dropping${C.reset}`, `${C.yellow}${name}${C.reset}`);
89
+ const dropTablesSuccess = (count) => {
90
+ log(` ${LINE}`);
91
+ log(` ${C.green}${I.success}${C.reset} ${C.dim}Dropped ${count} table(s)${C.reset}`);
92
+ log();
93
+ log();
94
+ };
95
+
96
+ // Rollback
97
+ const rollbackHeader = (count) => header(I.arrow, C.yellow, "Rolling Back", `(${count} migration${count > 1 ? 's' : ''})`);
98
+ const rollbackDone = (file, batch) => step(I.success, C.yellow, "Rolled back", `${C.white}${file}${C.reset}`, ` ${C.gray}(batch ${batch})${C.reset}`);
99
+ const rollbackSuccess = () => footer("Rollback completed successfully");
100
+
101
+ // Status
102
+ const statusHeader = () => header(I.info, C.blue, "Migration Status");
103
+ const statusRow = (status, name, batch) => {
104
+ if (status === 'Ran') {
105
+ step(I.success, C.green, `${C.green}Ran${C.reset} `, `${C.white}${name}${C.reset}`, ` ${C.gray}(batch ${batch})${C.reset}`);
106
+ }
107
+ else {
108
+ step(I.pending, C.yellow, `${C.yellow}Pending${C.reset}`, `${C.gray}${name}${C.reset}`);
109
+ }
110
+ };
111
+ const statusFooter = (total, ran, pend) => {
112
+ log(` ${LINE}`);
113
+ log(` ${C.dim}Total: ${total} | Ran: ${ran} | Pending: ${pend}${C.reset}`);
114
+ log();
115
+ };
116
+
117
+ export default {
118
+ COLORS: C,
119
+ ICONS: I,
120
+ success,
121
+ error,
122
+ warn,
123
+ info,
124
+ dim,
125
+ newline,
126
+ errorDetail,
127
+ done,
128
+ pending,
129
+ migrationHeader,
130
+ migrationSuccess,
131
+ frameworkMigration,
132
+ seederHeader,
133
+ seederSuccess,
134
+ dropTablesHeader,
135
+ droppingTable,
136
+ dropTablesSuccess,
137
+ rollbackHeader,
138
+ rollbackDone,
139
+ rollbackSuccess,
140
+ statusHeader,
141
+ statusRow,
142
+ statusFooter
143
+ };
@@ -21,7 +21,8 @@ class Config {
21
21
  for (const name of configFiles) {
22
22
  try {
23
23
  this.#configs[name] = (await import(Paths.configUrl(name))).default;
24
- } catch (err) {
24
+ }
25
+ catch (err) {
25
26
  // Config file might not exist, that's ok
26
27
  this.#configs[name] = {};
27
28
  }
package/lib/Core/Paths.js CHANGED
@@ -4,6 +4,14 @@ import { pathToFileURL } from "url";
4
4
 
5
5
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
6
 
7
+ /**
8
+ * Path manager providing consistent access to framework and project directories.
9
+ * All paths are resolved relative to the project root (process.cwd()).
10
+ *
11
+ * @example
12
+ * Paths.controllers // /project/app/Controllers
13
+ * Paths.views // /project/resources/views
14
+ */
7
15
  class Paths {
8
16
  static #framework = path.resolve(__dirname, "../..");
9
17
  static #project = process.cwd();
@@ -92,14 +100,6 @@ class Paths {
92
100
  return path.join(this.#project, "database/seeders");
93
101
  }
94
102
 
95
- static get seedersProd() {
96
- return path.join(this.#project, "database/seeders/prod");
97
- }
98
-
99
- static get seedersDev() {
100
- return path.join(this.#project, "database/seeders/dev");
101
- }
102
-
103
103
  static get storage() {
104
104
  return path.join(this.#project, "storage");
105
105
  }
@@ -1,84 +1,174 @@
1
- import DatabaseManager from "./Manager.js";
1
+ import MySQLDriver from "./Drivers/MySQLDriver.js";
2
2
  import { query as createQueryBuilder, RawExpression } from "./QueryBuilder.js";
3
+ import Config from "../Core/Config.js";
3
4
 
5
+ /**
6
+ * Database manager providing query builder access, raw queries, and transactions.
7
+ * Supports multiple database drivers (currently MySQL).
8
+ *
9
+ * @example
10
+ * const users = await DB.table("users").where("active", 1).get();
11
+ * await DB.transaction(async (conn) => { ... });
12
+ */
4
13
  class DB {
5
- static manager() {
6
- return DatabaseManager.getInstance();
14
+ static #driver = null;
15
+ static #config = null;
16
+ static #credentials = null;
17
+
18
+ /**
19
+ * Initializes database connection with configured driver.
20
+ * @returns {Promise<void>}
21
+ * @throws {Error} If health check fails
22
+ */
23
+ static async setup() {
24
+ this.#config = Config.all("database");
25
+
26
+ const envDriver = (process.env.DATABASE_DRIVER || "mysql").toLowerCase();
27
+
28
+ if (envDriver === "none") {
29
+ return;
30
+ }
31
+
32
+ this.#driver = this.#createDriver(envDriver);
33
+
34
+ const isHealthy = await this.#driver.healthCheck();
35
+
36
+ if (!isHealthy) {
37
+ throw new Error("Database connection health check failed. Check your .env credentials and ensure MySQL is running.");
38
+ }
7
39
  }
8
40
 
9
- static connection(name = null) {
10
- if (!this.#isEnabled()) return null;
11
- return this.manager().connection(name);
41
+ /**
42
+ * Closes database connection and releases resources.
43
+ * @returns {Promise<void>}
44
+ */
45
+ static async close() {
46
+ if (this.#driver) {
47
+ await this.#driver.close();
48
+ this.#driver = null;
49
+ }
12
50
  }
13
51
 
14
- static async query(sql, bindings = []) {
15
- if (!this.#isEnabled()) return null;
16
- return await this.manager().query(sql, bindings);
52
+ /**
53
+ * Creates a query builder for the specified table.
54
+ * @param {string} table - Table name
55
+ * @param {Function|null} modelClass - Optional model class for result mapping
56
+ * @returns {Object} Query builder instance
57
+ * @throws {Error} If database is disabled
58
+ */
59
+ static table(table, modelClass = null) {
60
+ if (!this.#driver) {
61
+ throw new Error("Database is disabled (DATABASE_DRIVER=none)");
62
+ }
63
+
64
+ return createQueryBuilder(table, this.#driver, modelClass);
17
65
  }
18
66
 
67
+ /**
68
+ * Creates a raw SQL expression for use in queries.
69
+ * @param {string} expression - Raw SQL expression
70
+ * @returns {RawExpression} Raw expression wrapper
71
+ */
19
72
  static rawExpr(expression) {
20
73
  return new RawExpression(expression);
21
74
  }
22
75
 
76
+ /**
77
+ * Executes a raw SQL query.
78
+ * @param {string} sql - Raw SQL string
79
+ * @returns {Promise<Array>} Query result
80
+ * @throws {Error} If database is disabled
81
+ */
23
82
  static async rawQuery(sql) {
24
- if (!this.#isEnabled()) return null;
25
- return await this.connection().raw(sql);
26
- }
83
+ if (!this.#driver) {
84
+ throw new Error("Database is disabled (DATABASE_DRIVER=none)");
85
+ }
27
86
 
28
- static async transaction(callback) {
29
- if (!this.#isEnabled()) return null;
30
- return await this.connection().withTransaction(callback);
87
+ return await this.#driver.raw(sql);
31
88
  }
32
89
 
33
90
  /**
34
- * Create a query builder for a table.
35
- * Uses lazy connection loading - connection is fetched only when query is executed.
36
- * @param {string} table - The table name
37
- * @param {string|null} connectionName - Optional connection name
38
- * @param {Function|null} modelClass - Optional model class for hydration
39
- * @returns {QueryBuilder} A query builder instance
91
+ * Executes a callback within a database transaction.
92
+ * @param {Function} callback - Async function receiving connection
93
+ * @param {Object} options - Transaction options
94
+ * @param {number} options.timeout - Timeout in milliseconds (default: 30000)
95
+ * @returns {Promise<*>} Callback result
96
+ * @throws {Error} If transaction times out or fails
40
97
  */
41
- static table(table, connectionName = null, modelClass = null) {
42
- // Pass connection directly now (synchronous)
43
- const connection = this.connection(connectionName);
44
- return createQueryBuilder(table, connection, modelClass);
45
- }
46
-
47
- static async setup() {
48
- if (!this.#isEnabled()) return;
98
+ static async transaction(callback, options = {}) {
99
+ if (!this.#driver) {
100
+ throw new Error("Database is disabled (DATABASE_DRIVER=none)");
101
+ }
49
102
 
50
- const manager = this.manager();
51
- const defaultConnection = manager.getDefaultConnection();
103
+ const timeout = options.timeout || 30000;
104
+ const controller = new AbortController();
105
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
52
106
 
53
107
  try {
54
- const connection = manager.connection(defaultConnection);
55
- const isHealthy = await connection.healthCheck();
108
+ const result = await this.#driver.withTransaction(callback, { signal: controller.signal });
109
+ clearTimeout(timeoutId);
56
110
 
57
- if (!isHealthy) {
58
- throw new Error(`Database connection '${defaultConnection}' health check failed`);
59
- }
111
+ return result;
60
112
  }
61
- catch (error) {
62
- console.error('[Database] Setup failed:', error.message);
63
- console.error('[Database] Check your .env credentials and ensure MySQL is running');
64
- throw error;
113
+ catch (err) {
114
+ clearTimeout(timeoutId);
115
+
116
+ if (controller.signal.aborted) {
117
+ throw new Error(
118
+ `Transaction timeout after ${timeout}ms. ` +
119
+ "Consider: 1) Adding await to all async operations, 2) Increasing timeout, 3) Optimizing queries."
120
+ );
121
+ }
122
+
123
+ throw err;
65
124
  }
66
125
  }
67
126
 
68
- static async close() {
69
- if (!this.#isEnabled()) return;
127
+ /**
128
+ * Reconfigures database with new credentials.
129
+ * Used by installer to test and save database settings.
130
+ * @param {Object} credentials - Database credentials
131
+ * @param {string} credentials.host - Database host
132
+ * @param {number} credentials.port - Database port
133
+ * @param {string} credentials.database - Database name
134
+ * @param {string} credentials.username - Database username
135
+ * @param {string} credentials.password - Database password
136
+ * @returns {Promise<void>}
137
+ */
138
+ static async reconfigure(credentials) {
139
+ await this.close();
70
140
 
71
- try {
72
- await this.manager().closeAll();
73
- }
74
- catch (error) {
75
- console.error('[Database] Error during shutdown:', error.message);
76
- }
141
+ this.#credentials = {
142
+ host: credentials.host,
143
+ port: String(credentials.port),
144
+ database: credentials.database,
145
+ username: credentials.username,
146
+ password: credentials.password || ""
147
+ };
148
+
149
+ this.#driver = this.#createDriver("mysql");
77
150
  }
78
151
 
79
- static #isEnabled() {
80
- return !!DatabaseManager.getInstance().getDefaultConnection();
152
+ /** @private */
153
+ static #createDriver(driverName) {
154
+ const connections = this.#config?.connections || {};
155
+ const config = connections[driverName];
156
+
157
+ if (!config) {
158
+ throw new Error(`Database connection '${driverName}' is not configured`);
159
+ }
160
+
161
+ switch (config.driver) {
162
+ case "mysql":
163
+ return new MySQLDriver(config, this.#credentials);
164
+ case "postgres":
165
+ throw new Error("Postgres driver not yet implemented");
166
+ case "mongodb":
167
+ throw new Error("MongoDB driver not yet implemented");
168
+ default:
169
+ throw new Error(`Unknown database driver: ${config.driver}`);
170
+ }
81
171
  }
82
172
  }
83
173
 
84
- export default DB;
174
+ export default DB;