@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
@@ -1,75 +1,108 @@
1
- import { pathToFileURL } from "node:url";
2
1
  import dotenv from "dotenv";
3
- import path from "node:path";
4
2
  import fastify from "fastify";
5
3
  import fastifyCors from "@fastify/cors";
6
4
  import fastifyStatic from "@fastify/static";
7
5
  import fastifyCookie from "@fastify/cookie";
8
6
  import fastifyHelmet from "@fastify/helmet";
9
7
  import fastifyMultipart from "@fastify/multipart";
8
+ import fastifyFormbody from "@fastify/formbody";
9
+ import fs from "node:fs";
10
+ import { fileURLToPath } from "node:url";
11
+ import path from "node:path";
10
12
  import Paths from "../Core/Paths.js";
11
13
  import Config from "../Core/Config.js";
12
14
  import Environment from "../Core/Environment.js";
13
- import Route from "../Route/Manager.js";
14
- import View from "../View/Manager.js";
15
- import Auth from "../Auth/Manager.js";
15
+ import Router from "../Route/Router.js";
16
+ import View from "../View/View.js";
17
+ import Auth from "../Auth/Auth.js";
16
18
  import SessionManager from "../Session/Manager.js";
17
19
  import DB from "../Database/DB.js";
18
- import Log from "../Logging/Manager.js";
19
- import Loader from "../Route/Loader.js";
20
+ import Log from "../Logging/Log.js";
20
21
  import HMRServer from "../HMR/Server.js";
21
22
 
23
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
24
+ const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, "../../package.json"), "utf8"));
25
+
26
+ /**
27
+ * HTTP server manager built on Fastify.
28
+ * Handles server configuration, plugin registration, and lifecycle management.
29
+ *
30
+ * @example
31
+ * import { Server } from "@nitronjs/framework";
32
+ * await Server.start();
33
+ */
22
34
  class Server {
23
35
  static #server;
24
- static #serverConfigs;
36
+ static #config;
25
37
 
38
+ /**
39
+ * Starts the HTTP server with all configured plugins and routes.
40
+ * @returns {Promise<void>}
41
+ */
26
42
  static async start() {
27
43
  dotenv.config({ quiet: true });
28
-
29
- // Initialize config system first (loads all configs once)
30
44
  await Config.initialize();
31
-
32
- // Load project configs dynamically
33
- this.#serverConfigs = Config.all("server");
34
45
 
35
- await Loader.initialize();
46
+ this.#config = Config.all("server");
47
+ const { web_server = {}, cors = {} } = this.#config;
36
48
 
37
- // Load project routes dynamically
38
- await import(Paths.routeUrl("web"));
39
-
40
- await this.#createServer();
41
- await DB.setup();
42
- await SessionManager.setup(this.#server);
43
- Auth.setup(this.#server);
44
- await Route.setup(this.#server);
45
- View.setup(this.#server);
46
- await this.#listen();
47
- }
49
+ const serverDefaults = {
50
+ bodyLimit: 50 * 1024 * 1024,
51
+ trustProxy: true,
52
+ maxParamLength: 150
53
+ };
54
+
55
+ const webServerConfig = { ...serverDefaults, ...web_server };
48
56
 
49
- static async #createServer() {
57
+ // Create Fastify instance
50
58
  this.#server = fastify({
51
59
  logger: false,
52
- bodyLimit: this.#serverConfigs.web_server.bodyLimit,
53
- trustProxy: this.#serverConfigs.web_server.trustProxy,
60
+ bodyLimit: webServerConfig.bodyLimit,
61
+ trustProxy: webServerConfig.trustProxy,
54
62
  requestIdHeader: "x-request-id",
55
63
  exposeHeadRoutes: false,
56
64
  requestTimeout: 60000,
57
65
  connectionTimeout: 120000,
58
66
  routerOptions: {
59
67
  ignoreTrailingSlash: true,
60
- maxParamLength: this.#serverConfigs.web_server.maxParamLength
68
+ maxParamLength: webServerConfig.maxParamLength
61
69
  }
62
70
  });
63
71
 
64
- this.#registerPlugins();
65
- this.#registerHooks();
66
- this.#registerShutdownHandlers();
67
- }
68
-
69
- static #registerPlugins() {
72
+ // Register plugins
70
73
  this.#server.register(fastifyCors, {
71
- origin: this.#getCorsOrigin.bind(this),
72
- credentials: this.#serverConfigs.cors.origin === "*" ? false : this.#serverConfigs.cors.credentials === true
74
+ origin: (requestOrigin, cb) => {
75
+ if (!requestOrigin) {
76
+ return cb(null, true);
77
+ }
78
+
79
+ const origin = cors.origin;
80
+
81
+ if (origin === "*") {
82
+ return cb(null, true);
83
+ }
84
+ else if (origin === "auto") {
85
+ const url = process.env.APP_URL;
86
+ const port = process.env.APP_PORT;
87
+
88
+ if (!url) {
89
+ return cb(null, false);
90
+ }
91
+
92
+ const appOrigin = !port || port === "80" || port === "443" ? url : `${url}:${port}`;
93
+
94
+ return cb(null, requestOrigin === appOrigin);
95
+ }
96
+ else if (typeof origin === "string") {
97
+ return cb(null, requestOrigin === origin);
98
+ }
99
+ else if (Array.isArray(origin)) {
100
+ return cb(null, origin.includes(requestOrigin));
101
+ }
102
+
103
+ return cb(null, false);
104
+ },
105
+ credentials: cors.origin === "*" ? false : cors.credentials === true
73
106
  });
