@kevisual/router 0.2.11 → 0.2.14
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/README.md +169 -12
- package/dist/app.js +1271 -550
- package/dist/commander.d.ts +53 -38
- package/dist/commander.js +1211 -492
- package/dist/opencode.d.ts +53 -38
- package/dist/opencode.js +1209 -488
- package/dist/router-browser.d.ts +53 -38
- package/dist/router-browser.js +1238 -519
- package/dist/router-define.d.ts +53 -38
- package/dist/router-simple.js +1 -1
- package/dist/router.d.ts +53 -38
- package/dist/router.js +1261 -540
- package/dist/ws.d.ts +53 -38
- package/dist/ws.js +14 -14
- package/package.json +11 -10
- package/src/result/error.ts +4 -0
- package/src/route.ts +52 -36
- package/src/test/app-type.ts +1 -1
- package/src/test/route-ts.ts +17 -3
- package/src/test/run-schema.ts +15 -1
- package/src/types/index.ts +39 -0
- package/src/test/chat.ts +0 -17
package/dist/ws.d.ts
CHANGED
|
@@ -76,6 +76,57 @@ declare class MockProcess {
|
|
|
76
76
|
desctroy(): void;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** JSON Schema 基本类型映射到 TypeScript 类型 */
|
|
80
|
+
type JsonSchemaTypeToTS<T> = T extends {
|
|
81
|
+
type: "string";
|
|
82
|
+
} ? string : T extends {
|
|
83
|
+
type: "boolean";
|
|
84
|
+
} ? boolean : T extends {
|
|
85
|
+
type: "number";
|
|
86
|
+
} ? number : T extends {
|
|
87
|
+
type: "integer";
|
|
88
|
+
} ? number : T extends {
|
|
89
|
+
type: "object";
|
|
90
|
+
} ? object : T extends {
|
|
91
|
+
type: "array";
|
|
92
|
+
} ? any[] : any;
|
|
93
|
+
/** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
|
|
94
|
+
type ArgsShapeToPayload<T> = {
|
|
95
|
+
[K in keyof T as T[K] extends {
|
|
96
|
+
optional: true;
|
|
97
|
+
} ? never : K]: JsonSchemaTypeToTS<T[K]>;
|
|
98
|
+
} & {
|
|
99
|
+
[K in keyof T as T[K] extends {
|
|
100
|
+
optional: true;
|
|
101
|
+
} ? K : never]?: JsonSchemaTypeToTS<T[K]>;
|
|
102
|
+
};
|
|
103
|
+
/** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
|
|
104
|
+
type ArgsToPayload<T> = T extends {
|
|
105
|
+
type: "object";
|
|
106
|
+
properties: infer P;
|
|
107
|
+
} ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
|
|
108
|
+
/** 从 API 定义中提取 metadata.args */
|
|
109
|
+
type ExtractArgs<T> = T extends {
|
|
110
|
+
metadata: {
|
|
111
|
+
args: infer A;
|
|
112
|
+
};
|
|
113
|
+
} ? A : {};
|
|
114
|
+
/** 从 API 定义中提取 metadata.returns */
|
|
115
|
+
type ExtractReturns<T> = T extends {
|
|
116
|
+
metadata: {
|
|
117
|
+
returns: infer R;
|
|
118
|
+
};
|
|
119
|
+
} ? R : unknown;
|
|
120
|
+
/** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
|
|
121
|
+
type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
|
|
122
|
+
/** runAction 的返回类型,根据 API 定义中的 metadata.returns 推断 data 字段类型 */
|
|
123
|
+
type RunActionReturns<T> = {
|
|
124
|
+
code: number | string;
|
|
125
|
+
data?: unknown extends ExtractReturns<T> ? any : ArgsToPayload<ExtractReturns<T>>;
|
|
126
|
+
message?: string;
|
|
127
|
+
[key: string]: any;
|
|
128
|
+
};
|
|
129
|
+
|
|
79
130
|
type RouterContextT = {
|
|
80
131
|
code?: number;
|
|
81
132
|
[key: string]: any;
|
|
@@ -516,8 +567,9 @@ declare class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Q
|
|
|
516
567
|
key?: string;
|
|
517
568
|
metadata?: {
|
|
518
569
|
args?: any;
|
|
570
|
+
returns?: any;
|
|
519
571
|
};
|
|
520
|
-
} = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<
|
|
572
|
+
} = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<RunActionReturns<T>>;
|
|
521
573
|
/**
|
|
522
574
|
* 创建认证相关的中间件,默认是 auth, auth-admin, auth-can 三个中间件
|
|
523
575
|
* @param fun 认证函数,接收 RouteContext 和认证类型
|
|
@@ -526,43 +578,6 @@ declare class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Q
|
|
|
526
578
|
overwrite?: boolean;
|
|
527
579
|
}): Promise<void>;
|
|
528
580
|
}
|
|
529
|
-
/** JSON Schema 基本类型映射到 TypeScript 类型 */
|
|
530
|
-
type JsonSchemaTypeToTS<T> = T extends {
|
|
531
|
-
type: "string";
|
|
532
|
-
} ? string : T extends {
|
|
533
|
-
type: "boolean";
|
|
534
|
-
} ? boolean : T extends {
|
|
535
|
-
type: "number";
|
|
536
|
-
} ? number : T extends {
|
|
537
|
-
type: "integer";
|
|
538
|
-
} ? number : T extends {
|
|
539
|
-
type: "object";
|
|
540
|
-
} ? object : T extends {
|
|
541
|
-
type: "array";
|
|
542
|
-
} ? any[] : any;
|
|
543
|
-
/** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
|
|
544
|
-
type ArgsShapeToPayload<T> = {
|
|
545
|
-
[K in keyof T as T[K] extends {
|
|
546
|
-
optional: true;
|
|
547
|
-
} ? never : K]: JsonSchemaTypeToTS<T[K]>;
|
|
548
|
-
} & {
|
|
549
|
-
[K in keyof T as T[K] extends {
|
|
550
|
-
optional: true;
|
|
551
|
-
} ? K : never]?: JsonSchemaTypeToTS<T[K]>;
|
|
552
|
-
};
|
|
553
|
-
/** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
|
|
554
|
-
type ArgsToPayload<T> = T extends {
|
|
555
|
-
type: "object";
|
|
556
|
-
properties: infer P;
|
|
557
|
-
} ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
|
|
558
|
-
/** 从 API 定义中提取 metadata.args */
|
|
559
|
-
type ExtractArgs<T> = T extends {
|
|
560
|
-
metadata: {
|
|
561
|
-
args: infer A;
|
|
562
|
-
};
|
|
563
|
-
} ? A : {};
|
|
564
|
-
/** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
|
|
565
|
-
type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
|
|
566
581
|
|
|
567
582
|
type Cors = {
|
|
568
583
|
/**
|
package/dist/ws.js
CHANGED
|
@@ -32,7 +32,7 @@ var __toESM = (mod, isNodeMode, target) => {
|
|
|
32
32
|
var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
|
|
33
33
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
34
34
|
|
|
35
|
-
// node_modules/ws/lib/constants.js
|
|
35
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/constants.js
|
|
36
36
|
var require_constants = __commonJS((exports, module) => {
|
|
37
37
|
var BINARY_TYPES = ["nodebuffer", "arraybuffer", "fragments"];
|
|
38
38
|
var hasBlob = typeof Blob !== "undefined";
|
|
@@ -51,7 +51,7 @@ var require_constants = __commonJS((exports, module) => {
|
|
|
51
51
|
};
|
|
52
52
|
});
|
|
53
53
|
|
|
54
|
-
// node_modules/ws/lib/buffer-util.js
|
|
54
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/buffer-util.js
|
|
55
55
|
var require_buffer_util = __commonJS((exports, module) => {
|
|
56
56
|
var { EMPTY_BUFFER } = require_constants();
|
|
57
57
|
var FastBuffer = Buffer[Symbol.species];
|
|
@@ -112,7 +112,7 @@ var require_buffer_util = __commonJS((exports, module) => {
|
|
|
112
112
|
};
|
|
113
113
|
});
|
|
114
114
|
|
|
115
|
-
// node_modules/ws/lib/limiter.js
|
|
115
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/limiter.js
|
|
116
116
|
var require_limiter = __commonJS((exports, module) => {
|
|
117
117
|
var kDone = Symbol("kDone");
|
|
118
118
|
var kRun = Symbol("kRun");
|
|
@@ -144,7 +144,7 @@ var require_limiter = __commonJS((exports, module) => {
|
|
|
144
144
|
module.exports = Limiter;
|
|
145
145
|
});
|
|
146
146
|
|
|
147
|
-
// node_modules/ws/lib/permessage-deflate.js
|
|
147
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/permessage-deflate.js
|
|
148
148
|
var require_permessage_deflate = __commonJS((exports, module) => {
|
|
149
149
|
var zlib = __require("zlib");
|
|
150
150
|
var bufferUtil = require_buffer_util();
|
|
@@ -404,7 +404,7 @@ var require_permessage_deflate = __commonJS((exports, module) => {
|
|
|
404
404
|
}
|
|
405
405
|
});
|
|
406
406
|
|
|
407
|
-
// node_modules/ws/lib/validation.js
|
|
407
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/validation.js
|
|
408
408
|
var require_validation = __commonJS((exports, module) => {
|
|
409
409
|
var { isUtf8 } = __require("buffer");
|
|
410
410
|
var { hasBlob } = require_constants();
|
|
@@ -584,7 +584,7 @@ var require_validation = __commonJS((exports, module) => {
|
|
|
584
584
|
}
|
|
585
585
|
});
|
|
586
586
|
|
|
587
|
-
// node_modules/ws/lib/receiver.js
|
|
587
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/receiver.js
|
|
588
588
|
var require_receiver = __commonJS((exports, module) => {
|
|
589
589
|
var { Writable } = __require("stream");
|
|
590
590
|
var PerMessageDeflate = require_permessage_deflate();
|
|
@@ -965,7 +965,7 @@ var require_receiver = __commonJS((exports, module) => {
|
|
|
965
965
|
module.exports = Receiver;
|
|
966
966
|
});
|
|
967
967
|
|
|
968
|
-
// node_modules/ws/lib/sender.js
|
|
968
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/sender.js
|
|
969
969
|
var require_sender = __commonJS((exports, module) => {
|
|
970
970
|
var { Duplex } = __require("stream");
|
|
971
971
|
var { randomFillSync } = __require("crypto");
|
|
@@ -1319,7 +1319,7 @@ var require_sender = __commonJS((exports, module) => {
|
|
|
1319
1319
|
}
|
|
1320
1320
|
});
|
|
1321
1321
|
|
|
1322
|
-
// node_modules/ws/lib/event-target.js
|
|
1322
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/event-target.js
|
|
1323
1323
|
var require_event_target = __commonJS((exports, module) => {
|
|
1324
1324
|
var { kForOnEventAttribute, kListener } = require_constants();
|
|
1325
1325
|
var kCode = Symbol("kCode");
|
|
@@ -1470,7 +1470,7 @@ var require_event_target = __commonJS((exports, module) => {
|
|
|
1470
1470
|
}
|
|
1471
1471
|
});
|
|
1472
1472
|
|
|
1473
|
-
// node_modules/ws/lib/extension.js
|
|
1473
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/extension.js
|
|
1474
1474
|
var require_extension = __commonJS((exports, module) => {
|
|
1475
1475
|
var { tokenChars } = require_validation();
|
|
1476
1476
|
function push(dest, name, elem) {
|
|
@@ -1635,7 +1635,7 @@ var require_extension = __commonJS((exports, module) => {
|
|
|
1635
1635
|
module.exports = { format, parse };
|
|
1636
1636
|
});
|
|
1637
1637
|
|
|
1638
|
-
// node_modules/ws/lib/websocket.js
|
|
1638
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/websocket.js
|
|
1639
1639
|
var require_websocket = __commonJS((exports, module) => {
|
|
1640
1640
|
var EventEmitter = __require("events");
|
|
1641
1641
|
var https = __require("https");
|
|
@@ -2394,7 +2394,7 @@ var require_websocket = __commonJS((exports, module) => {
|
|
|
2394
2394
|
}
|
|
2395
2395
|
});
|
|
2396
2396
|
|
|
2397
|
-
// node_modules/ws/lib/stream.js
|
|
2397
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/stream.js
|
|
2398
2398
|
var require_stream = __commonJS((exports, module) => {
|
|
2399
2399
|
var WebSocket = require_websocket();
|
|
2400
2400
|
var { Duplex } = __require("stream");
|
|
@@ -2497,7 +2497,7 @@ var require_stream = __commonJS((exports, module) => {
|
|
|
2497
2497
|
module.exports = createWebSocketStream;
|
|
2498
2498
|
});
|
|
2499
2499
|
|
|
2500
|
-
// node_modules/ws/lib/subprotocol.js
|
|
2500
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/subprotocol.js
|
|
2501
2501
|
var require_subprotocol = __commonJS((exports, module) => {
|
|
2502
2502
|
var { tokenChars } = require_validation();
|
|
2503
2503
|
function parse(header) {
|
|
@@ -2542,7 +2542,7 @@ var require_subprotocol = __commonJS((exports, module) => {
|
|
|
2542
2542
|
module.exports = { parse };
|
|
2543
2543
|
});
|
|
2544
2544
|
|
|
2545
|
-
// node_modules/ws/lib/websocket-server.js
|
|
2545
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/lib/websocket-server.js
|
|
2546
2546
|
var require_websocket_server = __commonJS((exports, module) => {
|
|
2547
2547
|
var EventEmitter = __require("events");
|
|
2548
2548
|
var http = __require("http");
|
|
@@ -2841,7 +2841,7 @@ var require_websocket_server = __commonJS((exports, module) => {
|
|
|
2841
2841
|
}
|
|
2842
2842
|
});
|
|
2843
2843
|
|
|
2844
|
-
// node_modules/ws/wrapper.mjs
|
|
2844
|
+
// node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/wrapper.mjs
|
|
2845
2845
|
var import_stream = __toESM(require_stream(), 1);
|
|
2846
2846
|
var import_receiver = __toESM(require_receiver(), 1);
|
|
2847
2847
|
var import_sender = __toESM(require_sender(), 1);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@kevisual/router",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.14",
|
|
5
5
|
"description": "",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "./dist/router.js",
|
|
@@ -23,26 +23,26 @@
|
|
|
23
23
|
"license": "MIT",
|
|
24
24
|
"devDependencies": {
|
|
25
25
|
"@kevisual/code-builder": "^0.0.7",
|
|
26
|
-
"@kevisual/context": "^0.
|
|
26
|
+
"@kevisual/context": "^0.1.1",
|
|
27
27
|
"@kevisual/dts": "^0.0.4",
|
|
28
28
|
"@kevisual/js-filter": "^0.0.6",
|
|
29
29
|
"@kevisual/local-proxy": "^0.0.8",
|
|
30
|
-
"@kevisual/query": "^0.0.
|
|
30
|
+
"@kevisual/query": "^0.0.58",
|
|
31
31
|
"@kevisual/remote-app": "^0.0.7",
|
|
32
32
|
"@kevisual/use-config": "^1.0.30",
|
|
33
|
-
"@opencode-ai/plugin": "^1.
|
|
34
|
-
"@types/bun": "^1.3.
|
|
33
|
+
"@opencode-ai/plugin": "^1.15.13",
|
|
34
|
+
"@types/bun": "^1.3.14",
|
|
35
35
|
"@types/crypto-js": "^4.2.2",
|
|
36
|
-
"@types/node": "^25.
|
|
36
|
+
"@types/node": "^25.9.1",
|
|
37
37
|
"@types/send": "^1.2.1",
|
|
38
38
|
"@types/ws": "^8.18.1",
|
|
39
39
|
"@types/xml2js": "^0.4.14",
|
|
40
|
-
"commander": "^
|
|
40
|
+
"commander": "^15.0.0",
|
|
41
41
|
"crypto-js": "^4.2.0",
|
|
42
|
-
"es-toolkit": "^1.
|
|
42
|
+
"es-toolkit": "^1.47.0",
|
|
43
43
|
"eventemitter3": "^5.0.4",
|
|
44
44
|
"fast-glob": "^3.3.3",
|
|
45
|
-
"nanoid": "^5.1.
|
|
45
|
+
"nanoid": "^5.1.11",
|
|
46
46
|
"path-to-regexp": "^8.4.2",
|
|
47
47
|
"send": "^1.2.1",
|
|
48
48
|
"typescript": "^6.0.3",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"url": "git+https://github.com/abearxiong/kevisual-router.git"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"zod": "^4.3
|
|
57
|
+
"zod": "^4.4.3"
|
|
58
58
|
},
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
@@ -70,6 +70,7 @@
|
|
|
70
70
|
"./ws": "./dist/ws.js",
|
|
71
71
|
"./mod.ts": "./mod.ts",
|
|
72
72
|
"./src/*": "./src/*",
|
|
73
|
+
"./types/*": "./src/types/*",
|
|
73
74
|
"./modules/*": "./src/modules/*"
|
|
74
75
|
}
|
|
75
76
|
}
|
package/src/result/error.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export type CustomErrorOptions = {
|
|
2
2
|
cause?: Error | string;
|
|
3
3
|
code?: number;
|
|
4
|
+
data?: any;
|
|
4
5
|
message?: string;
|
|
5
6
|
}
|
|
6
7
|
/** 自定义错误 */
|
|
@@ -19,6 +20,9 @@ export class CustomError extends Error {
|
|
|
19
20
|
this.name = 'RouterError';
|
|
20
21
|
let codeNum = opts?.code || (typeof code === 'number' ? code : undefined);
|
|
21
22
|
this.code = codeNum ?? 500;
|
|
23
|
+
if (opts.data) {
|
|
24
|
+
this.data = opts.data;
|
|
25
|
+
}
|
|
22
26
|
this.message = message!;
|
|
23
27
|
// 这一步可不写,默认会保存堆栈追踪信息到自定义错误构造函数之前,
|
|
24
28
|
// 而如果写成 `Error.captureStackTrace(this)` 则自定义错误的构造函数也会被保存到堆栈追踪信息
|
package/src/route.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { CustomError, throwError } from './result/error.ts';
|
|
2
2
|
import { pick } from './utils/pick.ts';
|
|
3
3
|
import { listenProcess, MockProcess } from './utils/listen-process.ts';
|
|
4
|
-
import { z } from 'zod';
|
|
4
|
+
import { z, ZodError } from 'zod';
|
|
5
5
|
import { hashIdMd5Sync, randomId } from './utils/random.ts';
|
|
6
6
|
import * as schema from './validator/schema.ts';
|
|
7
|
-
|
|
7
|
+
import type { RunActionPayload, RunActionReturns } from './types/index.ts'
|
|
8
8
|
export type RouterContextT = { code?: number;[key: string]: any };
|
|
9
9
|
|
|
10
10
|
type BuildRouteContext<M, U> = M extends { args?: infer A }
|
|
@@ -57,6 +57,11 @@ export type RouteContext<T = { code?: number }, U extends SimpleObject = {}, S =
|
|
|
57
57
|
* 进度
|
|
58
58
|
*/
|
|
59
59
|
progress?: [string, string][];
|
|
60
|
+
safeParseAsync?: (data?: any, opts?: {
|
|
61
|
+
schema?: { [key: string]: z.ZodTypeAny },
|
|
62
|
+
zodOptions?: any,
|
|
63
|
+
stop?: boolean, // 如果验证失败,是否停止后续的 route 执行,默认 true
|
|
64
|
+
}) => Promise<{ success: boolean; data?: any; error?: any }>;
|
|
60
65
|
// onlyForNextRoute will be clear after next route
|
|
61
66
|
nextQuery?: { [key: string]: any };
|
|
62
67
|
// end
|
|
@@ -93,7 +98,7 @@ export type RouteOpts<U = {}, T = SimpleObject> = {
|
|
|
93
98
|
run?: Run<U>;
|
|
94
99
|
nextRoute?: NextRoute; // route to run after this route
|
|
95
100
|
description?: string;
|
|
96
|
-
metadata?: T
|
|
101
|
+
metadata?: Metadata<T>;
|
|
97
102
|
middleware?: RouteMiddleware[]; // middleware
|
|
98
103
|
type?: 'route' | 'middleware' | 'compound'; // compound表示这个 route 作为一个聚合体,没有实际的 run,而是一个 router 的聚合列表
|
|
99
104
|
isDebug?: boolean;
|
|
@@ -129,12 +134,16 @@ export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
|
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
|
|
132
|
-
|
|
137
|
+
export type Metadata<T = SimpleObject> = {
|
|
138
|
+
args?: Record<string, z.ZodTypeAny> | z.ZodObject<any>;
|
|
139
|
+
returns?: Record<string, z.ZodTypeAny> | z.ZodObject<any>;
|
|
140
|
+
check?: boolean;
|
|
141
|
+
} & T;
|
|
133
142
|
/**
|
|
134
143
|
* @M 是 route的 metadate的类型,默认是 SimpleObject
|
|
135
144
|
* @U 是 RouteContext 里 state的类型
|
|
136
145
|
*/
|
|
137
|
-
export class Route<M extends
|
|
146
|
+
export class Route<M extends Metadata = Metadata, U extends SimpleObject = SimpleObject> implements throwError {
|
|
138
147
|
/**
|
|
139
148
|
* 一级路径
|
|
140
149
|
*/
|
|
@@ -315,6 +324,21 @@ export class QueryRouter<T extends SimpleObject = SimpleObject> implements throw
|
|
|
315
324
|
removeById(uniqueId: string) {
|
|
316
325
|
this.routes = this.routes.filter((r) => r.rid !== uniqueId);
|
|
317
326
|
}
|
|
327
|
+
safeParseAsyncRoute(data: any, opts: { route: RouteInfo, schema?: { [key: string]: z.ZodTypeAny }, zodOptions?: any }): Promise<{ success: boolean; data?: any; error?: any }> {
|
|
328
|
+
const route = opts.route;
|
|
329
|
+
const argZod = route.metadata?.args as Record<string, z.ZodTypeAny>;
|
|
330
|
+
const schemaZod = opts.schema || {};
|
|
331
|
+
const zodOptions = opts.zodOptions;
|
|
332
|
+
const keys = Object.keys(argZod || {});
|
|
333
|
+
if (argZod && keys.length > 0) {
|
|
334
|
+
const mgZod = z.object({
|
|
335
|
+
...argZod,
|
|
336
|
+
...schemaZod
|
|
337
|
+
});
|
|
338
|
+
return mgZod.safeParseAsync(data, zodOptions);
|
|
339
|
+
}
|
|
340
|
+
return Promise.resolve({ success: true, data });
|
|
341
|
+
}
|
|
318
342
|
/**
|
|
319
343
|
* 执行route
|
|
320
344
|
* @param path
|
|
@@ -332,6 +356,21 @@ export class QueryRouter<T extends SimpleObject = SimpleObject> implements throw
|
|
|
332
356
|
ctx.currentRoute = route;
|
|
333
357
|
ctx.index = (ctx.index || 0) + 1;
|
|
334
358
|
const progress = [path, key] as [string, string];
|
|
359
|
+
ctx.safeParseAsync = async (data?: any, opts?: { schema?: { [key: string]: z.ZodTypeAny }, zodOptions?: any, stop?: boolean }) => {
|
|
360
|
+
const stop = opts?.stop ?? true;
|
|
361
|
+
const _query = { ...ctx.query, ...data };
|
|
362
|
+
const res = await this.safeParseAsyncRoute(_query, { route: route, ...opts });
|
|
363
|
+
if (!res.success && stop) {
|
|
364
|
+
const issues = res.error.issues;
|
|
365
|
+
ctx.throw({
|
|
366
|
+
// Unprocessable Entity
|
|
367
|
+
code: 422,
|
|
368
|
+
data: issues,
|
|
369
|
+
message: 'Validation Error:' + JSON.stringify(issues, null, 2),
|
|
370
|
+
})
|
|
371
|
+
}
|
|
372
|
+
return res;
|
|
373
|
+
}
|
|
335
374
|
if (ctx.progress) {
|
|
336
375
|
ctx.progress.push(progress);
|
|
337
376
|
} else {
|
|
@@ -406,7 +445,7 @@ export class QueryRouter<T extends SimpleObject = SimpleObject> implements throw
|
|
|
406
445
|
if (e instanceof CustomError || e?.code) {
|
|
407
446
|
ctx.code = e.code;
|
|
408
447
|
ctx.message = e.message;
|
|
409
|
-
ctx.body =
|
|
448
|
+
ctx.body = e.data;
|
|
410
449
|
} else {
|
|
411
450
|
console.error(`[router error] fn:${route.path}-${route.key}:${route.rid}`);
|
|
412
451
|
console.error(`[router error] middleware:${middleware.path}-${middleware.key}:${middleware.rid}`);
|
|
@@ -427,6 +466,9 @@ export class QueryRouter<T extends SimpleObject = SimpleObject> implements throw
|
|
|
427
466
|
if (route) {
|
|
428
467
|
if (route.run) {
|
|
429
468
|
try {
|
|
469
|
+
if (route.metadata?.check) {
|
|
470
|
+
await ctx.safeParseAsync(null, { stop: true });
|
|
471
|
+
}
|
|
430
472
|
await route.run(ctx as Required<RouteContext<T>>);
|
|
431
473
|
} catch (e) {
|
|
432
474
|
if (route?.isDebug) {
|
|
@@ -437,13 +479,14 @@ export class QueryRouter<T extends SimpleObject = SimpleObject> implements throw
|
|
|
437
479
|
if (e instanceof CustomError || e?.code) {
|
|
438
480
|
ctx.code = e.code;
|
|
439
481
|
ctx.message = e.message;
|
|
482
|
+
ctx.body = e.data;
|
|
440
483
|
} else {
|
|
441
484
|
console.error(`[router error] fn:${route.path}-${route.key}:${route.rid}`);
|
|
442
485
|
console.error(`[router error] error`, e);
|
|
443
486
|
ctx.code = 500;
|
|
444
487
|
ctx.message = 'Internal Server Error';
|
|
488
|
+
ctx.body = null;
|
|
445
489
|
}
|
|
446
|
-
ctx.body = null;
|
|
447
490
|
return ctx;
|
|
448
491
|
}
|
|
449
492
|
if (ctx.end) {
|
|
@@ -781,11 +824,11 @@ export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Qu
|
|
|
781
824
|
async runLocal(msg: { rid?: string; path?: string; key?: string; payload?: any, args?: any, token?: string, data?: any }, ctx?: Partial<RouteContext<C>>) {
|
|
782
825
|
return this.run(msg, { ...ctx, appId: this.appId } as RouteContext<C>);
|
|
783
826
|
}
|
|
784
|
-
async runAction<T extends { rid?: string; path?: string; key?: string; metadata?: { args?: any } } = {}>(
|
|
827
|
+
async runAction<T extends { rid?: string; path?: string; key?: string; metadata?: { args?: any, returns?: any } } = {}>(
|
|
785
828
|
api: T,
|
|
786
829
|
payload: RunActionPayload<T>,
|
|
787
830
|
ctx?: RouteContext<C>
|
|
788
|
-
) {
|
|
831
|
+
): Promise<RunActionReturns<T>> {
|
|
789
832
|
const { path, key, rid } = api as any;
|
|
790
833
|
return this.run({ path, key, rid, payload }, ctx);
|
|
791
834
|
}
|
|
@@ -834,30 +877,3 @@ export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Qu
|
|
|
834
877
|
|
|
835
878
|
export class Mini extends QueryRouterServer { }
|
|
836
879
|
|
|
837
|
-
/** JSON Schema 基本类型映射到 TypeScript 类型 */
|
|
838
|
-
type JsonSchemaTypeToTS<T> =
|
|
839
|
-
T extends { type: "string" } ? string :
|
|
840
|
-
T extends { type: "boolean" } ? boolean :
|
|
841
|
-
T extends { type: "number" } ? number :
|
|
842
|
-
T extends { type: "integer" } ? number :
|
|
843
|
-
T extends { type: "object" } ? object :
|
|
844
|
-
T extends { type: "array" } ? any[] :
|
|
845
|
-
any;
|
|
846
|
-
|
|
847
|
-
/** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
|
|
848
|
-
type ArgsShapeToPayload<T> =
|
|
849
|
-
{ [K in keyof T as T[K] extends { optional: true } ? never : K]: JsonSchemaTypeToTS<T[K]> } &
|
|
850
|
-
{ [K in keyof T as T[K] extends { optional: true } ? K : never]?: JsonSchemaTypeToTS<T[K]> };
|
|
851
|
-
|
|
852
|
-
/** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
|
|
853
|
-
type ArgsToPayload<T> =
|
|
854
|
-
T extends { type: "object"; properties: infer P }
|
|
855
|
-
? ArgsShapeToPayload<P>
|
|
856
|
-
: ArgsShapeToPayload<T>;
|
|
857
|
-
|
|
858
|
-
/** 从 API 定义中提取 metadata.args */
|
|
859
|
-
type ExtractArgs<T> =
|
|
860
|
-
T extends { metadata: { args: infer A } } ? A : {};
|
|
861
|
-
|
|
862
|
-
/** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
|
|
863
|
-
export type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
|
package/src/test/app-type.ts
CHANGED
package/src/test/route-ts.ts
CHANGED
|
@@ -1,15 +1,29 @@
|
|
|
1
1
|
import { QueryRouterServer } from "@/route.ts";
|
|
2
2
|
import z from "zod";
|
|
3
|
+
import { tr } from "zod/v4/locales";
|
|
3
4
|
|
|
4
5
|
const router = new QueryRouterServer()
|
|
5
6
|
|
|
6
7
|
router.route({
|
|
8
|
+
path: 'test',
|
|
7
9
|
metadata: {
|
|
8
10
|
args: {
|
|
9
11
|
a: z.string(),
|
|
10
|
-
}
|
|
12
|
+
},
|
|
13
|
+
check: true,
|
|
11
14
|
},
|
|
12
15
|
}).define(async (ctx) => {
|
|
13
|
-
const
|
|
16
|
+
const argZod = ctx.currentRoute.metadata.args as Record<string, z.ZodTypeAny>;
|
|
17
|
+
const mgZod = z.object(argZod);
|
|
18
|
+
// console.log('argZod', argZod);
|
|
19
|
+
// const argA: string = ctx.args.a;
|
|
20
|
+
// const res = await ctx.safeParseAsync(null, { stop: true });
|
|
21
|
+
// // console.log('argA', argA);
|
|
22
|
+
// if (!res.success) {
|
|
23
|
+
// console.log('res===', res.error.issues);
|
|
24
|
+
// }
|
|
14
25
|
ctx.body = '1';
|
|
15
|
-
}).addTo(router);
|
|
26
|
+
}).addTo(router);
|
|
27
|
+
|
|
28
|
+
const res = await router.run({ path: 'test', payload: { a: 'abc' } });
|
|
29
|
+
console.log('res', res);
|
package/src/test/run-schema.ts
CHANGED
|
@@ -70,6 +70,19 @@ const api = {
|
|
|
70
70
|
"includeProfile": {
|
|
71
71
|
"type": "boolean"
|
|
72
72
|
}
|
|
73
|
+
},
|
|
74
|
+
"returns": {
|
|
75
|
+
"type": "object",
|
|
76
|
+
"properties": {
|
|
77
|
+
"name": {
|
|
78
|
+
"type": "string"
|
|
79
|
+
},
|
|
80
|
+
"age": {
|
|
81
|
+
"type": "number"
|
|
82
|
+
}
|
|
83
|
+
},
|
|
84
|
+
"additionalProperties": false,
|
|
85
|
+
"required": ["name", "age"]
|
|
73
86
|
}
|
|
74
87
|
}
|
|
75
88
|
}
|
|
@@ -85,4 +98,5 @@ app.runAction(a2.app_domain_manager.get, { data: { idd: "1" }, })
|
|
|
85
98
|
app.runAction(api.app_domain_manager.delete, { domainId: "d1" })
|
|
86
99
|
|
|
87
100
|
// getUser 的 args 是 { userId: string, includeProfile: boolean }
|
|
88
|
-
app.runAction(api.user_manager.getUser, { userId: "u1", includeProfile: true })
|
|
101
|
+
const res = await app.runAction(api.user_manager.getUser, { userId: "u1", includeProfile: true })
|
|
102
|
+
const name: string = res.data.name;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** JSON Schema 基本类型映射到 TypeScript 类型 */
|
|
2
|
+
type JsonSchemaTypeToTS<T> =
|
|
3
|
+
T extends { type: "string" } ? string :
|
|
4
|
+
T extends { type: "boolean" } ? boolean :
|
|
5
|
+
T extends { type: "number" } ? number :
|
|
6
|
+
T extends { type: "integer" } ? number :
|
|
7
|
+
T extends { type: "object" } ? object :
|
|
8
|
+
T extends { type: "array" } ? any[] :
|
|
9
|
+
any;
|
|
10
|
+
|
|
11
|
+
/** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
|
|
12
|
+
type ArgsShapeToPayload<T> =
|
|
13
|
+
{ [K in keyof T as T[K] extends { optional: true } ? never : K]: JsonSchemaTypeToTS<T[K]> } &
|
|
14
|
+
{ [K in keyof T as T[K] extends { optional: true } ? K : never]?: JsonSchemaTypeToTS<T[K]> };
|
|
15
|
+
|
|
16
|
+
/** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
|
|
17
|
+
type ArgsToPayload<T> =
|
|
18
|
+
T extends { type: "object"; properties: infer P }
|
|
19
|
+
? ArgsShapeToPayload<P>
|
|
20
|
+
: ArgsShapeToPayload<T>;
|
|
21
|
+
|
|
22
|
+
/** 从 API 定义中提取 metadata.args */
|
|
23
|
+
type ExtractArgs<T> =
|
|
24
|
+
T extends { metadata: { args: infer A } } ? A : {};
|
|
25
|
+
|
|
26
|
+
/** 从 API 定义中提取 metadata.returns */
|
|
27
|
+
type ExtractReturns<T> =
|
|
28
|
+
T extends { metadata: { returns: infer R } } ? R : unknown;
|
|
29
|
+
|
|
30
|
+
/** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
|
|
31
|
+
export type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
|
|
32
|
+
|
|
33
|
+
/** runAction 的返回类型,根据 API 定义中的 metadata.returns 推断 data 字段类型 */
|
|
34
|
+
export type RunActionReturns<T> = {
|
|
35
|
+
code: number | string;
|
|
36
|
+
data?: unknown extends ExtractReturns<T> ? any : ArgsToPayload<ExtractReturns<T>>;
|
|
37
|
+
message?: string;
|
|
38
|
+
[key: string]: any;
|
|
39
|
+
};
|
package/src/test/chat.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { App } from '../app.ts'
|
|
2
|
-
import { RouterChat } from '@/modules/chat.ts';
|
|
3
|
-
|
|
4
|
-
const app = new App();
|
|
5
|
-
|
|
6
|
-
app.prompt(`获取时间的工具`).define(async (ctx) => {
|
|
7
|
-
ctx.body = '123'
|
|
8
|
-
}).addTo(app);
|
|
9
|
-
|
|
10
|
-
app.prompt('获取天气的工具。\n参数是 city 为对应的城市').define(async (ctx) => {
|
|
11
|
-
ctx.body = '晴天'
|
|
12
|
-
}).addTo(app);
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
export const chat = new RouterChat({ router: app.router });
|
|
16
|
-
|
|
17
|
-
console.log(chat.getChatPrompt());
|