diff --git a/.vscode/settings.json b/.vscode/settings.json index a8a3f5d..9dbf756 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,7 +9,8 @@ "editor.minimap.enabled": false, "editor.detectIndentation": false, "editor.codeActionsOnSave": { - "source.organizeImports": true + "source.organizeImports": "explicit", + "source.fixAll": "never" }, "files.exclude": { "**/node_modules/": true, diff --git a/README.md b/README.md index ded6d6f..1f06e88 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -![npm](https://img.shields.io/npm/dw/neste) [![Teste](https://github.com/Sirherobrine23/neste/actions/workflows/test.yml/badge.svg)](https://github.com/Sirherobrine23/neste/actions/workflows/test.yml) +![npm](https://img.shields.io/npm/dw/neste) [![Teste](https://sirherobrine23.org/utils/neste/actions/workflows/test.yml/badge.svg)](https://sirherobrine23.org/utils/neste/actions/workflows/test.yml) A fork of [express](https://github.com/expressjs/express) with patches and improvements for new integrations, fixes and more. diff --git a/package.json b/package.json index 34fb847..ed44b64 100644 --- a/package.json +++ b/package.json @@ -9,10 +9,10 @@ "types": "./src/index.d.ts", "repository": { "type": "git", - "url": "git+https://github.com/Sirherobrine23/neste.git" + "url": "git+https://sirherobrine23.org/utils/neste.git" }, "bugs": { - "url": "https://github.com/Sirherobrine23/neste/issues" + "url": "https://sirherobrine23.org/utils/neste/issues" }, "keywords": [ "express", @@ -33,18 +33,21 @@ "postpack": "tsc --build --clean" }, "devDependencies": { - "@types/busboy": "^1.5.2", + "@types/busboy": "^1.5.3", "@types/cookie": "^0.6.0", - "@types/node": "^20.8.9", - "@types/ws": "^8.5.8", - "ts-node": "^10.9.1", - "typescript": "^5.2.2" + "@types/node": "^20.10.4", + "@types/ws": "^8.5.10", + "ts-node": "^10.9.2", + "typescript": "^5.3.3" }, "dependencies": { "busboy": "^1.6.0", "cookie": "^0.6.0", "path-to-regexp": "^6.2.1", - "ws": "^8.14.2", - "yaml": "^2.3.3" + "ws": "^8.15.1", + "yaml": "^2.3.4" + }, + "optionalDependencies": { + "next": "*" } } diff --git a/src/application.ts b/src/application.ts index c43a550..ac0a83b 100644 --- a/src/application.ts +++ b/src/application.ts @@ -7,7 +7,13 @@ import { WebSocket, WebSocketServer } from "ws"; import { ErrorRequestHandler, Handlers, Layer, NextFunction, RequestHandler, WsRequestHandler, WsResponse, assignRequest, assignResponse, assignWsResponse } from "./handler.js"; import { Methods, methods } from "./util.js"; -export type RouterSettingsConfig = Record; +export type RouterSettingsConfig = Partial> & Record & { + "path resolve"?: boolean; + "json replacer"?: (key: string, value: any) => any; + "json escape"?: boolean; + "json space"?: string|number; +}; + export class RouterSettings extends Map { constructor(sets?: RouterSettingsConfig) { super(); @@ -71,6 +77,52 @@ export class Router extends Function { settings: RouterSettings; wsRooms: WsRoom = new WsRoom(); + use(...fn: RequestHandler[]): this; + use(path: string|RegExp, ...fn: RequestHandler[]): this; + use() { + let p: [string|RegExp, Handlers[]]; + if (!(arguments[0] instanceof RegExp || typeof arguments[0] === "string" && arguments[0].trim())) p = ["(.*)", Array.from(arguments)]; + else p = [arguments[0], Array.from(arguments).slice(1)]; + for (const fn of p[1]) { + if (typeof fn !== "function") throw new Error(util.format("Invalid middleare, require function, recived %s", typeof fn)); + this.layers.push(new Layer(p[0], fn, { isRoute: true, strict: false, end: false })); + } + return this; + } + + useError(...fn: ErrorRequestHandler[]): this; + useError(path: string|RegExp, ...fn: ErrorRequestHandler[]): this; + useError() { + this.use(...arguments); + return this; + } + + all(path: string|RegExp, ...handlers: RequestHandler[]) { + if (!(path instanceof RegExp || typeof path === "string" && path.trim())) throw new Error("Set path"); + for (const fn of handlers) { + const layerHand = new Layer(path, fn); + this.layers.push(layerHand); + } + return this; + } + + /** + * Generic set router with request methods + * + * @param method - Method - GET, POST, PUT, etc. + * @param path - Request path + * @param handlers - Callbacks + */ + __method(method: Methods, path: string|RegExp, ...handlers: RequestHandler[]) { + if (!(path instanceof RegExp || typeof path === "string" && path.trim())) throw new Error("Set path"); + for (const fn of handlers) { + const layerHand = new Layer(path, fn); + layerHand.method = method; + this.layers.push(layerHand); + } + return this; + } + handler(req: IncomingMessage, res: WebSocket|ServerResponse, next?: NextFunction) { if (typeof next !== "function") next = (err?: any) => { if (err && !(err === "router" || err === "route") && this.settings.get("env") !== "production") console.error(err); @@ -89,10 +141,9 @@ export class Router extends Function { } const { layers } = this, method = (res instanceof WebSocket ? "ws" : (String(req.method||"").toLowerCase())), saveParms = Object.freeze(req["params"] || {}); - let originalPath: string = req["path"]||(parse(req.url)).pathname; + let originalPath: string = req["path"]||(parse(req.url)).pathname, layersIndex = 0; if (this.settings.get("path resolve")) originalPath = path.posix.resolve("/", originalPath); if (this.settings.has("app path") && typeof this.settings.get("app path") === "string" && originalPath.startsWith(this.settings.get("app path"))) originalPath = path.posix.resolve("/", originalPath.slice(path.posix.resolve("/", this.settings.get("app path")).length)); - let layersIndex = 0; const nextHandler = async (err?: any) => { req["path"] = originalPath; @@ -106,18 +157,26 @@ export class Router extends Function { if (!layerMatch) return nextHandler(err); req["path"] = layerMatch.path; if (err && layer.handler.length !== 4) return nextHandler(err); + const reqMod = assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)); + if (!req["__set_cookie"]) { + req["__set_cookie"] = true; + Object.defineProperty(req.headers, "set-cookie", { + set(v) { reqMod.Cookies.fromString(v); }, + get() { return reqMod.Cookies.toString(); }, + }); + } try { if (err) { if (res instanceof WebSocket) return nextHandler(err); const fn = layer.handler as ErrorRequestHandler; - await fn(err, assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)), assignResponse(this, res), nextHandler); + await fn(err, reqMod, assignResponse(this, res), nextHandler); } else { if (res instanceof WebSocket) { const fn = layer.handler as WsRequestHandler; - await fn(assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)), assignWsResponse(this, res), nextHandler); + await fn(reqMod, assignWsResponse(this, res), nextHandler); } else { const fn = layer.handler as RequestHandler; - await fn(assignRequest(this, req, method, Object.assign({}, saveParms, layerMatch.params)), assignResponse(this, res), nextHandler); + await fn(reqMod, assignResponse(this, res), nextHandler); } } } catch (err) { @@ -126,39 +185,6 @@ export class Router extends Function { } nextHandler().catch(next); } - - - use(...fn: RequestHandler[]): this; - use(path: string|RegExp, ...fn: RequestHandler[]): this; - use() { - let p: [string|RegExp, Handlers[]]; - if (!(arguments[0] instanceof RegExp || typeof arguments[0] === "string" && arguments[0].trim())) p = ["(.*)", Array.from(arguments)]; - else p = [arguments[0], Array.from(arguments).slice(1)]; - for (const fn of p[1]) { - if (typeof fn !== "function") throw new Error(util.format("Invalid middleare, require function, recived %s", typeof fn)); - this.layers.push(new Layer(p[0], fn, { isRoute: true, strict: false, end: false })); - } - return this; - } - - all(path: string|RegExp, ...handlers: RequestHandler[]) { - if (!(path instanceof RegExp || typeof path === "string" && path.trim())) throw new Error("Set path"); - for (const fn of handlers) { - const layerHand = new Layer(path, fn); - this.layers.push(layerHand); - } - return this; - } - - __method(method: Methods, path: string|RegExp, ...handlers: RequestHandler[]) { - if (!(path instanceof RegExp || typeof path === "string" && path.trim())) throw new Error("Set path"); - for (const fn of handlers) { - const layerHand = new Layer(path, fn); - layerHand.method = method; - this.layers.push(layerHand); - } - return this; - } }; methods.forEach(method => Router.prototype[method] = function(this: Router) { return this.__method.apply(this, ([method] as any[]).concat(Array.from(arguments))) } as any) diff --git a/src/handler.ts b/src/handler.ts index eeb8162..17dea31 100644 --- a/src/handler.ts +++ b/src/handler.ts @@ -1,22 +1,21 @@ -import { MatchFunction, ParseOptions, RegexpToFunctionOptions, TokensToRegexpOptions, match as regexMatch } from "path-to-regexp"; -import { IncomingMessage, ServerResponse } from "http"; -import { WebSocket } from "ws"; import cookie from "cookie"; -import { parse } from "url"; +import { IncomingMessage, ServerResponse, STATUS_CODES } from "http"; import { isIP } from "net"; -import { defineProperties, mixin } from "./util.js"; +import { posix } from "path"; +import { MatchFunction, ParseOptions, RegexpToFunctionOptions, TokensToRegexpOptions, match as regexMatch } from "path-to-regexp"; import stream from "stream"; +import { parse } from "url"; +import { WebSocket } from "ws"; import yaml from "yaml"; -import * as ranger from "./ranger.js"; import { Router } from "./application.js"; -import { posix } from "path"; +import * as ranger from "./ranger.js"; +import { defineProperties, mixin } from "./util.js"; export class CookieManeger extends Map { constructor(public initialCookies: string) { super(); if (!initialCookies) return; - const parsed = cookie.parse(initialCookies); - Object.keys(parsed).forEach(k => this.set(k, parsed[k])); + this.fromString(initialCookies); } // @ts-ignore @@ -30,6 +29,12 @@ export class CookieManeger extends Map parsed[key] && parsed[key] !== value).map(([key, [value, opt]]) => cookie.serialize(key, value, opt)).join("; "); } + fromString(cookieHead: string): this { + const parsed = cookie.parse(cookieHead); + Object.keys(parsed).forEach(k => this.set(k, parsed[k])); + return this; + } + get sizeDiff() { const parsed = cookie.parse(this.initialCookies); return Array.from(this.entries()).filter(([key, [value]]) => parsed[key] && parsed[key] !== value).length; @@ -42,6 +47,7 @@ export class CookieManeger extends Map { req: Request; diff --git a/src/index.ts b/src/index.ts index f1cc766..66d8fd1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,8 @@ import { Neste, Router, RouterSettings, WsRoom } from "./application.js"; export { staticFile } from "./middles/staticFile.js"; export { parseBody } from "./middles/bodyParse.js"; +export { nextjsPages } from "./middles/nextjs.js"; + export type { FileStorage, LocalFile, ParseBodyOptions } from "./middles/bodyParse.js"; export type { Handlers, ErrorRequestHandler, RequestHandler, WsRequestHandler, NextFunction } from "./handler.js"; @@ -10,5 +12,6 @@ export type { RouterSettingsConfig } from "./application.js"; function router() { return new Router(); } function neste() { return new Neste(); } + export { neste, router, CookieManeger, Layer, Request, Response, WsResponse, Neste, Router, RouterSettings, WsRoom }; export default neste; \ No newline at end of file diff --git a/src/middles/nextjs.ts b/src/middles/nextjs.ts new file mode 100644 index 0000000..a20527b --- /dev/null +++ b/src/middles/nextjs.ts @@ -0,0 +1,40 @@ +import type { NextServer, NextServerOptions } from "next/dist/server/next.js"; +import type { RequestHandler } from "../handler.js"; +import { defineProperties } from "../util.js"; + +/** + * Create config to Nextjs pages + * + * in config set full pages path `dir`, and current middle path + * + * @example + * { + * dir: "/root/nextjs_example", + * conf: { + * basePath: "/foo/bar/page_next" + * } + * } + * + * @param config - Next config + * @returns + */ +export async function nextjsPages(config: Omit): Promise { + const app = ((await import("next")).default as any as (options: NextServerOptions) => NextServer)({ + ...config, + customServer: true, + dir: "/root/nextjs_example", + conf: { + basePath: "/page_next" + } + }); + await app.prepare(); + const handler = await app.getRequestHandler(); + return (req, res) => handler(defineProperties(req, { + "url": { + writable: true, + configurable: true, + enumerable: true, + value: String().concat(req.path, ...(Object.keys(req.query).length > 0 ? [ "?", Object.keys(req.query).map(k => String().concat(k, "=", req.query[k])).join("&") ] : [])) + } + }), res); +} \ No newline at end of file diff --git a/src/util.ts b/src/util.ts index d0a4717..cab3837 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,4 +1,4 @@ -const __methods = [ "ws", "get", "post", "put", "delete", "head", "connect", "options", "trace" ] as const; +const __methods = [ "ws", "acl", "bind", "checkout", "connect", "copy", "delete", "get", "head", "link", "lock", "m-search", "merge", "mkactivity", "mkcalendar", "mkcol", "move", "notify", "options", "patch", "post", "propfind", "proppatch", "purge", "put", "rebind", "report", "search", "source", "subscribe", "trace", "unbind", "unlink", "unlock", "unsubscribe" ] as const; export type Methods = typeof __methods[number]; export const methods: Methods[] = Object.freeze(__methods) as any;