74
107
 
75
108
  this.#server.register(fastifyStatic, {
@@ -100,108 +133,90 @@ class Server {
100
133
  xssFilter: true
101
134
  });
102
135
 
136
+ const multipartDefaults = {
137
+ fieldNameSize: 100,
138
+ fieldSize: 2 * 1024 * 1024,
139
+ maxFields: 50,
140
+ maxFileSize: 20 * 1024 * 1024,
141
+ maxFiles: 10,
142
+ maxParts: 60,
143
+ attachFieldsToBody: true
144
+ };
145
+
146
+ const multipartConfig = { ...multipartDefaults, ...web_server.multipart };
147
+
103
148
  this.#server.register(fastifyMultipart, {
104
149
  limits: {
105
- fieldNameSize: this.#serverConfigs.web_server.multipart.fieldNameSize,
106
- fieldSize: this.#serverConfigs.web_server.multipart.fieldSize,
107
- fields: this.#serverConfigs.web_server.multipart.maxFields,
108
- fileSize: this.#serverConfigs.web_server.multipart.maxFileSize,
109
- files: this.#serverConfigs.web_server.multipart.maxFiles,
110
- parts: this.#serverConfigs.web_server.multipart.maxParts
150
+ fieldNameSize: multipartConfig.fieldNameSize,
151
+ fieldSize: multipartConfig.fieldSize,
152
+ fields: multipartConfig.maxFields,
153
+ fileSize: multipartConfig.maxFileSize,
154
+ files: multipartConfig.maxFiles,
155
+ parts: multipartConfig.maxParts
111
156
  },
112
- attachFieldsToBody: this.#serverConfigs.web_server.multipart.attachFieldsToBody
157
+ attachFieldsToBody: multipartConfig.attachFieldsToBody
113
158
  });
114
- }
115
-
116
- static #getCorsOrigin(requestOrigin, cb) {
117
- if (!requestOrigin) return cb(null, true);
118
-
119
- const { origin } = this.#serverConfigs.cors;
120
-
121
- if (origin === "*") return cb(null, true);
122
-
123
- if (origin === "auto") {
124
- const url = process.env.APP_URL;
125
- const port = process.env.APP_PORT;
126
- if (!url) return cb(null, false);
127
159
 
128
- const appOrigin = !port || port === "80" || port === "443" ? url : `${url}:${port}`;
129
- return cb(null, requestOrigin === appOrigin);
130
- }
160
+ this.#server.register(fastifyFormbody);
131
161
 
132
- if (typeof origin === "string") return cb(null, requestOrigin === origin);
133
- if (Array.isArray(origin)) return cb(null, origin.includes(requestOrigin));
162
+ // Register hooks
163
+ this.#server.addHook("preHandler", async (req) => {
164
+ if (!req.isMultipart() || !req.body) {
165
+ return;
166
+ }
134
167
 
135
- return cb(null, false);
136
- }
168
+ const normalized = {};
169
+ const files = {};
137
170
 
138
- static #parseBracketNotation(data) {
139
- if (!data || typeof data !== "object") {
140
- return data;
141
- }
171
+ for (const [key, field] of Object.entries(req.body)) {
172
+ if (field?.toBuffer) {
173
+ if (field.filename) {
174
+ files[key] = field;
175
+ }
176
+ }
177
+ else if (field && typeof field === "object" && "value" in field) {
178
+ normalized[key] = field.value;
179
+ }
180
+ else {
181
+ normalized[key] = field;
182
+ }
183
+ }
142
184
 
