@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 CHANGED
@@ -20,7 +20,7 @@
20
20
  ## 📦 Installation
21
21
 
22
22
  ```bash
23
- yarn add github:wxn0brP/FalconFrame#dist
23
+ yarn add @wxn0brp/falcon-frame
24
24
  ```
25
25
 
26
26
  ## 🚦 Usage Example
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/falcon-frame",
3
- "version": "0.0.2",
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
@@ -1,10 +0,0 @@
1
- {
2
- "cmd": "yarn build",
3
- "watch": [
4
- "src"
5
- ],
6
- "restart_cmd": "clear",
7
- "events": {
8
- "rs": "clear"
9
- }
10
- }
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
- }