nyte 1.0.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/LICENSE +13 -0
- package/README.md +59 -0
- package/dist/adapters/express.d.ts +7 -0
- package/dist/adapters/express.js +63 -0
- package/dist/adapters/factory.d.ts +23 -0
- package/dist/adapters/factory.js +121 -0
- package/dist/adapters/fastify.d.ts +25 -0
- package/dist/adapters/fastify.js +61 -0
- package/dist/adapters/native.d.ts +8 -0
- package/dist/adapters/native.js +200 -0
- package/dist/api/console.d.ts +81 -0
- package/dist/api/console.js +318 -0
- package/dist/api/http.d.ts +180 -0
- package/dist/api/http.js +469 -0
- package/dist/bin/nytejs.d.ts +2 -0
- package/dist/bin/nytejs.js +277 -0
- package/dist/builder.d.ts +32 -0
- package/dist/builder.js +634 -0
- package/dist/client/DefaultNotFound.d.ts +1 -0
- package/dist/client/DefaultNotFound.js +79 -0
- package/dist/client/client.d.ts +4 -0
- package/dist/client/client.js +27 -0
- package/dist/client/clientRouter.d.ts +58 -0
- package/dist/client/clientRouter.js +132 -0
- package/dist/client/entry.client.d.ts +1 -0
- package/dist/client/entry.client.js +455 -0
- package/dist/client/rpc.d.ts +8 -0
- package/dist/client/rpc.js +97 -0
- package/dist/components/Link.d.ts +7 -0
- package/dist/components/Link.js +13 -0
- package/dist/global/global.d.ts +117 -0
- package/dist/global/global.js +17 -0
- package/dist/helpers.d.ts +20 -0
- package/dist/helpers.js +604 -0
- package/dist/hotReload.d.ts +32 -0
- package/dist/hotReload.js +545 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +515 -0
- package/dist/loaders.d.ts +1 -0
- package/dist/loaders.js +138 -0
- package/dist/renderer.d.ts +14 -0
- package/dist/renderer.js +380 -0
- package/dist/router.d.ts +101 -0
- package/dist/router.js +659 -0
- package/dist/rpc/server.d.ts +11 -0
- package/dist/rpc/server.js +166 -0
- package/dist/rpc/types.d.ts +22 -0
- package/dist/rpc/types.js +20 -0
- package/dist/types/framework.d.ts +37 -0
- package/dist/types/framework.js +2 -0
- package/dist/types.d.ts +218 -0
- package/dist/types.js +2 -0
- package/package.json +87 -0
- package/src/adapters/express.ts +87 -0
- package/src/adapters/factory.ts +112 -0
- package/src/adapters/fastify.ts +104 -0
- package/src/adapters/native.ts +245 -0
- package/src/api/console.ts +348 -0
- package/src/api/http.ts +535 -0
- package/src/bin/nytejs.js +331 -0
- package/src/builder.js +690 -0
- package/src/client/DefaultNotFound.tsx +119 -0
- package/src/client/client.ts +24 -0
- package/src/client/clientRouter.ts +153 -0
- package/src/client/entry.client.tsx +529 -0
- package/src/client/rpc.ts +101 -0
- package/src/components/Link.tsx +38 -0
- package/src/global/global.ts +171 -0
- package/src/helpers.ts +657 -0
- package/src/hotReload.ts +566 -0
- package/src/index.ts +582 -0
- package/src/loaders.js +160 -0
- package/src/renderer.tsx +421 -0
- package/src/router.ts +732 -0
- package/src/rpc/server.ts +190 -0
- package/src/rpc/types.ts +45 -0
- package/src/types/framework.ts +58 -0
- package/src/types.ts +288 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2025 itsmuzin
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<picture>
|
|
3
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://i.imgur.com/zUTrtM5.png">
|
|
4
|
+
<img alt="Nyte.js logo" src="https://i.imgur.com/zUTrtM5.png" height="128">
|
|
5
|
+
</picture>
|
|
6
|
+
<h1>Nyte.js</h1>
|
|
7
|
+
|
|
8
|
+
[](https://www.npmjs.com/package/nyte)
|
|
9
|
+
[](../../LICENSE)
|
|
10
|
+
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
> A modern full-stack web framework for Node.js, focused on simplicity, developer experience, and speed.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## ✨ Features
|
|
18
|
+
|
|
19
|
+
- **🚀 Blazing Fast** - Built on esbuild for lightning-fast builds and hot reload
|
|
20
|
+
- **📁 File-based Routing** - Automatic routing for pages (`src/web/routes`) and APIs (`src/backend/routes`)
|
|
21
|
+
- **⚛️ React 19** - Latest React with client-side hydration and streaming SSR
|
|
22
|
+
- **📘 TypeScript First** - Fully typed out of the box
|
|
23
|
+
- **🎨 Asset Imports** - Import images, markdown, SVG, JSON, fonts, audio, video directly
|
|
24
|
+
- **🔌 WebSockets** - Native WebSocket support in backend routes
|
|
25
|
+
- **🎯 Dynamic Routes** - Support for dynamic parameters in frontend and backend
|
|
26
|
+
- **🛡️ Middleware** - Per-folder and per-route middleware support
|
|
27
|
+
- **🔄 Hot Reload** - Native hot reload via internal WebSocket in dev mode
|
|
28
|
+
- **🎭 Layouts** - Global layouts and custom 404 pages
|
|
29
|
+
- **📝 Dynamic Metadata** - Per-page metadata generation
|
|
30
|
+
- **📦 Smart Bundling** - Single bundle or code splitting
|
|
31
|
+
- **🔒 Built-in SSL** - HTTPS out-of-the-box in Native mode
|
|
32
|
+
- **🔐 Authentication** - JWT authentication built-in (HWebAuth)
|
|
33
|
+
- **⚙️ CLI** - Own CLI (`nyte`) for dev and production
|
|
34
|
+
- **📂 Static Files** - Serve static files from `public/`
|
|
35
|
+
- **🛡️ Security** - Built-in sanitization and rate limiting
|
|
36
|
+
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
## 💬 Need Help?
|
|
40
|
+
|
|
41
|
+
If you have questions or need support, feel free to reach out:
|
|
42
|
+
|
|
43
|
+
[](https://discord.com/users/1264710048786026588)
|
|
44
|
+
[](mailto:murillofrazaocunha@gmail.com)
|
|
45
|
+
[](https://instagram.com/itsmuh_)
|
|
46
|
+
[](https://github.com/murillo-frazao-cunha)
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## 🪪 License
|
|
51
|
+
|
|
52
|
+
Copyright 2025 itsmuzin
|
|
53
|
+
|
|
54
|
+
This project is licensed under the [Apache License 2.0](../../LICENSE).
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Request as ExpressRequest, Response as ExpressResponse } from 'express';
|
|
2
|
+
import { GenericRequest, GenericResponse, FrameworkAdapter } from '../types/framework';
|
|
3
|
+
export declare class ExpressAdapter implements FrameworkAdapter {
|
|
4
|
+
type: "express";
|
|
5
|
+
parseRequest(req: ExpressRequest): GenericRequest;
|
|
6
|
+
createResponse(res: ExpressResponse): GenericResponse;
|
|
7
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ExpressAdapter = void 0;
|
|
4
|
+
class ExpressAdapter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.type = 'express';
|
|
7
|
+
}
|
|
8
|
+
parseRequest(req) {
|
|
9
|
+
return {
|
|
10
|
+
method: req.method,
|
|
11
|
+
url: req.url,
|
|
12
|
+
headers: req.headers,
|
|
13
|
+
body: req.body,
|
|
14
|
+
query: req.query,
|
|
15
|
+
params: req.params,
|
|
16
|
+
cookies: req.cookies || {},
|
|
17
|
+
raw: req,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
createResponse(res) {
|
|
21
|
+
return new ExpressResponseWrapper(res);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.ExpressAdapter = ExpressAdapter;
|
|
25
|
+
class ExpressResponseWrapper {
|
|
26
|
+
constructor(res) {
|
|
27
|
+
this.res = res;
|
|
28
|
+
}
|
|
29
|
+
get raw() {
|
|
30
|
+
return this.res;
|
|
31
|
+
}
|
|
32
|
+
status(code) {
|
|
33
|
+
this.res.status(code);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
header(name, value) {
|
|
37
|
+
this.res.setHeader(name, value);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
cookie(name, value, options) {
|
|
41
|
+
this.res.cookie(name, value, options || {});
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
clearCookie(name, options) {
|
|
45
|
+
// Filter out the deprecated 'expires' option to avoid Express deprecation warning
|
|
46
|
+
const { expires, ...filteredOptions } = options || {};
|
|
47
|
+
this.res.clearCookie(name, filteredOptions);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
json(data) {
|
|
51
|
+
this.res.json(data);
|
|
52
|
+
}
|
|
53
|
+
text(data) {
|
|
54
|
+
this.res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
55
|
+
this.res.send(data);
|
|
56
|
+
}
|
|
57
|
+
send(data) {
|
|
58
|
+
this.res.send(data);
|
|
59
|
+
}
|
|
60
|
+
redirect(url) {
|
|
61
|
+
this.res.redirect(url);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FrameworkAdapter } from '../types/framework';
|
|
2
|
+
/**
|
|
3
|
+
* Factory para criar o adapter correto baseado no framework detectado
|
|
4
|
+
*/
|
|
5
|
+
export declare class FrameworkAdapterFactory {
|
|
6
|
+
private static adapter;
|
|
7
|
+
/**
|
|
8
|
+
* Detecta automaticamente o framework baseado na requisição/resposta
|
|
9
|
+
*/
|
|
10
|
+
static detectFramework(req: any, res: any): FrameworkAdapter;
|
|
11
|
+
/**
|
|
12
|
+
* Força o uso de um framework específico
|
|
13
|
+
*/
|
|
14
|
+
static setFramework(framework: 'express' | 'fastify' | 'native'): void;
|
|
15
|
+
/**
|
|
16
|
+
* Reset do adapter (útil para testes)
|
|
17
|
+
*/
|
|
18
|
+
static reset(): void;
|
|
19
|
+
/**
|
|
20
|
+
* Retorna o adapter atual (se já foi detectado)
|
|
21
|
+
*/
|
|
22
|
+
static getCurrentAdapter(): FrameworkAdapter | null;
|
|
23
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.FrameworkAdapterFactory = void 0;
|
|
37
|
+
const express_1 = require("./express");
|
|
38
|
+
const fastify_1 = require("./fastify");
|
|
39
|
+
const native_1 = require("./native");
|
|
40
|
+
const console_1 = __importStar(require("../api/console"));
|
|
41
|
+
/**
|
|
42
|
+
* Factory para criar o adapter correto baseado no framework detectado
|
|
43
|
+
*/
|
|
44
|
+
class FrameworkAdapterFactory {
|
|
45
|
+
/**
|
|
46
|
+
* Detecta automaticamente o framework baseado na requisição/resposta
|
|
47
|
+
*/
|
|
48
|
+
static detectFramework(req, res) {
|
|
49
|
+
// Se já detectamos antes, retorna o mesmo adapter
|
|
50
|
+
if (this.adapter) {
|
|
51
|
+
return this.adapter;
|
|
52
|
+
}
|
|
53
|
+
const msg = console_1.default.dynamicLine(` ${console_1.Colors.FgYellow}● ${console_1.Colors.Reset}Detecting web framework...`);
|
|
54
|
+
// Detecta Express
|
|
55
|
+
if (req.app && req.route && res.locals !== undefined) {
|
|
56
|
+
msg.end(` ${console_1.Colors.FgGreen}● ${console_1.Colors.Reset}Framework detected: Express`);
|
|
57
|
+
this.adapter = new express_1.ExpressAdapter();
|
|
58
|
+
return this.adapter;
|
|
59
|
+
}
|
|
60
|
+
// Detecta Fastify
|
|
61
|
+
if (req.server && req.routerPath !== undefined && res.request) {
|
|
62
|
+
msg.end(` ${console_1.Colors.FgGreen}● ${console_1.Colors.Reset}Framework detected: Fastify`);
|
|
63
|
+
this.adapter = new fastify_1.FastifyAdapter();
|
|
64
|
+
return this.adapter;
|
|
65
|
+
}
|
|
66
|
+
// Detecta HTTP nativo do Node.js
|
|
67
|
+
if (req.method !== undefined && req.url !== undefined && req.headers !== undefined &&
|
|
68
|
+
res.statusCode !== undefined && res.setHeader !== undefined && res.end !== undefined) {
|
|
69
|
+
msg.end(` ${console_1.Colors.FgGreen}● ${console_1.Colors.Reset}Framework detected: Nyte.js Native (HTTP)`);
|
|
70
|
+
this.adapter = new native_1.NativeAdapter();
|
|
71
|
+
return this.adapter;
|
|
72
|
+
}
|
|
73
|
+
// Fallback mais específico para Express
|
|
74
|
+
if (res.status && res.send && res.json && res.cookie) {
|
|
75
|
+
msg.end(` ${console_1.Colors.FgGreen}● ${console_1.Colors.Reset}Framework detected: Express (fallback)`);
|
|
76
|
+
this.adapter = new express_1.ExpressAdapter();
|
|
77
|
+
return this.adapter;
|
|
78
|
+
}
|
|
79
|
+
// Fallback mais específico para Fastify
|
|
80
|
+
if (res.code && res.send && res.type && res.setCookie) {
|
|
81
|
+
msg.end(` ${console_1.Colors.FgGreen}● ${console_1.Colors.Reset}Framework detected: Fastify (fallback)`);
|
|
82
|
+
this.adapter = new fastify_1.FastifyAdapter();
|
|
83
|
+
return this.adapter;
|
|
84
|
+
}
|
|
85
|
+
msg.end(` ${console_1.Colors.FgYellow}● ${console_1.Colors.Reset}Unable to detect framework. Using Nyte.js Native as default.`);
|
|
86
|
+
this.adapter = new native_1.NativeAdapter();
|
|
87
|
+
return this.adapter;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Força o uso de um framework específico
|
|
91
|
+
*/
|
|
92
|
+
static setFramework(framework) {
|
|
93
|
+
switch (framework) {
|
|
94
|
+
case 'express':
|
|
95
|
+
this.adapter = new express_1.ExpressAdapter();
|
|
96
|
+
break;
|
|
97
|
+
case 'fastify':
|
|
98
|
+
this.adapter = new fastify_1.FastifyAdapter();
|
|
99
|
+
break;
|
|
100
|
+
case 'native':
|
|
101
|
+
this.adapter = new native_1.NativeAdapter();
|
|
102
|
+
break;
|
|
103
|
+
default:
|
|
104
|
+
throw new Error(`Unsupported framework: ${framework}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Reset do adapter (útil para testes)
|
|
109
|
+
*/
|
|
110
|
+
static reset() {
|
|
111
|
+
this.adapter = null;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Retorna o adapter atual (se já foi detectado)
|
|
115
|
+
*/
|
|
116
|
+
static getCurrentAdapter() {
|
|
117
|
+
return this.adapter;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
exports.FrameworkAdapterFactory = FrameworkAdapterFactory;
|
|
121
|
+
FrameworkAdapterFactory.adapter = null;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
interface FastifyRequest {
|
|
2
|
+
method: string;
|
|
3
|
+
url: string;
|
|
4
|
+
headers: Record<string, string | string[]>;
|
|
5
|
+
body?: any;
|
|
6
|
+
query?: Record<string, any>;
|
|
7
|
+
params?: Record<string, string>;
|
|
8
|
+
cookies?: Record<string, string>;
|
|
9
|
+
}
|
|
10
|
+
interface FastifyReply {
|
|
11
|
+
status(code: number): FastifyReply;
|
|
12
|
+
header(name: string, value: string): FastifyReply;
|
|
13
|
+
setCookie(name: string, value: string, options?: any): FastifyReply;
|
|
14
|
+
clearCookie(name: string, options?: any): FastifyReply;
|
|
15
|
+
type(contentType: string): FastifyReply;
|
|
16
|
+
send(data: any): void;
|
|
17
|
+
redirect(url: string): void;
|
|
18
|
+
}
|
|
19
|
+
import { GenericRequest, GenericResponse, FrameworkAdapter } from '../types/framework';
|
|
20
|
+
export declare class FastifyAdapter implements FrameworkAdapter {
|
|
21
|
+
type: "fastify";
|
|
22
|
+
parseRequest(req: FastifyRequest): GenericRequest;
|
|
23
|
+
createResponse(reply: FastifyReply): GenericResponse;
|
|
24
|
+
}
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FastifyAdapter = void 0;
|
|
4
|
+
class FastifyAdapter {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.type = 'fastify';
|
|
7
|
+
}
|
|
8
|
+
parseRequest(req) {
|
|
9
|
+
return {
|
|
10
|
+
method: req.method,
|
|
11
|
+
url: req.url,
|
|
12
|
+
headers: req.headers,
|
|
13
|
+
body: req.body,
|
|
14
|
+
query: req.query,
|
|
15
|
+
params: req.params,
|
|
16
|
+
cookies: req.cookies || {},
|
|
17
|
+
raw: req
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
createResponse(reply) {
|
|
21
|
+
return new FastifyResponseWrapper(reply);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
exports.FastifyAdapter = FastifyAdapter;
|
|
25
|
+
class FastifyResponseWrapper {
|
|
26
|
+
constructor(reply) {
|
|
27
|
+
this.reply = reply;
|
|
28
|
+
}
|
|
29
|
+
get raw() {
|
|
30
|
+
return this.reply;
|
|
31
|
+
}
|
|
32
|
+
status(code) {
|
|
33
|
+
this.reply.status(code);
|
|
34
|
+
return this;
|
|
35
|
+
}
|
|
36
|
+
header(name, value) {
|
|
37
|
+
this.reply.header(name, value);
|
|
38
|
+
return this;
|
|
39
|
+
}
|
|
40
|
+
cookie(name, value, options) {
|
|
41
|
+
this.reply.setCookie(name, value, options);
|
|
42
|
+
return this;
|
|
43
|
+
}
|
|
44
|
+
clearCookie(name, options) {
|
|
45
|
+
this.reply.clearCookie(name, options);
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
json(data) {
|
|
49
|
+
this.reply.send(data);
|
|
50
|
+
}
|
|
51
|
+
text(data) {
|
|
52
|
+
this.reply.type('text/plain; charset=utf-8');
|
|
53
|
+
this.reply.send(data);
|
|
54
|
+
}
|
|
55
|
+
send(data) {
|
|
56
|
+
this.reply.send(data);
|
|
57
|
+
}
|
|
58
|
+
redirect(url) {
|
|
59
|
+
this.reply.redirect(url);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'http';
|
|
2
|
+
import { GenericRequest, GenericResponse, FrameworkAdapter } from '../types/framework';
|
|
3
|
+
export declare class NativeAdapter implements FrameworkAdapter {
|
|
4
|
+
type: "native";
|
|
5
|
+
parseRequest(req: IncomingMessage): GenericRequest;
|
|
6
|
+
createResponse(res: ServerResponse): GenericResponse;
|
|
7
|
+
private parseCookies;
|
|
8
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NativeAdapter = void 0;
|
|
4
|
+
// --- Funções Auxiliares de Segurança ---
|
|
5
|
+
/**
|
|
6
|
+
* Remove caracteres de quebra de linha (\r, \n) de uma string para prevenir
|
|
7
|
+
* ataques de HTTP Header Injection (CRLF Injection).
|
|
8
|
+
* @param value O valor a ser sanitizado.
|
|
9
|
+
* @returns A string sanitizada.
|
|
10
|
+
*/
|
|
11
|
+
function sanitizeHeaderValue(value) {
|
|
12
|
+
return String(value).replace(/[\r\n]/g, '');
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Valida se o nome de um cookie contém apenas caracteres permitidos pela RFC 6265.
|
|
16
|
+
* Isso previne a criação de cookies com nomes inválidos ou maliciosos.
|
|
17
|
+
* @param name O nome do cookie a ser validado.
|
|
18
|
+
* @returns `true` se o nome for válido, `false` caso contrário.
|
|
19
|
+
*/
|
|
20
|
+
function isValidCookieName(name) {
|
|
21
|
+
// A RFC 6265 define 'token' como 1 ou mais caracteres que não são controle nem separadores.
|
|
22
|
+
// Separadores: ( ) < > @ , ; : \ " / [ ] ? = { }
|
|
23
|
+
const validCookieNameRegex = /^[a-zA-Z0-9!#$%&'*+-.^_`|~]+$/;
|
|
24
|
+
return validCookieNameRegex.test(name);
|
|
25
|
+
}
|
|
26
|
+
class NativeAdapter {
|
|
27
|
+
constructor() {
|
|
28
|
+
this.type = 'native';
|
|
29
|
+
}
|
|
30
|
+
parseRequest(req) {
|
|
31
|
+
// URL absoluta é obrigatória para a API WHATWG
|
|
32
|
+
const fullUrl = new URL(req.url || '/', `http://${req.headers.host || 'localhost'}`);
|
|
33
|
+
return {
|
|
34
|
+
method: req.method || 'GET',
|
|
35
|
+
url: req.url || '/',
|
|
36
|
+
headers: req.headers,
|
|
37
|
+
body: req.body ?? null,
|
|
38
|
+
// Converte URLSearchParams para Record<string, string | string[]>
|
|
39
|
+
query: Object.fromEntries(Array.from(fullUrl.searchParams.entries()).map(([key, value]) => {
|
|
40
|
+
const all = fullUrl.searchParams.getAll(key);
|
|
41
|
+
return [key, all.length > 1 ? all : value];
|
|
42
|
+
})),
|
|
43
|
+
params: {}, // preenchido pelo roteador
|
|
44
|
+
cookies: this.parseCookies(req.headers.cookie || ''),
|
|
45
|
+
raw: req
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
createResponse(res) {
|
|
49
|
+
return new NativeResponseWrapper(res);
|
|
50
|
+
}
|
|
51
|
+
parseCookies(cookieHeader) {
|
|
52
|
+
const cookies = {};
|
|
53
|
+
if (!cookieHeader)
|
|
54
|
+
return cookies;
|
|
55
|
+
cookieHeader.split(';').forEach(cookie => {
|
|
56
|
+
const [name, ...rest] = cookie.trim().split('=');
|
|
57
|
+
if (name && rest.length > 0) {
|
|
58
|
+
try {
|
|
59
|
+
// Tenta decodificar o valor do cookie.
|
|
60
|
+
cookies[name] = decodeURIComponent(rest.join('='));
|
|
61
|
+
}
|
|
62
|
+
catch (e) {
|
|
63
|
+
// Prevenção de crash: Ignora cookies com valores malformados (e.g., URI inválida).
|
|
64
|
+
console.error(`Warning: Malformed cookie with name "${name}" was ignored.`);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
return cookies;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
exports.NativeAdapter = NativeAdapter;
|
|
72
|
+
class NativeResponseWrapper {
|
|
73
|
+
constructor(res) {
|
|
74
|
+
this.res = res;
|
|
75
|
+
this.statusCode = 200;
|
|
76
|
+
this.headers = {};
|
|
77
|
+
this.cookiesToSet = []; // Array para lidar corretamente com múltiplos cookies.
|
|
78
|
+
this.finished = false;
|
|
79
|
+
}
|
|
80
|
+
get raw() {
|
|
81
|
+
return this.res;
|
|
82
|
+
}
|
|
83
|
+
status(code) {
|
|
84
|
+
this.statusCode = code;
|
|
85
|
+
return this;
|
|
86
|
+
}
|
|
87
|
+
header(name, value) {
|
|
88
|
+
// Medida de segurança CRÍTICA: Previne HTTP Header Injection (CRLF Injection).
|
|
89
|
+
// Sanitiza tanto o nome quanto o valor do header para remover quebras de linha.
|
|
90
|
+
const sanitizedName = sanitizeHeaderValue(name);
|
|
91
|
+
const sanitizedValue = sanitizeHeaderValue(value);
|
|
92
|
+
if (name !== sanitizedName || String(value) !== sanitizedValue) {
|
|
93
|
+
console.warn(`Warning: Potential HTTP Header Injection attempt detected and sanitized. Original header: "${name}"`);
|
|
94
|
+
}
|
|
95
|
+
this.headers[sanitizedName] = sanitizedValue;
|
|
96
|
+
return this;
|
|
97
|
+
}
|
|
98
|
+
cookie(name, value, options) {
|
|
99
|
+
// Medida de segurança: Valida o nome do cookie.
|
|
100
|
+
if (!isValidCookieName(name)) {
|
|
101
|
+
console.error(`Error: Invalid cookie name "${name}". The cookie will not be set.`);
|
|
102
|
+
return this;
|
|
103
|
+
}
|
|
104
|
+
let cookieString = `${name}=${encodeURIComponent(value)}`;
|
|
105
|
+
if (options) {
|
|
106
|
+
// Sanitiza as opções que são strings para prevenir Header Injection.
|
|
107
|
+
if (options.domain)
|
|
108
|
+
cookieString += `; Domain=${sanitizeHeaderValue(options.domain)}`;
|
|
109
|
+
if (options.path)
|
|
110
|
+
cookieString += `; Path=${sanitizeHeaderValue(options.path)}`;
|
|
111
|
+
if (options.expires)
|
|
112
|
+
cookieString += `; Expires=${options.expires.toUTCString()}`;
|
|
113
|
+
if (options.maxAge)
|
|
114
|
+
cookieString += `; Max-Age=${options.maxAge}`;
|
|
115
|
+
if (options.httpOnly)
|
|
116
|
+
cookieString += '; HttpOnly';
|
|
117
|
+
if (options.secure)
|
|
118
|
+
cookieString += '; Secure';
|
|
119
|
+
if (options.sameSite) {
|
|
120
|
+
const sameSiteValue = typeof options.sameSite === 'boolean' ? 'Strict' : options.sameSite;
|
|
121
|
+
cookieString += `; SameSite=${sanitizeHeaderValue(sameSiteValue)}`;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
this.cookiesToSet.push(cookieString);
|
|
125
|
+
return this;
|
|
126
|
+
}
|
|
127
|
+
clearCookie(name, options) {
|
|
128
|
+
const clearOptions = { ...options, expires: new Date(0), maxAge: 0 };
|
|
129
|
+
return this.cookie(name, '', clearOptions);
|
|
130
|
+
}
|
|
131
|
+
writeHeaders() {
|
|
132
|
+
if (this.finished)
|
|
133
|
+
return;
|
|
134
|
+
this.res.statusCode = this.statusCode;
|
|
135
|
+
Object.entries(this.headers).forEach(([name, value]) => {
|
|
136
|
+
this.res.setHeader(name, value);
|
|
137
|
+
});
|
|
138
|
+
// CORREÇÃO: Envia múltiplos cookies corretamente como headers 'Set-Cookie' separados.
|
|
139
|
+
// O método antigo de juntar com vírgula estava incorreto.
|
|
140
|
+
if (this.cookiesToSet.length > 0) {
|
|
141
|
+
this.res.setHeader('Set-Cookie', this.cookiesToSet);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
json(data) {
|
|
145
|
+
if (this.finished)
|
|
146
|
+
return;
|
|
147
|
+
this.header('Content-Type', 'application/json; charset=utf-8');
|
|
148
|
+
this.writeHeaders();
|
|
149
|
+
const jsonString = JSON.stringify(data);
|
|
150
|
+
this.res.end(jsonString);
|
|
151
|
+
this.finished = true;
|
|
152
|
+
}
|
|
153
|
+
text(data) {
|
|
154
|
+
if (this.finished)
|
|
155
|
+
return;
|
|
156
|
+
this.header('Content-Type', 'text/plain; charset=utf-8');
|
|
157
|
+
this.writeHeaders();
|
|
158
|
+
this.res.end(data);
|
|
159
|
+
this.finished = true;
|
|
160
|
+
}
|
|
161
|
+
send(data) {
|
|
162
|
+
if (this.finished)
|
|
163
|
+
return;
|
|
164
|
+
const existingContentType = this.headers['Content-Type'];
|
|
165
|
+
if (typeof data === 'string') {
|
|
166
|
+
if (!existingContentType) {
|
|
167
|
+
this.header('Content-Type', 'text/plain; charset=utf-8');
|
|
168
|
+
}
|
|
169
|
+
this.writeHeaders();
|
|
170
|
+
this.res.end(data);
|
|
171
|
+
}
|
|
172
|
+
else if (Buffer.isBuffer(data)) {
|
|
173
|
+
this.writeHeaders();
|
|
174
|
+
this.res.end(data);
|
|
175
|
+
}
|
|
176
|
+
else if (data !== null && typeof data === 'object') {
|
|
177
|
+
this.json(data); // Reutiliza o método json para consistência
|
|
178
|
+
return; // O método json já finaliza a resposta
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
if (!existingContentType) {
|
|
182
|
+
this.header('Content-Type', 'text/plain; charset=utf-8');
|
|
183
|
+
}
|
|
184
|
+
this.writeHeaders();
|
|
185
|
+
this.res.end(String(data));
|
|
186
|
+
}
|
|
187
|
+
this.finished = true;
|
|
188
|
+
}
|
|
189
|
+
redirect(url) {
|
|
190
|
+
if (this.finished)
|
|
191
|
+
return;
|
|
192
|
+
this.status(302);
|
|
193
|
+
// A sanitização no método .header() previne que um URL manipulado
|
|
194
|
+
// cause um ataque de Open Redirect via Header Injection.
|
|
195
|
+
this.header('Location', url);
|
|
196
|
+
this.writeHeaders();
|
|
197
|
+
this.res.end();
|
|
198
|
+
this.finished = true;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Options as BoxenOptions } from 'boxen';
|
|
2
|
+
/**
|
|
3
|
+
* Um "handle" para uma linha dinâmica. As instâncias desta classe
|
|
4
|
+
* são retornadas por `Console.dynamicLine()` e usadas para controlar
|
|
5
|
+
* o conteúdo da linha.
|
|
6
|
+
*/
|
|
7
|
+
export declare class DynamicLine {
|
|
8
|
+
private readonly _id;
|
|
9
|
+
constructor(initialContent: string);
|
|
10
|
+
/**
|
|
11
|
+
* Atualiza o conteúdo da linha no console.
|
|
12
|
+
* @param newContent O novo texto a ser exibido.
|
|
13
|
+
*/
|
|
14
|
+
update(newContent: string): void;
|
|
15
|
+
/**
|
|
16
|
+
* Finaliza a linha, opcionalmente com um texto final, e a torna estática.
|
|
17
|
+
* @param finalContent O texto final a ser exibido.
|
|
18
|
+
*/
|
|
19
|
+
end(finalContent: string): void;
|
|
20
|
+
}
|
|
21
|
+
export declare enum Colors {
|
|
22
|
+
Reset = "\u001B[0m",
|
|
23
|
+
Bright = "\u001B[1m",
|
|
24
|
+
Dim = "\u001B[2m",
|
|
25
|
+
Underscore = "\u001B[4m",
|
|
26
|
+
Blink = "\u001B[5m",
|
|
27
|
+
Reverse = "\u001B[7m",
|
|
28
|
+
Hidden = "\u001B[8m",
|
|
29
|
+
FgBlack = "\u001B[30m",
|
|
30
|
+
FgRed = "\u001B[31m",
|
|
31
|
+
FgGreen = "\u001B[32m",
|
|
32
|
+
FgYellow = "\u001B[33m",
|
|
33
|
+
FgBlue = "\u001B[34m",
|
|
34
|
+
FgMagenta = "\u001B[35m",
|
|
35
|
+
FgCyan = "\u001B[36m",
|
|
36
|
+
FgWhite = "\u001B[37m",
|
|
37
|
+
FgGray = "\u001B[90m",
|
|
38
|
+
FgAlmostWhite = "\u001B[38;2;220;220;220m",
|
|
39
|
+
BgBlack = "\u001B[40m",
|
|
40
|
+
BgRed = "\u001B[41m",
|
|
41
|
+
BgGreen = "\u001B[42m",
|
|
42
|
+
BgYellow = "\u001B[43m",
|
|
43
|
+
BgBlue = "\u001B[44m",
|
|
44
|
+
BgMagenta = "\u001B[45m",
|
|
45
|
+
BgCyan = "\u001B[46m",
|
|
46
|
+
BgWhite = "\u001B[47m",
|
|
47
|
+
BgGray = "\u001B[100m"
|
|
48
|
+
}
|
|
49
|
+
export declare enum Levels {
|
|
50
|
+
ERROR = "ERROR",
|
|
51
|
+
WARN = "WARN",
|
|
52
|
+
INFO = "INFO",
|
|
53
|
+
DEBUG = "DEBUG",
|
|
54
|
+
SUCCESS = "SUCCESS"
|
|
55
|
+
}
|
|
56
|
+
export default class Console {
|
|
57
|
+
private static activeLines;
|
|
58
|
+
private static lastRenderedLines;
|
|
59
|
+
private static redrawDynamicLines;
|
|
60
|
+
private static writeStatic;
|
|
61
|
+
private static formatLog;
|
|
62
|
+
private static registerDynamicLine;
|
|
63
|
+
private static updateDynamicLine;
|
|
64
|
+
private static endDynamicLine;
|
|
65
|
+
static error(...args: any[]): void;
|
|
66
|
+
static warn(...args: any[]): void;
|
|
67
|
+
static info(...args: any[]): void;
|
|
68
|
+
static success(...args: any[]): void;
|
|
69
|
+
static debug(...args: any[]): void;
|
|
70
|
+
static logCustomLevel(levelName: string, without?: boolean, color?: Colors, ...args: any[]): void;
|
|
71
|
+
static logWithout(level: Levels, colors?: Colors, ...args: any[]): void;
|
|
72
|
+
static log(level: Levels, colors?: Colors | null, ...args: any[]): void;
|
|
73
|
+
static ask(question: string, defaultValue?: string): Promise<string>;
|
|
74
|
+
static confirm(message: string, defaultYes?: boolean): Promise<boolean>;
|
|
75
|
+
static table(data: Record<string, any> | Array<{
|
|
76
|
+
Field: string;
|
|
77
|
+
Value: any;
|
|
78
|
+
}>): void;
|
|
79
|
+
static box(content: string, options?: BoxenOptions): void;
|
|
80
|
+
static dynamicLine(initialContent: string): DynamicLine;
|
|
81
|
+
}
|