fix: update

This commit is contained in:
熊潇 2025-05-18 02:49:45 +08:00
parent eede990ec8
commit e31b19f931
3 changed files with 199 additions and 10 deletions

View File

@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "@kevisual/router",
"version": "0.0.20",
"version": "0.0.21-beta",
"description": "",
"type": "module",
"main": "./dist/router.js",

View File

@ -1,8 +1,12 @@
import { pathToRegexp, Key } from 'path-to-regexp';
import type { IncomingMessage, ServerResponse } from 'node:http';
import type { IncomingMessage, ServerResponse, Server } from 'node:http';
import { parseBody, parseSearch, parseSearchValue } from './server/parse-body.ts';
import { ListenOptions } from 'node:net';
type Req = IncomingMessage & { params?: Record<string, string> };
type SimpleObject = {
[key: string]: any;
};
interface Route {
method: string;
regexp: RegExp;
@ -28,7 +32,6 @@ export class SimpleRouter {
use(method: string, route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
const handlers = Array.isArray(fns) ? fns.flat() : [];
const pattern = pathToRegexp(route);
this.routes.push({ method: method.toLowerCase(), regexp: pattern.regexp, keys: pattern.keys, handlers });
return this;
}
@ -38,11 +41,35 @@ export class SimpleRouter {
post(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
return this.use('post', route, ...fns);
}
sse(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
return this.use('sse', route, ...fns);
}
all(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
this.use('post', route, ...fns);
this.use('get', route, ...fns);
this.use('sse', route, ...fns);
return this;
}
getJson(v: string | number | boolean | SimpleObject) {
if (typeof v === 'object') {
return v;
}
try {
return JSON.parse(v as string);
} catch (e) {
return {};
}
}
isSse(req: Req) {
const { headers } = req;
if (headers['accept'] && headers['accept'].includes('text/event-stream')) {
return true;
}
if (headers['content-type'] && headers['content-type'].includes('text/event-stream')) {
return true;
}
return false;
}
/**
* req res
* @param req
@ -51,10 +78,12 @@ export class SimpleRouter {
*/
parse(req: Req, res: ServerResponse) {
const { pathname } = new URL(req.url, 'http://localhost');
const method = req.method.toLowerCase();
let method = req.method.toLowerCase();
if (this.exclude.includes(pathname)) {
return 'is_exclude';
}
const isSse = this.isSse(req);
if (isSse) method = 'sse';
const route = this.routes.find((route) => {
const matchResult = route.regexp.exec(pathname);
if (matchResult && route.method === method) {
@ -74,4 +103,166 @@ export class SimpleRouter {
return 'not_found';
}
/**
* HttpChain
* @param req
* @param res
* @returns
*/
chain(req?: Req, res?: ServerResponse) {
const chain = new HttpChain({ req, res, simpleRouter: this });
return chain;
}
static Chain(opts?: HttpChainOpts) {
return new HttpChain(opts);
}
}
type HttpChainOpts = {
req?: Req;
res?: ServerResponse;
simpleRouter?: SimpleRouter;
};
export class HttpChain {
req: Req;
res: ServerResponse;
simpleRouter: SimpleRouter;
server: Server;
hasSetHeader: boolean = false;
isSseSet: boolean = false;
constructor(opts?: HttpChainOpts) {
this.req = opts?.req;
this.res = opts?.res;
this.simpleRouter = opts?.simpleRouter;
}
setReq(req: Req) {
this.req = req;
return this;
}
setRes(res: ServerResponse) {
this.res = res;
return this;
}
setRouter(router: SimpleRouter) {
this.simpleRouter = router;
return this;
}
setServer(server: Server) {
this.server = server;
return this;
}
/**
* express
* @param status
* @returns
*/
status(status: number) {
if (!this.res) return this;
if (this.hasSetHeader) {
return this;
}
this.hasSetHeader = true;
this.res.writeHead(status);
return this;
}
writeHead(status: number) {
if (!this.res) return this;
if (this.hasSetHeader) {
return this;
}
this.hasSetHeader = true;
this.res.writeHead(status);
return this;
}
json(data: SimpleObject) {
if (!this.res) return this;
this.res.end(JSON.stringify(data));
return this;
}
/**
* express
* @param data
* @returns
*/
end(data: SimpleObject | string) {
if (!this.res) return this;
if (typeof data === 'object') {
this.res.end(JSON.stringify(data));
} else if (typeof data === 'string') {
this.res.end(data);
} else {
this.res.end('nothing');
}
return this;
}
listen(opts: ListenOptions, callback?: () => void) {
this.server.listen(opts, callback);
return this;
}
parse() {
if (!this.server || !this.simpleRouter) {
throw new Error('Server and SimpleRouter must be set before calling parse');
}
const that = this;
const listener = (req: Req, res: ServerResponse) => {
try {
that.simpleRouter.parse(req, res);
} catch (error) {
console.error('Error parsing request:', error);
if (!res.headersSent) {
res.writeHead(500);
res.end(JSON.stringify({ code: 500, message: 'Internal Server Error' }));
}
}
};
this.server.on('request', listener);
return () => {
that.server.removeListener('request', listener);
};
}
getString(value: string | SimpleObject) {
if (typeof value === 'string') {
return value;
}
return JSON.stringify(value);
}
sse(value: string | SimpleObject) {
const res = this.res;
const req = this.req;
if (!res || !req) return;
const data = this.getString(value);
if (this.isSseSet) {
res.write(`data: ${data}\n\n`);
return this;
}
const headersMap = new Map<string, string>([
['Content-Type', 'text/event-stream'],
['Cache-Control', 'no-cache'],
['Connection', 'keep-alive'],
]);
this.isSseSet = true;
let intervalId: NodeJS.Timeout;
if (!this.hasSetHeader) {
this.hasSetHeader = true;
res.setHeaders(headersMap);
// 每隔 2 秒发送一个空行,保持连接
setInterval(() => {
res.write('\n'); // 发送一个空行,保持连接
}, 3000);
// 客户端断开连接时清理
req.on('close', () => {
clearInterval(intervalId);
res.end();
});
}
this.res.write(`data: ${data}\n\n`);
return this;
}
close() {
if (this.req?.destroy) {
this.req.destroy();
}
return this;
}
}

View File

@ -8,8 +8,8 @@ type BaseRule = {
type RuleString = {
type: 'string';
minLength?: number;
maxLength?: number;
min?: number;
max?: number;
regex?: string;
} & BaseRule;
@ -26,8 +26,6 @@ type RuleBoolean = {
type RuleArray = {
type: 'array';
items: Rule;
minItems?: number;
maxItems?: number;
} & BaseRule;
type RuleObject = {
@ -45,8 +43,8 @@ export const schemaFormRule = (rule: Rule): z.ZodType<any, any, any> => {
switch (rule.type) {
case 'string':
let stringSchema = z.string();
if (rule.minLength) stringSchema = stringSchema.min(rule.minLength, `String must be at least ${rule.minLength} characters long.`);
if (rule.maxLength) stringSchema = stringSchema.max(rule.maxLength, `String must not exceed ${rule.maxLength} characters.`);
if (rule.min) stringSchema = stringSchema.min(rule.min, `String must be at least ${rule.min} characters long.`);
if (rule.max) stringSchema = stringSchema.max(rule.max, `String must not exceed ${rule.max} characters.`);
if (rule.regex) stringSchema = stringSchema.regex(new RegExp(rule.regex), 'Invalid format');
return stringSchema;
case 'number':