@wxn0brp/falcon-frame 0.9.2 → 0.9.4

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/dist/body.d.ts CHANGED
@@ -4,5 +4,4 @@ export declare const urlencoded: ParseBodyFunction;
4
4
  export declare const yaml: ParseBodyFunction;
5
5
  export declare const toml: ParseBodyFunction;
6
6
  export declare const json5: ParseBodyFunction;
7
- export declare const xml: ParseBodyFunction;
8
7
  export declare const text: ParseBodyFunction;
package/dist/body.js CHANGED
@@ -4,7 +4,6 @@ export const urlencoded = body => querystring.parse(body);
4
4
  export const yaml = body => Bun.YAML.parse(body);
5
5
  export const toml = body => Bun.TOML.parse(body);
6
6
  export const json5 = body => Bun.JSON5.parse(body);
7
- export const xml = body => new DOMParser().parseFromString(body, "text/xml");
8
7
  export const text = body => ({
9
8
  data: body,
10
9
  });
package/dist/debug.js CHANGED
@@ -13,7 +13,8 @@ export function createDebug(opts = {}) {
13
13
  };
14
14
  const pass = process.env.FF_DEBUG_PASS;
15
15
  function isHostAllowed(req) {
16
- return hosts.some(h => h === req.socket.remoteAddress);
16
+ const ip = req.ip || req.socket.remoteAddress || "";
17
+ return hosts.some(h => h === ip);
17
18
  }
