@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
@@ -5,254 +5,200 @@ import File from "./File.js";
5
5
  import Session from "./Session.js";
6
6
 
7
7
  /**
8
- * SessionManager
9
- *
10
- * Singleton that manages all sessions for the application.
11
- * Handles loading, creation, persistence, and lifecycle.
12
- *
13
- * Features:
14
- * - Cookie-based session IDs (signed)
15
- * - Pluggable storage drivers (Memory, File)
16
- * - Automatic session persistence
17
- * - Lifetime management and expiration
18
- * - Garbage collection for expired sessions
8
+ * Session manager singleton.
9
+ * Handles session loading, creation, persistence, and garbage collection.
19
10
  */
20
11
  class SessionManager {
21
- static instance = null;
22
- static #isInternal = false;
23
-
24
- // Instance properties
25
- config = null;
26
- store = null;
27
- ready = null;
28
- gcTimer = null;
29
-
30
- constructor () {
31
- if (!SessionManager.#isInternal) {
32
- throw new Error("SessionManager must be initialized with await SessionManager.getInstance()");
33
- }
34
- }
35
-
36
- static getSessionConfig() {
37
- return Config.all("session");
38
- }
12
+ static #instance = null;
39
13
 
40
- static async createInstance() {
41
- SessionManager.#isInternal = true;
42
- const instance = new SessionManager();
43
- SessionManager.#isInternal = false;
44
-
45
- instance.config = this.getSessionConfig();
46
- instance.config.cookie.signed = true; // Enforce signed cookies
47
- // Load storage driver based on config
48
- instance.store = instance.#createDriver(instance.config.driver);
49
- // Store ready promise (for File driver initialization)
50
- instance.ready = instance.store.ready || Promise.resolve();
51
- return instance;
52
- }
14
+ #config = null;
15
+ #store = null;
16
+ #gcTimer = null;
53
17
 
54
18
  /**
55
- * Create storage driver instance
56
- * @param {string} driver - Driver name (memory, file)
57
- * @returns {object} Driver instance
19
+ * Gets the singleton instance.
20
+ * @returns {Promise<SessionManager>}
58
21
  */
59
- #createDriver(driver) {
60
- switch (driver) {
61
- case 'file':
62
- return new File();
63
- case 'memory':
64
- default:
65
- return new Memory();
22
+ static async getInstance() {
23
+ if (!this.#instance) {
24
+ this.#instance = new SessionManager();
25
+ await this.#instance.#initialize();
66
26
  }
27
+
28
+ return this.#instance;
67
29
  }
68
30
 
69
- /**
70
- * Get singleton instance
71
- */
72
- static async getInstance () {
73
- if (!SessionManager.instance) {
74
- SessionManager.instance = await SessionManager.createInstance();
31
+ /** @private */
32
+ async #initialize() {
33
+ this.#config = Config.all("session");
34
+ this.#config.cookie.signed = true;
35
+
36
+ this.#store = this.#config.driver === "file" ? new File() : new Memory();
37
+
38
+ if (this.#store.ready) {
39
+ await this.#store.ready;
75
40
  }
76
- return SessionManager.instance;
77
41
  }
78
42
 
79
- // ========================================
80
- // Session Lifecycle
81
- // ========================================
82
-
83
43
  /**
84
- * Load existing session or create a new one
85
- *
86
- * Validates cookie signature, checks session lifetime,
87
- * and retrieves session data from the store.
88
- *
89
- * @param {object} request - Fastify request object
90
- * @param {object} response - Fastify response object
91
- * @returns {Session} Session instance
44
+ * Loads existing session or creates a new one.
45
+ * @param {import("fastify").FastifyRequest} request
46
+ * @param {import("fastify").FastifyReply} response
47
+ * @returns {Promise<Session>}
92
48
  */
93
- async load (request, response) {
94
- // Wait for store initialization (File driver directory setup)
95
- await this.ready;
96
-
97
- const signedCookie = request.cookies[this.config.cookieName];
98
-
99
- // No cookie → new session
49
+ async load(request, response) {
50
+ const signedCookie = request.cookies[this.#config.cookieName];
51
+
100
52
  if (!signedCookie) {
101
- return this.create(response);
53
+ return this.#create(response);
102
54
  }
103
55
 
104
- // Verify cookie signature
105
56
  const unsignResult = request.unsignCookie(signedCookie);
57
+
106
58
  if (!unsignResult.valid) {
107
- response.clearCookie(this.config.cookieName);
108
-
109
- return this.create(response);
59
+ response.clearCookie(this.#config.cookieName);
60
+
61
+ return this.#create(response);
110
62
  }
111
63
 
112
64
  const sessionId = unsignResult.value;
113
- const sessionData = await this.store.get(sessionId);
65
+ const sessionData = await this.#store.get(sessionId);
114
66
 
115
- // Session not found → new session
116
67
  if (!sessionData) {
117
- response.clearCookie(this.config.cookieName);
68
+ response.clearCookie(this.#config.cookieName);
118
69
 
119
- return this.create(response);
70
+ return this.#create(response);
120
71
  }
121
72
 
122
- // Check if session expired (lifetime)
123
73
  const lastActivity = sessionData.lastActivity || sessionData.createdAt;
124
- if (Date.now() - lastActivity > this.config.lifetime) {
125
- await this.store.delete(sessionId);
126
- response.clearCookie(this.config.cookieName);
127
74
 
128
- return this.create(response);
75
+ if (Date.now() - lastActivity > this.#config.lifetime) {
76
+ await this.#store.delete(sessionId);
77
+ response.clearCookie(this.#config.cookieName);
78
+
79
+ return this.#create(response);
129
80
  }
130
81
 
131
82
  return new Session(sessionId, sessionData);
132
83
  }
133
84
 
134
85
  /**
135
- * Create new session
136
- * @param {object} response - Fastify response
137
- * @returns {Session} New session instance
86
+ * Persists session data to store.
87
+ * @param {Session} session
138
88
  */
139
- async create (response) {
140
- const id = crypto.randomBytes(32).toString("hex");
141
- const sessionData = {
142
- data: {},
143
- createdAt: Date.now(),
144
- };
145
-
146
- await this.store.set(id, sessionData);
147
-
148
- // Set signed cookie
149
- const cookieOptions = { ...this.config.cookie, signed: true };
150
- response.setCookie(this.config.cookieName, id, cookieOptions);
89
+ async persist(session) {
90
+ await this.#store.set(session.id, {
91
+ data: session.all(),
92
+ createdAt: session.createdAt,
93
+ lastActivity: Date.now()
94
+ });
95
+ }
151
96
 
152
- return new Session(id, sessionData);
97
+ /**
98
+ * Deletes old session after regeneration.
99
+ * @param {string} oldId
100
+ */
101
+ async deleteOld(oldId) {
102
+ if (oldId) {
103
+ await this.#store.delete(oldId);
104
+ }
153
105
  }
154
106
 
155
- // ========================================
156
- // Garbage Collection
157
- // ========================================
107
+ /**
108
+ * Gets config for cookie options.
109
+ * @returns {Object}
110
+ */
111
+ get cookieConfig() {
112
+ return { ...this.#config.cookie, signed: true };
113
+ }
158
114
 
159
115
  /**
160
- * Start automatic garbage collection (every 15 minutes)
116
+ * Gets session cookie name.
117
+ * @returns {string}
161
118
  */
162
- startGarbageCollection () {
163
- if (typeof this.store.gc !== 'function') {
164
- return; // Store doesn't support GC
119
+ get cookieName() {
120
+ return this.#config.cookieName;
121
+ }
122
+
123
+ /** @private */
124
+ async #create(response) {
125
+ const id = crypto.randomBytes(32).toString("hex");
126
+ const sessionData = { data: {}, createdAt: Date.now() };
127
+
128
+ await this.#store.set(id, sessionData);
129
+ response.setCookie(this.#config.cookieName, id, this.cookieConfig);
130
+
131
+ return new Session(id, sessionData);
132
+ }
133
+
134
+ /** @private */
135
+ #startGC() {
136
+ if (typeof this.#store.gc !== "function") {
137
+ return;
165
138
  }
166
139
 
167
- const gcInterval = 15 * 60 * 1000; // 15 minutes
140
+ const interval = 15 * 60 * 1000;
168
141
 
169
- this.gcTimer = setInterval(async () => {
142
+ this.#gcTimer = setInterval(async () => {
170
143
  try {
171
- const deleted = await this.store.gc(this.config.lifetime);
144
+ const deleted = await this.#store.gc(this.#config.lifetime);
145
+
172
146
  if (deleted > 0) {
173
- console.log(`[Session GC] Deleted ${deleted} expired sessions`);
147
+ console.log(`[Session] GC: ${deleted} expired sessions deleted`);
174
148
  }
175
149
  }
176
- catch (error) {
177
- console.error('[Session GC] Error:', error.message);
150
+ catch (err) {
151
+ console.error("[Session] GC error:", err.message);
178
152
  }
179
- }, gcInterval);
153
+ }, interval);
180
154
 
181
- // Run immediately on startup
182
- this.store.gc(this.config.lifetime).catch(err => {
183
- console.error('[Session GC] Initial run failed:', err.message);
184
- });
155
+ this.#store.gc(this.#config.lifetime).catch(() => {});
185
156
  }
186
157
 
187
158
  /**
188
- * Stop garbage collection (graceful shutdown)
159
+ * Stops the garbage collection timer.
189
160
  */
190
- stopGarbageCollection () {
191
- if (this.gcTimer) {
192
- clearInterval(this.gcTimer);
193
- this.gcTimer = null;
161
+ stopGC() {
162
+ if (this.#gcTimer) {
163
+ clearInterval(this.#gcTimer);
164
+ this.#gcTimer = null;
194
165
  }
195
166
  }
196
167
 
197
- // ========================================
198
- // Server Setup
199
- // ========================================
200
-
201
168
  /**
202
- * Setup session middleware for Fastify server
203
- * @param {object} server - Fastify instance
169
+ * Sets up session middleware for Fastify.
170
+ * @param {import("fastify").FastifyInstance} server
204
171
  */
205
- static async setup (server) {
172
+ static async setup(server) {
206
173
  const manager = await SessionManager.getInstance();
207
-
174
+
208
175
  server.decorateRequest("session", null);
209
176
 
210
- // Load session before handling request
211
- server.addHook('preHandler', async (request, response) => {
177
+ server.addHook("preHandler", async (request, response) => {
212
178
  request.session = await manager.load(request, response);
213
179
  });
214
180
 
215
- // Handle cookie changes before response is sent
216
- server.addHook('onSend', async (request, response, payload) => {
217
- if (!request.session) return payload;
218
-
219
- // Handle regeneration - set new session cookie
220
- if (request.session.shouldRegenerate()) {
221
- response.setCookie(
222
- manager.config.cookieName,
223
- request.session.id,
224
- { ...manager.config.cookie, signed: true }
225
- );
181
+ server.addHook("onSend", async (request, response, payload) => {
182
+ if (request.session?.shouldRegenerate()) {
183
+ response.setCookie(manager.cookieName, request.session.id, manager.cookieConfig);
226
184
  }
227
185
 
228
186
  return payload;
229
187
  });
230
188
 
231
- // Persist session after sending response
232
- server.addHook('onResponse', async (request, response) => {
233
- if (!request.session) return;
234
-
235
- // Skip persistence for error responses (4xx/5xx)
236
- if (response.statusCode >= 400) return;
189
+ server.addHook("onResponse", async (request, response) => {
190
+ if (!request.session || response.statusCode >= 400) {
191
+ return;
192
+ }
237
193
 
238
- // Handle regeneration - delete old session
239
194
  if (request.session.shouldRegenerate()) {
240
- const oldId = request.session.getOldId();
241
- if (oldId) {
242
- await manager.store.delete(oldId);
243
- }
195
+ await manager.deleteOld(request.session.getOldId());
244
196
  }
245
197
 
246
- // Persist session data
247
- await manager.store.set(request.session.id, {
248
- data: request.session.all(),
249
- createdAt: request.session.createdAt,
250
- lastActivity: Date.now(),
251
- });
198
+ await manager.persist(request.session);
252
199
  });
253
200
 
254
- // Start garbage collection
255
- manager.startGarbageCollection();
201
+ manager.#startGC();
256
202
  }
257
203
  }
258
204
 
@@ -1,61 +1,51 @@
1
1
  /**
2
- * Memory Session Store
3
- *
4
- * In-memory session storage using JavaScript Map.
5
- * Fast but not persistent - all data is lost on server restart.
6
- *
7
- * Use cases:
8
- * - Development and testing
9
- * - Single-server deployments with acceptable data loss
10
- *
11
- * Production: Use File store for persistence or Redis for clustering.
2
+ * In-memory session storage.
3
+ * Fast but not persistent - data is lost on restart.
12
4
  */
13
- class Memory {
14
- constructor () {
15
- this.sessions = new Map();
16
- }
5
+ class MemoryStore {
6
+ #sessions = new Map();
17
7
 
18
8
  /**
19
- * Get session data by ID
20
- * @param {string} id - Session ID
21
- * @returns {object|null} Session data or null if not found
9
+ * Gets session data by ID.
10
+ * @param {string} id
11
+ * @returns {Promise<Object|null>}
22
12
  */
23
- async get (id) {
24
- return this.sessions.get(id) || null;
13
+ async get(id) {
14
+ return this.#sessions.get(id) || null;
25
15
  }
26
16
 
27
17
  /**
28
- * Save session data
29
- * @param {string} id - Session ID
30
- * @param {object} value - Session data
18
+ * Stores session data.
19
+ * @param {string} id
20
+ * @param {Object} value
31
21
  */
32
- async set (id, value) {
33
- this.sessions.set(id, value);
22
+ async set(id, value) {
23
+ this.#sessions.set(id, value);
34
24
  }
35
25
 
36
26
  /**
37
- * Delete session
38
- * @param {string} id - Session ID
39
- * @returns {boolean} True if deleted
27
+ * Deletes a session.
28
+ * @param {string} id
29
+ * @returns {Promise<boolean>}
40
30
  */
41
- async delete (id) {
42
- return this.sessions.delete(id);
31
+ async delete(id) {
32
+ return this.#sessions.delete(id);
43
33
  }
44
34
 
45
35
  /**
46
- * Garbage collection - remove expired sessions
47
- * @param {number} lifetime - Session lifetime in milliseconds
48
- * @returns {number} Number of deleted sessions
36
+ * Garbage collection - removes expired sessions.
37
+ * @param {number} lifetime - Max lifetime in milliseconds
38
+ * @returns {Promise<number>} Number of deleted sessions
49
39
  */
50
- async gc (lifetime) {
40
+ async gc(lifetime) {
51
41
  const now = Date.now();
52
42
  let deleted = 0;
53
43
 
54
- for (const [id, sessionData] of this.sessions.entries()) {
55
- const lastActivity = sessionData.lastActivity || sessionData.createdAt;
56
-
44
+ for (const [id, data] of this.#sessions.entries()) {
45
+ const lastActivity = data.lastActivity || data.createdAt;
46
+
57
47
  if (now - lastActivity > lifetime) {
58
- this.sessions.delete(id);
48
+ this.#sessions.delete(id);
59
49
  deleted++;
60
50
  }
61
51
  }
@@ -64,4 +54,4 @@ class Memory {
64
54
  }
65
55
  }
66
56
 
67
- export default Memory;
57
+ export default MemoryStore;