fix: update
This commit is contained in:
parent
eede990ec8
commit
e31b19f931
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"$schema": "https://json.schemastore.org/package",
|
"$schema": "https://json.schemastore.org/package",
|
||||||
"name": "@kevisual/router",
|
"name": "@kevisual/router",
|
||||||
"version": "0.0.20",
|
"version": "0.0.21-beta",
|
||||||
"description": "",
|
"description": "",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "./dist/router.js",
|
"main": "./dist/router.js",
|
||||||
|
@ -1,8 +1,12 @@
|
|||||||
import { pathToRegexp, Key } from 'path-to-regexp';
|
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 { parseBody, parseSearch, parseSearchValue } from './server/parse-body.ts';
|
||||||
|
import { ListenOptions } from 'node:net';
|
||||||
|
|
||||||
type Req = IncomingMessage & { params?: Record<string, string> };
|
type Req = IncomingMessage & { params?: Record<string, string> };
|
||||||
|
type SimpleObject = {
|
||||||
|
[key: string]: any;
|
||||||
|
};
|
||||||
interface Route {
|
interface Route {
|
||||||
method: string;
|
method: string;
|
||||||
regexp: RegExp;
|
regexp: RegExp;
|
||||||
@ -28,7 +32,6 @@ export class SimpleRouter {
|
|||||||
use(method: string, route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
use(method: string, route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
||||||
const handlers = Array.isArray(fns) ? fns.flat() : [];
|
const handlers = Array.isArray(fns) ? fns.flat() : [];
|
||||||
const pattern = pathToRegexp(route);
|
const pattern = pathToRegexp(route);
|
||||||
|
|
||||||
this.routes.push({ method: method.toLowerCase(), regexp: pattern.regexp, keys: pattern.keys, handlers });
|
this.routes.push({ method: method.toLowerCase(), regexp: pattern.regexp, keys: pattern.keys, handlers });
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -38,11 +41,35 @@ export class SimpleRouter {
|
|||||||
post(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
post(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
||||||
return this.use('post', route, ...fns);
|
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>) {
|
all(route: string, ...fns: Array<(req: Req, res: ServerResponse) => Promise<void> | void>) {
|
||||||
this.use('post', route, ...fns);
|
this.use('post', route, ...fns);
|
||||||
this.use('get', route, ...fns);
|
this.use('get', route, ...fns);
|
||||||
|
this.use('sse', route, ...fns);
|
||||||
return this;
|
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 请求
|
* 解析 req 和 res 请求
|
||||||
* @param req
|
* @param req
|
||||||
@ -51,10 +78,12 @@ export class SimpleRouter {
|
|||||||
*/
|
*/
|
||||||
parse(req: Req, res: ServerResponse) {
|
parse(req: Req, res: ServerResponse) {
|
||||||
const { pathname } = new URL(req.url, 'http://localhost');
|
const { pathname } = new URL(req.url, 'http://localhost');
|
||||||
const method = req.method.toLowerCase();
|
let method = req.method.toLowerCase();
|
||||||
if (this.exclude.includes(pathname)) {
|
if (this.exclude.includes(pathname)) {
|
||||||
return 'is_exclude';
|
return 'is_exclude';
|
||||||
}
|
}
|
||||||
|
const isSse = this.isSse(req);
|
||||||
|
if (isSse) method = 'sse';
|
||||||
const route = this.routes.find((route) => {
|
const route = this.routes.find((route) => {
|
||||||
const matchResult = route.regexp.exec(pathname);
|
const matchResult = route.regexp.exec(pathname);
|
||||||
if (matchResult && route.method === method) {
|
if (matchResult && route.method === method) {
|
||||||
@ -74,4 +103,166 @@ export class SimpleRouter {
|
|||||||
|
|
||||||
return 'not_found';
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,8 @@ type BaseRule = {
|
|||||||
|
|
||||||
type RuleString = {
|
type RuleString = {
|
||||||
type: 'string';
|
type: 'string';
|
||||||
minLength?: number;
|
min?: number;
|
||||||
maxLength?: number;
|
max?: number;
|
||||||
regex?: string;
|
regex?: string;
|
||||||
} & BaseRule;
|
} & BaseRule;
|
||||||
|
|
||||||
@ -26,8 +26,6 @@ type RuleBoolean = {
|
|||||||
type RuleArray = {
|
type RuleArray = {
|
||||||
type: 'array';
|
type: 'array';
|
||||||
items: Rule;
|
items: Rule;
|
||||||
minItems?: number;
|
|
||||||
maxItems?: number;
|
|
||||||
} & BaseRule;
|
} & BaseRule;
|
||||||
|
|
||||||
type RuleObject = {
|
type RuleObject = {
|
||||||
@ -45,8 +43,8 @@ export const schemaFormRule = (rule: Rule): z.ZodType<any, any, any> => {
|
|||||||
switch (rule.type) {
|
switch (rule.type) {
|
||||||
case 'string':
|
case 'string':
|
||||||
let stringSchema = z.string();
|
let stringSchema = z.string();
|
||||||
if (rule.minLength) stringSchema = stringSchema.min(rule.minLength, `String must be at least ${rule.minLength} characters long.`);
|
if (rule.min) stringSchema = stringSchema.min(rule.min, `String must be at least ${rule.min} characters long.`);
|
||||||
if (rule.maxLength) stringSchema = stringSchema.max(rule.maxLength, `String must not exceed ${rule.maxLength} characters.`);
|
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');
|
if (rule.regex) stringSchema = stringSchema.regex(new RegExp(rule.regex), 'Invalid format');
|
||||||
return stringSchema;
|
return stringSchema;
|
||||||
case 'number':
|
case 'number':
|
||||||
|
Loading…
x
Reference in New Issue
Block a user