143
- const hasBrackets = Object.keys(data).some(key => key.includes("["));
144
- if (!hasBrackets) {
145
- return data;
146
- }
185
+ const data = { ...normalized, ...files };
186
+ const hasBrackets = Object.keys(data).some(key => key.includes("["));
147
187
 
148
- const result = {};
188
+ if (!hasBrackets) {
189
+ req.body = data;
149
190
 
150
- for (const [key, value] of Object.entries(data)) {
151
- if (!key.includes("[")) {
152
- result[key] = value;
153
- continue;
191
+ return;
154
192
  }
155
193
 
156
- const parts = key.replace(/\]/g, "").split("[");
157
- let current = result;
158
-
159
- for (let i = 0; i < parts.length - 1; i++) {
160
- const part = parts[i];
161
- const nextPart = parts[i + 1];
162
- const isNextIndex = /^\d+$/.test(nextPart);
194
+ const result = {};
163
195
 
164
- if (current[part] === undefined) {
165
- current[part] = isNextIndex ? [] : {};
196
+ for (const [key, value] of Object.entries(data)) {
197
+ if (!key.includes("[")) {
198
+ result[key] = value;
199
+ continue;
166
200
  }
167
201
 
168
- current = current[part];
169
- }
170
-
171
- const lastPart = parts[parts.length - 1];
172
- current[lastPart] = value;
173
- }
202
+ const parts = key.replace(/\]/g, "").split("[");
203
+ let current = result;
174
204
 
175
- return result;
176
- }
205
+ for (let i = 0; i < parts.length - 1; i++) {
206
+ const part = parts[i];
207
+ const isNextIndex = /^\d+$/.test(parts[i + 1]);
177
208
 
178
- static #registerHooks() {
179
- this.#server.addHook("preHandler", async (req) => {
180
- if (req.isMultipart() && req.body) {
181
- const normalized = {};
182
- const files = {};
183
-
184
- for (const [key, field] of Object.entries(req.body)) {
185
- if (field && typeof field === 'object') {
186
- if (field.toBuffer) {
187
- if (!field.filename) continue;
188
-
189
- files[key] = field;
190
- }
191
- else if ('value' in field) {
192
- normalized[key] = field.value;
193
- }
194
- else {
195
- normalized[key] = field;
196
- }
197
- }
198
- else {
199
- normalized[key] = field;
209
+ if (current[part] === undefined) {
210
+ current[part] = isNextIndex ? [] : {};
200
211
  }
212
+
213
+ current = current[part];
201
214
  }
202
215
 
203
- req.body = this.#parseBracketNotation({ ...normalized, ...files });
216
+ current[parts[parts.length - 1]] = value;
204
217
  }
218
+
219
+ req.body = result;
205
220
  });
206
221
 
207
222
  this.#server.addHook("onResponse", async (req, res) => {
@@ -217,26 +232,51 @@ class Server {
217
232
  requestId: req.id
218
233
  });
219
234
  });
220
- }
221
235
 
222
- static #registerShutdownHandlers() {
236
+ // Register shutdown handlers
223
237
  const shutdown = async () => {
224
- const sessionManager = await SessionManager.getInstance();
225
- sessionManager.stopGarbageCollection();
226
- await DB.close();
227
- await this.#server.close();
228
- process.exit(0);
238
+ console.log();
239
+ console.log("\x1b[33m⏻ Server shutting down...\x1b[0m");
240
+ console.log("\x1b[32m✓ Server stopped gracefully\x1b[0m");
241
+
242
+ try {
243
+ (await SessionManager.getInstance()).stopGC();
244
+ await Promise.race([
245
+ DB.close(),
246
+ new Promise(resolve => setTimeout(resolve, 1000))
247
+ ]);
248
+ await Promise.race([
249
+ this.#server.close(),
250
+ new Promise(resolve => setTimeout(resolve, 1000))
251
+ ]);
252
+ }
253
+ catch {
254
+ // Ignore errors during shutdown
255
+ }
229
256
  };
230
257
 
231
258
  process.on("SIGTERM", shutdown);
232
259
  process.on("SIGINT", shutdown);
233
- }
234
260
 
