Compare commits

...

5 Commits

Author SHA1 Message Date
87068cd626 update 2025-10-24 21:15:02 +08:00
ac32ff9d4a udpate 2025-10-24 03:04:06 +08:00
cc74dc6803 update add no path and random path 2025-10-22 17:05:20 +08:00
24166f9899 feat: add Mini 2025-10-15 21:13:37 +08:00
10506503eb update: add listen process 2025-10-15 20:48:20 +08:00
9 changed files with 250 additions and 27 deletions

28
demo/simple/src/nopath.ts Normal file
View File

@@ -0,0 +1,28 @@
import { Route, App } from '@kevisual/router/src/index.ts';
const app = new App();
app.route({
description: 'sdf'
}).define(async (ctx) => {
ctx.body = 'this is no path fns';
return ctx;
}).addTo(app);
let id = ''
console.log('routes', app.router.routes.map(item => {
id = item.id;
return {
path: item.path,
key: item.key,
id: item.id,
description: item.description
}
}))
app.call({id: id}).then(res => {
console.log('id', id);
console.log('res', res);
})

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://json.schemastore.org/package",
"name": "@kevisual/router",
"version": "0.0.28",
"version": "0.0.31",
"description": "",
"type": "module",
"main": "./dist/router.js",
@@ -24,18 +24,18 @@
"@kevisual/local-proxy": "^0.0.6",
"@kevisual/query": "^0.0.29",
"@rollup/plugin-alias": "^5.1.1",
"@rollup/plugin-commonjs": "^28.0.6",
"@rollup/plugin-commonjs": "28.0.8",
"@rollup/plugin-node-resolve": "^16.0.3",
"@rollup/plugin-typescript": "^12.1.4",
"@rollup/plugin-typescript": "^12.3.0",
"@types/lodash-es": "^4.17.12",
"@types/node": "^24.7.2",
"@types/node": "^24.9.1",
"@types/send": "^1.2.0",
"@types/ws": "^8.18.1",
"@types/xml2js": "^0.4.14",
"cookie": "^1.0.2",
"lodash-es": "^4.17.21",
"nanoid": "^5.1.6",
"rollup": "^4.52.4",
"rollup": "^4.52.5",
"rollup-plugin-dts": "^6.2.3",
"ts-loader": "^9.5.4",
"ts-node": "^10.9.2",

View File

