@wxn0brp/falcon-frame 0.0.2 → 0.0.3
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 +1 -1
- package/package.json +4 -1
- package/.github/workflows/build.yml +0 -20
- package/CHANGELOG.md +0 -18
- package/public/index.html +0 -47
- package/public/style.css +0 -41
- package/src/helpers.ts +0 -79
- package/src/index.ts +0 -87
- package/src/req.ts +0 -123
- package/src/res.ts +0 -43
- package/src/test.ts +0 -68
- package/src/types.ts +0 -56
- package/src/valid.ts +0 -62
- package/suglite.json +0 -10
- package/tsconfig.json +0 -22
package/README.md
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wxn0brp/falcon-frame",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"author": "wxn0brP",
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
"dependencies": {
|
|
15
15
|
"@wxn0brp/wts-logger": "github:wxn0brP/ts-shared#dist-logger"
|
|
16
16
|
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist"
|
|
19
|
+
],
|
|
17
20
|
"exports": {
|
|
18
21
|
".": {
|
|
19
22
|
"types": "./dist/index.d.ts",
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
name: Build
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches:
|
|
6
|
-
- master
|
|
7
|
-
tags:
|
|
8
|
-
- "*"
|
|
9
|
-
|
|
10
|
-
workflow_dispatch:
|
|
11
|
-
|
|
12
|
-
jobs:
|
|
13
|
-
build:
|
|
14
|
-
uses: wxn0brP/workflow-dist/.github/workflows/build-ts.yml@main
|
|
15
|
-
with:
|
|
16
|
-
scriptsHandling: "remove-all"
|
|
17
|
-
publishToNpm: true
|
|
18
|
-
|
|
19
|
-
secrets:
|
|
20
|
-
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
package/CHANGELOG.md
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
-
|
|
5
|
-
### 0.0.2 (2025-05-15)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
### Features
|
|
9
|
-
|
|
10
|
-
* add alternative export ([4e69a6b](https://github.com/wxn0brP/FalconFrame/commit/4e69a6b0fa771c35d9aef2270174cba3026251c3))
|
|
11
|
-
* add build workflow ([d89bd23](https://github.com/wxn0brP/FalconFrame/commit/d89bd2385a4e5855cfa4205c86563ba65f89c978))
|
|
12
|
-
* add simple valid function ([8c247d2](https://github.com/wxn0brP/FalconFrame/commit/8c247d2610c78c748fca4efc25840b51359e9490))
|
|
13
|
-
* add type export ([0ef9d6c](https://github.com/wxn0brP/FalconFrame/commit/0ef9d6cd61221c88c79f25b07b3240bec379ca76))
|
|
14
|
-
* enhance middlewares ([3c2063b](https://github.com/wxn0brP/FalconFrame/commit/3c2063b6366514b55e2ac19a83b1dba9d42072cb))
|
|
15
|
-
* make public ([63b38fc](https://github.com/wxn0brP/FalconFrame/commit/63b38fc5fdfbacef9e2e9be2c4ffd0d66553443c))
|
|
16
|
-
* refactor engine ([d2a40c4](https://github.com/wxn0brP/FalconFrame/commit/d2a40c471195ea8a0bfdbfd5edeebc2dcd4a438d))
|
|
17
|
-
* return server on listen ([f98e6c9](https://github.com/wxn0brP/FalconFrame/commit/f98e6c931e0afd4b5627b363707d0bf3460b5770))
|
|
18
|
-
* update exports ([2feac36](https://github.com/wxn0brP/FalconFrame/commit/2feac36f12de6e06f1d023d5964eefe271c49f4f))
|
package/public/index.html
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
-
<title>Yep</title>
|
|
7
|
-
<link rel="stylesheet" href="style.css">
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<h1>
|
|
11
|
-
Work
|
|
12
|
-
</h1>
|
|
13
|
-
<br>
|
|
14
|
-
<form>
|
|
15
|
-
<label>
|
|
16
|
-
Login
|
|
17
|
-
<input type="text" name="login">
|
|
18
|
-
</label>
|
|
19
|
-
<label>
|
|
20
|
-
Password
|
|
21
|
-
<input type="password" name="password">
|
|
22
|
-
</label>
|
|
23
|
-
<button type="submit">Submit</button>
|
|
24
|
-
</form>
|
|
25
|
-
|
|
26
|
-
<script type="module">
|
|
27
|
-
const form = document.querySelector("form");
|
|
28
|
-
form.addEventListener("submit", async e => {
|
|
29
|
-
e.preventDefault();
|
|
30
|
-
const formData = new FormData(form);
|
|
31
|
-
const data = Object.fromEntries(formData);
|
|
32
|
-
|
|
33
|
-
const response = await fetch("/submit", {
|
|
34
|
-
method: "POST",
|
|
35
|
-
headers: {
|
|
36
|
-
"Content-Type": "application/json"
|
|
37
|
-
},
|
|
38
|
-
body: JSON.stringify(data)
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
const result = await response.json();
|
|
42
|
-
console.log(result);
|
|
43
|
-
alert(JSON.stringify(result, null, 2));
|
|
44
|
-
});
|
|
45
|
-
</script>
|
|
46
|
-
</body>
|
|
47
|
-
</html>
|
package/public/style.css
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
*{
|
|
3
|
-
margin: 0;
|
|
4
|
-
padding: 0;
|
|
5
|
-
box-sizing: border-box;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
body{
|
|
9
|
-
background-color: #181818;
|
|
10
|
-
color: #c9d6de;
|
|
11
|
-
text-align: center;
|
|
12
|
-
font-family: Verdana, Geneva, Tahoma, sans-serif
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
h1 {
|
|
16
|
-
color: rebeccapurple;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
form {
|
|
20
|
-
display: flex;
|
|
21
|
-
flex-direction: column;
|
|
22
|
-
width: 300px;
|
|
23
|
-
border: 1px solid rebeccapurple;
|
|
24
|
-
padding: 1rem;
|
|
25
|
-
border-radius: 0.75rem;
|
|
26
|
-
margin-inline: auto;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
form label {
|
|
30
|
-
margin: 0.5rem 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
form button {
|
|
34
|
-
margin-top: 1rem;
|
|
35
|
-
padding: 0.5rem 1rem;
|
|
36
|
-
background-color: rebeccapurple;
|
|
37
|
-
color: white;
|
|
38
|
-
border: none;
|
|
39
|
-
border-radius: 0.25rem;
|
|
40
|
-
cursor: pointer;
|
|
41
|
-
}
|
package/src/helpers.ts
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import fs from "fs";
|
|
2
|
-
import path from "path";
|
|
3
|
-
import querystring from "querystring";
|
|
4
|
-
import { FFResponse } from "./res";
|
|
5
|
-
import { Body, Cookies, FFRequest, RouteHandler } from "./types";
|
|
6
|
-
|
|
7
|
-
export function parseCookies(cookieHeader: string): Cookies {
|
|
8
|
-
const cookies: Cookies = {};
|
|
9
|
-
cookieHeader.split(";").forEach(cookie => {
|
|
10
|
-
const [name, ...valueParts] = cookie.split("=");
|
|
11
|
-
const value = decodeURIComponent(valueParts.join("=").trim());
|
|
12
|
-
cookies[name.trim()] = value;
|
|
13
|
-
});
|
|
14
|
-
return cookies;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function parseBody(contentType: string, body: string): Body {
|
|
18
|
-
if (contentType.includes("application/json")) {
|
|
19
|
-
try {
|
|
20
|
-
return JSON.parse(body);
|
|
21
|
-
} catch {
|
|
22
|
-
return {};
|
|
23
|
-
}
|
|
24
|
-
} else if (contentType.includes("application/x-www-form-urlencoded")) {
|
|
25
|
-
return querystring.parse(body);
|
|
26
|
-
}
|
|
27
|
-
return {};
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export function getContentType(filePath: string): string {
|
|
31
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
32
|
-
switch (ext) {
|
|
33
|
-
case ".html":
|
|
34
|
-
return "text/html";
|
|
35
|
-
case ".css":
|
|
36
|
-
return "text/css";
|
|
37
|
-
case ".js":
|
|
38
|
-
return "application/javascript";
|
|
39
|
-
case ".json":
|
|
40
|
-
return "application/json";
|
|
41
|
-
case ".png":
|
|
42
|
-
return "image/png";
|
|
43
|
-
case ".jpg":
|
|
44
|
-
case ".jpeg":
|
|
45
|
-
return "image/jpeg";
|
|
46
|
-
case ".gif":
|
|
47
|
-
return "image/gif";
|
|
48
|
-
case ".svg":
|
|
49
|
-
return "image/svg+xml";
|
|
50
|
-
default:
|
|
51
|
-
return "application/octet-stream";
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function handleStaticFiles(apiPath: string, dirPath: string): RouteHandler {
|
|
56
|
-
return (req: FFRequest, res: FFResponse, next: () => void) => {
|
|
57
|
-
if (!req.path.startsWith(apiPath)) return next();
|
|
58
|
-
const filePath = path.join(dirPath, req.path.slice(apiPath.length));
|
|
59
|
-
|
|
60
|
-
if (fs.existsSync(filePath) && fs.statSync(filePath).isFile()) {
|
|
61
|
-
res.setHeader("Content-Type", getContentType(filePath));
|
|
62
|
-
fs.createReadStream(filePath).pipe(res);
|
|
63
|
-
return true;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (req.path.endsWith("/")) {
|
|
67
|
-
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
|
68
|
-
const indexPath = path.join(filePath, "index.html");
|
|
69
|
-
if (fs.existsSync(indexPath) && fs.statSync(indexPath).isFile()) {
|
|
70
|
-
res.setHeader("Content-Type", getContentType(indexPath));
|
|
71
|
-
fs.createReadStream(indexPath).pipe(res);
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
next();
|
|
78
|
-
}
|
|
79
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
import { Logger, LoggerOptions } from "@wxn0brp/wts-logger";
|
|
2
|
-
import http from "http";
|
|
3
|
-
import { handleStaticFiles } from "./helpers";
|
|
4
|
-
import { handleRequest } from "./req";
|
|
5
|
-
import { FFResponse } from "./res";
|
|
6
|
-
import { FFRequest, Method, Middleware, RouteHandler } from "./types";
|
|
7
|
-
|
|
8
|
-
export class FalconFrame {
|
|
9
|
-
public middlewares: Middleware[] = [];
|
|
10
|
-
public logger: Logger;
|
|
11
|
-
|
|
12
|
-
constructor(loggerOpts?: LoggerOptions) {
|
|
13
|
-
this.logger = new Logger({
|
|
14
|
-
loggerName: "falcon-frame",
|
|
15
|
-
...loggerOpts
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
addRoute(method: Method, path: string, ...handlers: RouteHandler[]): void {
|
|
20
|
-
const handler = handlers.pop();
|
|
21
|
-
handlers.forEach(middleware => this.use(path, middleware));
|
|
22
|
-
this.middlewares.push({ path, middleware: handler, method });
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
use(path: string | RouteHandler = "/", middleware?: RouteHandler, method: Method = "all"): void {
|
|
26
|
-
if (typeof path === "function") {
|
|
27
|
-
middleware = path;
|
|
28
|
-
path = "/";
|
|
29
|
-
}
|
|
30
|
-
this.middlewares.push({ path, middleware, method, use: true });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
get(path: string, ...handlers: RouteHandler[]): void {
|
|
34
|
-
this.addRoute("get", path, ...handlers);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
post(path: string, ...handlers: RouteHandler[]): void {
|
|
38
|
-
this.addRoute("post", path, ...handlers);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
put(path: string, ...handlers: RouteHandler[]): void {
|
|
42
|
-
this.addRoute("put", path, ...handlers);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
delete(path: string, ...handlers: RouteHandler[]): void {
|
|
46
|
-
this.addRoute("delete", path, ...handlers);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
all(path: string, ...handlers: RouteHandler[]): void {
|
|
50
|
-
this.addRoute("all", path, ...handlers);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
static(apiPath: string, dirPath: string): void {
|
|
54
|
-
this.middlewares.push({
|
|
55
|
-
path: (apiPath+"/*").replace("//","/"),
|
|
56
|
-
method: "get",
|
|
57
|
-
middleware: handleStaticFiles(apiPath, dirPath)
|
|
58
|
-
});
|
|
59
|
-
this.middlewares.push({
|
|
60
|
-
path: apiPath,
|
|
61
|
-
method: "get",
|
|
62
|
-
middleware: handleStaticFiles(apiPath, dirPath)
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
listen(port: number, callback?: () => void) {
|
|
67
|
-
const server = http.createServer((req, res) => {
|
|
68
|
-
handleRequest(req as FFRequest, res as FFResponse, this);
|
|
69
|
-
});
|
|
70
|
-
server.listen(port, callback);
|
|
71
|
-
return server;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
getApp() {
|
|
75
|
-
return (req: any, res: any) => {
|
|
76
|
-
handleRequest(req as FFRequest, res as FFResponse, this);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
export default FalconFrame;
|
|
82
|
-
|
|
83
|
-
export {
|
|
84
|
-
FFResponse,
|
|
85
|
-
FFRequest,
|
|
86
|
-
RouteHandler,
|
|
87
|
-
}
|
package/src/req.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import { URL } from "url";
|
|
2
|
-
import FalconFrame from ".";
|
|
3
|
-
import { parseBody, parseCookies } from "./helpers";
|
|
4
|
-
import { FFResponse } from "./res";
|
|
5
|
-
import { FFRequest, Middleware } from "./types";
|
|
6
|
-
import { validate } from "./valid";
|
|
7
|
-
|
|
8
|
-
export function handleRequest(req: FFRequest, res: FFResponse, FF: FalconFrame): void {
|
|
9
|
-
Object.setPrototypeOf(res, FFResponse.prototype);
|
|
10
|
-
const originalEnd = res.end;
|
|
11
|
-
res.end = function (...any: any[]) {
|
|
12
|
-
res._ended = true;
|
|
13
|
-
return originalEnd.call(res, ...any);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const { logger, middlewares } = FF;
|
|
17
|
-
const parsedUrl = new URL(req.url || "", "http://localhost");
|
|
18
|
-
req.path = parsedUrl.pathname || "/";
|
|
19
|
-
req.query = Object.fromEntries(parsedUrl.searchParams);
|
|
20
|
-
req.cookies = parseCookies(req.headers.cookie || "");
|
|
21
|
-
req.params = {};
|
|
22
|
-
req.valid = (schema: any) => validate(schema, req.body);
|
|
23
|
-
|
|
24
|
-
logger.info(`Incoming request: ${req.method} ${req.url}`);
|
|
25
|
-
|
|
26
|
-
let body = "";
|
|
27
|
-
req.on("data", chunk => (body += chunk.toString()));
|
|
28
|
-
req.on("end", () => {
|
|
29
|
-
const contentType = req.headers["content-type"] || "";
|
|
30
|
-
req.body = parseBody(contentType, body);
|
|
31
|
-
|
|
32
|
-
logger.debug(`Request body: ${JSON.stringify(req.body)}`);
|
|
33
|
-
|
|
34
|
-
const matchedTypeMiddlewares = middlewares.filter(middleware => middleware.method === req.method.toLocaleLowerCase() || middleware.method === "all");
|
|
35
|
-
const matchedMiddlewares = matchMiddleware(req.path, matchedTypeMiddlewares);
|
|
36
|
-
|
|
37
|
-
if (matchedMiddlewares.length === 0) {
|
|
38
|
-
return res.status(404).end("404: File had second thoughts.");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
logger.debug("Matched middlewares: " + matchedMiddlewares.map(middleware => middleware.path).join(", "));
|
|
42
|
-
|
|
43
|
-
let middlewareIndex = 0;
|
|
44
|
-
const next = async () => {
|
|
45
|
-
if (middlewareIndex >= matchedMiddlewares.length) {
|
|
46
|
-
return res.status(404).end("404: File had second thoughts");
|
|
47
|
-
}
|
|
48
|
-
const middleware = matchedMiddlewares[middlewareIndex++];
|
|
49
|
-
logger.debug(`Executing middleware ${middlewareIndex} of ${matchedMiddlewares.length} matched for path [${middleware.path}]`);
|
|
50
|
-
if (middleware.path.includes(":")) {
|
|
51
|
-
const middlewareParts = middleware.path.split("/");
|
|
52
|
-
const reqPathParts = req.path.split("/");
|
|
53
|
-
req.params = {};
|
|
54
|
-
for (let i = 0; i < middlewareParts.length; i++) {
|
|
55
|
-
if (middlewareParts[i].startsWith(":")) {
|
|
56
|
-
const paramName = middlewareParts[i].slice(1);
|
|
57
|
-
req.params[paramName] = reqPathParts[i];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
const result = await middleware.middleware(req, res, next);
|
|
62
|
-
if (result && !res._ended) {
|
|
63
|
-
if (typeof result === "string") {
|
|
64
|
-
return res.end(result);
|
|
65
|
-
} else if (typeof result === "object") {
|
|
66
|
-
return res.json(result);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
next();
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function matchMiddleware(url: string, middlewares: Middleware[]): Middleware[] {
|
|
76
|
-
const matchedMiddlewares: Middleware[] = [];
|
|
77
|
-
|
|
78
|
-
url = url.replace(/\/$/, "");
|
|
79
|
-
|
|
80
|
-
for (const middleware of middlewares) {
|
|
81
|
-
const cleanedMiddleware = middleware.path.replace(/\/$/, "");
|
|
82
|
-
|
|
83
|
-
if (middleware.use) {
|
|
84
|
-
if (url.startsWith(cleanedMiddleware)) {
|
|
85
|
-
matchedMiddlewares.push(middleware);
|
|
86
|
-
}
|
|
87
|
-
} else if (cleanedMiddleware === "*") {
|
|
88
|
-
matchedMiddlewares.push(middleware);
|
|
89
|
-
} else if (cleanedMiddleware.endsWith("/*")) {
|
|
90
|
-
const prefix = cleanedMiddleware.slice(0, -2);
|
|
91
|
-
if (url.startsWith(prefix)) {
|
|
92
|
-
matchedMiddlewares.push(middleware);
|
|
93
|
-
}
|
|
94
|
-
} else if (cleanedMiddleware.includes(":")) {
|
|
95
|
-
const middlewareParts = cleanedMiddleware.split("/");
|
|
96
|
-
const urlParts = url.split("/");
|
|
97
|
-
|
|
98
|
-
if (middlewareParts.length !== urlParts.length) {
|
|
99
|
-
continue;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
let matches = true;
|
|
103
|
-
for (let i = 0; i < middlewareParts.length; i++) {
|
|
104
|
-
if (middlewareParts[i].startsWith(":")) {
|
|
105
|
-
continue;
|
|
106
|
-
} else if (middlewareParts[i] !== urlParts[i]) {
|
|
107
|
-
matches = false;
|
|
108
|
-
break;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (matches) {
|
|
113
|
-
matchedMiddlewares.push(middleware);
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
if (url === cleanedMiddleware) {
|
|
117
|
-
matchedMiddlewares.push(middleware);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
return matchedMiddlewares;
|
|
123
|
-
}
|
package/src/res.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import http from "http";
|
|
2
|
-
import { CookieOptions } from "./types";
|
|
3
|
-
import { getContentType } from "./helpers";
|
|
4
|
-
import { createReadStream } from "fs";
|
|
5
|
-
|
|
6
|
-
export class FFResponse extends http.ServerResponse {
|
|
7
|
-
_ended = false;
|
|
8
|
-
|
|
9
|
-
json(data: any): void {
|
|
10
|
-
this.setHeader("Content-Type", "application/json");
|
|
11
|
-
this.end(JSON.stringify(data));
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
cookie(name: string, value: string, options: CookieOptions = {}): this {
|
|
15
|
-
let cookie = `${name}=${encodeURIComponent(value)}`;
|
|
16
|
-
|
|
17
|
-
if (options.maxAge) cookie += `; Max-Age=${options.maxAge}`;
|
|
18
|
-
if (options.path) cookie += `; Path=${options.path}`;
|
|
19
|
-
if (options.httpOnly) cookie += `; HttpOnly`;
|
|
20
|
-
if (options.secure) cookie += `; Secure`;
|
|
21
|
-
if (options.sameSite) cookie += `; SameSite=${options.sameSite}`;
|
|
22
|
-
|
|
23
|
-
this.setHeader("Set-Cookie", cookie);
|
|
24
|
-
return this;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
status(code: number): this {
|
|
28
|
-
this.statusCode = code;
|
|
29
|
-
return this;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
redirect(url: string): this {
|
|
33
|
-
this.statusCode = 302;
|
|
34
|
-
this.setHeader("Location", url);
|
|
35
|
-
return this;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
sendFile(filePath: string): this {
|
|
39
|
-
this.setHeader("Content-Type", getContentType(filePath));
|
|
40
|
-
createReadStream(filePath).pipe(this);
|
|
41
|
-
return this;
|
|
42
|
-
}
|
|
43
|
-
}
|
package/src/test.ts
DELETED
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
import FalconFrame from ".";
|
|
2
|
-
|
|
3
|
-
const app = new FalconFrame({
|
|
4
|
-
logLevel: "INFO",
|
|
5
|
-
});
|
|
6
|
-
|
|
7
|
-
app.use((req, res, next) => {
|
|
8
|
-
console.log(`[${req.method}] ${req.path}`);
|
|
9
|
-
next();
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
app.static("/", "public");
|
|
13
|
-
|
|
14
|
-
app.get("/hello", (req, res) => {
|
|
15
|
-
const name = req.query.name || "World";
|
|
16
|
-
res.json({
|
|
17
|
-
message: `Hello, ${name}?`,
|
|
18
|
-
query: req.query,
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
app.get("/hello/*", (req, res) => {
|
|
23
|
-
res.json({
|
|
24
|
-
message: `Hello, ${req.params.name}!`,
|
|
25
|
-
query: req.query,
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
app.get("/greet/:name", (req, res, next) => {
|
|
30
|
-
console.log(req.params);
|
|
31
|
-
next();
|
|
32
|
-
}, (req, res) => {
|
|
33
|
-
res.json({
|
|
34
|
-
message: `Hello, ${req.params.name}!`,
|
|
35
|
-
});
|
|
36
|
-
})
|
|
37
|
-
|
|
38
|
-
app.post("/submit", (req, res, next) => {
|
|
39
|
-
const { validErrors, valid } = req.valid({
|
|
40
|
-
login: "required|string",
|
|
41
|
-
password: "required|string|min:8",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
if (!valid) {
|
|
45
|
-
res.status(400).json({
|
|
46
|
-
status: "error",
|
|
47
|
-
errors: validErrors,
|
|
48
|
-
});
|
|
49
|
-
} else next()
|
|
50
|
-
}, async (req, res) => {
|
|
51
|
-
console.log("run")
|
|
52
|
-
res.redirect("/hello?name=" + req.body.login);
|
|
53
|
-
return {
|
|
54
|
-
status: "success",
|
|
55
|
-
data: `Hello ${req.body.login}`,
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
app.use((req, res) => {
|
|
60
|
-
res.status(404);
|
|
61
|
-
res.json({
|
|
62
|
-
message: "Not found",
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
app.listen(3000, () => {
|
|
67
|
-
console.log("Server running on http://localhost:3000");
|
|
68
|
-
});
|
package/src/types.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { FFResponse } from "./res";
|
|
2
|
-
import http from "http";
|
|
3
|
-
|
|
4
|
-
export type RouteHandler = (req: FFRequest, res: FFResponse, next?: () => void) => void | any;
|
|
5
|
-
export type Method = "get" | "post" | "put" | "delete" | "all";
|
|
6
|
-
|
|
7
|
-
export interface Params {
|
|
8
|
-
[key: string]: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface Cookies {
|
|
12
|
-
[key: string]: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export interface Query {
|
|
16
|
-
[key: string]: string | string[];
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface Body {
|
|
20
|
-
[key: string]: any;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export class FFRequest extends http.IncomingMessage {
|
|
24
|
-
path!: string;
|
|
25
|
-
query!: Query;
|
|
26
|
-
params!: Params;
|
|
27
|
-
cookies!: Cookies;
|
|
28
|
-
body!: Body;
|
|
29
|
-
valid!: (schema: ValidationSchema) => ValidationResult;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface Middleware {
|
|
33
|
-
path: string;
|
|
34
|
-
method: Method;
|
|
35
|
-
middleware: RouteHandler;
|
|
36
|
-
use?: true;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface CookieOptions {
|
|
40
|
-
maxAge?: number;
|
|
41
|
-
path?: string;
|
|
42
|
-
httpOnly?: boolean;
|
|
43
|
-
secure?: boolean;
|
|
44
|
-
sameSite?: "Strict" | "Lax" | "None";
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export interface ValidationSchema {
|
|
48
|
-
[key: string]: string;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
export interface ValidationResult {
|
|
52
|
-
valid: boolean;
|
|
53
|
-
validErrors: {
|
|
54
|
-
[key: string]: string[];
|
|
55
|
-
};
|
|
56
|
-
}
|
package/src/valid.ts
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
import { ValidationResult, ValidationSchema } from "./types";
|
|
2
|
-
|
|
3
|
-
export function validate(schema: ValidationSchema, data: any): ValidationResult {
|
|
4
|
-
const errors: any = {};
|
|
5
|
-
let isValid = true;
|
|
6
|
-
|
|
7
|
-
for (const key in schema) {
|
|
8
|
-
const rules = schema[key].split("|");
|
|
9
|
-
const value = data[key];
|
|
10
|
-
const fieldErrors: string[] = [];
|
|
11
|
-
|
|
12
|
-
for (const rule of rules) {
|
|
13
|
-
const [ruleName, param] = rule.split(":");
|
|
14
|
-
switch(ruleName) {
|
|
15
|
-
case "required":
|
|
16
|
-
if (!value && value !== 0) {
|
|
17
|
-
fieldErrors.push(`${key} is required`);
|
|
18
|
-
}
|
|
19
|
-
break;
|
|
20
|
-
case "string":
|
|
21
|
-
if (typeof value !== "string") {
|
|
22
|
-
fieldErrors.push(`${key} must be a string`);
|
|
23
|
-
}
|
|
24
|
-
break;
|
|
25
|
-
case "number":
|
|
26
|
-
if (typeof value !== "number") {
|
|
27
|
-
fieldErrors.push(`${key} must be a number`);
|
|
28
|
-
}
|
|
29
|
-
break;
|
|
30
|
-
case "min":
|
|
31
|
-
if (typeof value === "string" && value.length < parseInt(param)) {
|
|
32
|
-
fieldErrors.push(`${key} must be at least ${param} characters long`);
|
|
33
|
-
}
|
|
34
|
-
if (typeof value === "number" && value < parseInt(param)) {
|
|
35
|
-
fieldErrors.push(`${key} must be at least ${param}`);
|
|
36
|
-
}
|
|
37
|
-
break;
|
|
38
|
-
case "max":
|
|
39
|
-
if (typeof value === "string" && value.length > parseInt(param)) {
|
|
40
|
-
fieldErrors.push(`${key} must not exceed ${param} characters`);
|
|
41
|
-
}
|
|
42
|
-
if (typeof value === "number" && value > parseInt(param)) {
|
|
43
|
-
fieldErrors.push(`${key} must not exceed ${param}`);
|
|
44
|
-
}
|
|
45
|
-
break;
|
|
46
|
-
case "email":
|
|
47
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
48
|
-
if (!emailRegex.test(value)) {
|
|
49
|
-
fieldErrors.push(`${key} must be a valid email`);
|
|
50
|
-
}
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (fieldErrors.length > 0) {
|
|
56
|
-
isValid = false;
|
|
57
|
-
errors[key] = fieldErrors;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return { valid: isValid, validErrors: errors };
|
|
62
|
-
}
|
package/suglite.json
DELETED
package/tsconfig.json
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"module": "ES2022",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"moduleResolution": "node",
|
|
6
|
-
"paths": {},
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"outDir": "./dist",
|
|
10
|
-
"declaration": true
|
|
11
|
-
},
|
|
12
|
-
"include": [
|
|
13
|
-
"./src"
|
|
14
|
-
],
|
|
15
|
-
"exclude": [
|
|
16
|
-
"node_modules"
|
|
17
|
-
],
|
|
18
|
-
"tsc-alias": {
|
|
19
|
-
"resolveFullPaths": true,
|
|
20
|
-
"verbose": false
|
|
21
|
-
}
|
|
22
|
-
}
|