@nitronjs/framework 0.3.2 → 0.3.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.
@@ -1,219 +1,217 @@
1
- /**
2
- * HMR Client — Browser-side hot module replacement handler.
3
- *
4
- * Connects to the dev server via Socket.IO WebSocket.
5
- * On file change events, fetches a fresh RSC (React Server Components)
6
- * Flight payload and lets React reconcile the DOM — no full page reload needed.
7
- *
8
- * Wire format from /__nitron/rsc:
9
- * "<flightLength>\n<flightPayload><jsonMetadata>"
10
- * - First line: byte length of the Flight payload
11
- * - Then the Flight payload itself (React serialization format)
12
- * - Then a JSON object with { meta, css, translations }
13
- */
14
- (function() {
15
- "use strict";
16
-
17
- var RSC_ENDPOINT = "/__nitron/rsc";
18
- var socket = null;
19
-
20
- // --- Connection ---
21
-
22
- function connect() {
23
- if (socket) return;
24
-
25
- if (typeof io === "undefined") {
26
- setTimeout(connect, 100);
27
-
28
- return;
29
- }
30
-
31
- try {
32
- socket = io({
33
- path: "/__nitron_hmr",
34
- transports: ["websocket"],
35
- reconnection: true,
36
- reconnectionAttempts: Infinity,
37
- reconnectionDelay: 1000,
38
- reconnectionDelayMax: 5000,
39
- timeout: 5000
40
- });
41
- }
42
- catch (e) {
43
- // Socket.IO not available — HMR disabled silently (dev-only feature)
44
- return;
45
- }
46
-
47
- socket.on("connect", function() {
48
- window.__nitron_hmr_connected__ = true;
49
- hideErrorOverlay();
50
- });
51
-
52
- socket.on("disconnect", function() {
53
- window.__nitron_hmr_connected__ = false;
54
- });
55
-
56
- socket.on("connect_error", function() {
57
- window.__nitron_hmr_connected__ = false;
58
- });
59
-
60
- // Unified RSC-based change handler
61
- socket.on("hmr:change", function(data) {
62
- hideErrorOverlay();
63
-
64
- if (data.changeType === "css") {
65
- refreshCss();
66
- return;
67
- }
68
-
69
- // page, layout all use RSC refetch
70
- refetchRSC(data.cssChanged);
71
- });
72
-
73
- socket.on("hmr:reload", function() {
74
- location.reload();
75
- });
76
-
77
- socket.on("hmr:error", function(data) {
78
- showErrorOverlay(data.message || "Unknown error", data.file);
79
- });
80
- }
81
-
82
- // --- RSC Wire Format Parser ---
83
-
84
- // Parses the length-prefixed response from /__nitron/rsc.
85
- // Format: "<length>\n<flight payload><json metadata>"
86
- // Returns { payload, meta, css, translations } or null on malformed input.
87
- function parseLengthPrefixed(text) {
88
- var nl = text.indexOf("\n");
89
- if (nl === -1) return null;
90
-
91
- var len = parseInt(text.substring(0, nl), 10);
92
- if (isNaN(len) || len < 0) return null;
93
-
94
- var flight = text.substring(nl + 1, nl + 1 + len);
95
- var jsonStr = text.substring(nl + 1 + len);
96
- var data;
97
-
98
- try { data = JSON.parse(jsonStr); }
99
- catch (e) { return null; }
100
-
101
- return {
102
- payload: flight,
103
- meta: data.meta || null,
104
- css: data.css || null,
105
- translations: data.translations || null
106
- };
107
- }
108
-
109
- // --- RSC Page Update ---
110
-
111
- // Fetches a fresh Flight payload for the current URL and hands it to React.
112
- // React's reconciliation updates only the changed DOM nodes — no full reload.
113
- function refetchRSC(cssChanged) {
114
- var rsc = window.__NITRON_RSC__;
115
-
116
- if (!rsc || !rsc.root) {
117
- location.reload();
118
- return;
119
- }
120
-
121
- var url = location.pathname + location.search;
122
-
123
- fetch(RSC_ENDPOINT + "?url=" + encodeURIComponent(url), {
124
- headers: { "X-Nitron-SPA": "1" },
125
- credentials: "same-origin"
126
- })
127
- .then(function(r) {
128
- if (!r.ok) throw new Error("HTTP " + r.status);
129
-
130
- return r.text().then(function(text) {
131
- return parseLengthPrefixed(text);
132
- });
133
- })
134
- .then(function(d) {
135
- if (!d || !d.payload) {
136
- location.reload();
137
- return;
138
- }
139
-
140
- // Merge translations BEFORE navigate — persistent components (layouts) keep their keys
141
- if (d.translations) {
142
- var prev = window.__NITRON_TRANSLATIONS__;
143
-
144
- window.__NITRON_TRANSLATIONS__ = prev
145
- ? Object.assign({}, prev, d.translations)
146
- : d.translations;
147
- }
148
-
149
- // React reconciliation — only changed DOM nodes update
150
- rsc.navigate(d.payload);
151
-
152
- if (d.meta && d.meta.title) document.title = d.meta.title;
153
-
154
- if (cssChanged) refreshCss();
155
- })
156
- .catch(function() {
157
- // RSC fetch failed — full reload as last resort
158
- location.reload();
159
- });
160
- }
161
-
162
- // --- CSS Hot Reload ---
163
-
164
- // Cache-busts all stylesheets by appending ?t=<timestamp>
165
- function refreshCss() {
166
- var links = document.querySelectorAll('link[rel="stylesheet"]');
167
- var timestamp = Date.now();
168
-
169
- for (var i = 0; i < links.length; i++) {
170
- var href = (links[i].href || "").split("?")[0];
171
- links[i].href = href + "?t=" + timestamp;
172
- }
173
- }
174
-
175
- // --- Build Error Overlay ---
176
-
177
- function showErrorOverlay(message, file) {
178
- hideErrorOverlay();
179
-
180
- var overlay = document.createElement("div");
181
- overlay.id = "__nitron_error__";
182
- overlay.innerHTML =
183
- '<div style="position:fixed;inset:0;background:rgba(0,0,0,.95);color:#ff4444;padding:32px;font-family:monospace;z-index:999999;overflow:auto">' +
184
- '<div style="font-size:24px;font-weight:bold;margin-bottom:16px">Build Error</div>' +
185
- '<div style="color:#888;margin-bottom:16px">' + escapeHtml(file || "") + '</div>' +
186
- '<pre style="white-space:pre-wrap;background:#1a1a2e;padding:16px;border-radius:8px">' + escapeHtml(message) + '</pre>' +
187
- '<button onclick="this.parentNode.parentNode.remove()" style="position:absolute;top:16px;right:16px;background:#333;color:#fff;border:none;padding:8px 16px;cursor:pointer;border-radius:4px">Close</button>' +
188
- '</div>';
189
-
190
- document.body.appendChild(overlay);
191
- }
192
-
193
- function hideErrorOverlay() {
194
- var el = document.getElementById("__nitron_error__");
195
-
196
- if (el) {
197
- el.remove();
198
- }
199
- }
200
-
201
- // --- Utilities ---
202
-
203
- function escapeHtml(str) {
204
- return String(str || "")
205
- .replace(/&/g, "&amp;")
206
- .replace(/</g, "&lt;")
207
- .replace(/>/g, "&gt;");
208
- }
209
-
210
- // --- Initialize ---
211
-
212
- if (document.readyState === "loading") {
213
- document.addEventListener("DOMContentLoaded", connect);
214
- }
215
- else {
216
- connect();
217
- }
218
-
219
- })();
1
+ (function() {
2
+ "use strict";
3
+
4
+ var RSC_ENDPOINT = "/__nitron/rsc";
5
+ var ws = null;
6
+ var reconnectTimer = null;
7
+
8
+ function connect() {
9
+ if (ws) return;
10
+
11
+ var protocol = location.protocol === "https:" ? "wss:" : "ws:";
12
+ var url = protocol + "//" + location.host + "/__nitron_hmr";
13
+
14
+ try {
15
+ ws = new WebSocket(url);
16
+ }
17
+ catch (e) {
18
+ return;
19
+ }
20
+
21
+ ws.onopen = function() {
22
+ window.__nitron_hmr_connected__ = true;
23
+ hideErrorOverlay();
24
+ };
25
+
26
+ ws.onclose = function() {
27
+ window.__nitron_hmr_connected__ = false;
28
+ ws = null;
29
+
30
+ clearTimeout(reconnectTimer);
31
+ reconnectTimer = setTimeout(connect, 1000);
32
+ };
33
+
34
+ ws.onerror = function() {
35
+ window.__nitron_hmr_connected__ = false;
36
+ };
37
+
38
+ ws.onmessage = function(event) {
39
+ var msg;
40
+
41
+ try {
42
+ msg = JSON.parse(event.data);
43
+ }
44
+ catch (e) {
45
+ return;
46
+ }
47
+
48
+ if (msg.type === "change") {
49
+ hideErrorOverlay();
50
+
51
+ if (msg.changeType === "css") {
52
+ refreshCss();
53
+ return;
54
+ }
55
+
56
+ refetchRSC(msg.cssChanged);
57
+ }
58
+
59
+ if (msg.type === "fast-refresh") {
60
+ hideErrorOverlay();
61
+ handleFastRefresh(msg);
62
+ }
63
+
64
+ if (msg.type === "reload") {
65
+ location.reload();
66
+ }
67
+
68
+ if (msg.type === "error") {
69
+ showErrorOverlay(msg.message || "Unknown error", msg.file);
70
+ }
71
+ };
72
+ }
73
+
74
+ function parseLengthPrefixed(text) {
75
+ var nl = text.indexOf("\n");
76
+ if (nl === -1) return null;
77
+
78
+ var len = parseInt(text.substring(0, nl), 10);
79
+ if (isNaN(len) || len < 0) return null;
80
+
81
+ var flight = text.substring(nl + 1, nl + 1 + len);
82
+ var jsonStr = text.substring(nl + 1 + len);
83
+ var data;
84
+
85
+ try { data = JSON.parse(jsonStr); }
86
+ catch (e) { return null; }
87
+
88
+ return {
89
+ payload: flight,
90
+ meta: data.meta || null,
91
+ css: data.css || null,
92
+ translations: data.translations || null
93
+ };
94
+ }
95
+
96
+ function refetchRSC(cssChanged) {
97
+ var rsc = window.__NITRON_RSC__;
98
+
99
+ if (!rsc || !rsc.root) {
100
+ location.reload();
101
+ return;
102
+ }
103
+
104
+ var url = location.pathname + location.search;
105
+
106
+ fetch(RSC_ENDPOINT + "?url=" + encodeURIComponent(url), {
107
+ headers: { "X-Nitron-SPA": "1" },
108
+ credentials: "same-origin"
109
+ })
110
+ .then(function(r) {
111
+ if (!r.ok) throw new Error("HTTP " + r.status);
112
+
113
+ return r.text().then(function(text) {
114
+ return parseLengthPrefixed(text);
115
+ });
116
+ })
117
+ .then(function(d) {
118
+ if (!d || !d.payload) {
119
+ location.reload();
120
+ return;
121
+ }
122
+
123
+ if (d.translations) {
124
+ var prev = window.__NITRON_TRANSLATIONS__;
125
+
126
+ window.__NITRON_TRANSLATIONS__ = prev
127
+ ? Object.assign({}, prev, d.translations)
128
+ : d.translations;
129
+ }
130
+
131
+ rsc.navigate(d.payload);
132
+
133
+ if (d.meta && d.meta.title) document.title = d.meta.title;
134
+
135
+ if (cssChanged) refreshCss();
136
+ })
137
+ .catch(function() {
138
+ location.reload();
139
+ });
140
+ }
141
+
142
+ function refreshCss() {
143
+ var links = document.querySelectorAll('link[rel="stylesheet"]');
144
+ var timestamp = Date.now();
145
+
146
+ for (var i = 0; i < links.length; i++) {
147
+ var href = (links[i].href || "").split("?")[0];
148
+ links[i].href = href + "?t=" + timestamp;
149
+ }
150
+ }
151
+
152
+ function showErrorOverlay(message, file) {
153
+ hideErrorOverlay();
154
+
155
+ var overlay = document.createElement("div");
156
+ overlay.id = "__nitron_error__";
157
+ overlay.innerHTML =
158
+ '<div style="position:fixed;inset:0;background:rgba(0,0,0,.95);color:#ff4444;padding:32px;font-family:monospace;z-index:999999;overflow:auto">' +
159
+ '<div style="font-size:24px;font-weight:bold;margin-bottom:16px">Build Error</div>' +
160
+ '<div style="color:#888;margin-bottom:16px">' + escapeHtml(file || "") + '</div>' +
161
+ '<pre style="white-space:pre-wrap;background:#1a1a2e;padding:16px;border-radius:8px">' + escapeHtml(message) + '</pre>' +
162
+ '<button onclick="this.parentNode.parentNode.remove()" style="position:absolute;top:16px;right:16px;background:#333;color:#fff;border:none;padding:8px 16px;cursor:pointer;border-radius:4px">Close</button>' +
163
+ '</div>';
164
+
165
+ document.body.appendChild(overlay);
166
+ }
167
+
168
+ function hideErrorOverlay() {
169
+ var el = document.getElementById("__nitron_error__");
170
+
171
+ if (el) {
172
+ el.remove();
173
+ }
174
+ }
175
+
176
+ function handleFastRefresh(msg) {
177
+ const chunks = msg.chunks || [];
178
+ const timestamp = msg.timestamp || Date.now();
179
+ const cssChanged = msg.cssChanged || false;
180
+
181
+ const promises = chunks.map(function(chunk) {
182
+ if (chunk.includes("..") || chunk.includes("://")) return Promise.resolve();
183
+
184
+ return import("/storage/" + chunk + "?t=" + timestamp);
185
+ });
186
+
187
+ Promise.all(promises)
188
+ .then(function() {
189
+ if (window.$RefreshRuntime$) {
190
+ window.$RefreshRuntime$.performReactRefresh();
191
+ }
192
+
193
+ if (cssChanged) {
194
+ refreshCss();
195
+ }
196
+ })
197
+ .catch(function(err) {
198
+ console.warn("[HMR] Fast Refresh failed, full reload:", err.message);
199
+ location.reload();
200
+ });
201
+ }
202
+
203
+ function escapeHtml(str) {
204
+ return String(str || "")
205
+ .replace(/&/g, "&amp;")
206
+ .replace(/</g, "&lt;")
207
+ .replace(/>/g, "&gt;");
208
+ }
209
+
210
+ if (document.readyState === "loading") {
211
+ document.addEventListener("DOMContentLoaded", connect);
212
+ }
213
+ else {
214
+ connect();
215
+ }
216
+
217
+ })();
@@ -0,0 +1 @@
1
+ module.exports = require("react-refresh/runtime");
package/lib/View/View.js CHANGED
@@ -723,11 +723,6 @@ class View {
723
723
 
724
724
  let runtimeScript = `<script${nonceAttr}>window.__NITRON_RUNTIME__=${JSON.stringify(runtimeData)};`;
725
725
 
726
- if (hasFlightPayload) {
727
- const escapedPayload = flightPayload.replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
728
- runtimeScript += `window.__NITRON_FLIGHT__=${JSON.stringify(escapedPayload)};`;
729
- }
730
-
731
726
  if (translations && Object.keys(translations).length > 0) {
732
727
  const escapedTranslations = JSON.stringify(translations).replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
733
728
  runtimeScript += `window.__NITRON_TRANSLATIONS__=${escapedTranslations};`;
@@ -735,10 +730,20 @@ class View {
735
730
 
736
731
  runtimeScript += `</script>`;
737
732
 
733
+ if (hasFlightPayload) {
734
+ const escapedPayload = flightPayload.replace(/</g, "\\u003c").replace(/>/g, "\\u003e");
735
+ runtimeScript += `<script${nonceAttr}>window.__NITRON_FLIGHT__=${JSON.stringify(escapedPayload)};</script>`;
736
+ }
737
+
738
+ const refreshScript = this.#isDev
739
+ ? `<script src="/storage/js/react-refresh-runtime.js"${nonceAttr}></script>`
740
+ + `<script${nonceAttr}>$RefreshRuntime$.injectIntoGlobalHook(window);window.$RefreshReg$=function(t,i){$RefreshRuntime$.register(t,"__mod__ "+i)};window.$RefreshSig$=function(){return $RefreshRuntime$.createSignatureFunctionForTransform()};</script>`
741
+ : "";
742
+
738
743
  const vendorScript = `<script src="/storage/js/vendor.js"${nonceAttr}></script>`;
739
744
 
740
745
  const hmrScript = this.#isDev
741
- ? `<script src="/__nitron_client/socket.io.js"${nonceAttr}></script><script src="/storage/js/hmr.js"${nonceAttr}></script>`
746
+ ? `<script src="/storage/js/hmr.js"${nonceAttr}></script>`
742
747
  : "";
743
748
 
744
749
  const consumerScript = hasFlightPayload
@@ -757,7 +762,7 @@ ${this.#generateHead(meta, css)}
757
762
  <body>
758
763
  <div id="app">${html}</div>
759
764
  ${runtimeScript}
760
- ${vendorScript}${hmrScript}${consumerScript}${spaScript}${devIndicator}
765
+ ${refreshScript}${vendorScript}${hmrScript}${consumerScript}${spaScript}${devIndicator}
761
766
  </body>
762
767
  </html>`;
763
768
  }
package/lib/index.d.ts CHANGED
@@ -273,8 +273,10 @@ export class DateTime {
273
273
  static getDate(timestamp?: string | number | null, format?: string): string;
274
274
  static addDays(days: number): string;
275
275
  static addHours(hours: number): string;
276
+ static addMinutes(minutes: number): string;
276
277
  static subDays(days: number): string;
277
278
  static subHours(hours: number): string;
279
+ static subMinutes(minutes: number): string;
278
280
  }
279
281
 
280
282
  export class Str {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nitronjs/framework",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "NitronJS is a modern and extensible Node.js MVC framework built on Fastify. It focuses on clean architecture, modular structure, and developer productivity, offering built-in routing, middleware, configuration management, CLI tooling, and native React integration for scalable full-stack applications.",
5
5
  "bin": {
6
6
  "njs": "./cli/njs.js"
@@ -21,11 +21,11 @@
21
21
  "@fastify/helmet": "^13.0.2",
22
22
  "@fastify/multipart": "^9.3.0",
23
23
  "@fastify/static": "^8.3.0",
24
- "@tailwindcss/postcss": "^4.1.18",
24
+ "@parcel/watcher": "^2.5.6",
25
+ "@tailwindcss/node": "^4.2.2",
25
26
  "@types/react": "^19.2.7",
26
27
  "@types/react-dom": "^19.2.3",
27
28
  "bcrypt": "^5.1.1",
28
- "chokidar": "^5.0.0",
29
29
  "dotenv": "^17.2.3",
30
30
  "es-module-lexer": "^2.0.0",
31
31
  "esbuild": "^0.27.2",
@@ -33,15 +33,17 @@
33
33
  "mysql2": "^3.16.0",
34
34
  "nodemailer": "^7.0.11",
35
35
  "otpauth": "^9.5.0",
36
- "postcss": "^8.5.6",
36
+ "oxc-parser": "^0.121.0",
37
+ "oxc-transform": "^0.121.0",
37
38
  "qrcode": "^1.5.4",
38
39
  "react": "^19.2.3",
39
40
  "react-dom": "^19.2.3",
41
+ "react-refresh": "^0.18.0",
40
42
  "react-server-dom-webpack": "^19.2.4",
41
43
  "redis": "^5.6.0",
42
- "socket.io": "^4.8.1",
43
44
  "tailwindcss": "^4.1.18",
44
- "typescript": "^5.9.3"
45
+ "typescript": "^5.9.3",
46
+ "ws": "^8.18.3"
45
47
  },
46
48
  "keywords": [
47
49
  "nitronjs",