@nitronjs/framework 0.2.3 → 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 (68) hide show
  1. package/README.md +3 -1
  2. package/cli/create.js +88 -72
  3. package/cli/njs.js +13 -6
  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 +18 -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 +143 -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 +79 -9
  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 +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 +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,86 +1,89 @@
1
- import fs from "fs";
2
- import path from "path";
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
3
  import Config from "../Core/Config.js";
4
4
 
5
- class LogManager {
6
- static #levels = {
7
- debug: 0,
8
- info: 1,
9
- warn: 2,
10
- error: 3,
11
- fatal: 4
12
- };
13
-
14
- static #levelLabels = {
15
- debug: "DEBUG",
16
- info: "INFO ",
17
- warn: "WARN ",
18
- error: "ERROR",
19
- fatal: "FATAL"
20
- };
21
-
5
+ /**
6
+ * Application logger with console and file output support.
7
+ * Supports multiple log levels: debug, info, warn, error, fatal.
8
+ */
9
+ class Log {
10
+ static #levels = { debug: 0, info: 1, warn: 2, error: 3, fatal: 4 };
11
+ static #levelLabels = { debug: "DEBUG", info: "INFO", warn: "WARN", error: "ERROR", fatal: "FATAL" };
12
+
13
+ /**
14
+ * Logs a debug message.
15
+ * @param {string} message - Log message
16
+ * @param {Object} context - Additional context data
17
+ */
22
18
  static debug(message, context = {}) {
23
19
  this.#log("debug", message, context);
24
20
  }
25
21
 
22
+ /**
23
+ * Logs an info message.
24
+ * @param {string} message - Log message
25
+ * @param {Object} context - Additional context data
26
+ */
26
27
  static info(message, context = {}) {
27
28
  this.#log("info", message, context);
28
29
  }
29
30
 
31
+ /**
32
+ * Logs a warning message.
33
+ * @param {string} message - Log message
34
+ * @param {Object} context - Additional context data
35
+ */
30
36
  static warn(message, context = {}) {
31
37
  this.#log("warn", message, context);
32
38
  }
33
39
 
40
+ /**
41
+ * Logs an error message.
42
+ * @param {string} message - Log message
43
+ * @param {Object} context - Additional context data
44
+ */
34
45
  static error(message, context = {}) {
35
46
  this.#log("error", message, context);
36
47
  }
37
48
 
49
+ /**
50
+ * Logs a fatal error message.
51
+ * @param {string} message - Log message
52
+ * @param {Object} context - Additional context data
53
+ */
38
54
  static fatal(message, context = {}) {
39
55
  this.#log("fatal", message, context);
40
56
  }
41
57
 
42
- static #getConfig() {
43
- return {
44
- channel: Config.get("server.log.channel", "console"),
45
- level: Config.get("server.log.level", "info"),
46
- file: Config.get("server.log.file", "./storage/logs/app.log"),
47
- sync: Config.get("server.log.sync", false)
48
- };
49
- }
50
-
58
+ /** @private */
51
59
  static #log(level, message, context) {
