@wxn0brp/falcon-frame 0.4.1 → 0.5.1
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 +3 -15
- package/dist/cors.d.ts +7 -0
- package/dist/cors.js +39 -0
- package/dist/helpers.d.ts +1 -0
- package/dist/helpers.js +10 -5
- package/dist/index.d.ts +8 -5
- package/dist/index.js +12 -6
- package/dist/res.d.ts +2 -2
- package/dist/res.js +16 -12
- package/dist/router.d.ts +3 -2
- package/dist/router.js +2 -3
- package/dist/types.d.ts +9 -0
- package/package.json +1 -1
- package/dist/plugin.d.ts +0 -54
- package/dist/plugin.js +0 -101
- package/dist/plugins/cors.d.ts +0 -8
- package/dist/plugins/cors.js +0 -42
- package/dist/plugins/rateLimit.d.ts +0 -2
- package/dist/plugins/rateLimit.js +0 -23
- package/dist/plugins/security.d.ts +0 -2
- package/dist/plugins/security.js +0 -14
package/README.md
CHANGED
|
@@ -4,8 +4,6 @@
|
|
|
4
4
|
|
|
5
5
|
**FalconFrame** is a minimalist web framework inspired by the middleware-first style, with routing similar to Express, but written in pure TypeScript. It supports static files, data validation, and cookie management, all without external dependencies apart from a logger.
|
|
6
6
|
|
|
7
|
-
---
|
|
8
|
-
|
|
9
7
|
## ✨ Features
|
|
10
8
|
|
|
11
9
|
- 🚀 Zero-dependency routing engine
|
|
@@ -15,8 +13,6 @@
|
|
|
15
13
|
- 🍪 Cookie management
|
|
16
14
|
- 🔍 Debuggable logger
|
|
17
15
|
|
|
18
|
-
---
|
|
19
|
-
|
|
20
16
|
## 📦 Installation
|
|
21
17
|
|
|
22
18
|
```bash
|
|
@@ -27,16 +23,9 @@ yarn add @wxn0brp/falcon-frame
|
|
|
27
23
|
|
|
28
24
|
```ts
|
|
29
25
|
import FalconFrame from "@wxn0brp/falcon-frame";
|
|
30
|
-
import { createCORSPlugin } from "@wxn0brp/falcon-frame/plugins/cors";
|
|
31
26
|
const app = new FalconFrame();
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
// Initialize CORS plugin
|
|
36
|
-
pluginSystem.register(createCORSPlugin(["http://localhost:3000", "https://example.com"]));
|
|
37
|
-
|
|
38
|
-
// Register plugins
|
|
39
|
-
app.use(pluginSystem.getRouteHandler());
|
|
28
|
+
app.setOrigin("*");
|
|
40
29
|
|
|
41
30
|
const USERS = {
|
|
42
31
|
admin: "hunter2",
|
|
@@ -112,9 +101,8 @@ app.all("*", (req, res) => {
|
|
|
112
101
|
res.status(404).json({ error: "Not found" });
|
|
113
102
|
});
|
|
114
103
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
});
|
|
104
|
+
// Start the server (env.PORT || 3000)
|
|
105
|
+
app.l(3000);
|
|
118
106
|
```
|
|
119
107
|
|
|
120
108
|
## 📜 License
|
package/dist/cors.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { RouteHandler } from "./types.js";
|
|
2
|
+
export interface Opts {
|
|
3
|
+
accessControlAllowMethods?: boolean;
|
|
4
|
+
accessControlAllowHeaders?: boolean;
|
|
5
|
+
headers?: Record<string, string>;
|
|
6
|
+
}
|
|
7
|
+
export declare function createCORS(allowedOrigins: string[], opts?: Opts): RouteHandler;
|
package/dist/cors.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
function setHeader(res, opts) {
|
|
2
|
+
if (opts.accessControlAllowMethods)
|
|
3
|
+
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
4
|
+
if (opts.accessControlAllowHeaders)
|
|
5
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
6
|
+
}
|
|
7
|
+
export function createCORS(allowedOrigins, opts = {}) {
|
|
8
|
+
opts = {
|
|
9
|
+
accessControlAllowMethods: true,
|
|
10
|
+
accessControlAllowHeaders: true,
|
|
11
|
+
...opts,
|
|
12
|
+
};
|
|
13
|
+
return (req, res, next) => {
|
|
14
|
+
if (opts.headers) {
|
|
15
|
+
for (const [key, value] of Object.entries(opts.headers)) {
|
|
16
|
+
res.setHeader(key, value);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
if (allowedOrigins.includes("*")) {
|
|
20
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
21
|
+
setHeader(res, opts);
|
|
22
|
+
if (req.method === "OPTIONS") {
|
|
23
|
+
res.statusCode = 204;
|
|
24
|
+
return res.end();
|
|
25
|
+
}
|
|
26
|
+
return next();
|
|
27
|
+
}
|
|
28
|
+
const origin = req.headers.origin;
|
|
29
|
+
if (origin && allowedOrigins.includes(origin)) {
|
|
30
|
+
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
31
|
+
setHeader(res, opts);
|
|
32
|
+
if (req.method === "OPTIONS") {
|
|
33
|
+
res.statusCode = 204;
|
|
34
|
+
return res.end();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
next();
|
|
38
|
+
};
|
|
39
|
+
}
|
package/dist/helpers.d.ts
CHANGED
|
@@ -2,3 +2,4 @@ import { Cookies, RouteHandler, StaticServeOptions } from "./types.js";
|
|
|
2
2
|
export declare function parseCookies(cookieHeader: string): Cookies;
|
|
3
3
|
export declare function getContentType(filePath: string, utf8?: boolean): string;
|
|
4
4
|
export declare function handleStaticFiles(dirPath: string, opts: StaticServeOptions): RouteHandler;
|
|
5
|
+
export declare const ensureLeadingDot: (str: string) => string;
|
package/dist/helpers.js
CHANGED
|
@@ -45,6 +45,15 @@ export function handleStaticFiles(dirPath, opts) {
|
|
|
45
45
|
throw new Error(`Directory ${dirPath} does not exist`);
|
|
46
46
|
}
|
|
47
47
|
const serveFile = (req, res, filePath, stats) => {
|
|
48
|
+
if (opts.render) {
|
|
49
|
+
const defaultRenderer = res.FF.getVar("view engine");
|
|
50
|
+
const renderStatement = (defaultRenderer && filePath.endsWith(ensureLeadingDot(defaultRenderer))) ||
|
|
51
|
+
(!opts.notRenderHtml && filePath.endsWith(".html"));
|
|
52
|
+
if (renderStatement) {
|
|
53
|
+
res.render(filePath, {}, { notUseViews: true });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
48
57
|
if (opts.etag) {
|
|
49
58
|
const etag = `W/"${stats.size}-${stats.mtime.getTime()}"`;
|
|
50
59
|
if (req.headers["if-none-match"] === etag) {
|
|
@@ -53,11 +62,6 @@ export function handleStaticFiles(dirPath, opts) {
|
|
|
53
62
|
}
|
|
54
63
|
res.setHeader("ETag", etag);
|
|
55
64
|
}
|
|
56
|
-
if (opts.render && filePath.endsWith(".html")) {
|
|
57
|
-
res.ct("text/html");
|
|
58
|
-
res.render(filePath);
|
|
59
|
-
return true;
|
|
60
|
-
}
|
|
61
65
|
res.ct(getContentType(filePath, opts.utf8));
|
|
62
66
|
fs.createReadStream(filePath).pipe(res);
|
|
63
67
|
return true;
|
|
@@ -104,3 +108,4 @@ export function handleStaticFiles(dirPath, opts) {
|
|
|
104
108
|
next();
|
|
105
109
|
};
|
|
106
110
|
}
|
|
111
|
+
export const ensureLeadingDot = (str) => (str.startsWith(".") ? str : "." + str);
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { Logger, LoggerOptions } from "@wxn0brp/lucerna-log";
|
|
2
2
|
import http from "http";
|
|
3
|
-
import { PluginSystem } from "./plugin.js";
|
|
4
3
|
import { renderHTML } from "./render.js";
|
|
5
4
|
import { FFResponse } from "./res.js";
|
|
6
5
|
import { Router } from "./router.js";
|
|
7
|
-
import type { BeforeHandleRequest, FFRequest, RouteHandler } from "./types.js";
|
|
8
|
-
export type EngineCallback = (path: string, options: any, callback: (e: any, rendered?: string) => void) => void;
|
|
6
|
+
import type { BeforeHandleRequest, EngineCallback, FFRequest, RouteHandler } from "./types.js";
|
|
9
7
|
export interface Opts {
|
|
10
8
|
loggerOpts?: LoggerOptions;
|
|
11
9
|
bodyLimit?: string;
|
|
@@ -34,8 +32,13 @@ export declare class FalconFrame<Vars extends Record<string, any> = any> extends
|
|
|
34
32
|
* app.setOrigin(["http://example.com", "https://example.com"]);
|
|
35
33
|
*/
|
|
36
34
|
setOrigin(origin?: string[] | string): void;
|
|
35
|
+
/**
|
|
36
|
+
* Listens to the specified port, or the environment variable PORT if available.
|
|
37
|
+
* @param port - The port number to listen to.
|
|
38
|
+
* @returns The server object returned by the listen method.
|
|
39
|
+
*/
|
|
40
|
+
l(port: number): http.Server<typeof http.IncomingMessage, typeof http.ServerResponse>;
|
|
37
41
|
}
|
|
38
42
|
export default FalconFrame;
|
|
39
43
|
export * as Helpers from "./helpers.js";
|
|
40
|
-
export
|
|
41
|
-
export { FFRequest, FFResponse, PluginSystem, renderHTML, RouteHandler, Router };
|
|
44
|
+
export { FFRequest, FFResponse, renderHTML, RouteHandler, Router };
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { Logger } from "@wxn0brp/lucerna-log";
|
|
2
2
|
import http from "http";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { json, urlencoded } from "./body.js";
|
|
4
|
+
import { createCORS } from "./cors.js";
|
|
5
5
|
import { renderHTML } from "./render.js";
|
|
6
6
|
import { handleRequest } from "./req.js";
|
|
7
7
|
import { FFResponse } from "./res.js";
|
|
8
8
|
import { Router } from "./router.js";
|
|
9
|
-
import { json, urlencoded } from "./body.js";
|
|
10
9
|
export class FalconFrame extends Router {
|
|
11
10
|
logger;
|
|
12
11
|
bodyParsers = [];
|
|
@@ -89,10 +88,17 @@ export class FalconFrame extends Router {
|
|
|
89
88
|
* app.setOrigin(["http://example.com", "https://example.com"]);
|
|
90
89
|
*/
|
|
91
90
|
setOrigin(origin = "*") {
|
|
92
|
-
this.use(
|
|
91
|
+
this.use(createCORS(Array.isArray(origin) ? origin : [origin]));
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Listens to the specified port, or the environment variable PORT if available.
|
|
95
|
+
* @param port - The port number to listen to.
|
|
96
|
+
* @returns The server object returned by the listen method.
|
|
97
|
+
*/
|
|
98
|
+
l(port) {
|
|
99
|
+
return this.listen(+process.env.PORT || port, true);
|
|
93
100
|
}
|
|
94
101
|
}
|
|
95
102
|
export default FalconFrame;
|
|
96
103
|
export * as Helpers from "./helpers.js";
|
|
97
|
-
export
|
|
98
|
-
export { FFResponse, PluginSystem, renderHTML, Router };
|
|
104
|
+
export { FFResponse, renderHTML, Router };
|
package/dist/res.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from "http";
|
|
2
2
|
import FalconFrame from "./index.js";
|
|
3
|
-
import { CookieOptions } from "./types.js";
|
|
3
|
+
import { CookieOptions, RenderOptions } from "./types.js";
|
|
4
4
|
export declare class FFResponse extends http.ServerResponse {
|
|
5
5
|
_ended: boolean;
|
|
6
6
|
FF: FalconFrame;
|
|
@@ -62,7 +62,7 @@ export declare class FFResponse extends http.ServerResponse {
|
|
|
62
62
|
* @param data An object containing data to be injected into the template.
|
|
63
63
|
* @returns The response object.
|
|
64
64
|
*/
|
|
65
|
-
render(view: string, data?: any): this;
|
|
65
|
+
render(view: string, data?: any, opts?: RenderOptions): this;
|
|
66
66
|
/**
|
|
67
67
|
* Initialize SSE headers to start a server-sent event stream.
|
|
68
68
|
* Sets:
|
package/dist/res.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createReadStream } from "fs";
|
|
2
2
|
import http from "http";
|
|
3
3
|
import path from "path";
|
|
4
|
-
import { getContentType } from "./helpers.js";
|
|
4
|
+
import { ensureLeadingDot, getContentType } from "./helpers.js";
|
|
5
5
|
export class FFResponse extends http.ServerResponse {
|
|
6
6
|
_ended = false;
|
|
7
7
|
FF;
|
|
@@ -104,20 +104,24 @@ export class FFResponse extends http.ServerResponse {
|
|
|
104
104
|
* @param data An object containing data to be injected into the template.
|
|
105
105
|
* @returns The response object.
|
|
106
106
|
*/
|
|
107
|
-
render(view, data = {}) {
|
|
107
|
+
render(view, data = {}, opts = {}) {
|
|
108
108
|
const ff = this.FF;
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
let
|
|
109
|
+
const viewsDir = opts.baseDir ??
|
|
110
|
+
(opts.notUseViews ? "." : (ff.getVar("views") || "."));
|
|
111
|
+
let engineName = path.extname(view);
|
|
112
112
|
let filePath = view;
|
|
113
|
-
if (
|
|
114
|
-
|
|
115
|
-
finalExt = defaultExt;
|
|
116
|
-
filePath += finalExt;
|
|
113
|
+
if (opts.engine) {
|
|
114
|
+
engineName = ensureLeadingDot(opts.engine);
|
|
117
115
|
}
|
|
118
|
-
|
|
116
|
+
else if (!engineName) {
|
|
117
|
+
const defaultEngine = ff.getVar("view engine");
|
|
118
|
+
engineName = defaultEngine ? ensureLeadingDot(defaultEngine) : ".html";
|
|
119
|
+
if (!opts.notAppendExt)
|
|
120
|
+
filePath += engineName;
|
|
121
|
+
}
|
|
122
|
+
const engine = ff.engines[engineName];
|
|
119
123
|
if (!engine) {
|
|
120
|
-
const errMessage = `No engine registered for extension ${
|
|
124
|
+
const errMessage = `No engine registered for extension ${engineName}`;
|
|
121
125
|
ff.logger.error(errMessage);
|
|
122
126
|
this.status(500).end("Server Error: " + errMessage);
|
|
123
127
|
return this;
|
|
@@ -130,7 +134,7 @@ export class FFResponse extends http.ServerResponse {
|
|
|
130
134
|
this.status(500).end("Server Error: Failed to render view.");
|
|
131
135
|
}
|
|
132
136
|
else {
|
|
133
|
-
this.
|
|
137
|
+
this.ct(opts.contentType || "text/html");
|
|
134
138
|
this.end(str);
|
|
135
139
|
}
|
|
136
140
|
});
|
package/dist/router.d.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { PluginSystem } from "./plugin.js";
|
|
2
1
|
import { SSEManager } from "./sse.js";
|
|
3
2
|
import { Method, Middleware, RouteHandler, StaticServeOptions } from "./types.js";
|
|
4
|
-
export type MiddlewareFn = RouteHandler | Router |
|
|
3
|
+
export type MiddlewareFn = RouteHandler | Router | {
|
|
4
|
+
getRouteHandler(): RouteHandler;
|
|
5
|
+
};
|
|
5
6
|
export declare class Router {
|
|
6
7
|
middlewares: Middleware[];
|
|
7
8
|
addRoute(method: Method, path: string, ...handlers: RouteHandler[]): number;
|
package/dist/router.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { handleStaticFiles } from "./helpers.js";
|
|
2
|
-
import { PluginSystem } from "./plugin.js";
|
|
3
2
|
import { SSEManager } from "./sse.js";
|
|
4
3
|
export class Router {
|
|
5
4
|
middlewares = [];
|
|
@@ -9,7 +8,7 @@ export class Router {
|
|
|
9
8
|
return this.middlewares.push({ path, middleware: handler, method });
|
|
10
9
|
}
|
|
11
10
|
use(path = "/", middlewareFn, method = "all") {
|
|
12
|
-
if (typeof path
|
|
11
|
+
if (typeof path !== "string") {
|
|
13
12
|
middlewareFn = path;
|
|
14
13
|
path = "/";
|
|
15
14
|
}
|
|
@@ -22,7 +21,7 @@ export class Router {
|
|
|
22
21
|
if (middlewareFn instanceof Router) {
|
|
23
22
|
middleware.router = middlewareFn.middlewares;
|
|
24
23
|
}
|
|
25
|
-
else if (
|
|
24
|
+
else if ("getRouteHandler" in middlewareFn) {
|
|
26
25
|
middleware.middleware = middlewareFn.getRouteHandler();
|
|
27
26
|
}
|
|
28
27
|
else {
|
package/dist/types.d.ts
CHANGED
|
@@ -59,4 +59,13 @@ export interface StaticServeOptions {
|
|
|
59
59
|
render?: boolean;
|
|
60
60
|
etag?: boolean;
|
|
61
61
|
errorIfDirNotFound?: boolean;
|
|
62
|
+
notRenderHtml?: boolean;
|
|
63
|
+
}
|
|
64
|
+
export type EngineCallback = (path: string, options: any, callback: (e: any, rendered?: string) => void) => void;
|
|
65
|
+
export interface RenderOptions {
|
|
66
|
+
notUseViews?: boolean;
|
|
67
|
+
contentType?: string;
|
|
68
|
+
baseDir?: string;
|
|
69
|
+
engine?: string;
|
|
70
|
+
notAppendExt?: boolean;
|
|
62
71
|
}
|
package/package.json
CHANGED
package/dist/plugin.d.ts
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import { RouteHandler } from "./types.js";
|
|
2
|
-
export type PluginId = string;
|
|
3
|
-
export interface Plugin {
|
|
4
|
-
id: PluginId;
|
|
5
|
-
process: RouteHandler;
|
|
6
|
-
before?: PluginId | PluginId[];
|
|
7
|
-
after?: PluginId | PluginId[];
|
|
8
|
-
}
|
|
9
|
-
export interface PluginOptions {
|
|
10
|
-
before?: PluginId | PluginId[];
|
|
11
|
-
after?: PluginId | PluginId[];
|
|
12
|
-
optional?: boolean;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* @deprecated
|
|
16
|
-
* The `PluginSystem` class is deprecated and should not be used in new projects.
|
|
17
|
-
*
|
|
18
|
-
* ⚠️ Notes:
|
|
19
|
-
* - It may be refactored in the future, but any refactor will introduce breaking changes.
|
|
20
|
-
* - If your system currently relies on it and it works, you may continue using it.
|
|
21
|
-
* - If it does not work in your setup, it is recommended to skip it or wait for an update.
|
|
22
|
-
*/
|
|
23
|
-
export declare class PluginSystem {
|
|
24
|
-
private plugins;
|
|
25
|
-
private executionOrder;
|
|
26
|
-
/**
|
|
27
|
-
* Registers a new plugin in the system
|
|
28
|
-
* @param plugin - Plugin to register
|
|
29
|
-
* @param options - Options for positioning plugins
|
|
30
|
-
*/
|
|
31
|
-
register(plugin: Plugin, options?: PluginOptions): void;
|
|
32
|
-
/**
|
|
33
|
-
* Updates the execution order of plugins
|
|
34
|
-
* @param pluginId - ID of the plugin to position
|
|
35
|
-
* @param options - Options for positioning
|
|
36
|
-
*/
|
|
37
|
-
private updateExecutionOrder;
|
|
38
|
-
/**
|
|
39
|
-
* Returns a RouteHandler that executes all registered plugins in the correct order
|
|
40
|
-
*/
|
|
41
|
-
getRouteHandler(): RouteHandler;
|
|
42
|
-
/**
|
|
43
|
-
* Recursively executes plugins in the correct order
|
|
44
|
-
* @param req - Request object
|
|
45
|
-
* @param res - Response object
|
|
46
|
-
* @param next - Next function
|
|
47
|
-
* @param index - Current index in the execution order
|
|
48
|
-
*/
|
|
49
|
-
private executePlugins;
|
|
50
|
-
/**
|
|
51
|
-
* Returns the list of plugins in the execution order
|
|
52
|
-
*/
|
|
53
|
-
getPluginsInOrder(): Plugin[];
|
|
54
|
-
}
|
package/dist/plugin.js
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @deprecated
|
|
3
|
-
* The `PluginSystem` class is deprecated and should not be used in new projects.
|
|
4
|
-
*
|
|
5
|
-
* ⚠️ Notes:
|
|
6
|
-
* - It may be refactored in the future, but any refactor will introduce breaking changes.
|
|
7
|
-
* - If your system currently relies on it and it works, you may continue using it.
|
|
8
|
-
* - If it does not work in your setup, it is recommended to skip it or wait for an update.
|
|
9
|
-
*/
|
|
10
|
-
export class PluginSystem {
|
|
11
|
-
plugins = [];
|
|
12
|
-
executionOrder = [];
|
|
13
|
-
/**
|
|
14
|
-
* Registers a new plugin in the system
|
|
15
|
-
* @param plugin - Plugin to register
|
|
16
|
-
* @param options - Options for positioning plugins
|
|
17
|
-
*/
|
|
18
|
-
register(plugin, options) {
|
|
19
|
-
if (this.plugins.some((p) => p.id === plugin.id)) {
|
|
20
|
-
throw new Error(`Plugin with id '${plugin.id}' already registered`);
|
|
21
|
-
}
|
|
22
|
-
this.plugins.push(plugin);
|
|
23
|
-
this.updateExecutionOrder(plugin, options);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Updates the execution order of plugins
|
|
27
|
-
* @param pluginId - ID of the plugin to position
|
|
28
|
-
* @param options - Options for positioning
|
|
29
|
-
*/
|
|
30
|
-
updateExecutionOrder(plugin, options) {
|
|
31
|
-
const pluginId = plugin.id;
|
|
32
|
-
if (this.executionOrder.includes(pluginId))
|
|
33
|
-
return;
|
|
34
|
-
const resolveTarget = (target) => {
|
|
35
|
-
if (!target)
|
|
36
|
-
return null;
|
|
37
|
-
const list = Array.isArray(target) ? target : [target];
|
|
38
|
-
return list.find((id) => this.executionOrder.includes(id)) || null;
|
|
39
|
-
};
|
|
40
|
-
const beforeTarget = resolveTarget(options?.before || plugin.before);
|
|
41
|
-
const afterTarget = resolveTarget(options?.after || plugin.after);
|
|
42
|
-
if (beforeTarget) {
|
|
43
|
-
const index = this.executionOrder.indexOf(beforeTarget);
|
|
44
|
-
this.executionOrder.splice(index, 0, pluginId);
|
|
45
|
-
}
|
|
46
|
-
else if (afterTarget) {
|
|
47
|
-
const index = this.executionOrder.indexOf(afterTarget);
|
|
48
|
-
this.executionOrder.splice(index + 1, 0, pluginId);
|
|
49
|
-
}
|
|
50
|
-
else if (options?.before || options?.after) {
|
|
51
|
-
if (options.optional) {
|
|
52
|
-
this.executionOrder.push(pluginId); // fallback: add to the end
|
|
53
|
-
}
|
|
54
|
-
else {
|
|
55
|
-
throw new Error(`Plugin dependency not found for '${pluginId}': ` +
|
|
56
|
-
`before=${JSON.stringify(options?.before)}, ` +
|
|
57
|
-
`after=${JSON.stringify(options?.after)}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
else {
|
|
61
|
-
this.executionOrder.push(pluginId);
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
/**
|
|
65
|
-
* Returns a RouteHandler that executes all registered plugins in the correct order
|
|
66
|
-
*/
|
|
67
|
-
getRouteHandler() {
|
|
68
|
-
return (req, res, next) => {
|
|
69
|
-
this.executePlugins(req, res, next, 0);
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Recursively executes plugins in the correct order
|
|
74
|
-
* @param req - Request object
|
|
75
|
-
* @param res - Response object
|
|
76
|
-
* @param next - Next function
|
|
77
|
-
* @param index - Current index in the execution order
|
|
78
|
-
*/
|
|
79
|
-
executePlugins(req, res, next, index) {
|
|
80
|
-
if (index >= this.executionOrder.length) {
|
|
81
|
-
next();
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
const pluginId = this.executionOrder[index];
|
|
85
|
-
const plugin = this.plugins.find((p) => p.id === pluginId);
|
|
86
|
-
if (!plugin) {
|
|
87
|
-
throw new Error(`Plugin '${pluginId}' not found`);
|
|
88
|
-
}
|
|
89
|
-
plugin.process(req, res, () => {
|
|
90
|
-
this.executePlugins(req, res, next, index + 1);
|
|
91
|
-
});
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Returns the list of plugins in the execution order
|
|
95
|
-
*/
|
|
96
|
-
getPluginsInOrder() {
|
|
97
|
-
return this.executionOrder
|
|
98
|
-
.map((id) => this.plugins.find((p) => p.id === id))
|
|
99
|
-
.filter((p) => p !== undefined);
|
|
100
|
-
}
|
|
101
|
-
}
|
package/dist/plugins/cors.d.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { Plugin } from "../plugin.js";
|
|
2
|
-
interface Opts {
|
|
3
|
-
accessControlAllowMethods?: boolean;
|
|
4
|
-
accessControlAllowHeaders?: boolean;
|
|
5
|
-
headers?: Record<string, string>;
|
|
6
|
-
}
|
|
7
|
-
export declare function createCORSPlugin(allowedOrigins: string[], opts?: Opts): Plugin;
|
|
8
|
-
export {};
|
package/dist/plugins/cors.js
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
function setHeader(res, opts) {
|
|
2
|
-
if (opts.accessControlAllowMethods)
|
|
3
|
-
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS");
|
|
4
|
-
if (opts.accessControlAllowHeaders)
|
|
5
|
-
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
|
|
6
|
-
}
|
|
7
|
-
export function createCORSPlugin(allowedOrigins, opts = {}) {
|
|
8
|
-
opts = {
|
|
9
|
-
accessControlAllowMethods: true,
|
|
10
|
-
accessControlAllowHeaders: true,
|
|
11
|
-
...opts,
|
|
12
|
-
};
|
|
13
|
-
return {
|
|
14
|
-
id: "cors",
|
|
15
|
-
process: (req, res, next) => {
|
|
16
|
-
if (opts.headers) {
|
|
17
|
-
for (const [key, value] of Object.entries(opts.headers)) {
|
|
18
|
-
res.setHeader(key, value);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
if (allowedOrigins.includes("*")) {
|
|
22
|
-
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
23
|
-
setHeader(res, opts);
|
|
24
|
-
if (req.method === "OPTIONS") {
|
|
25
|
-
res.statusCode = 204;
|
|
26
|
-
return res.end();
|
|
27
|
-
}
|
|
28
|
-
return next();
|
|
29
|
-
}
|
|
30
|
-
const origin = req.headers.origin;
|
|
31
|
-
if (origin && allowedOrigins.includes(origin)) {
|
|
32
|
-
res.setHeader("Access-Control-Allow-Origin", origin);
|
|
33
|
-
setHeader(res, opts);
|
|
34
|
-
if (req.method === "OPTIONS") {
|
|
35
|
-
res.statusCode = 204;
|
|
36
|
-
return res.end();
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
next();
|
|
40
|
-
},
|
|
41
|
-
};
|
|
42
|
-
}
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
export function createRateLimiterPlugin(maxRequests, windowMs) {
|
|
2
|
-
const rateLimitMap = new Map();
|
|
3
|
-
return {
|
|
4
|
-
id: "rateLimiter",
|
|
5
|
-
process: (req, res, next) => {
|
|
6
|
-
const ip = req.socket.remoteAddress ?? "unknown";
|
|
7
|
-
const now = Date.now();
|
|
8
|
-
const record = rateLimitMap.get(ip);
|
|
9
|
-
if (!record || now - record.lastRequest > windowMs) {
|
|
10
|
-
rateLimitMap.set(ip, { count: 1, lastRequest: now });
|
|
11
|
-
return next();
|
|
12
|
-
}
|
|
13
|
-
if (record.count >= maxRequests) {
|
|
14
|
-
res.statusCode = 429;
|
|
15
|
-
return res.end("Too Many Requests");
|
|
16
|
-
}
|
|
17
|
-
record.count++;
|
|
18
|
-
record.lastRequest = now;
|
|
19
|
-
rateLimitMap.set(ip, record);
|
|
20
|
-
next();
|
|
21
|
-
},
|
|
22
|
-
};
|
|
23
|
-
}
|
package/dist/plugins/security.js
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export function createSecurityPlugin() {
|
|
2
|
-
return {
|
|
3
|
-
id: "security",
|
|
4
|
-
process: (req, res, next) => {
|
|
5
|
-
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
6
|
-
res.setHeader("X-Frame-Options", "SAMEORIGIN");
|
|
7
|
-
res.setHeader("Referrer-Policy", "no-referrer");
|
|
8
|
-
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
9
|
-
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
10
|
-
next();
|
|
11
|
-
},
|
|
12
|
-
after: "cors",
|
|
13
|
-
};
|
|
14
|
-
}
|