@kevisual/router 0.0.82 → 0.0.84
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/agent/routes/index.ts +16 -10
- package/agent/routes/route-create.ts +3 -3
- package/dist/app.js +98 -69
- package/dist/opencode.d.ts +89 -86
- package/dist/opencode.js +10 -5
- package/dist/router-browser.d.ts +87 -55
- package/dist/router-browser.js +26 -24
- package/dist/router-define.d.ts +84 -55
- package/dist/router.d.ts +92 -86
- package/dist/router.js +28 -62
- package/dist/ws.d.ts +112 -73
- package/package.json +2 -2
- package/readme.md +78 -63
- package/src/app.ts +7 -50
- package/src/opencode.ts +12 -5
- package/src/result/error.ts +22 -0
- package/src/route.ts +92 -78
- package/src/test/app-type.ts +66 -10
- package/src/test/chat.ts +1 -1
- package/src/test/route-ts.ts +15 -0
package/src/app.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AddOpts, QueryRouter, Route, RouteContext, RouteOpts } from './route.ts';
|
|
1
|
+
import { AddOpts, QueryRouter, QueryRouterServer, Route, RouteContext, RouteOpts } from './route.ts';
|
|
2
2
|
import { ServerNode, ServerNodeOpts } from './server/server.ts';
|
|
3
3
|
import { HandleCtx } from './server/server-base.ts';
|
|
4
4
|
import { ServerType } from './server/server-type.ts';
|
|
@@ -10,7 +10,7 @@ import { randomId } from './utils/random.ts';
|
|
|
10
10
|
|
|
11
11
|
type RouterHandle = (msg: { path: string;[key: string]: any }) => { code: string; data?: any; message?: string;[key: string]: any };
|
|
12
12
|
type AppOptions<T = {}> = {
|
|
13
|
-
router?:
|
|
13
|
+
router?: QueryRouterServer;
|
|
14
14
|
server?: ServerType;
|
|
15
15
|
/** handle msg 关联 */
|
|
16
16
|
routerHandle?: RouterHandle;
|
|
@@ -19,18 +19,19 @@ type AppOptions<T = {}> = {
|
|
|
19
19
|
appId?: string;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
export type AppRouteContext<T
|
|
22
|
+
export type AppRouteContext<T> = HandleCtx & RouteContext<T> & { app: App<T> };
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* 封装了 Router 和 Server 的 App 模块,处理http的请求和响应,内置了 Cookie 和 Token 和 res 的处理
|
|
26
26
|
* U - Route Context的扩展类型
|
|
27
27
|
*/
|
|
28
|
-
export class App<U = {}> extends
|
|
28
|
+
export class App<U = {}> extends QueryRouterServer<AppRouteContext<U>> {
|
|
29
29
|
declare appId: string;
|
|
30
|
-
router:
|
|
30
|
+
router: QueryRouterServer;
|
|
31
31
|
server: ServerType;
|
|
32
|
+
declare context: AppRouteContext<U>;
|
|
32
33
|
constructor(opts?: AppOptions<U>) {
|
|
33
|
-
super();
|
|
34
|
+
super({ initHandle: false, context: { needSerialize: true, ...opts?.routerContext } as any });
|
|
34
35
|
const router = this;
|
|
35
36
|
let server = opts?.server;
|
|
36
37
|
if (!server) {
|
|
@@ -42,7 +43,6 @@ export class App<U = {}> extends QueryRouter {
|
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
45
|
server.setHandle(router.getHandle(router, opts?.routerHandle, opts?.routerContext));
|
|
45
|
-
router.setContext({ needSerialize: true, ...opts?.routerContext });
|
|
46
46
|
this.router = router;
|
|
47
47
|
this.server = server;
|
|
48
48
|
if (opts?.appId) {
|
|
@@ -64,50 +64,7 @@ export class App<U = {}> extends QueryRouter {
|
|
|
64
64
|
// @ts-ignore
|
|
65
65
|
this.server.listen(...args);
|
|
66
66
|
}
|
|
67
|
-
addRoute(route: Route, opts?: AddOpts) {
|
|
68
|
-
super.add(route, opts);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
67
|
Route = Route;
|
|
72
|
-
route(opts: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
|
|
73
|
-
route(path: string, key?: string): Route<AppRouteContext<U>>;
|
|
74
|
-
route(path: string, opts?: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
|
|
75
|
-
route(path: string, key?: string, opts?: RouteOpts<AppRouteContext<U>>): Route<AppRouteContext<U>>;
|
|
76
|
-
route(...args: any[]) {
|
|
77
|
-
const [path, key, opts] = args;
|
|
78
|
-
if (typeof path === 'object') {
|
|
79
|
-
return new Route(path.path, path.key, path);
|
|
80
|
-
}
|
|
81
|
-
if (typeof path === 'string') {
|
|
82
|
-
if (opts) {
|
|
83
|
-
return new Route(path, key, opts);
|
|
84
|
-
}
|
|
85
|
-
if (key && typeof key === 'object') {
|
|
86
|
-
return new Route(path, key?.key || '', key);
|
|
87
|
-
}
|
|
88
|
-
return new Route(path, key);
|
|
89
|
-
}
|
|
90
|
-
return new Route(path, key, opts);
|
|
91
|
-
}
|
|
92
|
-
prompt(description: string): Route<AppRouteContext<U>>
|
|
93
|
-
prompt(description: Function): Route<AppRouteContext<U>>
|
|
94
|
-
prompt(...args: any[]) {
|
|
95
|
-
const [desc] = args;
|
|
96
|
-
let description = ''
|
|
97
|
-
if (typeof desc === 'string') {
|
|
98
|
-
description = desc;
|
|
99
|
-
} else if (typeof desc === 'function') {
|
|
100
|
-
description = desc() || ''; // 如果是Promise,需要addTo App之前就要获取应有的函数了。
|
|
101
|
-
}
|
|
102
|
-
return new Route('', '', { description });
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
async call(message: { id?: string, path?: string; key?: string; payload?: any }, ctx?: AppRouteContext<U> & { [key: string]: any }) {
|
|
106
|
-
return await super.call(message, ctx);
|
|
107
|
-
}
|
|
108
|
-
async run(msg: { id?: string, path?: string; key?: string; payload?: any }, ctx?: Partial<AppRouteContext<U>> & { [key: string]: any }) {
|
|
109
|
-
return await super.run(msg, ctx);
|
|
110
|
-
}
|
|
111
68
|
static handleRequest(req: IncomingMessage, res: ServerResponse) {
|
|
112
69
|
return handleServer(req, res);
|
|
113
70
|
}
|
package/src/opencode.ts
CHANGED
|
@@ -10,7 +10,7 @@ export const addCallFn = (app: App) => {
|
|
|
10
10
|
path: 'call',
|
|
11
11
|
key: '',
|
|
12
12
|
description: '调用',
|
|
13
|
-
middleware: ['auth'],
|
|
13
|
+
middleware: ['auth-admin'],
|
|
14
14
|
metadata: {
|
|
15
15
|
tags: ['opencode'],
|
|
16
16
|
...createSkill({
|
|
@@ -24,7 +24,7 @@ export const addCallFn = (app: App) => {
|
|
|
24
24
|
args: {
|
|
25
25
|
path: tool.schema.string().describe('应用路径,例如 cnb'),
|
|
26
26
|
key: tool.schema.string().optional().describe('应用key,例如 list-repos'),
|
|
27
|
-
payload: tool.schema.object({}).optional().describe('
|
|
27
|
+
payload: tool.schema.object({}).optional().describe('调用参数, 为对象, 例如 { "query": "javascript" }'),
|
|
28
28
|
}
|
|
29
29
|
})
|
|
30
30
|
},
|
|
@@ -59,9 +59,16 @@ export const createRouterAgentPluginFn = (opts?: {
|
|
|
59
59
|
if (!router.hasRoute('call', '')) {
|
|
60
60
|
addCallFn(router as App)
|
|
61
61
|
}
|
|
62
|
-
if (
|
|
63
|
-
router.route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App
|
|
62
|
+
if (router) {
|
|
63
|
+
(router as any).route({ path: 'auth', key: '', id: 'auth', description: '认证' }).define(async (ctx) => { }).addTo(router as App, {
|
|
64
|
+
overwrite: false
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
(router as any).route({ path: 'auth-admin', key: '', id: 'auth-admin', description: '认证' }).define(async (ctx) => { }).addTo(router as App, {
|
|
68
|
+
overwrite: false
|
|
69
|
+
})
|
|
64
70
|
}
|
|
71
|
+
|
|
65
72
|
const _routes = filter(router.routes, opts?.query || '')
|
|
66
73
|
const routes = _routes.filter(r => {
|
|
67
74
|
const metadata = r.metadata as Skill
|
|
@@ -75,7 +82,7 @@ export const createRouterAgentPluginFn = (opts?: {
|
|
|
75
82
|
});
|
|
76
83
|
// opencode run "使用技能查看系统信息"
|
|
77
84
|
const AgentPlugin: Plugin = async (pluginInput) => {
|
|
78
|
-
useContextKey<PluginInput>('plugin-input', () => pluginInput, true)
|
|
85
|
+
useContextKey<PluginInput>('plugin-input', () => pluginInput, { isNew: true })
|
|
79
86
|
const hooks = opts?.hooks ? await opts.hooks(pluginInput) : {}
|
|
80
87
|
return {
|
|
81
88
|
...hooks,
|
package/src/result/error.ts
CHANGED
|
@@ -47,6 +47,22 @@ export class CustomError extends Error {
|
|
|
47
47
|
static isError(error: unknown): error is CustomError {
|
|
48
48
|
return error instanceof CustomError || (typeof error === 'object' && error !== null && 'code' in error);
|
|
49
49
|
}
|
|
50
|
+
static throw(code?: number | string, message?: string): void;
|
|
51
|
+
static throw(code?: number | string, opts?: CustomErrorOptions): void;
|
|
52
|
+
static throw(opts?: CustomErrorOptions): void;
|
|
53
|
+
static throw(...args: any[]) {
|
|
54
|
+
const [args0, args1] = args;
|
|
55
|
+
if (args0 && typeof args0 === 'object') {
|
|
56
|
+
throw new CustomError(args0);
|
|
57
|
+
}
|
|
58
|
+
if (args1 && typeof args1 === 'object') {
|
|
59
|
+
throw new CustomError(args0, args1);
|
|
60
|
+
} else if (args1) {
|
|
61
|
+
throw new CustomError(args0, { message: args1 });
|
|
62
|
+
}
|
|
63
|
+
// args1 不存在;
|
|
64
|
+
throw new CustomError(args0);
|
|
65
|
+
}
|
|
50
66
|
parse(e?: CustomError) {
|
|
51
67
|
if (e) {
|
|
52
68
|
return CustomError.parseError(e);
|
|
@@ -61,6 +77,12 @@ export class CustomError extends Error {
|
|
|
61
77
|
}
|
|
62
78
|
}
|
|
63
79
|
|
|
80
|
+
export interface throwError {
|
|
81
|
+
throw(code?: number | string, message?: string): void;
|
|
82
|
+
throw(code?: number | string, opts?: CustomErrorOptions): void;
|
|
83
|
+
throw(opts?: CustomErrorOptions): void;
|
|
84
|
+
}
|
|
85
|
+
|
|
64
86
|
/*
|
|
65
87
|
try {
|
|
66
88
|
//
|
package/src/route.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CustomError, CustomErrorOptions } from './result/error.ts';
|
|
1
|
+
import { CustomError, throwError, CustomErrorOptions } from './result/error.ts';
|
|
2
2
|
import { pick } from './utils/pick.ts';
|
|
3
3
|
import { listenProcess, MockProcess } from './utils/listen-process.ts';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -6,7 +6,24 @@ import { randomId } from './utils/random.ts';
|
|
|
6
6
|
import * as schema from './validator/schema.ts';
|
|
7
7
|
|
|
8
8
|
export type RouterContextT = { code?: number;[key: string]: any };
|
|
9
|
-
|
|
9
|
+
|
|
10
|
+
type ExtractArgs<A> = A extends z.ZodTypeAny ? z.infer<A> : A;
|
|
11
|
+
|
|
12
|
+
type OptionalKeys<T> = {
|
|
13
|
+
[K in keyof T]-?: {} extends Pick<T, K> ? K : never;
|
|
14
|
+
}[keyof T];
|
|
15
|
+
|
|
16
|
+
type MakeOptional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
|
17
|
+
|
|
18
|
+
type BuildRouteContext<M, U> = M extends { args?: infer A }
|
|
19
|
+
? A extends z.ZodObject<any>
|
|
20
|
+
? RouteContext<{ args?: z.infer<A> }, U>
|
|
21
|
+
: A extends Record<string, z.ZodTypeAny>
|
|
22
|
+
? RouteContext<{ args?: { [K in keyof A]: z.infer<A[K]> } }, U>
|
|
23
|
+
: RouteContext<U>
|
|
24
|
+
: RouteContext<U>;
|
|
25
|
+
|
|
26
|
+
export type RouteContext<T = { code?: number }, U extends SimpleObject = {}, S = { [key: string]: any }> = {
|
|
10
27
|
/**
|
|
11
28
|
* 本地自己调用的时候使用,可以标识为当前自调用,那么 auth 就不许重复的校验
|
|
12
29
|
* 或者不需要登录的,直接调用
|
|
@@ -23,9 +40,15 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
|
|
23
40
|
code?: number;
|
|
24
41
|
/** return msg */
|
|
25
42
|
message?: string;
|
|
26
|
-
|
|
43
|
+
/**
|
|
44
|
+
* 传递状态
|
|
45
|
+
*/
|
|
27
46
|
state?: S;
|
|
28
47
|
// transfer data
|
|
48
|
+
/**
|
|
49
|
+
* 当前routerId
|
|
50
|
+
*/
|
|
51
|
+
currentId?: string;
|
|
29
52
|
/**
|
|
30
53
|
* 当前路径
|
|
31
54
|
*/
|
|
@@ -54,19 +77,19 @@ export type RouteContext<T = { code?: number }, S = any> = {
|
|
|
54
77
|
ctx?: RouteContext & { [key: string]: any },
|
|
55
78
|
) => Promise<any>;
|
|
56
79
|
/** 请求 route的返回结果,解析了body为data,就类同于 query.post获取的数据*/
|
|
57
|
-
run?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext
|
|
80
|
+
run?: (message: { path: string; key?: string; payload?: any }, ctx?: RouteContext) => Promise<any>;
|
|
58
81
|
index?: number;
|
|
59
|
-
throw?:
|
|
82
|
+
throw?: throwError['throw'];
|
|
60
83
|
/** 是否需要序列化, 使用JSON.stringify和JSON.parse */
|
|
61
84
|
needSerialize?: boolean;
|
|
62
|
-
} & T;
|
|
85
|
+
} & T & U;
|
|
63
86
|
export type SimpleObject = Record<string, any>;
|
|
64
87
|
export type Run<T extends SimpleObject = {}> = (ctx: Required<RouteContext<T>>) => Promise<typeof ctx | null | void>;
|
|
65
88
|
export type RunMessage = { path?: string; key?: string; id?: string; payload?: any; };
|
|
66
89
|
export type NextRoute = Pick<Route, 'id' | 'path' | 'key'>;
|
|
67
90
|
export type RouteMiddleware =
|
|
68
91
|
| {
|
|
69
|
-
path
|
|
92
|
+
path?: string;
|
|
70
93
|
key?: string;
|
|
71
94
|
id?: string;
|
|
72
95
|
}
|
|
@@ -123,7 +146,13 @@ export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
|
|
|
123
146
|
|
|
124
147
|
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
|
|
125
148
|
|
|
126
|
-
|
|
149
|
+
type ExtractMetadata<M> = M extends { metadata?: infer Meta } ? Meta extends SimpleObject ? Meta : SimpleObject : SimpleObject;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @M 是 route的 metadate的类型,默认是 SimpleObject
|
|
153
|
+
* @U 是 RouteContext 里 state的类型
|
|
154
|
+
*/
|
|
155
|
+
export class Route<M extends SimpleObject = SimpleObject, U extends SimpleObject = SimpleObject> implements throwError {
|
|
127
156
|
/**
|
|
128
157
|
* 一级路径
|
|
129
158
|
*/
|
|
@@ -133,10 +162,10 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|
|
133
162
|
*/
|
|
134
163
|
key?: string;
|
|
135
164
|
id?: string;
|
|
136
|
-
run?: Run
|
|
165
|
+
run?: Run<BuildRouteContext<M, U>>;
|
|
137
166
|
nextRoute?: NextRoute; // route to run after this route
|
|
138
167
|
description?: string;
|
|
139
|
-
metadata?:
|
|
168
|
+
metadata?: M;
|
|
140
169
|
middleware?: RouteMiddleware[]; // middleware
|
|
141
170
|
type? = 'route';
|
|
142
171
|
/**
|
|
@@ -157,10 +186,10 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|
|
157
186
|
const delimiter = opts.delimiter ?? '$#$';
|
|
158
187
|
this.id = path + delimiter + key;
|
|
159
188
|
}
|
|
160
|
-
this.run = opts.run
|
|
189
|
+
this.run = opts.run as Run<BuildRouteContext<M, U>>;
|
|
161
190
|
this.nextRoute = opts.nextRoute;
|
|
162
191
|
this.description = opts.description;
|
|
163
|
-
this.metadata = opts.metadata as
|
|
192
|
+
this.metadata = opts.metadata as M;
|
|
164
193
|
this.type = opts.type || 'route';
|
|
165
194
|
this.middleware = opts.middleware || [];
|
|
166
195
|
this.key = opts.key || key;
|
|
@@ -184,9 +213,9 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|
|
184
213
|
return this;
|
|
185
214
|
}
|
|
186
215
|
define<T extends { [key: string]: any } = RouterContextT>(opts: DefineRouteOpts): this;
|
|
187
|
-
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & U
|
|
188
|
-
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & U
|
|
189
|
-
define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & U
|
|
216
|
+
define<T extends { [key: string]: any } = RouterContextT>(fn: Run<T & BuildRouteContext<M, U>>): this;
|
|
217
|
+
define<T extends { [key: string]: any } = RouterContextT>(key: string, fn: Run<T & BuildRouteContext<M, U>>): this;
|
|
218
|
+
define<T extends { [key: string]: any } = RouterContextT>(path: string, key: string, fn: Run<T & BuildRouteContext<M, U>>): this;
|
|
190
219
|
define(...args: any[]) {
|
|
191
220
|
const [path, key, opts] = args;
|
|
192
221
|
// 全覆盖,所以opts需要准确,不能由idUsePath 需要check的变量
|
|
@@ -209,7 +238,7 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|
|
209
238
|
return this;
|
|
210
239
|
}
|
|
211
240
|
if (typeof path === 'function') {
|
|
212
|
-
this.run = path
|
|
241
|
+
this.run = path as Run<BuildRouteContext<M, U>>;
|
|
213
242
|
return this;
|
|
214
243
|
}
|
|
215
244
|
if (typeof path === 'string' && typeof key === 'function') {
|
|
@@ -242,9 +271,8 @@ export class Route<U = { [key: string]: any }, T extends SimpleObject = SimpleOb
|
|
|
242
271
|
addTo(router: QueryRouter | { add: (route: Route) => void;[key: string]: any }, opts?: AddOpts) {
|
|
243
272
|
router.add(this, opts);
|
|
244
273
|
}
|
|
245
|
-
throw(code?: number | string, message?: string, tips?: string): void;
|
|
246
274
|
throw(...args: any[]) {
|
|
247
|
-
throw
|
|
275
|
+
CustomError.throw(...args);
|
|
248
276
|
}
|
|
249
277
|
}
|
|
250
278
|
|
|
@@ -263,11 +291,11 @@ export const fromJSONSchema = schema.fromJSONSchema;
|
|
|
263
291
|
* @parmas overwrite 是否覆盖已存在的route,默认true
|
|
264
292
|
*/
|
|
265
293
|
export type AddOpts = { overwrite?: boolean };
|
|
266
|
-
export class QueryRouter {
|
|
294
|
+
export class QueryRouter<T extends SimpleObject = SimpleObject> implements throwError {
|
|
267
295
|
appId: string = '';
|
|
268
296
|
routes: Route[];
|
|
269
297
|
maxNextRoute = 40;
|
|
270
|
-
context?: RouteContext = {}
|
|
298
|
+
context?: RouteContext<T> = {} as RouteContext<T>; // default context for call
|
|
271
299
|
constructor() {
|
|
272
300
|
this.routes = [];
|
|
273
301
|
}
|
|
@@ -310,11 +338,12 @@ export class QueryRouter {
|
|
|
310
338
|
* @param ctx
|
|
311
339
|
* @returns
|
|
312
340
|
*/
|
|
313
|
-
async runRoute(path: string, key: string, ctx?: RouteContext) {
|
|
341
|
+
async runRoute(path: string, key: string, ctx?: RouteContext<T>): Promise<RouteContext<T>> {
|
|
314
342
|
const route = this.routes.find((r) => r.path === path && r.key === key);
|
|
315
343
|
const maxNextRoute = this.maxNextRoute;
|
|
316
|
-
ctx = (ctx || {}) as RouteContext
|
|
344
|
+
ctx = (ctx || {}) as RouteContext<T>;
|
|
317
345
|
ctx.currentPath = path;
|
|
346
|
+
ctx.currentId = route?.id;
|
|
318
347
|
ctx.currentKey = key;
|
|
319
348
|
ctx.currentRoute = route;
|
|
320
349
|
ctx.index = (ctx.index || 0) + 1;
|
|
@@ -328,7 +357,7 @@ export class QueryRouter {
|
|
|
328
357
|
ctx.code = 500;
|
|
329
358
|
ctx.message = 'Too many nextRoute';
|
|
330
359
|
ctx.body = null;
|
|
331
|
-
return;
|
|
360
|
+
return ctx;
|
|
332
361
|
}
|
|
333
362
|
// run middleware
|
|
334
363
|
if (route && route.middleware && route.middleware.length > 0) {
|
|
@@ -383,7 +412,7 @@ export class QueryRouter {
|
|
|
383
412
|
const middleware = routeMiddleware[i];
|
|
384
413
|
if (middleware) {
|
|
385
414
|
try {
|
|
386
|
-
await middleware.run(ctx as Required<RouteContext
|
|
415
|
+
await middleware.run(ctx as Required<RouteContext<T>>);
|
|
387
416
|
} catch (e) {
|
|
388
417
|
if (route?.isDebug) {
|
|
389
418
|
console.error('=====debug====:middlerware error');
|
|
@@ -405,6 +434,7 @@ export class QueryRouter {
|
|
|
405
434
|
return ctx;
|
|
406
435
|
}
|
|
407
436
|
if (ctx.end) {
|
|
437
|
+
return ctx;
|
|
408
438
|
}
|
|
409
439
|
}
|
|
410
440
|
}
|
|
@@ -413,7 +443,7 @@ export class QueryRouter {
|
|
|
413
443
|
if (route) {
|
|
414
444
|
if (route.run) {
|
|
415
445
|
try {
|
|
416
|
-
await route.run(ctx as Required<RouteContext
|
|
446
|
+
await route.run(ctx as Required<RouteContext<T>>);
|
|
417
447
|
} catch (e) {
|
|
418
448
|
if (route?.isDebug) {
|
|
419
449
|
console.error('=====debug====:route error');
|
|
@@ -468,7 +498,7 @@ export class QueryRouter {
|
|
|
468
498
|
}
|
|
469
499
|
}
|
|
470
500
|
// 如果没有找到route,返回404,这是因为出现了错误
|
|
471
|
-
return Promise.resolve({ code: 404, body: 'Not found' });
|
|
501
|
+
return Promise.resolve({ code: 404, body: 'Not found' } as RouteContext<T>);
|
|
472
502
|
}
|
|
473
503
|
/**
|
|
474
504
|
* 第一次执行
|
|
@@ -476,12 +506,12 @@ export class QueryRouter {
|
|
|
476
506
|
* @param ctx
|
|
477
507
|
* @returns
|
|
478
508
|
*/
|
|
479
|
-
async parse(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
|
509
|
+
async parse(message: { path: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
|
|
480
510
|
if (!message?.path) {
|
|
481
|
-
return Promise.resolve({ code: 404, body: null, message: 'Not found path' });
|
|
511
|
+
return Promise.resolve({ code: 404, body: null, message: 'Not found path' } as RouteContext<T>);
|
|
482
512
|
}
|
|
483
513
|
const { path, key = '', payload = {}, ...query } = message;
|
|
484
|
-
ctx = ctx || {}
|
|
514
|
+
ctx = ctx || {} as RouteContext<T>;
|
|
485
515
|
ctx.query = { ...ctx.query, ...query, ...payload };
|
|
486
516
|
ctx.args = ctx.query;
|
|
487
517
|
ctx.state = { ...ctx?.state };
|
|
@@ -515,7 +545,7 @@ export class QueryRouter {
|
|
|
515
545
|
* @param ctx
|
|
516
546
|
* @returns
|
|
517
547
|
*/
|
|
518
|
-
async call(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
|
548
|
+
async call(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
|
|
519
549
|
let path = message.path;
|
|
520
550
|
let key = message.key;
|
|
521
551
|
// 优先 path + key
|
|
@@ -556,7 +586,7 @@ export class QueryRouter {
|
|
|
556
586
|
* @param ctx
|
|
557
587
|
* @returns
|
|
558
588
|
*/
|
|
559
|
-
async run(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext & { [key: string]: any }) {
|
|
589
|
+
async run(message: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext<T> & { [key: string]: any }) {
|
|
560
590
|
const res = await this.call(message, { ...this.context, ...ctx });
|
|
561
591
|
return {
|
|
562
592
|
code: res.code,
|
|
@@ -570,7 +600,7 @@ export class QueryRouter {
|
|
|
570
600
|
* @param ctx
|
|
571
601
|
*/
|
|
572
602
|
setContext(ctx: RouteContext) {
|
|
573
|
-
this.context = ctx
|
|
603
|
+
this.context = ctx as RouteContext<T>;
|
|
574
604
|
}
|
|
575
605
|
getList(filter?: (route: Route) => boolean): RouteInfo[] {
|
|
576
606
|
return this.routes.filter(filter || (() => true)).map((r) => {
|
|
@@ -581,11 +611,11 @@ export class QueryRouter {
|
|
|
581
611
|
/**
|
|
582
612
|
* 获取handle函数, 这里会去执行parse函数
|
|
583
613
|
*/
|
|
584
|
-
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn
|
|
585
|
-
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext) => {
|
|
614
|
+
getHandle<T = any>(router: QueryRouter, wrapperFn?: HandleFn, ctx?: RouteContext) {
|
|
615
|
+
return async (msg: { id?: string; path?: string; key?: string;[key: string]: any }, handleContext?: RouteContext<T>) => {
|
|
586
616
|
try {
|
|
587
617
|
const context = { ...ctx, ...handleContext };
|
|
588
|
-
const res = await router.call(msg, context);
|
|
618
|
+
const res = await router.call(msg, context) as any;
|
|
589
619
|
if (wrapperFn) {
|
|
590
620
|
res.data = res.body;
|
|
591
621
|
return wrapperFn(res, context);
|
|
@@ -610,21 +640,8 @@ export class QueryRouter {
|
|
|
610
640
|
importRouter(router: QueryRouter) {
|
|
611
641
|
this.importRoutes(router.routes);
|
|
612
642
|
}
|
|
613
|
-
throw(code?: number | string, message?: string): void;
|
|
614
|
-
throw(code?: number | string, opts?: CustomErrorOptions): void;
|
|
615
|
-
throw(opts?: CustomErrorOptions): void;
|
|
616
643
|
throw(...args: any[]) {
|
|
617
|
-
|
|
618
|
-
if (args0 && typeof args0 === 'object') {
|
|
619
|
-
throw new CustomError(args0);
|
|
620
|
-
}
|
|
621
|
-
if (args1 && typeof args1 === 'object') {
|
|
622
|
-
throw new CustomError(args0, args1);
|
|
623
|
-
} else if (args1) {
|
|
624
|
-
throw new CustomError(args0, { message: args1 });
|
|
625
|
-
}
|
|
626
|
-
// args1 不存在;
|
|
627
|
-
throw new CustomError(args0);
|
|
644
|
+
CustomError.throw(...args);
|
|
628
645
|
}
|
|
629
646
|
hasRoute(path: string, key: string = '') {
|
|
630
647
|
return this.routes.find((r) => r.path === path && r.key === key);
|
|
@@ -651,7 +668,7 @@ export class QueryRouter {
|
|
|
651
668
|
description: '列出当前应用下的所有的路由信息',
|
|
652
669
|
middleware: opts?.middleware || [],
|
|
653
670
|
run: async (ctx: RouteContext) => {
|
|
654
|
-
const tokenUser = ctx.state
|
|
671
|
+
const tokenUser = ctx.state as unknown as { tokenUser?: any };
|
|
655
672
|
let isUser = !!tokenUser;
|
|
656
673
|
const list = this.getList(opts?.filter).filter((item) => {
|
|
657
674
|
if (item.id === 'auth' || item.id === 'auth-can' || item.id === 'check-auth-admin' || item.id === 'auth-admin') {
|
|
@@ -697,10 +714,11 @@ export class QueryRouter {
|
|
|
697
714
|
fromJSONSchema = fromJSONSchema;
|
|
698
715
|
}
|
|
699
716
|
|
|
700
|
-
type QueryRouterServerOpts = {
|
|
717
|
+
type QueryRouterServerOpts<C extends SimpleObject = SimpleObject> = {
|
|
701
718
|
handleFn?: HandleFn;
|
|
702
|
-
context?: RouteContext
|
|
719
|
+
context?: RouteContext<C>;
|
|
703
720
|
appId?: string;
|
|
721
|
+
initHandle?: boolean;
|
|
704
722
|
};
|
|
705
723
|
interface HandleFn<T = any> {
|
|
706
724
|
(msg: { path: string;[key: string]: any }, ctx?: any): { code: string; data?: any; message?: string;[key: string]: any };
|
|
@@ -709,13 +727,18 @@ interface HandleFn<T = any> {
|
|
|
709
727
|
/**
|
|
710
728
|
* QueryRouterServer
|
|
711
729
|
* @description 移除server相关的功能,只保留router相关的功能,和http.createServer不相关,独立
|
|
730
|
+
* @template C 自定义 RouteContext 类型
|
|
712
731
|
*/
|
|
713
|
-
export class QueryRouterServer extends QueryRouter {
|
|
732
|
+
export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends QueryRouter<C> {
|
|
714
733
|
declare appId: string;
|
|
715
734
|
handle: any;
|
|
716
|
-
|
|
735
|
+
declare context: RouteContext<C>;
|
|
736
|
+
constructor(opts?: QueryRouterServerOpts<C>) {
|
|
717
737
|
super();
|
|
718
|
-
|
|
738
|
+
const initHandle = opts?.initHandle ?? true;
|
|
739
|
+
if (initHandle || opts?.handleFn) {
|
|
740
|
+
this.handle = this.getHandle(this, opts?.handleFn, opts?.context);
|
|
741
|
+
}
|
|
719
742
|
this.setContext({ needSerialize: false, ...opts?.context });
|
|
720
743
|
if (opts?.appId) {
|
|
721
744
|
this.appId = opts.appId;
|
|
@@ -730,37 +753,28 @@ export class QueryRouterServer extends QueryRouter {
|
|
|
730
753
|
this.add(route, opts);
|
|
731
754
|
}
|
|
732
755
|
Route = Route;
|
|
733
|
-
route(opts: RouteOpts): Route<Required<RouteContext
|
|
734
|
-
route(path: string,
|
|
735
|
-
route(path: string,
|
|
736
|
-
route(path: string, key?: string, opts?: RouteOpts): Route<Required<RouteContext
|
|
737
|
-
route(...args: any[]) {
|
|
756
|
+
route<M extends SimpleObject = SimpleObject>(opts: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
|
757
|
+
route<M extends SimpleObject = SimpleObject>(path: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
|
758
|
+
route<M extends SimpleObject = SimpleObject>(path: string, key?: string): Route<M, Required<RouteContext<C>>>;
|
|
759
|
+
route<M extends SimpleObject = SimpleObject>(path: string, key?: string, opts?: RouteOpts & { metadata?: M }): Route<M, Required<RouteContext<C>>>;
|
|
760
|
+
route<M extends SimpleObject = SimpleObject>(...args: any[]) {
|
|
738
761
|
const [path, key, opts] = args;
|
|
739
762
|
if (typeof path === 'object') {
|
|
740
|
-
return new Route(path.path, path.key, path);
|
|
763
|
+
return new Route<M, Required<RouteContext<C>>>(path.path, path.key, path);
|
|
741
764
|
}
|
|
742
765
|
if (typeof path === 'string') {
|
|
743
766
|
if (opts) {
|
|
744
|
-
return new Route(path, key, opts);
|
|
767
|
+
return new Route<M, Required<RouteContext<C>>>(path, key, opts);
|
|
745
768
|
}
|
|
746
769
|
if (key && typeof key === 'object') {
|
|
747
|
-
return new Route(path, key?.key || '', key);
|
|
770
|
+
return new Route<M, Required<RouteContext<C>>>(path, key?.key || '', key);
|
|
748
771
|
}
|
|
749
|
-
return new Route(path, key);
|
|
772
|
+
return new Route<M, Required<RouteContext<C>>>(path, key);
|
|
750
773
|
}
|
|
751
|
-
return new Route(path, key, opts);
|
|
774
|
+
return new Route<M, Required<RouteContext<C>>>(path, key, opts);
|
|
752
775
|
}
|
|
753
|
-
prompt(description: string)
|
|
754
|
-
|
|
755
|
-
prompt(...args: any[]) {
|
|
756
|
-
const [desc] = args;
|
|
757
|
-
let description = ''
|
|
758
|
-
if (typeof desc === 'string') {
|
|
759
|
-
description = desc;
|
|
760
|
-
} else if (typeof desc === 'function') {
|
|
761
|
-
description = desc() || ''; // 如果是Promise,需要addTo App之前就要获取应有的函数了。
|
|
762
|
-
}
|
|
763
|
-
return new Route('', '', { description });
|
|
776
|
+
prompt(description: string) {
|
|
777
|
+
return new Route(undefined, undefined, { description });
|
|
764
778
|
}
|
|
765
779
|
|
|
766
780
|
/**
|
|
@@ -768,12 +782,12 @@ export class QueryRouterServer extends QueryRouter {
|
|
|
768
782
|
* @param param0
|
|
769
783
|
* @returns
|
|
770
784
|
*/
|
|
771
|
-
async run(msg: { id?: string; path?: string; key?: string; payload?: any }, ctx?: RouteContext
|
|
785
|
+
async run(msg: { id?: string; path?: string; key?: string; payload?: any }, ctx?: Partial<RouteContext<C>>) {
|
|
772
786
|
const handle = this.handle;
|
|
773
787
|
if (handle) {
|
|
774
788
|
return handle(msg, ctx);
|
|
775
789
|
}
|
|
776
|
-
return super.run(msg, ctx);
|
|
790
|
+
return super.run(msg, ctx as RouteContext<C>);
|
|
777
791
|
}
|
|
778
792
|
}
|
|
779
793
|
|
package/src/test/app-type.ts
CHANGED
|
@@ -1,13 +1,69 @@
|
|
|
1
|
-
import { App } from
|
|
1
|
+
import { App, AppRouteContext } from "@/app.ts";
|
|
2
|
+
import { QueryRouterServer, RouteContext } from "@/app.ts";
|
|
3
|
+
import z from "zod";
|
|
4
|
+
const route: RouteContext<{ customField: string }> = {} as any;
|
|
5
|
+
route.customField
|
|
6
|
+
const appRoute: AppRouteContext<{ customField: string }> = {} as any;
|
|
7
|
+
appRoute.customField
|
|
8
|
+
// 示例 1: 使用 App,它会自动使用 AppRouteContext<U> 作为 ctx 类型
|
|
9
|
+
const app = new App<{
|
|
10
|
+
customField: string;
|
|
11
|
+
}>();
|
|
12
|
+
app.context.customField = "customValue"; // 可以在 app.context 中添加自定义字段,这些字段会在 ctx 中可用
|
|
13
|
+
app.route({
|
|
14
|
+
path: 'test1',
|
|
15
|
+
metadata: {
|
|
16
|
+
args: {
|
|
17
|
+
name: z.string(),
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
}).define(async (ctx) => {
|
|
21
|
+
// ctx.app 是 App 类型
|
|
22
|
+
const appName = ctx.app.appId;
|
|
23
|
+
// ctx.customField 来自自定义泛型参数
|
|
24
|
+
const customField: string | undefined = ctx.customField;
|
|
2
25
|
|
|
3
|
-
|
|
26
|
+
// ctx.req 和 ctx.res 来自 HandleCtx
|
|
27
|
+
const req = ctx.req;
|
|
28
|
+
const res = ctx.res;
|
|
4
29
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
}
|
|
30
|
+
// ctx.args 从 metadata.args 推断
|
|
31
|
+
const name: string = ctx.args.name;
|
|
32
|
+
const name2: string = ctx.query.name;
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
ctx.body = `Hello ${name}!`;
|
|
36
|
+
});
|
|
37
|
+
// 示例 2: 使用 QueryRouterServer,它可以传递自定义的 Context 类型
|
|
38
|
+
const router = new QueryRouterServer<{
|
|
39
|
+
routerContextField: number;
|
|
40
|
+
}>();
|
|
41
|
+
router.context.routerContextField
|
|
42
|
+
router.route({
|
|
43
|
+
path: 'router-test',
|
|
44
|
+
metadata: {
|
|
45
|
+
args: {
|
|
46
|
+
value: z.number(),
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
}).define(async (ctx) => {
|
|
50
|
+
const value: number = ctx.args.value;
|
|
51
|
+
const field: number | undefined = ctx.routerContextField;
|
|
52
|
+
|
|
53
|
+
ctx.body = value;
|
|
54
|
+
});
|
|
55
|
+
// 示例 3: 不带泛型参数的 QueryRouterServer,使用默认的 RouteContext
|
|
56
|
+
const defaultRouter = new QueryRouterServer();
|
|
57
|
+
defaultRouter.route({
|
|
58
|
+
path: 'default-test',
|
|
59
|
+
metadata: {
|
|
60
|
+
args: {
|
|
61
|
+
id: z.string(),
|
|
62
|
+
}
|
|
63
|
+
},
|
|
11
64
|
}).define(async (ctx) => {
|
|
12
|
-
|
|
13
|
-
|
|
65
|
+
const id: string = ctx.args.id;
|
|
66
|
+
|
|
67
|
+
ctx.body = id;
|
|
68
|
+
});
|
|
69
|
+
export { app, router, defaultRouter };
|
package/src/test/chat.ts
CHANGED