@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.
- package/README.md +3 -1
- package/cli/create.js +88 -72
- package/cli/njs.js +14 -7
- package/lib/Auth/Auth.js +167 -0
- package/lib/Build/CssBuilder.js +9 -0
- package/lib/Build/FileAnalyzer.js +16 -0
- package/lib/Build/HydrationBuilder.js +17 -0
- package/lib/Build/Manager.js +15 -0
- package/lib/Build/colors.js +4 -0
- package/lib/Build/plugins.js +84 -20
- package/lib/Console/Commands/DevCommand.js +13 -9
- package/lib/Console/Commands/MakeCommand.js +24 -10
- package/lib/Console/Commands/MigrateCommand.js +0 -1
- package/lib/Console/Commands/MigrateFreshCommand.js +17 -25
- package/lib/Console/Commands/MigrateRollbackCommand.js +6 -3
- package/lib/Console/Commands/MigrateStatusCommand.js +6 -3
- package/lib/Console/Commands/SeedCommand.js +4 -2
- package/lib/Console/Commands/StorageLinkCommand.js +20 -5
- package/lib/Console/Output.js +142 -0
- package/lib/Core/Config.js +2 -1
- package/lib/Core/Paths.js +8 -0
- package/lib/Database/DB.js +141 -51
- package/lib/Database/Drivers/MySQLDriver.js +102 -157
- package/lib/Database/Migration/Checksum.js +3 -8
- package/lib/Database/Migration/MigrationRepository.js +25 -35
- package/lib/Database/Migration/MigrationRunner.js +56 -61
- package/lib/Database/Model.js +157 -83
- package/lib/Database/QueryBuilder.js +31 -0
- package/lib/Database/QueryValidation.js +36 -44
- package/lib/Database/Schema/Blueprint.js +25 -36
- package/lib/Database/Schema/Manager.js +31 -68
- package/lib/Database/Seeder/SeederRunner.js +12 -31
- package/lib/Date/DateTime.js +9 -0
- package/lib/Encryption/Encryption.js +52 -0
- package/lib/Faker/Faker.js +11 -0
- package/lib/Filesystem/Storage.js +120 -0
- package/lib/HMR/Server.js +81 -10
- package/lib/Hashing/Hash.js +41 -0
- package/lib/Http/Server.js +177 -152
- package/lib/Logging/{Manager.js → Log.js} +68 -80
- package/lib/Mail/Mail.js +187 -0
- package/lib/Route/Router.js +416 -0
- package/lib/Session/File.js +135 -233
- package/lib/Session/Manager.js +117 -171
- package/lib/Session/Memory.js +28 -38
- package/lib/Session/Session.js +71 -107
- package/lib/Support/Str.js +103 -0
- package/lib/Translation/Lang.js +54 -0
- package/lib/View/Client/hmr-client.js +94 -51
- package/lib/View/Client/nitronjs-icon.png +0 -0
- package/lib/View/{Manager.js → View.js} +44 -29
- package/lib/index.d.ts +42 -8
- package/lib/index.js +19 -12
- package/package.json +1 -1
- package/skeleton/app/Controllers/HomeController.js +7 -1
- package/skeleton/resources/css/global.css +1 -0
- package/skeleton/resources/views/Site/Home.tsx +456 -79
- package/skeleton/tsconfig.json +6 -1
- package/lib/Auth/Manager.js +0 -111
- package/lib/Database/Connection.js +0 -61
- package/lib/Database/Manager.js +0 -162
- package/lib/Encryption/Manager.js +0 -47
- package/lib/Filesystem/Manager.js +0 -74
- package/lib/Hashing/Manager.js +0 -25
- package/lib/Mail/Manager.js +0 -120
- package/lib/Route/Loader.js +0 -80
- package/lib/Route/Manager.js +0 -286
- 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
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
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 =
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
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
|
-
}
|
|
80
|
+
}
|
|
81
|
+
catch (e) {
|
|
75
82
|
console.error("Logger failure:", e.message);
|
|
76
83
|
}
|
|
77
84
|
}
|
|
78
85
|
|
|
79
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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
|
-
|
|
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
|
-
|
|
184
|
-
lines.push(...contextLines);
|
|
172
|
+
lines.push(...this.#formatContext(context, " "));
|
|
185
173
|
}
|
|
186
|
-
|
|
187
|
-
lines.push("");
|
|
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
|
-
}
|
|
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
|
|
191
|
+
export default Log;
|
package/lib/Mail/Mail.js
ADDED
|
@@ -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;
|