18
19
  function checkAuth(req) {
19
20
  if (!pass)
@@ -0,0 +1,19 @@
1
+ import { FalconFrame } from "./index.js";
2
+ export interface OfflineRequest {
3
+ method?: string;
4
+ url: string;
5
+ headers?: Record<string, string>;
6
+ body?: any;
7
+ cookies?: Record<string, string>;
8
+ }
9
+ export interface OfflineResponse {
10
+ status: number;
11
+ headers: Record<string, string>;
12
+ body: any;
13
+ }
14
+ export declare class OfflineEngine {
15
+ private ff;
16
+ constructor(ff: FalconFrame<any>);
17
+ handle(request: OfflineRequest): Promise<OfflineResponse>;
18
+ private buildResponse;
19
+ }
package/dist/engine.js ADDED
@@ -0,0 +1,146 @@
1
+ import { getContentType } from "./body-utils.js";
2
+ import { parseCookies } from "./helpers.js";
3
+ import { extractParams, getMiddlewares, matchMiddleware } from "./middleware.js";
4
+ import { validate } from "./valid.js";
5
+ export class OfflineEngine {
6
+ ff;
7
+ constructor(ff) {
8
+ this.ff = ff;
9
+ }
10
+ async handle(request) {
11
+ const urlParts = request.url.split("?");
12
+ const path = decodeURIComponent(urlParts[0]);
13
+ const query = urlParts[1]
14
+ ? Object.fromEntries(new URLSearchParams(urlParts[1]))
15
+ : {};
16
+ request.method ??= "GET";
17
+ const cookies = request.cookies
18
+ ? {
19
+ ...request.cookies,
20
+ ...parseCookies(request.cookies.cookie || ""),
21
+ }
22
+ : parseCookies(request.headers?.cookie || "");
23
+ const res = {
24
+ statusCode: 200,
25
+ headers: {},
26
+ body: "",
27
+ _ended: false,
28
+ FF: this.ff,
29
+ json: (data) => {
30
+ res.headers["content-type"] = "application/json";
31
+ res.body = data;
32
+ res._ended = true;
33
+ },
34
+ send: (data) => {
35
+ res.body = data;
36
+ res._ended = true;
37
+ },
38
+ end: (data) => {
39
+ res.body = data;
40
+ res._ended = true;
41
+ },
42
+ status: (code) => {
43
+ res.statusCode = code;
44
+ return res;
45
+ },
46
+ setHeader: (name, value) => {
47
+ res.headers[name.toLowerCase()] = value;
48
+ },
49
+ ct: (type) => {
50
+ res.headers["content-type"] = type;
51
+ return res;
52
+ },
53
+ cookie: (name, value, opts = {}) => {
54
+ let c = `${name}=${encodeURIComponent(value)}`;
55
+ if (opts.maxAge !== undefined)
56
+ c += `; Max-Age=${opts.maxAge}`;
57
+ if (opts.path)
58
+ c += `; Path=${opts.path}`;
59
+ if (opts.httpOnly)
60
+ c += `; HttpOnly`;
61
+ if (opts.secure)
62
+ c += `; Secure`;
63
+ const existing = res.headers["set-cookie"];
64
+ res.headers["set-cookie"] = existing ? `${existing}, ${c}` : c;
65
+ },
66
+ };
67
+ const req = {
68
+ path,
69
+ query,
70
+ params: {},
71
+ body: {},
72
+ cookies,
73
+ headers: request.headers || {},
74
+ isBodyParsed: false,
75
+ FF: this.ff,
76
+ header: (name) => request.headers?.[name.toLowerCase()] || "",
77
+ valid: (schema, regexRules) => validate({
78
+ schema,
79
+ data: req.body,
80
+ regexRules,
81
+ isBodyParsed: req.isBodyParsed,
82
+ }),
83
+ };
84
+ if (request.body !== undefined && request.body !== null) {
85
+ const ct = getContentType(req) || "application/json";
86
+ const entry = this.ff.bodyParsers[ct];
87
+ if (entry) {
88
+ const raw = typeof request.body === "string"
89
+ ? request.body
90
+ : JSON.stringify(request.body);
91
+ try {
92
+ req.body = (await entry.parse(raw, req, res)) ?? {};
93
+ req.isBodyParsed = true;
94
+ }
95
+ catch {
96
+ req.body = {};
97
+ }
98
+ }
99
+ else {
100
+ req.body = request.body;
101
+ req.isBodyParsed = true;
102
+ }
103
+ }
104
+ const middlewares = getMiddlewares(this.ff.middlewares, path + "/");
105
+ const matched = matchMiddleware(path, middlewares.filter(m => m.method === request.method.toLowerCase() || m.method === "all"));
106
+ if (matched.length === 0) {
107
+ res.statusCode = 404;
108
+ this.ff._404(req, res);
109
+ return this.buildResponse(res);
110
+ }
111
+ let i = 0;
112
+ const next = async () => {
113
+ if (i >= matched.length) {
114
+ res.statusCode = 404;
115
+ this.ff._404(req, res);
116
+ return;
117
+ }
118
+ const mw = matched[i++];
119
+ req.params = extractParams(mw.path, path);
120
+ try {
121
+ const result = await mw.middleware(req, res, next);
122
+ if (result && !res._ended) {
123
+ typeof result === "string" ? res.send(result) : res.json(result);
124
+ }
125
+ }
126
+ catch (err) {
127
+ if (!res._ended) {
128
+ res.statusCode = 500;
129
+ this.ff._500(err, req, res);
130
+ }
131
+ }
132
+ };
133
+ await next();
134
+ return this.buildResponse(res);
135
+ }
136
+ buildResponse(res) {
137
+ const ct = res.headers["content-type"] || "";
138
+ return {
139
+ status: res.statusCode,
140
+ headers: res.headers,
141
+ body: ct.includes("application/json") && res.body
142
+ ? res.body
143
+ : (res.body ?? ""),
144
+ };
145
+ }
146
+ }
package/dist/helpers.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "path";
2
2
  export function parseCookies(cookieHeader) {
3
- const cookies = {};
3
+ const cookies = Object.create(null);
4
4
  cookieHeader.split(";").forEach(cookie => {
5
5
  const [name, ...valueParts] = cookie.split("=");
6
6
  const value = decodeURIComponent(valueParts.join("=").trim());
package/dist/index.d.ts CHANGED
@@ -43,6 +43,7 @@ export declare class FalconFrame<Vars extends Record<string, any> = {}> extends
43
43
  set500(handler: ErrorHandler): void;
44
44
  }
45
45
  export default FalconFrame;
46
+ export { OfflineEngine, OfflineRequest, OfflineResponse } from "./engine.js";
46
47
  export * as Helpers from "./helpers.js";
47
48
  export type { FFOpts as Opts } from "./types.js";
48
49
  export { validateBody } from "./valid.js";
package/dist/index.js CHANGED
@@ -68,10 +68,6 @@ export class FalconFrame extends Router {
68
68
  fn: bodyParser.toml,
69
69
  ct: "application/toml",
70
70
  },
71
- xml: {
72
- fn: bodyParser.xml,
73
- ct: "application/xml",
74
- },
75
71
  text: {
76
72
  fn: bodyParser.text,
77
73
  ct: "text/plain",
@@ -196,6 +192,7 @@ export class FalconFrame extends Router {
196
192
  }
197
193
  }
198
194
  export default FalconFrame;
195
+ export { OfflineEngine } from "./engine.js";
199
196
  export * as Helpers from "./helpers.js";
200
197
  export { validateBody } from "./valid.js";
201
198
  export { FFResponse, renderHTML, Router };
@@ -1,3 +1,4 @@
1
1
  import { Middleware } from "./types.js";
2
2
  export declare function matchMiddleware(url: string, middlewares: Middleware[]): Middleware[];
3
3
  export declare function getMiddlewares(middlewares: Middleware[], matchUrl: string, basePath?: string): Middleware[];
4
+ export declare function extractParams(routePath: string, requestPath: string): Record<string, string>;
@@ -70,3 +70,16 @@ export function getMiddlewares(middlewares, matchUrl, basePath = "") {
70
70
  }
71
71
  return result;
72
72
  }
73
+ export function extractParams(routePath, requestPath) {
74
+ const params = {};
75
+ if (!routePath.includes(":"))
76
+ return params;
77
+ const routeParts = routePath.split("/");
78
+ const pathParts = requestPath.split("/");
79
+ for (let i = 0; i < routeParts.length; i++) {
80
+ if (routeParts[i].startsWith(":")) {
81
+ params[routeParts[i].slice(1)] = pathParts[i];
82
+ }
83
+ }
84
+ return params;
85
+ }
package/dist/req.js CHANGED
@@ -2,7 +2,7 @@ import { randomUUID } from "crypto";
2
2
  import { URL } from "url";
3
3
  import { getContentType, getRawBody } from "./body-utils.js";
4
4
  import { getIP, parseCookies } from "./helpers.js";
5
- import { getMiddlewares, matchMiddleware } from "./middleware.js";
5
+ import { extractParams, getMiddlewares, matchMiddleware } from "./middleware.js";
6
6
  import { FFResponse } from "./res.js";
7
7
  import { validate } from "./valid.js";
8
8
  export function handleRequest(req, res, FF, reRoute = false) {
@@ -77,17 +77,7 @@ export function handleRequest(req, res, FF, reRoute = false) {
77
77
  }
78
78
  const middleware = matchedMiddlewares[middlewareIndex++];
79
79
  logger.debug(`Executing middleware ${middlewareIndex} of ${matchedMiddlewares.length} matched for path [${middleware.path}]`);
80
- if (middleware.path.includes(":")) {
81
- const middlewareParts = middleware.path.split("/");
82
- const reqPathParts = req.path.split("/");
83
- req.params = {};
84
- for (let i = 0; i < middlewareParts.length; i++) {
85
- if (middlewareParts[i].startsWith(":")) {
86
- const paramName = middlewareParts[i].slice(1);
87
- req.params[paramName] = reqPathParts[i];
88
- }
89
- }
90
- }
80
+ req.params = extractParams(middleware.path, req.path);
91
81
  req.middleware = middleware;
92
82
  try {
93
83
  const result = await middleware.middleware(req, res, next);
package/dist/res.d.ts CHANGED
@@ -83,4 +83,11 @@ export declare class FFResponse extends http.ServerResponse {
83
83
  * @returns The response object
84
84
  */
85
85
  sseSend(data: string | object): this;
86
+ /**
87
+ * Sends a Server-Sent Event with an event type to the client.
88
+ * @param event The event type
89
+ * @param data The data to be sent. If an object, it will be JSON.stringified.
90
+ * @returns The response object
91
+ */
92
+ sseEventSend(event: string, data: string | object): this;
86
93
  }
package/dist/res.js CHANGED
@@ -180,4 +180,16 @@ export class FFResponse extends http.ServerResponse {
180
180
  this.write(`data: ${data}\n\n`);
181
181
  return this;
182
182
  }
183
+ /**
184
+ * Sends a Server-Sent Event with an event type to the client.
185
+ * @param event The event type
186
+ * @param data The data to be sent. If an object, it will be JSON.stringified.
187
+ * @returns The response object
188
+ */
189
+ sseEventSend(event, data) {
190
+ if (typeof data === "object")
191
+ data = JSON.stringify(data);
192
+ this.write(`event: ${event}\ndata: ${data}\n\n`);
193
+ return this;
194
+ }
183
195
  }
package/dist/router.d.ts CHANGED
@@ -16,5 +16,5 @@ export declare class Router {
16
16
  static(apiPath: string, dirPath?: string, opts?: StaticServeOptions): this;
17
17
  sse(path: string, ...handlers: RouteHandler[]): SSEManager;
18
18
  customParser(path: string, handler: RouteHandler, method?: Method): this;
19
- router(path: string): Router;
19
+ router(path?: string): Router;
20
20
  }
package/dist/router.js CHANGED
@@ -80,7 +80,7 @@ export class Router {
80
80
  this.middlewares[index - 1].customParser = true;
81
81
  return this;
82
82
  }
83
- router(path) {
83
+ router(path = "/") {
84
84
  const router = new Router();
85
85
  this.use(path, router);
86
86
  return router;
package/dist/sse.d.ts CHANGED
@@ -12,5 +12,7 @@ export declare class SSEManager {
12
12
  }>;
13
13
  sendAll(data: any): void;
14
14
  sendTo(id: string, data: any): void;
15
+ sendEventAll(event: string, data: any): void;
16
+ sendEventTo(id: string, event: string, data: any): void;
15
17
  disconnect(id: string): void;
16
18
  }
package/dist/sse.js CHANGED
@@ -20,21 +20,30 @@ export class SSEManager {
20
20
  return this._clients;
21
21
  }
22
22
  sendAll(data) {
23
- for (const { res } of this._clients.values()) {
23
+ for (const { res } of this._clients.values())
24
24
  res.sseSend(data);
25
- }
26
25
  }
27
26
  sendTo(id, data) {
28
27
  const client = this._clients.get(id);
29
- if (client) {
30
- client.res.sseSend(data);
31
- }
28
+ if (!client)
29
+ return;
30
+ client.res.sseSend(data);
31
+ }
32
+ sendEventAll(event, data) {
33
+ for (const { res } of this._clients.values())
34
+ res.sseEventSend(event, data);
35
+ }
36
+ sendEventTo(id, event, data) {
37
+ const client = this._clients.get(id);
38
+ if (!client)
39
+ return;
40
+ client.res.sseEventSend(event, data);
32
41
  }
33
42
  disconnect(id) {
34
43
  const client = this._clients.get(id);
35
- if (client) {
36
- client.res.end();
37
- this._clients.delete(id);
38
- }
44
+ if (!client)
45
+ return;
46
+ client.res.end();
47
+ this._clients.delete(id);
39
48
  }
40
49
  }
package/dist/types.d.ts CHANGED
@@ -94,7 +94,6 @@ export interface FFOpts {
94
94
  json5?: boolean;
95
95
  yaml?: boolean;
96
96
  toml?: boolean;
97
- xml?: boolean;
98
97
  text?: boolean;
99
98
  };
100
99
  xRequestId?: "auto" | "disable" | "manual";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wxn0brp/falcon-frame",
3
- "version": "0.9.2",
3
+ "version": "0.9.4",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "author": "wxn0brP",
@@ -29,7 +29,7 @@
29
29
  "typescript": "^7"
30
30
  },
31
31
  "dependencies": {
32
- "@wxn0brp/lucerna-log": "^0.2.4"
32
+ "@wxn0brp/lucerna-log": "^0.2.5"
33
33
  },
34
34
  "files": [
35
35
  "dist"