@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/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<any>;
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.11",
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.0.10",
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.56",
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.14.22",
34
- "@types/bun": "^1.3.13",
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.6.0",
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": "^14.0.3",
40
+ "commander": "^15.0.0",
41
41
  "crypto-js": "^4.2.0",
42
- "es-toolkit": "^1.46.0",
42
+ "es-toolkit": "^1.47.0",
43
43
  "eventemitter3": "^5.0.4",
44
44
  "fast-glob": "^3.3.3",
45
- "nanoid": "^5.1.9",
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.6"
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
  }
@@ -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 SimpleObject = SimpleObject, U extends SimpleObject = SimpleObject> implements throwError {
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 = null;
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>>;
@@ -15,7 +15,7 @@ app.route({
15
15
  metadata: {
16
16
  args: {
17
17
  name: z.string(),
18
- }
18
+ },
19
19
  },
20
20
  }).define(async (ctx) => {
21
21
  // ctx.app 是 App 类型
@@ -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 argA: string = ctx.args.a;
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);
@@ -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());