@nitronjs/framework 0.2.26 → 0.3.0
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 +260 -170
- package/lib/Auth/Auth.js +2 -2
- package/lib/Build/CssBuilder.js +5 -7
- package/lib/Build/EffectivePropUsage.js +174 -0
- package/lib/Build/FactoryTransform.js +1 -21
- package/lib/Build/FileAnalyzer.js +2 -33
- package/lib/Build/Manager.js +390 -58
- package/lib/Build/PropUsageAnalyzer.js +1189 -0
- package/lib/Build/jsxRuntime.js +25 -155
- package/lib/Build/plugins.js +212 -146
- package/lib/Build/propUtils.js +70 -0
- package/lib/Console/Commands/DevCommand.js +30 -10
- package/lib/Console/Commands/MakeCommand.js +8 -1
- package/lib/Console/Output.js +0 -2
- package/lib/Console/Stubs/rsc-consumer.tsx +74 -0
- package/lib/Console/Stubs/vendor-dev.tsx +30 -41
- package/lib/Console/Stubs/vendor.tsx +25 -1
- package/lib/Core/Config.js +0 -6
- package/lib/Core/Paths.js +0 -19
- package/lib/Database/Migration/Checksum.js +0 -3
- package/lib/Database/Migration/MigrationRepository.js +0 -8
- package/lib/Database/Migration/MigrationRunner.js +1 -2
- package/lib/Database/Model.js +19 -11
- package/lib/Database/QueryBuilder.js +25 -4
- package/lib/Database/Schema/Blueprint.js +10 -0
- package/lib/Database/Schema/Manager.js +2 -0
- package/lib/Date/DateTime.js +1 -1
- package/lib/Dev/DevContext.js +44 -0
- package/lib/Dev/DevErrorPage.js +990 -0
- package/lib/Dev/DevIndicator.js +836 -0
- package/lib/HMR/Server.js +16 -37
- package/lib/Http/Server.js +177 -24
- package/lib/Logging/Log.js +34 -2
- package/lib/Mail/Mail.js +41 -10
- package/lib/Route/Router.js +43 -19
- package/lib/Runtime/Entry.js +10 -6
- package/lib/Session/Manager.js +144 -1
- package/lib/Session/Redis.js +117 -0
- package/lib/Session/Session.js +0 -4
- package/lib/Support/Str.js +6 -4
- package/lib/Translation/Lang.js +376 -32
- package/lib/Translation/pluralize.js +81 -0
- package/lib/Validation/MagicBytes.js +120 -0
- package/lib/Validation/Validator.js +46 -29
- package/lib/View/Client/hmr-client.js +100 -90
- package/lib/View/Client/spa.js +121 -50
- package/lib/View/ClientManifest.js +60 -0
- package/lib/View/FlightRenderer.js +100 -0
- package/lib/View/Layout.js +0 -3
- package/lib/View/PropFilter.js +81 -0
- package/lib/View/View.js +230 -495
- package/lib/index.d.ts +22 -1
- package/package.json +3 -2
- package/skeleton/config/app.js +1 -0
- package/skeleton/config/server.js +13 -0
- package/skeleton/config/session.js +4 -0
- package/lib/Build/HydrationBuilder.js +0 -190
- package/lib/Console/Stubs/page-hydration-dev.tsx +0 -72
- package/lib/Console/Stubs/page-hydration.tsx +0 -53
package/lib/Session/Manager.js
CHANGED
|
@@ -2,6 +2,7 @@ import crypto from "crypto";
|
|
|
2
2
|
import Config from "../Core/Config.js";
|
|
3
3
|
import Memory from "./Memory.js";
|
|
4
4
|
import File from "./File.js";
|
|
5
|
+
import RedisStore from "./Redis.js";
|
|
5
6
|
import Session from "./Session.js";
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -44,7 +45,26 @@ class SessionManager {
|
|
|
44
45
|
this.#config = Config.all("session");
|
|
45
46
|
this.#config.cookie.signed = true;
|
|
46
47
|
|
|
47
|
-
|
|
48
|
+
switch (this.#config.driver) {
|
|
49
|
+
case "file":
|
|
50
|
+
this.#store = new File();
|
|
51
|
+
break;
|
|
52
|
+
|
|
53
|
+
case "memory":
|
|
54
|
+
this.#store = new Memory();
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
case "redis":
|
|
58
|
+
this.#store = new RedisStore(this.#config.lifetime);
|
|
59
|
+
break;
|
|
60
|
+
|
|
61
|
+
case "none":
|
|
62
|
+
this.#store = null;
|
|
63
|
+
return;
|
|
64
|
+
|
|
65
|
+
default:
|
|
66
|
+
throw new Error(`[Session] Unknown driver: "${this.#config.driver}"`);
|
|
67
|
+
}
|
|
48
68
|
|
|
49
69
|
if (this.#store.ready) {
|
|
50
70
|
await this.#store.ready;
|
|
@@ -122,6 +142,10 @@ class SessionManager {
|
|
|
122
142
|
get cookieConfig() {
|
|
123
143
|
const config = { ...this.#config.cookie, signed: true };
|
|
124
144
|
|
|
145
|
+
if (!config.sameSite) {
|
|
146
|
+
config.sameSite = "Lax";
|
|
147
|
+
}
|
|
148
|
+
|
|
125
149
|
if (config.maxAge) {
|
|
126
150
|
config.maxAge = Math.floor(config.maxAge / 1000);
|
|
127
151
|
}
|
|
@@ -182,6 +206,17 @@ class SessionManager {
|
|
|
182
206
|
}
|
|
183
207
|
}
|
|
184
208
|
|
|
209
|
+
/**
|
|
210
|
+
* Closes the session store connection.
|
|
211
|
+
*/
|
|
212
|
+
async close() {
|
|
213
|
+
this.stopGC();
|
|
214
|
+
|
|
215
|
+
if (this.#store && typeof this.#store.close === "function") {
|
|
216
|
+
await this.#store.close();
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
185
220
|
/**
|
|
186
221
|
* Sets up session middleware for Fastify.
|
|
187
222
|
* @param {import("fastify").FastifyInstance} server
|
|
@@ -189,9 +224,20 @@ class SessionManager {
|
|
|
189
224
|
static async setup(server) {
|
|
190
225
|
const manager = await SessionManager.getInstance();
|
|
191
226
|
|
|
227
|
+
if (!manager.#store) {
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
|
|
192
231
|
server.decorateRequest("session", null);
|
|
193
232
|
|
|
194
233
|
server.addHook("preHandler", async (request, response) => {
|
|
234
|
+
const lastSegment = request.url.split("/").pop().split("?")[0];
|
|
235
|
+
const dotIndex = lastSegment.lastIndexOf(".");
|
|
236
|
+
|
|
237
|
+
if (dotIndex > 0 && STATIC_EXTENSIONS.has(lastSegment.slice(dotIndex + 1).toLowerCase())) {
|
|
238
|
+
return;
|
|
239
|
+
}
|
|
240
|
+
|
|
195
241
|
request.session = await manager.load(request, response);
|
|
196
242
|
});
|
|
197
243
|
|
|
@@ -217,4 +263,101 @@ class SessionManager {
|
|
|
217
263
|
}
|
|
218
264
|
}
|
|
219
265
|
|
|
266
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
267
|
+
// Private Constants
|
|
268
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
269
|
+
|
|
270
|
+
const STATIC_EXTENSIONS = new Set([
|
|
271
|
+
// ── Styles ──────────────────────────────────────────────────────────────
|
|
272
|
+
"css", "less", "scss", "sass", "styl", "stylus", "pcss", "postcss", "sss",
|
|
273
|
+
|
|
274
|
+
// ── Scripts & Source Maps ───────────────────────────────────────────────
|
|
275
|
+
"js", "mjs", "cjs", "ts", "mts", "cts", "jsx", "tsx",
|
|
276
|
+
"map", "coffee", "litcoffee", "elm", "dart", "vue", "svelte",
|
|
277
|
+
|
|
278
|
+
// ── Images (Raster) ────────────────────────────────────────────────────
|
|
279
|
+
"png", "jpg", "jpeg", "jpe", "jfif", "gif", "bmp", "dib",
|
|
280
|
+
"tiff", "tif", "webp", "avif", "heic", "heif", "ico", "cur",
|
|
281
|
+
"psd", "psb", "ai", "eps", "raw", "cr2", "nef", "orf", "sr2", "arw",
|
|
282
|
+
"dng", "raf", "rw2", "pef", "srw", "x3f", "jxl", "qoi", "hdr", "exr",
|
|
283
|
+
|
|
284
|
+
// ── Images (Vector) ────────────────────────────────────────────────────
|
|
285
|
+
"svg", "svgz", "cgm", "emf", "wmf",
|
|
286
|
+
|
|
287
|
+
// ── Fonts ───────────────────────────────────────────────────────────────
|
|
288
|
+
"woff", "woff2", "ttf", "otf", "eot", "pfb", "pfm",
|
|
289
|
+
|
|
290
|
+
// ── Video ───────────────────────────────────────────────────────────────
|
|
291
|
+
"mp4", "m4v", "webm", "ogv", "ogg", "avi", "mov", "mkv", "flv", "wmv",
|
|
292
|
+
"mpg", "mpeg", "mpe", "3gp", "3g2", "ts", "mts", "m2ts", "vob", "divx",
|
|
293
|
+
"asf", "rm", "rmvb", "f4v", "swf", "hevc", "av1",
|
|
294
|
+
|
|
295
|
+
// ── Audio ───────────────────────────────────────────────────────────────
|
|
296
|
+
"mp3", "wav", "flac", "aac", "m4a", "wma", "opus", "oga",
|
|
297
|
+
"aiff", "aif", "aifc", "mid", "midi", "amr", "ape", "dsf", "dff",
|
|
298
|
+
"ac3", "dts", "pcm", "au", "snd", "ra", "tak", "tta", "wv", "caf",
|
|
299
|
+
|
|
300
|
+
// ── Documents & Office ──────────────────────────────────────────────────
|
|
301
|
+
"pdf", "doc", "docx", "docm", "dot", "dotx", "dotm",
|
|
302
|
+
"xls", "xlsx", "xlsm", "xlsb", "xlt", "xltx", "xltm",
|
|
303
|
+
"ppt", "pptx", "pptm", "pot", "potx", "potm", "pps", "ppsx",
|
|
304
|
+
"odt", "ods", "odp", "odg", "odf", "odb", "odc",
|
|
305
|
+
"rtf", "tex", "latex", "bib", "epub", "mobi", "azw", "azw3",
|
|
306
|
+
"djvu", "djv", "xps", "oxps", "pages", "numbers", "key", "keynote",
|
|
307
|
+
|
|
308
|
+
// ── Data & Config ───────────────────────────────────────────────────────
|
|
309
|
+
"xml", "json", "jsonl", "ndjson", "json5", "jsonc",
|
|
310
|
+
"yaml", "yml", "toml", "ini", "cfg", "conf", "properties",
|
|
311
|
+
"csv", "tsv", "dsv", "parquet", "avro", "arrow", "feather",
|
|
312
|
+
"sql", "sqlite", "db", "mdb", "accdb",
|
|
313
|
+
|
|
314
|
+
// ── Text & Markup ───────────────────────────────────────────────────────
|
|
315
|
+
"txt", "text", "md", "markdown", "rst", "adoc", "asciidoc",
|
|
316
|
+
"log", "diff", "patch", "nfo", "diz",
|
|
317
|
+
"htm", "html", "xhtml", "mhtml", "mht", "shtml",
|
|
318
|
+
"ics", "ical", "vcf", "vcard", "ldif",
|
|
319
|
+
|
|
320
|
+
// ── Archives & Compression ──────────────────────────────────────────────
|
|
321
|
+
"zip", "rar", "7z", "tar", "gz", "gzip", "bz2", "bzip2",
|
|
322
|
+
"xz", "lzma", "lz", "lz4", "zst", "zstd", "br", "brotli",
|
|
323
|
+
"cab", "arj", "ace", "lzh", "lha", "cpio", "rpm", "deb",
|
|
324
|
+
"pkg", "dmg", "iso", "img", "vhd", "vhdx", "vmdk", "ova", "ovf",
|
|
325
|
+
"apk", "ipa", "xpi", "crx", "nupkg", "snap", "flatpak", "appimage",
|
|
326
|
+
"jar", "war", "ear",
|
|
327
|
+
|
|
328
|
+
// ── Executables & Libraries ─────────────────────────────────────────────
|
|
329
|
+
"exe", "msi", "dll", "sys", "so", "dylib", "lib", "a", "o", "obj",
|
|
330
|
+
"bin", "dat", "com", "bat", "cmd", "sh", "bash", "ps1", "psm1",
|
|
331
|
+
|
|
332
|
+
// ── 3D & CAD & Design ───────────────────────────────────────────────────
|
|
333
|
+
"glb", "gltf", "obj", "stl", "fbx", "dae", "3ds", "blend", "max",
|
|
334
|
+
"c4d", "ma", "mb", "lwo", "lws", "ply", "wrl", "vrml", "x3d",
|
|
335
|
+
"dwg", "dxf", "dgn", "step", "stp", "iges", "igs", "sat",
|
|
336
|
+
"skp", "3dm", "usd", "usda", "usdc", "usdz",
|
|
337
|
+
|
|
338
|
+
// ── GIS & Mapping ───────────────────────────────────────────────────────
|
|
339
|
+
"shp", "shx", "dbf", "prj", "cpg", "geojson", "topojson",
|
|
340
|
+
"kml", "kmz", "gpx", "osm", "pbf", "mbtiles",
|
|
341
|
+
|
|
342
|
+
// ── Scientific & Research ───────────────────────────────────────────────
|
|
343
|
+
"hdf5", "h5", "hdf", "nc", "netcdf", "fits", "mat", "sav",
|
|
344
|
+
|
|
345
|
+
// ── WebAssembly & Runtime ───────────────────────────────────────────────
|
|
346
|
+
"wasm", "wat", "swc",
|
|
347
|
+
|
|
348
|
+
// ── Certificates & Keys ─────────────────────────────────────────────────
|
|
349
|
+
"pem", "crt", "cer", "der", "p12", "pfx", "p7b", "p7c",
|
|
350
|
+
"csr", "key", "pub", "asc", "gpg", "sig",
|
|
351
|
+
|
|
352
|
+
// ── Web Manifests & PWA ─────────────────────────────────────────────────
|
|
353
|
+
"webmanifest", "manifest", "browserconfig",
|
|
354
|
+
|
|
355
|
+
// ── Misc ────────────────────────────────────────────────────────────────
|
|
356
|
+
"ani", "icns", "lnk", "url", "webloc", "desktop",
|
|
357
|
+
"torrent", "metalink", "magnet",
|
|
358
|
+
"sub", "srt", "ass", "ssa", "vtt", "sbv", "dfxp", "ttml",
|
|
359
|
+
"bak", "tmp", "temp", "swp", "swo", "lock",
|
|
360
|
+
"pid", "socket", "fifo"
|
|
361
|
+
]);
|
|
362
|
+
|
|
220
363
|
export default SessionManager;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { createClient } from "redis";
|
|
2
|
+
import Log from "../Logging/Log.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Redis-based session storage.
|
|
6
|
+
* Stores sessions as JSON strings with automatic TTL expiry.
|
|
7
|
+
*/
|
|
8
|
+
class RedisStore {
|
|
9
|
+
#client;
|
|
10
|
+
#prefix;
|
|
11
|
+
#ttl;
|
|
12
|
+
ready;
|
|
13
|
+
|
|
14
|
+
constructor(lifetime) {
|
|
15
|
+
this.#prefix = (process.env.APP_NAME || "app") + ":session:";
|
|
16
|
+
this.#ttl = Math.ceil(lifetime / 1000);
|
|
17
|
+
this.ready = this.#connect();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Gets session data by ID.
|
|
22
|
+
* @param {string} id
|
|
23
|
+
* @returns {Promise<Object|null>}
|
|
24
|
+
*/
|
|
25
|
+
async get(id) {
|
|
26
|
+
const data = await this.#client.get(this.#prefix + id);
|
|
27
|
+
|
|
28
|
+
if (!data) {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(data);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
await this.delete(id);
|
|
37
|
+
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Stores session data with automatic TTL.
|
|
44
|
+
* @param {string} id
|
|
45
|
+
* @param {Object} value
|
|
46
|
+
*/
|
|
47
|
+
async set(id, value) {
|
|
48
|
+
await this.#client.set(this.#prefix + id, JSON.stringify(value), {
|
|
49
|
+
EX: this.#ttl
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Deletes a session.
|
|
55
|
+
* @param {string} id
|
|
56
|
+
*/
|
|
57
|
+
async delete(id) {
|
|
58
|
+
await this.#client.del(this.#prefix + id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Garbage collection — no-op for Redis.
|
|
63
|
+
* Redis handles expiry automatically via TTL.
|
|
64
|
+
* @returns {Promise<number>}
|
|
65
|
+
*/
|
|
66
|
+
async gc() {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Closes the Redis connection gracefully.
|
|
72
|
+
*/
|
|
73
|
+
async close() {
|
|
74
|
+
if (this.#client) {
|
|
75
|
+
await this.#client.quit();
|
|
76
|
+
this.#client = null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** @private */
|
|
81
|
+
async #connect() {
|
|
82
|
+
const host = process.env.REDIS_HOST || "127.0.0.1";
|
|
83
|
+
const port = Number(process.env.REDIS_PORT) || 6379;
|
|
84
|
+
const password = process.env.REDIS_PASSWORD || undefined;
|
|
85
|
+
|
|
86
|
+
this.#client = createClient({
|
|
87
|
+
socket: {
|
|
88
|
+
host,
|
|
89
|
+
port,
|
|
90
|
+
reconnectStrategy: (retries) => {
|
|
91
|
+
if (retries > 10) {
|
|
92
|
+
console.error("\x1b[31m✕ [Session Redis] Connection lost after 10 retries. Shutting down.\x1b[0m");
|
|
93
|
+
Log.fatal("Redis connection lost permanently", { retries });
|
|
94
|
+
process.emit("SIGTERM");
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return Math.min(retries * 100, 5000);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
password
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
this.#client.on("error", (err) => {
|
|
106
|
+
console.error(`\x1b[31m✕ [Session Redis] ${err.message}\x1b[0m`);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.#client.on("ready", () => {
|
|
110
|
+
console.log("\x1b[32m✓ [Session Redis] Connected\x1b[0m");
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
await this.#client.connect();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export default RedisStore;
|
package/lib/Session/Session.js
CHANGED
package/lib/Support/Str.js
CHANGED
|
@@ -1,24 +1,26 @@
|
|
|
1
|
+
import { randomBytes } from "crypto";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* String utility class with helper methods.
|
|
3
5
|
* Provides Laravel-style string manipulation functions.
|
|
4
|
-
*
|
|
6
|
+
*
|
|
5
7
|
* @example
|
|
6
8
|
* Str.slug("Hello World"); // "hello-world"
|
|
7
9
|
* Str.camel("user_name"); // "userName"
|
|
8
10
|
*/
|
|
9
11
|
class Str {
|
|
10
12
|
/**
|
|
11
|
-
* Generates a random alphanumeric string.
|
|
13
|
+
* Generates a cryptographically secure random alphanumeric string.
|
|
12
14
|
* @param {number} length - Desired string length
|
|
13
15
|
* @returns {string} Random string
|
|
14
16
|
*/
|
|
15
17
|
static random(length) {
|
|
16
18
|
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
19
|
+
const bytes = randomBytes(length);
|
|
17
20
|
let result = "";
|
|
18
|
-
const charactersLength = characters.length;
|
|
19
21
|
|
|
20
22
|
for (let i = 0; i < length; i++) {
|
|
21
|
-
result += characters.charAt(
|
|
23
|
+
result += characters.charAt(bytes[i] % characters.length);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
return result;
|