52
60
  try {
53
- const config = this.#getConfig();
54
-
55
- if (!this.#shouldLog(level, config)) {
61
+ const config = {
62
+ channel: Config.get("server.log.channel", "console"),
63
+ level: Config.get("server.log.level", "info"),
64
+ file: Config.get("server.log.file", "./storage/logs/app.log"),
65
+ sync: Config.get("server.log.sync", false)
66
+ };
67
+
68
+ if (this.#levels[level] < this.#levels[config.level] || config.channel === "none") {
56
69
  return;
57
70
  }
58
71
 
59
72
  const timestamp = new Date();
60
73
 
61
- const channel = config.channel;
62
-
63
- if (channel === "none") {
64
- return;
65
- }
66
-
67
- if (channel === "console") {
74
+ if (config.channel === "console") {
68
75
  this.#logToConsole(level, message, context, timestamp);
69
76
  }
70
-
71
- if (channel === "file") {
77
+ else if (config.channel === "file") {
72
78
  this.#logToFile(level, message, context, timestamp, config);
73
79
  }
74
- } catch (e) {
80
+ }
81
+ catch (e) {
75
82
  console.error("Logger failure:", e.message);
76
83
  }
77
84
  }
78
85
 
79
- static #shouldLog(level, config) {
80
- const configLevel = config.level;
81
- return this.#levels[level] >= this.#levels[configLevel];
82
- }
83
-
86
+ /** @private */
84
87
  static #formatTimestamp(date) {
85
88
  const year = date.getFullYear();
86
89
  const month = String(date.getMonth() + 1).padStart(2, "0");
@@ -89,32 +92,19 @@ class LogManager {
89
92
  const minutes = String(date.getMinutes()).padStart(2, "0");
90
93
  const seconds = String(date.getSeconds()).padStart(2, "0");
91
94
  const ms = String(date.getMilliseconds()).padStart(3, "0");
95
+
92
96
  return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
93
97
  }
94
98
 
99
+ /** @private */
95
100
  static #logToConsole(level, message, context, timestamp) {
96
- const colors = {
97
- debug: "\x1b[90m",
98
- info: "\x1b[36m",
99
- warn: "\x1b[33m",
100
- error: "\x1b[31m",
101
- fatal: "\x1b[35m"
102
- };
103
-
104
- const icons = {
105
- debug: "◆",
106
- info: "●",
107
- warn: "⚠",
108
- error: "✕",
109
- fatal: "■"
110
- };
111
-
101
+ const colors = { debug: "\x1b[90m", info: "\x1b[36m", warn: "\x1b[33m", error: "\x1b[31m", fatal: "\x1b[35m" };
102
+ const icons = { debug: "", info: "●", warn: "⚠", error: "✕", fatal: "■" };
112
103
  const color = colors[level] || "";
113
104
  const icon = icons[level] || "";
114
105
  const reset = "\x1b[0m";
115
106
  const bold = "\x1b[1m";
116
107
  const dim = "\x1b[2m";
117
-
118
108
  const time = this.#formatTimestamp(timestamp);
119
109
  const width = process.stdout.columns || 80;
120
110
 
@@ -128,8 +118,7 @@ class LogManager {
128
118
 
129
119
  if (Object.keys(context).length > 0) {
130
120
  console.log(`${color}│${reset} ${dim}Context:${reset}`);
131
- const contextJson = JSON.stringify(context, null, 2);
132
- contextJson.split("\n").forEach(line => {
121
+ JSON.stringify(context, null, 2).split("\n").forEach(line => {
133
122
  console.log(`${color}│${reset} ${dim}${line}${reset}`);
134
123
  });
135
124
  console.log(`${color}│${reset}`);
@@ -139,18 +128,20 @@ class LogManager {
139
128
  console.log();
140
129
  }
141
130
 
131
+ /** @private */
142
132
  static #formatContext(context, indent = "") {
143
133
  const lines = [];
144
-
134
+
145
135
  for (const [key, value] of Object.entries(context)) {
146
- if (value === null || value === undefined) continue;
136
+ if (value === null || value === undefined) {
137
+ continue;
138
+ }
147
139
 
148
140
  if (typeof value === "object" && !Array.isArray(value)) {
149
141
  lines.push(`${indent}${key}:`);
150
142
  lines.push(...this.#formatContext(value, indent + " "));
151
143
  }
152
144
  else if (key === "stack" && typeof value === "string") {
153
- // Format stack trace nicely
154
145
  lines.push(`${indent}${key}:`);
155
146
  value.split("\n").forEach(line => {
156
147
  lines.push(`${indent} ${line.trim()}`);
@@ -164,6 +155,7 @@ class LogManager {
164
155
  return lines;
165
156
  }
166
157
 
158
+ /** @private */
167
159
  static #logToFile(level, message, context, timestamp, config) {
168
160
  const filePath = path.resolve(process.cwd(), config.file);
169
161
  const dir = path.dirname(filePath);
@@ -174,23 +166,19 @@ class LogManager {
174
166
 
175
167
  const time = this.#formatTimestamp(timestamp);
176
168
  const label = this.#levelLabels[level];
177
-
178
- // Build readable log entry
179
- const lines = [];
180
- lines.push(`[${time}] [${label}] ${message}`);
181
-
169
+ const lines = [`[${time}] [${label}] ${message}`];
170
+
182
171
  if (Object.keys(context).length > 0) {
183
- const contextLines = this.#formatContext(context, " ");
184
- lines.push(...contextLines);
172
+ lines.push(...this.#formatContext(context, " "));
185
173
  }
186
-
187
- lines.push(""); // Empty line between entries
188
-
174
+
175
+ lines.push("");
189
176
  const logEntry = lines.join("\n") + "\n";
190
177
 
191
178
  if (config.sync) {
192
179
  fs.appendFileSync(filePath, logEntry);
193
- } else {
180
+ }
181
+ else {
194
182
  fs.appendFile(filePath, logEntry, (err) => {
195
183
  if (err) {
196
184
  console.error("Failed to write log:", err.message);
@@ -200,4 +188,4 @@ class LogManager {
200
188
  }
201
189
  }
202
190
 
203
- export default LogManager;
191
+ export default Log;
@@ -0,0 +1,187 @@
1
+ import nodemailer from "nodemailer";
2
+ import View from "../View/View.js";
3
+
4
+ /**
5
+ * Email sending utility with fluent API.
6
+ * Supports text, HTML views, attachments, and calendar invites.
7
+ *
8
+ * @example
9
+ * await Mail.from("noreply@example.com")
10
+ * .to("user@example.com")
11
+ * .subject("Welcome!")
12
+ * .view(res, "emails/welcome", { name: "John" })
13
+ * .send();
14
+ */
15
+ class Mail {
16
+ #fromAddress = null;
17
+ #toAddress = null;
18
+ #subjectText = null;
19
+ #textContent = null;
20
+ #htmlContent = null;
21
+ #attachments = [];
22
+ #calendarContent = null;
23
+
24
+ /**
25
+ * Creates a new Mail instance with sender address.
26
+ * @param {string} email - Sender email address
27
+ * @returns {Mail} New Mail instance
28
+ */
29
+ static from(email) {
30
+ const instance = new Mail();
31
+ instance.#fromAddress = email;
32
+
33
+ return instance;
34
+ }
35
+
36
+ /**
37
+ * Sets the sender address.
38
+ * @param {string} email - Sender email address
39
+ * @returns {Mail} This instance for chaining
40
+ */
41
+ from(email) {
42
+ this.#fromAddress = email;
43
+
44
+ return this;
45
+ }
46
+
47
+ /**
48
+ * Creates a new Mail instance with recipient address.
49
+ * @param {string} email - Recipient email address
50
+ * @returns {Mail} New Mail instance
51
+ */
52
+ static to(email) {
53
+ const instance = new Mail();
54
+ instance.#toAddress = email;
55
+
56
+ return instance;
57
+ }
58
+
59
+ /**
60
+ * Sets the recipient address.
61
+ * @param {string} email - Recipient email address
62
+ * @returns {Mail} This instance for chaining
63
+ */
64
+ to(email) {
65
+ this.#toAddress = email;
66
+
67
+ return this;
68
+ }
69
+
70
+ /**
71
+ * Sets the email subject.
72
+ * @param {string} subject - Email subject line
73
+ * @returns {Mail} This instance for chaining
74
+ */
75
+ subject(subject) {
76
+ this.#subjectText = subject;
77
+
78
+ return this;
79
+ }
80
+
81
+ /**
82
+ * Adds an attachment to the email.
83
+ * @param {Object} attachment - Nodemailer attachment object
84
+ * @returns {Mail} This instance for chaining
85
+ */
86
+ attachment(attachment) {
87
+ this.#attachments.push(attachment);
88
+
89
+ return this;
90
+ }
91
+
92
+ /**
93
+ * Adds a calendar invite (ICS) to the email.
94
+ * @param {string} icsContent - ICS calendar content
95
+ * @returns {Mail} This instance for chaining
96
+ */
97
+ calendar(icsContent) {
98
+ this.#calendarContent = icsContent;
99
+
100
+ return this;
101
+ }
102
+
103
+ /**
104
+ * Sets plain text content.
105
+ * @param {string} message - Plain text message
106
+ * @returns {Mail} This instance for chaining
107
+ */
108
+ text(message) {
109
+ this.#textContent = message;
110
+
111
+ return this;
112
+ }
113
+
114
+ /**
115
+ * Sets HTML content from a view template.
116
+ * @param {import("fastify").FastifyReply} res - Fastify response object
117
+ * @param {string} viewName - View template name
118
+ * @param {Object|null} data - Data to pass to the view
119
+ * @returns {Mail} This instance for chaining
120
+ */
121
+ view(res, viewName, data = null) {
122
+ this.#htmlContent = View.renderFile(viewName, data);
123
+
124
+ return this;
125
+ }
126
+
127
+ /**
128
+ * Sends the email.
129
+ * @param {Function|null} transportCallback - Optional callback to configure custom transport
130
+ * @returns {Promise<Object>} Nodemailer send result
131
+ */
132
+ async send(transportCallback = null) {
133
+ let transporter;
134
+
135
+ if (typeof transportCallback === "function") {
136
+ const transportData = {
137
+ MAIL_HOST: null,
138
+ MAIL_PORT: null,
139
+ MAIL_USERNAME: null,
140
+ MAIL_PASSWORD: null,
141
+ MAIL_SECURE: null
142
+ };
143
+ transportCallback(transportData);
144
+ transporter = this.#createTransport(transportData);
145
+ }
146
+ else {
147
+ transporter = this.#createTransport(process.env);
148
+ }
149
+
150
+ const mailOptions = {
151
+ from: this.#fromAddress,
152
+ to: this.#toAddress,
153
+ subject: this.#subjectText,
154
+ text: this.#textContent,
155
+ html: this.#htmlContent,
156
+ attachments: this.#attachments.length > 0 ? this.#attachments : undefined,
157
+ alternatives: this.#calendarContent
158
+ ? [{
159
+ contentType: "text/calendar; method=REQUEST; charset=\"UTF-8\"",
160
+ content: this.#calendarContent,
161
+ component: "VEVENT",
162
+ headers: { "Content-Class": "urn:content-classes:calendarmessage" }
163
+ }]
164
+ : undefined
165
+ };
166
+
167
+ const response = await transporter.sendMail(mailOptions);
168
+ transporter.close();
169
+
170
+ return response;
171
+ }
172
+
173
+ /** @private */
174
+ #createTransport(data) {
175
+ return nodemailer.createTransport({
176
+ host: data.MAIL_HOST,
177
+ port: data.MAIL_PORT,
178
+ secure: data.MAIL_SECURE || false,
179
+ auth: {
180
+ user: data.MAIL_USERNAME,
181
+ pass: data.MAIL_PASSWORD
182
+ }
183
+ });
184
+ }
185
+ }
186
+
187
+ export default Mail;