@@ -82,7 +82,20 @@ export class App<T = {}, U = AppReqRes> {
}
return new Route(path, key, opts);
}
async call(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
prompt(description: string): Route<Required<RouteContext>>;
prompt(description: Function): Route<Required<RouteContext>>;
prompt(...args: any[]) {
const [desc] = args;
let description = ''
if (typeof desc === 'string') {
description = desc;
} else if (typeof desc === 'function') {
description = desc() || ''; // 如果是Promise需要addTo App之前就要获取应有的函数了。
}
return new Route('', '', { description });
}
async call(message: { id?: string, path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
const router = this.router;
return await router.call(message, ctx);
}

View File

@@ -1,4 +1,4 @@
export { Route, QueryRouter, QueryRouterServer } from './route.ts';
export { Route, QueryRouter, QueryRouterServer, Mini } from './route.ts';
export type { Rule, Schema } from './validator/index.ts';

40
src/chat.ts Normal file
View File

@@ -0,0 +1,40 @@
import { QueryRouter } from "./route.ts";
type RouterChatOptions = {
router?: QueryRouter;
}
export class RouterChat {
router: QueryRouter;
prompt: string = '';
constructor(opts?: RouterChatOptions) {
this.router = opts?.router || new QueryRouter();
}
prefix(wrapperFn?: (routes: any[]) => string) {
if (this.prompt) {
return this.prompt;
}
let _prompt = `你是一个调用函数工具的助手,当用户询问时,如果拥有工具,请返回 JSON 数据,数据的值的内容是 id 和 payload 。如果有参数,请放到 payload 当中。
下面是你可以使用的工具列表:
`;
if (!wrapperFn) {
_prompt += this.router.routes.map(r => `工具名称: ${r.id}\n描述: ${r.description}\n`).join('\n');
} else {
_prompt += wrapperFn(this.router.exportRoutes());
}
_prompt += `当你需要使用工具时,请严格按照以下格式返回:
{
"id": "工具名称",
"payload": {
// 参数列表
}
}
如果你不需要使用工具,直接返回用户想要的内容即可,不要返回任何多余的信息。`;
return _prompt;
}
chat() {
const prompt = this.prefix();
return prompt;
}
}

View File

@@ -1,4 +1,4 @@
export { Route, QueryRouter, QueryRouterServer } from './route.ts';
export { Route, QueryRouter, QueryRouterServer, Mini } from './route.ts';
export { Connect, QueryConnect } from './connect.ts';
export type { RouteContext, RouteOpts, RouteMiddleware } from './route.ts';
@@ -11,7 +11,9 @@ export { Server, handleServer } from './server/index.ts';
*/
export { CustomError } from './result/error.ts';
export { Rule, Schema, createSchema } from './validator/index.ts';
export { createSchema } from './validator/index.ts';
export type { Rule, Schema, } from './validator/index.ts';
export { App } from './app.ts';

View File

@@ -1,8 +1,9 @@
import { nanoid } from 'nanoid';
import { nanoid, random } from 'nanoid';
import { CustomError } from './result/error.ts';
import { Schema, Rule, createSchema } from './validator/index.ts';
import { pick } from './utils/pick.ts';
import { get } from 'lodash-es';
import { listenProcess } from './utils/listen-process.ts';
export type RouterContextT = { code?: number;[key: string]: any };
export type RouteContext<T = { code?: number }, S = any> = {
@@ -87,7 +88,7 @@ export type RouteOpts = {
* }
*/
validator?: { [key: string]: Rule };
schema?: { [key: string]: Schema<any> };
schema?: { [key: string]: any };
isVerify?: boolean;
verify?: (ctx?: RouteContext, dev?: boolean) => boolean;
verifyKey?: (key: string, ctx?: RouteContext, dev?: boolean) => boolean;
@@ -102,7 +103,7 @@ export type RouteOpts = {
isDebug?: boolean;
};
export type DefineRouteOpts = Omit<RouteOpts, 'idUsePath' | 'verify' | 'verifyKey' | 'nextRoute'>;
const pickValue = ['path', 'key', 'id', 'description', 'type', 'validator', 'middleware'] as const;
const pickValue = ['path', 'key', 'id', 'description', 'type', 'validator', 'middleware', 'metadata'] as const;
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
export class Route<U = { [key: string]: any }> {
/**
@@ -122,7 +123,7 @@ export class Route<U = { [key: string]: any }> {
middleware?: RouteMiddleware[]; // middleware
type? = 'route';
private _validator?: { [key: string]: Rule };
schema?: { [key: string]: Schema<any> };
schema?: { [key: string]: any };
data?: any;
/**
* 是否需要验证
@@ -132,7 +133,10 @@ export class Route<U = { [key: string]: any }> {
* 是否开启debug开启后会打印错误信息
*/
isDebug?: boolean;
constructor(path: string, key: string = '', opts?: RouteOpts) {
constructor(path: string = '', key: string = '', opts?: RouteOpts) {
if (!path) {
path = nanoid(8)
}
path = path.trim();
key = key.trim();
this.path = path;
@@ -260,6 +264,17 @@ export class Route<U = { [key: string]: any }> {
this.validator = validator;
return this;
}
prompt(description: string): this;
prompt(description: Function): this;
prompt(...args: any[]) {
const [description] = args;
if (typeof description === 'string') {
this.description = description;
} else if (typeof description === 'function') {
this.description = description() || ''; // 如果是Promise需要addTo App之前就要获取应有的函数了。
}
return this;
}
define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this;
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & U>): this;
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & U>): this;
@@ -303,6 +318,27 @@ export class Route<U = { [key: string]: any }> {
}
return this;
}
update(opts: DefineRouteOpts, checkList?: string[]): this {
const keys = Object.keys(opts);
const defaultCheckList = ['path', 'key', 'run', 'nextRoute', 'description', 'metadata', 'middleware', 'type', 'validator', 'isVerify', 'isDebug'];
checkList = checkList || defaultCheckList;
for (let item of keys) {
if (!checkList.includes(item)) {
continue;
}
if (item === 'validator') {
this.validator = opts[item];
continue;
}
if (item === 'middleware') {
this.middleware = this.middleware.concat(opts[item]);
continue;
}
this[item] = opts[item];
}
return this;
}
addTo(router: QueryRouter | { add: (route: Route) => void;[key: string]: any }) {
router.add(this);
}
@@ -582,8 +618,9 @@ export class QueryRouter {
} else {
return { code: 404, body: null, message: 'Not found route' };
}
return await this.parse({ ...message, path, key }, { ...this.context, ...ctx });
} else if (path) {
return await this.parse({ ...message, path }, { ...this.context, ...ctx });
return await this.parse({ ...message, path, key }, { ...this.context, ...ctx });
} else {
return { code: 404, body: null, message: 'Not found path' };
}
@@ -595,7 +632,7 @@ export class QueryRouter {
* @param ctx
* @returns
*/
async queryRoute(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
async queryRoute(message: { id?: string; path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
const res = await this.parse(message, { ...this.context, ...ctx });
return {
code: res.code,
@@ -620,9 +657,19 @@ export class QueryRouter {
* 获取handle函数, 这里会去执行parse函数
*/
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn<T>, ctx?: RouteContext) {
return async (msg: { path: string; key?: string; [key: string]: any }, handleContext?: RouteContext) => {
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext) => {
try {
const context = { ...ctx, ...handleContext };
if (msg.id) {
const route = router.routes.find((r) => r.id === msg.id);
if (route) {
msg.path = route.path;
msg.key = route.key;
} else {
return { code: 404, message: 'Not found route' };
}
}
// @ts-ignore
const res = await router.parse(msg, context);
if (wrapperFn) {
res.data = res.body;
@@ -655,6 +702,17 @@ export class QueryRouter {
hasRoute(path: string, key: string = '') {
return this.routes.find((r) => r.path === path && r.key === key);
}
/**
* 等待程序运行, 获取到message的数据,就执行
*
* emitter = process
* -- .exit
* -- .on
* -- .send
*/
wait(params?: { path?: string; key?: string; payload?: any }, opts?: { emitter?: any, timeout?: number }) {
return listenProcess({ app: this, params, ...opts });
}
}
type QueryRouterServerOpts = {
@@ -709,6 +767,18 @@ export class QueryRouterServer extends QueryRouter {
}
return new Route(path, key, opts);
}
prompt(description: string): Route<Required<RouteContext>>;
prompt(description: Function): Route<Required<RouteContext>>;
prompt(...args: any[]) {
const [desc] = args;
let description = ''
if (typeof desc === 'string') {
description = desc;
} else if (typeof desc === 'function') {
description = desc() || ''; // 如果是Promise需要addTo App之前就要获取应有的函数了。
}
return new Route('', '', { description });
}
/**
* 等于queryRoute但是调用了handle
@@ -739,3 +809,6 @@ export class QueryRouterServer extends QueryRouter {
}
}
}
export const Mini = QueryRouterServer

17
src/test/chat.ts Normal file
View File

@@ -0,0 +1,17 @@
import { App } from '../app.ts'
import { RouterChat } from '@/chat.ts';
const app = new App();
app.prompt(`获取时间的工具`).define(async (ctx) => {
ctx.body = '123'
}).addTo(app);
app.prompt('获取天气的工具。\n参数是 city 为对应的城市').define(async (ctx) => {
ctx.body = '晴天'
}).addTo(app);
export const chat = new RouterChat({ router: app.router });
console.log(chat.chat());

View File

@@ -0,0 +1,50 @@
export type ListenProcessOptions = {
app?: any; // 传入的应用实例
emitter?: any; // 可选的事件发射器
params?: any; // 可选的参数
timeout?: number; // 可选的超时时间 (单位: 毫秒)
};
export const listenProcess = async ({ app, emitter, params, timeout = 10 * 60 * 60 * 1000 }: ListenProcessOptions) => {
const process = emitter || globalThis.process;
let isEnd = false;
const timer = setTimeout(() => {
if (isEnd) return;
isEnd = true;
process.send?.({ success: false, error: 'Timeout' });
process.exit?.(1);
}, timeout);
// 监听来自主进程的消息
const getParams = async (): Promise<any> => {
return new Promise((resolve) => {
process.on('message', (msg) => {
if (isEnd) return;
isEnd = true;
clearTimeout(timer);
resolve(msg)
})
})
}
try {
const { path = 'main', ...rest } = await getParams()
// 执行主要逻辑
const result = await app.queryRoute({ path, ...rest, ...params })
// 发送结果回主进程
const response = {
success: true,
data: result,
timestamp: new Date().toISOString()
}
process.send?.(response, (error) => {
process.exit?.(0)
})
} catch (error) {
process.send?.({
success: false,
error: error.message
})
process.exit?.(1)
}
}