@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,114 +1,97 @@
1
1
  import crypto from "crypto";
2
2
 
3
3
  /**
4
- * Session
5
- *
6
- * Represents a single user session with secure data storage.
7
- * All data is private and accessed through controlled methods.
8
- *
9
- * Features:
10
- * - Private data storage (no external mutation)
11
- * - CSRF token generation and verification
12
- * - Flash messages (one-time messages)
13
- * - Session regeneration (security)
14
- * - Session destruction (logout)
4
+ * Represents a single user session.
5
+ * Provides secure data storage, CSRF protection, and flash messages.
15
6
  */
16
7
  class Session {
17
- // Private fields
18
8
  #id;
19
9
  #data;
20
10
  #createdAt;
21
11
  #lastActivity;
22
- #flags = {
23
- shouldRegenerate: false,
24
- oldId: null
25
- };
12
+ #oldId = null;
13
+ #regenerated = false;
26
14
 
27
- constructor (id, sessionData) {
15
+ constructor(id, sessionData) {
28
16
  this.#id = id;
29
17
  this.#data = sessionData.data || {};
30
18
  this.#createdAt = sessionData.createdAt || Date.now();
31
19
  this.#lastActivity = sessionData.lastActivity || null;
32
20
  }
33
21
 
34
- // ========================================
35
- // Getters
36
- // ========================================
37
-
38
- get id () {
22
+ get id() {
39
23
  return this.#id;
40
24
  }
41
25
 
42
- get createdAt () {
26
+ get createdAt() {
43
27
  return this.#createdAt;
44
28
  }
45
29
 
46
- get lastActivity () {
30
+ get lastActivity() {
47
31
  return this.#lastActivity;
48
32
  }
49
33
 
50
- // ========================================
51
- // Data Management
52
- // ========================================
53
-
54
34
  /**
55
- * Get a session value
35
+ * Gets a session value.
36
+ * @param {string} key
37
+ * @returns {*}
56
38
  */
57
- get (key) {
39
+ get(key) {
58
40
  return this.#data[key];
59
41
  }
60
42
 
61
43
  /**
62
- * Set a session value
44
+ * Sets a session value.
45
+ * @param {string} key
46
+ * @param {*} value
63
47
  */
64
- set (key, value) {
48
+ set(key, value) {
65
49
  this.#data[key] = value;
66
50
  }
67
51
 
68
52
  /**
69
- * Get all session data (copy to prevent mutations)
53
+ * Gets all session data (copy).
54
+ * @returns {Object}
70
55
  */
71
- all () {
56
+ all() {
72
57
  return { ...this.#data };
73
58
  }
74
59
 
75
- // ========================================
76
- // CSRF Protection
77
- // ========================================
78
-
79
60
  /**
80
- * Generate new CSRF token
81
- * @returns {string} Generated token
61
+ * Generates a new CSRF token.
62
+ * @returns {string}
82
63
  */
83
- generateCsrfToken () {
84
- const token = crypto.randomBytes(32).toString('hex');
85
- this.set('_csrf', token);
64
+ generateCsrfToken() {
65
+ const token = crypto.randomBytes(32).toString("hex");
66
+ this.set("_csrf", token);
86
67
 
87
68
  return token;
88
69
  }
89
70
 
90
71
  /**
91
- * Get CSRF token (generate if doesn't exist)
92
- * @returns {string} CSRF token
72
+ * Gets the CSRF token, generating one if needed.
73
+ * @returns {string}
93
74
  */
94
- getCsrfToken () {
95
- return this.get('_csrf') || this.generateCsrfToken();
75
+ getCsrfToken() {
76
+ return this.get("_csrf") || this.generateCsrfToken();
96
77
  }
97
78
 
98
79
  /**
99
- * Verify CSRF token (timing-safe comparison)
100
- * @param {string} token - Token to verify
101
- * @returns {boolean} True if valid
80
+ * Verifies a CSRF token using timing-safe comparison.
81
+ * @param {string} token
82
+ * @returns {boolean}
102
83
  */
103
- verifyCsrfToken (token) {
104
- const sessionToken = this.get('_csrf');
105
-
106
- if (!sessionToken || !token) return false;
107
-
84
+ verifyCsrfToken(token) {
85
+ const sessionToken = this.get("_csrf");
86
+
87
+ if (!sessionToken || !token) {
88
+ return false;
89
+ }
90
+
108
91
  try {
109
92
  return crypto.timingSafeEqual(
110
- Buffer.from(sessionToken, 'hex'),
111
- Buffer.from(token, 'hex')
93
+ Buffer.from(sessionToken, "hex"),
94
+ Buffer.from(token, "hex")
112
95
  );
113
96
  }
114
97
  catch {
@@ -116,80 +99,61 @@ class Session {
116
99
  }
117
100
  }
118
101
 
119
- // ========================================
120
- // Flash Messages
121
- // ========================================
122
-
123
102
  /**
124
- * Store a flash message (available only for the next request)
125
- * @param {string} key - Message key
126
- * @param {any} value - Message value
103
+ * Stores a flash message (available only for next request).
104
+ * @param {string} key
105
+ * @param {*} value
127
106
  */
128
- flash (key, value) {
107
+ flash(key, value) {
129
108
  if (!this.#data._flash) {
130
109
  this.#data._flash = {};
131
110
  }
132
-
111
+
133
112
  this.#data._flash[key] = value;
134
113
  }
135
114
 
136
115
  /**
137
- * Get and remove a flash message
138
- * @param {string} key - Message key
139
- * @returns {any} Message value or undefined
116
+ * Gets and removes a flash message.
117
+ * @param {string} key
118
+ * @returns {*}
140
119
  */
141
- getFlash (key) {
142
- if (!this.#data._flash?.[key]) return undefined;
143
-
144
- const value = this.#data._flash[key];
120
+ getFlash(key) {
121
+ if (!this.#data._flash?.[key]) {
122
+ return undefined;
123
+ }
145
124
 
125
+ const value = this.#data._flash[key];
146
126
  delete this.#data._flash[key];
147
127
 
148
128
  return value;
149
129
  }
150
130
 
151
- // ========================================
152
- // Lifecycle Management
153
- // ========================================
154
-
155
131
  /**
156
- * Regenerate session ID and CSRF token
157
- *
158
- * Prevents session fixation attacks by creating a new session ID.
159
- * Call this after login, logout, or any privilege escalation.
160
- *
161
- * @returns {object} {oldId, newId}
132
+ * Regenerates session ID and CSRF token.
133
+ * Prevents session fixation attacks.
134
+ * @returns {{ oldId: string, newId: string }}
162
135
  */
163
- regenerate () {
164
- // Guard: Prevent concurrent regeneration (idempotent)
165
- if (this.#flags.shouldRegenerate) {
166
- return {
167
- oldId: this.#flags.oldId,
168
- newId: this.#id
169
- };
136
+ regenerate() {
137
+ if (this.#regenerated) {
138
+ return { oldId: this.#oldId, newId: this.#id };
170
139
  }
171
-
172
- this.#flags.oldId = this.#id;
173
- this.#id = crypto.randomBytes(32).toString('hex');
174
- this.#flags.shouldRegenerate = true;
140
+
141
+ this.#oldId = this.#id;
142
+ this.#id = crypto.randomBytes(32).toString("hex");
143
+ this.#regenerated = true;
175
144
  this.generateCsrfToken();
176
-
177
- return {
178
- oldId: this.#flags.oldId,
179
- newId: this.#id
180
- };
181
- }
182
145
 
183
- // ========================================
184
- // Internal Flags (used by Manager)
185
- // ========================================
146
+ return { oldId: this.#oldId, newId: this.#id };
147
+ }
186
148
 
187
- shouldRegenerate () {
188
- return this.#flags.shouldRegenerate;
149
+ /** @private */
150
+ shouldRegenerate() {
151
+ return this.#regenerated;
189
152
  }
190
153
 
191
- getOldId () {
192
- return this.#flags.oldId;
154
+ /** @private */
155
+ getOldId() {
156
+ return this.#oldId;
193
157
  }
194
158
  }
195
159
 
@@ -1,4 +1,17 @@
1
+ /**
2
+ * String utility class with helper methods.
3
+ * Provides Laravel-style string manipulation functions.
4
+ *
5
+ * @example
6
+ * Str.slug("Hello World"); // "hello-world"
7
+ * Str.camel("user_name"); // "userName"
8
+ */
1
9
  class Str {
10
+ /**
11
+ * Generates a random alphanumeric string.
12
+ * @param {number} length - Desired string length
13
+ * @returns {string} Random string
14
+ */
2
15
  static random(length) {
3
16
  const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
4
17
  let result = "";
@@ -11,6 +24,11 @@ class Str {
11
24
  return result;
12
25
  }
13
26
 
27
+ /**
28
+ * Converts a string to URL-friendly slug.
29
+ * @param {string} text - Input text
30
+ * @returns {string} Slugified string
31
+ */
14
32
  static slug(text) {
15
33
  const normalizedInput = text
16
34
  .toLowerCase()
@@ -25,28 +43,55 @@ class Str {
25
43
  return slug;
26
44
  }
27
45
 
46
+ /**
47
+ * Capitalizes the first character.
48
+ * @param {string} str - Input string
49
+ * @returns {string} String with first char uppercase
50
+ */
28
51
  static ucfirst(str) {
29
52
  if (!str) return "";
53
+
30
54
  return str.charAt(0).toUpperCase() + str.slice(1);
31
55
  }
32
56
 
57
+ /**
58
+ * Lowercases the first character.
59
+ * @param {string} str - Input string
60
+ * @returns {string} String with first char lowercase
61
+ */
33
62
  static lcfirst(str) {
34
63
  if (!str) return "";
64
+
35
65
  return str.charAt(0).toLowerCase() + str.slice(1);
36
66
  }
37
67
 
68
+ /**
69
+ * Converts to camelCase.
70
+ * @param {string} str - Input string
71
+ * @returns {string} camelCased string
72
+ */
38
73
  static camel(str) {
39
74
  return str
40
75
  .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
41
76
  .replace(/^(.)/, (c) => c.toLowerCase());
42
77
  }
43
78
 
79
+ /**
80
+ * Converts to PascalCase.
81
+ * @param {string} str - Input string
82
+ * @returns {string} PascalCased string
83
+ */
44
84
  static pascal(str) {
45
85
  return str
46
86
  .replace(/[-_\s]+(.)?/g, (_, c) => (c ? c.toUpperCase() : ""))
47
87
  .replace(/^(.)/, (c) => c.toUpperCase());
48
88
  }
49
89
 
90
+ /**
91
+ * Converts to snake_case.
92
+ * @param {string} str - Input string
93
+ * @returns {string} snake_cased string
94
+ */
50
95
  static snake(str) {
51
96
  return str
52
97
  .replace(/([a-z])([A-Z])/g, "$1_$2")
@@ -54,6 +99,11 @@ class Str {
54
99
  .toLowerCase();
55
100
  }
56
101
 
102
+ /**
103
+ * Converts to kebab-case.
104
+ * @param {string} str - Input string
105
+ * @returns {string} kebab-cased string
106
+ */
57
107
  static kebab(str) {
58
108
  return str
59
109
  .replace(/([a-z])([A-Z])/g, "$1-$2")
@@ -61,38 +111,91 @@ class Str {
61
111
  .toLowerCase();
62
112
  }
63
113
 
114
+ /**
115
+ * Limits string to specified length.
116
+ * @param {string} str - Input string
117
+ * @param {number} length - Maximum length
118
+ * @param {string} suffix - Suffix to append (default: "...")
119
+ * @returns {string} Truncated string
120
+ */
64
121
  static limit(str, length, suffix = "...") {
65
122
  if (!str || str.length <= length) return str;
123
+
66
124
  return str.substring(0, length) + suffix;
67
125
  }
68
126
 
127
+ /**
128
+ * Limits string to specified word count.
129
+ * @param {string} str - Input string
130
+ * @param {number} count - Maximum word count
131
+ * @param {string} suffix - Suffix to append (default: "...")
132
+ * @returns {string} Truncated string
133
+ */
69
134
  static words(str, count, suffix = "...") {
70
135
  const wordsArray = str.split(/\s+/);
136
+
71
137
  if (wordsArray.length <= count) return str;
138
+
72
139
  return wordsArray.slice(0, count).join(" ") + suffix;
73
140
  }
74
141
 
142
+ /**
143
+ * Checks if string contains substring.
144
+ * @param {string} haystack - String to search in
145
+ * @param {string} needle - String to search for
146
+ * @returns {boolean} True if found
147
+ */
75
148
  static contains(haystack, needle) {
76
149
  return haystack.includes(needle);
77
150
  }
78
151
 
152
+ /**
153
+ * Checks if string starts with substring.
154
+ * @param {string} haystack - String to check
155
+ * @param {string} needle - Prefix to look for
156
+ * @returns {boolean} True if starts with
157
+ */
79
158
  static startsWith(haystack, needle) {
80
159
  return haystack.startsWith(needle);
81
160
  }
82
161
 
162
+ /**
163
+ * Checks if string ends with substring.
164
+ * @param {string} haystack - String to check
165
+ * @param {string} needle - Suffix to look for
166
+ * @returns {boolean} True if ends with
167
+ */
83
168
  static endsWith(haystack, needle) {
84
169
  return haystack.endsWith(needle);
85
170
  }
86
171
 
172
+ /**
173
+ * Replaces first occurrence of search string.
174
+ * @param {string} str - Input string
175
+ * @param {string} search - String to find
176
+ * @param {string} replace - Replacement string
177
+ * @returns {string} Modified string
178
+ */
87
179
  static replaceFirst(str, search, replace) {
88
180
  const pos = str.indexOf(search);
181
+
89
182
  if (pos === -1) return str;
183
+
90
184
  return str.substring(0, pos) + replace + str.substring(pos + search.length);
91
185
  }
92
186
 
187
+ /**
188
+ * Replaces last occurrence of search string.
189
+ * @param {string} str - Input string
190
+ * @param {string} search - String to find
191
+ * @param {string} replace - Replacement string
192
+ * @returns {string} Modified string
193
+ */
93
194
  static replaceLast(str, search, replace) {
94
195
  const pos = str.lastIndexOf(search);
196
+
95
197
  if (pos === -1) return str;
198
+
96
199
  return str.substring(0, pos) + replace + str.substring(pos + search.length);
97
200
  }
98
201
  }
@@ -0,0 +1,54 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import Paths from "../Core/Paths.js";
4
+
5
+ /**
6
+ * Translation manager for multi-language support.
7
+ * Loads translations from JSON files and supports parameter replacement.
8
+ */
9
+ class Lang {
10
+ static #cache = new Map();
11
+
12
+ /**
13
+ * Gets a translated string for the current request language.
14
+ * @param {import("fastify").FastifyRequest} req - Fastify request with language property
15
+ * @param {string} key - Translation key to look up
16
+ * @param {Object|null} params - Optional parameters to replace in translation (e.g., { name: "John" } for ":name")
17
+ * @returns {string} Translated string or the key if not found
18
+ */
19
+ static get(req, key, params = null) {
20
+ const currentLang = req.language;
21
+ const langPath = path.join(Paths.langs, currentLang + ".json");
22
+
23
+ if (!fs.existsSync(langPath)) {
24
+ return key;
25
+ }
26
+
27
+ let translations = this.#cache.get(currentLang);
28
+
29
+ if (!translations) {
30
+ const langFile = fs.readFileSync(langPath, { encoding: "utf-8", flag: "r" });
31
+ translations = JSON.parse(langFile);
32
+ this.#cache.set(currentLang, translations);
33
+ }
34
+
35
+ let translation = translations[key] || key;
36
+
37
+ if (params) {
38
+ for (const [paramKey, paramValue] of Object.entries(params)) {
39
+ translation = translation.replaceAll(":" + paramKey, paramValue);
40
+ }
41
+ }
42
+
43
+ return translation;
44
+ }
45
+
46
+ /**
47
+ * Clears the translation cache. Useful for development hot reload.
48
+ */
49
+ static clearCache() {
50
+ this.#cache.clear();
51
+ }
52
+ }
53
+
54
+ export default Lang;