235
- static async #listen() {
261
+ // Setup managers
262
+ await DB.setup();
263
+ await SessionManager.setup(this.#server);
264
+ Auth.setup(this.#server);
265
+
266
+ // Register HMR routes before Router (dev only)
267
+ if (Environment.isDev) {
268
+ HMRServer.registerRoutes(this.#server);
269
+ }
270
+
271
+ await Router.setup(this.#server);
272
+ View.setup(this.#server);
273
+
274
+ // Listen
236
275
  try {
237
276
  const url = process.env.APP_URL ? new URL(process.env.APP_URL) : null;
238
- const host = url ? url.hostname : "127.0.0.1";
277
+ const host = url?.hostname || "127.0.0.1";
239
278
  const port = Number(process.env.APP_PORT) || 3000;
279
+
240
280
  const address = await this.#server.listen({ host, port });
241
281
 
242
282
  if (Environment.isDev) {
@@ -244,36 +284,30 @@ class Server {
244
284
  }
245
285
 
246
286
  this.#printBanner({ success: true, address, host, port });
247
-
248
- Log.info("Server started successfully!", {
249
- address,
250
- host,
251
- port,
252
- environment: Environment.isDev ? "development" : "production"
253
- });
287
+ Log.info("Server started successfully!", { address, host, port, environment: Environment.isDev ? "development" : "production" });
254
288
  }
255
289
  catch (err) {
256
290
  this.#printBanner({ success: false, error: err });
257
-
258
- Log.fatal("Server startup failed", {
259
- error: err.message,
260
- code: err.code,
261
- stack: err.stack
262
- });
263
-
291
+ Log.fatal("Server startup failed", { error: err.message, code: err.code, stack: err.stack });
264
292
  process.exit(1);
265
293
  }
266
294
  }
267
295
 
296
+ // Private Methods
268
297
  static #printBanner({ success, address, host, port, error }) {
269
- const { log } = this.#serverConfigs;
270
- if (log.channel !== "none" && log.channel !== "file") return;
298
+ if (this.#config.log.channel !== "none" && this.#config.log.channel !== "file") {
299
+ return;
300
+ }
271
301
 
272
302
  const color = success ? "\x1b[32m" : "\x1b[31m";
273
303
  const reset = "\x1b[0m";
274
304
  const bold = "\x1b[1m";
275
305
  const boxWidth = 70;
306
+ const stripAnsi = str => str.replace(/\x1b\[[0-9;]*m/g, "");
307
+ const pad = (content, length) => content + " ".repeat(Math.max(0, length - stripAnsi(content).length));
276
308
 
309
+ const version = `v${packageJson.version}`;
310
+
277
311
  const logo = `
278
312
  ${color}███╗ ██╗██╗████████╗██████╗ ██████╗ ███╗ ██╗ ██╗███████╗
279
313
  ████╗ ██║██║╚══██╔══╝██╔══██╗██╔═══██╗████╗ ██║ ██║██╔════╝
@@ -283,12 +317,6 @@ ${color}███╗ ██╗██╗████████╗████
283
317
  ╚═╝ ╚═══╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝ ╚════╝ ╚══════╝${reset}
284
318
  `;
285
319
 
286
- const stripAnsi = str => str.replace(/\x1b\[[0-9;]*m/g, "");
287
- const pad = (content, length) => {
288
- const visible = stripAnsi(content);
289
- return content + " ".repeat(Math.max(0, length - visible.length));
290
- };
291
-
292
320
  const lines = success
293
321
  ? [
294
322
  `${color}${bold}✓${reset} ${bold}Server started successfully!${reset}`,
@@ -296,7 +324,8 @@ ${color}███╗ ██╗██╗████████╗████
296
324
  `${bold}Address:${reset} ${address}`,
297
325
  `${bold}Host:${reset} ${host}`,
298
326
  `${bold}Port:${reset} ${port}`,
299
- `${bold}Mode:${reset} ${Environment.isDev ? "development" : "production"}`
327
+ `${bold}Mode:${reset} ${Environment.isDev ? "development" : "production"}`,
328
+ `${bold}Version:${reset} ${version}`
300
329
  ]
301
330
  : [
302
331
  `${color}${bold}✕${reset} ${bold}Server failed to start${reset}`,
@@ -314,7 +343,6 @@ ${color}███╗ ██╗██╗████████╗████
314
343
 
315
344
  console.log(`${color}│${reset} ${" ".repeat(boxWidth - 4)} ${color}│${reset}`);
316
345
  console.log(`${color}└${"─".repeat(boxWidth - 2)}┘${reset}`);
317
- console.log();
318
346
  }
319
347
  }
320
348