@kevisual/router 0.0.84 → 0.0.86

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/app.js CHANGED
@@ -3091,7 +3091,7 @@ function pick(obj, keys) {
3091
3091
  // node_modules/.pnpm/eventemitter3@5.0.4/node_modules/eventemitter3/index.mjs
3092
3092
  var import__ = __toESM(require_eventemitter3(), 1);
3093
3093
 
3094
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
3094
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
3095
3095
  function isPlainObject(value) {
3096
3096
  if (!value || typeof value !== "object") {
3097
3097
  return false;
@@ -3104,12 +3104,12 @@ function isPlainObject(value) {
3104
3104
  return Object.prototype.toString.call(value) === "[object Object]";
3105
3105
  }
3106
3106
 
3107
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
3107
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
3108
3108
  function isUnsafeProperty(key) {
3109
3109
  return key === "__proto__";
3110
3110
  }
3111
3111
 
3112
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/object/merge.mjs
3112
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/object/merge.mjs
3113
3113
  function merge(target, source) {
3114
3114
  const sourceKeys = Object.keys(source);
3115
3115
  for (let i = 0;i < sourceKeys.length; i++) {
@@ -16932,7 +16932,7 @@ class Route {
16932
16932
  if (opts) {
16933
16933
  this.id = opts.id || randomId(12, "rand-");
16934
16934
  if (!opts.id && opts.idUsePath) {
16935
- const delimiter = opts.delimiter ?? "$#$";
16935
+ const delimiter = opts.delimiter ?? "$$";
16936
16936
  this.id = path + delimiter + key;
16937
16937
  }
16938
16938
  this.run = opts.run;
@@ -17419,6 +17419,10 @@ class QueryRouterServer extends QueryRouter {
17419
17419
  }
17420
17420
  return super.run(msg, ctx);
17421
17421
  }
17422
+ async runAction(api2, payload, ctx) {
17423
+ const { path, key, id } = api2;
17424
+ return this.run({ path, key, id, payload }, ctx);
17425
+ }
17422
17426
  }
17423
17427
 
17424
17428
  // src/server/server.ts
@@ -17914,7 +17918,7 @@ class ServerBase {
17914
17918
  }
17915
17919
  }
17916
17920
  async onWsClose(ws) {
17917
- const id = ws?.data?.id || "";
17921
+ const id = ws?.wsId || "";
17918
17922
  if (id) {
17919
17923
  this.emitter.emit("close--" + id, { type: "close", ws, id });
17920
17924
  setTimeout(() => {
@@ -17927,6 +17931,9 @@ class ServerBase {
17927
17931
  if (this.showConnected)
17928
17932
  ws.send(JSON.stringify({ type: "connected" }));
17929
17933
  }
17934
+ createId() {
17935
+ return Math.random().toString(36).substring(2, 15);
17936
+ }
17930
17937
  }
17931
17938
 
17932
17939
  // node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/wrapper.mjs
@@ -17967,6 +17974,9 @@ class WsServerBase {
17967
17974
  token,
17968
17975
  id
17969
17976
  };
17977
+ if (!ws.wsId) {
17978
+ ws.wsId = this.createId();
17979
+ }
17970
17980
  ws.on("message", async (message) => {
17971
17981
  await this.server.onWebSocket({ ws, message, pathname, token, id });
17972
17982
  });
@@ -17977,6 +17987,9 @@ class WsServerBase {
17977
17987
  });
17978
17988
  });
17979
17989
  }
17990
+ createId() {
17991
+ return Math.random().toString(36).substring(2, 15);
17992
+ }
17980
17993
  }
17981
17994
 
17982
17995
  class WsServer extends WsServerBase {
@@ -18292,10 +18305,14 @@ class BunServer extends ServerBase {
18292
18305
  open: (ws) => {
18293
18306
  this.sendConnected(ws);
18294
18307
  },
18295
- message: async (ws, message) => {
18308
+ message: async (bunWs, message) => {
18309
+ const ws = bunWs;
18296
18310
  const pathname = ws.data.pathname || "";
18297
18311
  const token = ws.data.token || "";
18298
18312
  const id = ws.data.id || "";
18313
+ if (!ws.wsId) {
18314
+ ws.wsId = this.createId();
18315
+ }
18299
18316
  await this.onWebSocket({ ws, message, pathname, token, id });
18300
18317
  },
18301
18318
  close: (ws) => {
@@ -19562,7 +19579,7 @@ app
19562
19579
  10. **中间件找不到会返回 404**,错误信息中会包含找不到的中间件列表。
19563
19580
  `;
19564
19581
  // package.json
19565
- var version2 = "0.0.84";
19582
+ var version2 = "0.0.86";
19566
19583
 
19567
19584
  // agent/routes/route-create.ts
19568
19585
  app.route({
@@ -146,7 +146,7 @@ type RouteOpts<U = {}, T = SimpleObject> = {
146
146
  description?: string;
147
147
  metadata?: T;
148
148
  middleware?: RouteMiddleware[];
149
- type?: 'route' | 'middleware';
149
+ type?: 'route' | 'middleware' | 'compound';
150
150
  /**
151
151
  * $#$ will be used to split path and key
152
152
  */
@@ -450,7 +450,52 @@ declare class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Q
450
450
  key?: string;
451
451
  payload?: any;
452
452
  }, ctx?: Partial<RouteContext<C>>): Promise<any>;
453
+ runAction<T extends {
454
+ id?: string;
455
+ path?: string;
456
+ key?: string;
457
+ metadata?: {
458
+ args?: any;
459
+ };
460
+ } = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<any>;
453
461
  }
462
+ /** JSON Schema 基本类型映射到 TypeScript 类型 */
463
+ type JsonSchemaTypeToTS<T> = T extends {
464
+ type: "string";
465
+ } ? string : T extends {
466
+ type: "boolean";
467
+ } ? boolean : T extends {
468
+ type: "number";
469
+ } ? number : T extends {
470
+ type: "integer";
471
+ } ? number : T extends {
472
+ type: "object";
473
+ } ? object : T extends {
474
+ type: "array";
475
+ } ? any[] : any;
476
+ /** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
477
+ type ArgsShapeToPayload<T> = {
478
+ [K in keyof T as T[K] extends {
479
+ optional: true;
480
+ } ? never : K]: JsonSchemaTypeToTS<T[K]>;
481
+ } & {
482
+ [K in keyof T as T[K] extends {
483
+ optional: true;
484
+ } ? K : never]?: JsonSchemaTypeToTS<T[K]>;
485
+ };
486
+ /** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
487
+ type ArgsToPayload<T> = T extends {
488
+ type: "object";
489
+ properties: infer P;
490
+ } ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
491
+ /** 从 API 定义中提取 metadata.args */
492
+ type ExtractArgs<T> = T extends {
493
+ metadata: {
494
+ args: infer A;
495
+ };
496
+ } ? A : {};
497
+ /** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
498
+ type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
454
499
 
455
500
  type Cors = {
456
501
  /**
@@ -503,15 +548,32 @@ type OnWebSocketOptions<T = {}> = {
503
548
  message: string | Buffer;
504
549
  pathname: string;
505
550
  token?: string;
551
+ /** data 的id提取出来 */
506
552
  id?: string;
507
553
  };
508
554
  type WS<T = {}> = {
509
555
  send: (data: any) => void;
510
556
  close: (code?: number, reason?: string) => void;
557
+ /**
558
+ * ws 自己生成的一个id,主要是为了区分不同的ws连接,方便在onWebSocket中使用
559
+ */
560
+ wsId?: string;
511
561
  data?: {
562
+ /**
563
+ * ws连接时的url,包含pathname和searchParams
564
+ */
512
565
  url: URL;
566
+ /**
567
+ * ws连接时的pathname
568
+ */
513
569
  pathname: string;
570
+ /**
571
+ * ws连接时的url中的token参数
572
+ */
514
573
  token?: string;
574
+ /**
575
+ * ws连接时的url中的id参数.
576
+ */
515
577
  id?: string;
516
578
  /**
517
579
  * 鉴权后的获取的信息
@@ -187,7 +187,7 @@ type RouteOpts<U = {}, T = SimpleObject$1> = {
187
187
  description?: string;
188
188
  metadata?: T;
189
189
  middleware?: RouteMiddleware[];
190
- type?: 'route' | 'middleware';
190
+ type?: 'route' | 'middleware' | 'compound';
191
191
  /**
192
192
  * $#$ will be used to split path and key
193
193
  */
@@ -522,9 +522,54 @@ declare class QueryRouterServer<C extends SimpleObject$1 = SimpleObject$1> exten
522
522
  key?: string;
523
523
  payload?: any;
524
524
  }, ctx?: Partial<RouteContext<C>>): Promise<any>;
525
+ runAction<T extends {
526
+ id?: string;
527
+ path?: string;
528
+ key?: string;
529
+ metadata?: {
530
+ args?: any;
531
+ };
532
+ } = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<any>;
525
533
  }
526
534
  declare class Mini extends QueryRouterServer {
527
535
  }
536
+ /** JSON Schema 基本类型映射到 TypeScript 类型 */
537
+ type JsonSchemaTypeToTS<T> = T extends {
538
+ type: "string";
539
+ } ? string : T extends {
540
+ type: "boolean";
541
+ } ? boolean : T extends {
542
+ type: "number";
543
+ } ? number : T extends {
544
+ type: "integer";
545
+ } ? number : T extends {
546
+ type: "object";
547
+ } ? object : T extends {
548
+ type: "array";
549
+ } ? any[] : any;
550
+ /** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
551
+ type ArgsShapeToPayload<T> = {
552
+ [K in keyof T as T[K] extends {
553
+ optional: true;
554
+ } ? never : K]: JsonSchemaTypeToTS<T[K]>;
555
+ } & {
556
+ [K in keyof T as T[K] extends {
557
+ optional: true;
558
+ } ? K : never]?: JsonSchemaTypeToTS<T[K]>;
559
+ };
560
+ /** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
561
+ type ArgsToPayload<T> = T extends {
562
+ type: "object";
563
+ properties: infer P;
564
+ } ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
565
+ /** 从 API 定义中提取 metadata.args */
566
+ type ExtractArgs<T> = T extends {
567
+ metadata: {
568
+ args: infer A;
569
+ };
570
+ } ? A : {};
571
+ /** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
572
+ type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
528
573
 
529
574
  type BaseRule = {
530
575
  value?: any;
@@ -280,7 +280,7 @@ function pick(obj, keys) {
280
280
  // node_modules/.pnpm/eventemitter3@5.0.4/node_modules/eventemitter3/index.mjs
281
281
  var import__ = __toESM(require_eventemitter3(), 1);
282
282
 
283
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
283
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
284
284
  function isPlainObject(value) {
285
285
  if (!value || typeof value !== "object") {
286
286
  return false;
@@ -293,12 +293,12 @@ function isPlainObject(value) {
293
293
  return Object.prototype.toString.call(value) === "[object Object]";
294
294
  }
295
295
 
296
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
296
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
297
297
  function isUnsafeProperty(key) {
298
298
  return key === "__proto__";
299
299
  }
300
300
 
301
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/object/merge.mjs
301
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/object/merge.mjs
302
302
  function merge(target, source) {
303
303
  const sourceKeys = Object.keys(source);
304
304
  for (let i = 0;i < sourceKeys.length; i++) {
@@ -14096,7 +14096,7 @@ class Route {
14096
14096
  if (opts) {
14097
14097
  this.id = opts.id || randomId(12, "rand-");
14098
14098
  if (!opts.id && opts.idUsePath) {
14099
- const delimiter = opts.delimiter ?? "$#$";
14099
+ const delimiter = opts.delimiter ?? "$$";
14100
14100
  this.id = path + delimiter + key;
14101
14101
  }
14102
14102
  this.run = opts.run;
@@ -14583,6 +14583,10 @@ class QueryRouterServer extends QueryRouter {
14583
14583
  }
14584
14584
  return super.run(msg, ctx);
14585
14585
  }
14586
+ async runAction(api2, payload, ctx) {
14587
+ const { path, key, id } = api2;
14588
+ return this.run({ path, key, id, payload }, ctx);
14589
+ }
14586
14590
  }
14587
14591
 
14588
14592
  class Mini extends QueryRouterServer {
@@ -143,7 +143,7 @@ type RouteOpts<U = {}, T = SimpleObject$1> = {
143
143
  description?: string;
144
144
  metadata?: T;
145
145
  middleware?: RouteMiddleware[];
146
- type?: 'route' | 'middleware';
146
+ type?: 'route' | 'middleware' | 'compound';
147
147
  /**
148
148
  * $#$ will be used to split path and key
149
149
  */
@@ -447,7 +447,52 @@ declare class QueryRouterServer<C extends SimpleObject$1 = SimpleObject$1> exten
447
447
  key?: string;
448
448
  payload?: any;
449
449
  }, ctx?: Partial<RouteContext<C>>): Promise<any>;
450
+ runAction<T extends {
451
+ id?: string;
452
+ path?: string;
453
+ key?: string;
454
+ metadata?: {
455
+ args?: any;
456
+ };
457
+ } = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<any>;
450
458
  }
459
+ /** JSON Schema 基本类型映射到 TypeScript 类型 */
460
+ type JsonSchemaTypeToTS<T> = T extends {
461
+ type: "string";
462
+ } ? string : T extends {
463
+ type: "boolean";
464
+ } ? boolean : T extends {
465
+ type: "number";
466
+ } ? number : T extends {
467
+ type: "integer";
468
+ } ? number : T extends {
469
+ type: "object";
470
+ } ? object : T extends {
471
+ type: "array";
472
+ } ? any[] : any;
473
+ /** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
474
+ type ArgsShapeToPayload<T> = {
475
+ [K in keyof T as T[K] extends {
476
+ optional: true;
477
+ } ? never : K]: JsonSchemaTypeToTS<T[K]>;
478
+ } & {
479
+ [K in keyof T as T[K] extends {
480
+ optional: true;
481
+ } ? K : never]?: JsonSchemaTypeToTS<T[K]>;
482
+ };
483
+ /** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
484
+ type ArgsToPayload<T> = T extends {
485
+ type: "object";
486
+ properties: infer P;
487
+ } ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
488
+ /** 从 API 定义中提取 metadata.args */
489
+ type ExtractArgs<T> = T extends {
490
+ metadata: {
491
+ args: infer A;
492
+ };
493
+ } ? A : {};
494
+ /** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
495
+ type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
451
496
 
452
497
  type RouteObject = {
453
498
  [key: string]: RouteOpts;
package/dist/router.d.ts CHANGED
@@ -193,7 +193,7 @@ type RouteOpts<U = {}, T = SimpleObject$1> = {
193
193
  description?: string;
194
194
  metadata?: T;
195
195
  middleware?: RouteMiddleware[];
196
- type?: 'route' | 'middleware';
196
+ type?: 'route' | 'middleware' | 'compound';
197
197
  /**
198
198
  * $#$ will be used to split path and key
199
199
  */
@@ -528,9 +528,54 @@ declare class QueryRouterServer<C extends SimpleObject$1 = SimpleObject$1> exten
528
528
  key?: string;
529
529
  payload?: any;
530
530
  }, ctx?: Partial<RouteContext<C>>): Promise<any>;
531
+ runAction<T extends {
532
+ id?: string;
533
+ path?: string;
534
+ key?: string;
535
+ metadata?: {
536
+ args?: any;
537
+ };
538
+ } = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<any>;
531
539
  }
532
540
  declare class Mini extends QueryRouterServer {
533
541
  }
542
+ /** JSON Schema 基本类型映射到 TypeScript 类型 */
543
+ type JsonSchemaTypeToTS<T> = T extends {
544
+ type: "string";
545
+ } ? string : T extends {
546
+ type: "boolean";
547
+ } ? boolean : T extends {
548
+ type: "number";
549
+ } ? number : T extends {
550
+ type: "integer";
551
+ } ? number : T extends {
552
+ type: "object";
553
+ } ? object : T extends {
554
+ type: "array";
555
+ } ? any[] : any;
556
+ /** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
557
+ type ArgsShapeToPayload<T> = {
558
+ [K in keyof T as T[K] extends {
559
+ optional: true;
560
+ } ? never : K]: JsonSchemaTypeToTS<T[K]>;
561
+ } & {
562
+ [K in keyof T as T[K] extends {
563
+ optional: true;
564
+ } ? K : never]?: JsonSchemaTypeToTS<T[K]>;
565
+ };
566
+ /** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
567
+ type ArgsToPayload<T> = T extends {
568
+ type: "object";
569
+ properties: infer P;
570
+ } ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
571
+ /** 从 API 定义中提取 metadata.args */
572
+ type ExtractArgs<T> = T extends {
573
+ metadata: {
574
+ args: infer A;
575
+ };
576
+ } ? A : {};
577
+ /** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
578
+ type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
534
579
 
535
580
  type BaseRule = {
536
581
  value?: any;
@@ -688,16 +733,33 @@ type OnWebSocketOptions<T = {}> = {
688
733
  message: string | Buffer;
689
734
  pathname: string;
690
735
  token?: string;
736
+ /** data 的id提取出来 */
691
737
  id?: string;
692
738
  };
693
739
  type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
694
740
  type WS<T = {}> = {
695
741
  send: (data: any) => void;
696
742
  close: (code?: number, reason?: string) => void;
743
+ /**
744
+ * ws 自己生成的一个id,主要是为了区分不同的ws连接,方便在onWebSocket中使用
745
+ */
746
+ wsId?: string;
697
747
  data?: {
748
+ /**
749
+ * ws连接时的url,包含pathname和searchParams
750
+ */
698
751
  url: URL;
752
+ /**
753
+ * ws连接时的pathname
754
+ */
699
755
  pathname: string;
756
+ /**
757
+ * ws连接时的url中的token参数
758
+ */
700
759
  token?: string;
760
+ /**
761
+ * ws连接时的url中的id参数.
762
+ */
701
763
  id?: string;
702
764
  /**
703
765
  * 鉴权后的获取的信息
@@ -916,6 +978,7 @@ declare class ServerBase implements ServerType {
916
978
  */
917
979
  onWsClose(ws: WS): Promise<void>;
918
980
  sendConnected(ws: WS): Promise<void>;
981
+ createId(): string;
919
982
  }
920
983
 
921
984
  type WsServerBaseOpts = {
@@ -928,6 +991,7 @@ declare class WsServerBase {
928
991
  server: ServerType;
929
992
  constructor(opts: WsServerBaseOpts);
930
993
  listen(): void;
994
+ createId(): string;
931
995
  }
932
996
  declare class WsServer extends WsServerBase {
933
997
  constructor(server: ServerType);
package/dist/router.js CHANGED
@@ -3091,7 +3091,7 @@ function pick(obj, keys) {
3091
3091
  // node_modules/.pnpm/eventemitter3@5.0.4/node_modules/eventemitter3/index.mjs
3092
3092
  var import__ = __toESM(require_eventemitter3(), 1);
3093
3093
 
3094
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
3094
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
3095
3095
  function isPlainObject(value) {
3096
3096
  if (!value || typeof value !== "object") {
3097
3097
  return false;
@@ -3104,12 +3104,12 @@ function isPlainObject(value) {
3104
3104
  return Object.prototype.toString.call(value) === "[object Object]";
3105
3105
  }
3106
3106
 
3107
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
3107
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/_internal/isUnsafeProperty.mjs
3108
3108
  function isUnsafeProperty(key) {
3109
3109
  return key === "__proto__";
3110
3110
  }
3111
3111
 
3112
- // node_modules/.pnpm/es-toolkit@1.44.0/node_modules/es-toolkit/dist/object/merge.mjs
3112
+ // node_modules/.pnpm/es-toolkit@1.45.1/node_modules/es-toolkit/dist/object/merge.mjs
3113
3113
  function merge(target, source) {
3114
3114
  const sourceKeys = Object.keys(source);
3115
3115
  for (let i = 0;i < sourceKeys.length; i++) {
@@ -16929,7 +16929,7 @@ class Route {
16929
16929
  if (opts) {
16930
16930
  this.id = opts.id || randomId(12, "rand-");
16931
16931
  if (!opts.id && opts.idUsePath) {
16932
- const delimiter = opts.delimiter ?? "$#$";
16932
+ const delimiter = opts.delimiter ?? "$$";
16933
16933
  this.id = path + delimiter + key;
16934
16934
  }
16935
16935
  this.run = opts.run;
@@ -17416,6 +17416,10 @@ class QueryRouterServer extends QueryRouter {
17416
17416
  }
17417
17417
  return super.run(msg, ctx);
17418
17418
  }
17419
+ async runAction(api2, payload, ctx) {
17420
+ const { path, key, id } = api2;
17421
+ return this.run({ path, key, id, payload }, ctx);
17422
+ }
17419
17423
  }
17420
17424
 
17421
17425
  class Mini extends QueryRouterServer {
@@ -18088,7 +18092,7 @@ class ServerBase {
18088
18092
  }
18089
18093
  }
18090
18094
  async onWsClose(ws) {
18091
- const id = ws?.data?.id || "";
18095
+ const id = ws?.wsId || "";
18092
18096
  if (id) {
18093
18097
  this.emitter.emit("close--" + id, { type: "close", ws, id });
18094
18098
  setTimeout(() => {
@@ -18101,6 +18105,9 @@ class ServerBase {
18101
18105
  if (this.showConnected)
18102
18106
  ws.send(JSON.stringify({ type: "connected" }));
18103
18107
  }
18108
+ createId() {
18109
+ return Math.random().toString(36).substring(2, 15);
18110
+ }
18104
18111
  }
18105
18112
 
18106
18113
  // node_modules/.pnpm/@kevisual+ws@8.0.0/node_modules/@kevisual/ws/wrapper.mjs
@@ -18141,6 +18148,9 @@ class WsServerBase {
18141
18148
  token,
18142
18149
  id
18143
18150
  };
18151
+ if (!ws.wsId) {
18152
+ ws.wsId = this.createId();
18153
+ }
18144
18154
  ws.on("message", async (message) => {
18145
18155
  await this.server.onWebSocket({ ws, message, pathname, token, id });
18146
18156
  });
@@ -18151,6 +18161,9 @@ class WsServerBase {
18151
18161
  });
18152
18162
  });
18153
18163
  }
18164
+ createId() {
18165
+ return Math.random().toString(36).substring(2, 15);
18166
+ }
18154
18167
  }
18155
18168
 
18156
18169
  class WsServer extends WsServerBase {
@@ -18465,10 +18478,14 @@ class BunServer extends ServerBase {
18465
18478
  open: (ws) => {
18466
18479
  this.sendConnected(ws);
18467
18480
  },
18468
- message: async (ws, message) => {
18481
+ message: async (bunWs, message) => {
18482
+ const ws = bunWs;
18469
18483
  const pathname = ws.data.pathname || "";
18470
18484
  const token = ws.data.token || "";
18471
18485
  const id = ws.data.id || "";
18486
+ if (!ws.wsId) {
18487
+ ws.wsId = this.createId();
18488
+ }
18472
18489
  await this.onWebSocket({ ws, message, pathname, token, id });
18473
18490
  },
18474
18491
  close: (ws) => {
package/dist/ws.d.ts CHANGED
@@ -193,7 +193,7 @@ type RouteOpts<U = {}, T = SimpleObject> = {
193
193
  description?: string;
194
194
  metadata?: T;
195
195
  middleware?: RouteMiddleware[];
196
- type?: 'route' | 'middleware';
196
+ type?: 'route' | 'middleware' | 'compound';
197
197
  /**
198
198
  * $#$ will be used to split path and key
199
199
  */
@@ -497,7 +497,52 @@ declare class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Q
497
497
  key?: string;
498
498
  payload?: any;
499
499
  }, ctx?: Partial<RouteContext<C>>): Promise<any>;
500
+ runAction<T extends {
501
+ id?: string;
502
+ path?: string;
503
+ key?: string;
504
+ metadata?: {
505
+ args?: any;
506
+ };
507
+ } = {}>(api: T, payload: RunActionPayload<T>, ctx?: RouteContext<C>): Promise<any>;
500
508
  }
509
+ /** JSON Schema 基本类型映射到 TypeScript 类型 */
510
+ type JsonSchemaTypeToTS<T> = T extends {
511
+ type: "string";
512
+ } ? string : T extends {
513
+ type: "boolean";
514
+ } ? boolean : T extends {
515
+ type: "number";
516
+ } ? number : T extends {
517
+ type: "integer";
518
+ } ? number : T extends {
519
+ type: "object";
520
+ } ? object : T extends {
521
+ type: "array";
522
+ } ? any[] : any;
523
+ /** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
524
+ type ArgsShapeToPayload<T> = {
525
+ [K in keyof T as T[K] extends {
526
+ optional: true;
527
+ } ? never : K]: JsonSchemaTypeToTS<T[K]>;
528
+ } & {
529
+ [K in keyof T as T[K] extends {
530
+ optional: true;
531
+ } ? K : never]?: JsonSchemaTypeToTS<T[K]>;
532
+ };
533
+ /** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
534
+ type ArgsToPayload<T> = T extends {
535
+ type: "object";
536
+ properties: infer P;
537
+ } ? ArgsShapeToPayload<P> : ArgsShapeToPayload<T>;
538
+ /** 从 API 定义中提取 metadata.args */
539
+ type ExtractArgs<T> = T extends {
540
+ metadata: {
541
+ args: infer A;
542
+ };
543
+ } ? A : {};
544
+ /** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
545
+ type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
501
546
 
502
547
  type Cors = {
503
548
  /**
@@ -550,15 +595,32 @@ type OnWebSocketOptions<T = {}> = {
550
595
  message: string | Buffer;
551
596
  pathname: string;
552
597
  token?: string;
598
+ /** data 的id提取出来 */
553
599
  id?: string;
554
600
  };
555
601
  type WS<T = {}> = {
556
602
  send: (data: any) => void;
557
603
  close: (code?: number, reason?: string) => void;
604
+ /**
605
+ * ws 自己生成的一个id,主要是为了区分不同的ws连接,方便在onWebSocket中使用
606
+ */
607
+ wsId?: string;
558
608
  data?: {
609
+ /**
610
+ * ws连接时的url,包含pathname和searchParams
611
+ */
559
612
  url: URL;
613
+ /**
614
+ * ws连接时的pathname
615
+ */
560
616
  pathname: string;
617
+ /**
618
+ * ws连接时的url中的token参数
619
+ */
561
620
  token?: string;
621
+ /**
622
+ * ws连接时的url中的id参数.
623
+ */
562
624
  id?: string;
563
625
  /**
564
626
  * 鉴权后的获取的信息
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.0.84",
4
+ "version": "0.0.86",
5
5
  "description": "",
6
6
  "type": "module",
7
7
  "main": "./dist/router.js",
@@ -27,17 +27,17 @@
27
27
  "@kevisual/dts": "^0.0.4",
28
28
  "@kevisual/js-filter": "^0.0.5",
29
29
  "@kevisual/local-proxy": "^0.0.8",
30
- "@kevisual/query": "^0.0.49",
30
+ "@kevisual/query": "^0.0.53",
31
31
  "@kevisual/use-config": "^1.0.30",
32
- "@opencode-ai/plugin": "^1.2.10",
33
- "@types/bun": "^1.3.9",
34
- "@types/node": "^25.3.0",
32
+ "@opencode-ai/plugin": "^1.2.20",
33
+ "@types/bun": "^1.3.10",
34
+ "@types/node": "^25.3.5",
35
35
  "@types/send": "^1.2.1",
36
36
  "@types/ws": "^8.18.1",
37
37
  "@types/xml2js": "^0.4.14",
38
38
  "eventemitter3": "^5.0.4",
39
39
  "fast-glob": "^3.3.3",
40
- "hono": "^4.12.2",
40
+ "hono": "^4.12.5",
41
41
  "nanoid": "^5.1.6",
42
42
  "path-to-regexp": "^8.3.0",
43
43
  "send": "^1.2.1",
@@ -51,7 +51,7 @@
51
51
  "url": "git+https://github.com/abearxiong/kevisual-router.git"
52
52
  },
53
53
  "dependencies": {
54
- "es-toolkit": "^1.44.0"
54
+ "es-toolkit": "^1.45.1"
55
55
  },
56
56
  "publishConfig": {
57
57
  "access": "public"
package/src/route.ts CHANGED
@@ -7,14 +7,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
10
  type BuildRouteContext<M, U> = M extends { args?: infer A }
19
11
  ? A extends z.ZodObject<any>
20
12
  ? RouteContext<{ args?: z.infer<A> }, U>
@@ -103,7 +95,7 @@ export type RouteOpts<U = {}, T = SimpleObject> = {
103
95
  description?: string;
104
96
  metadata?: T;
105
97
  middleware?: RouteMiddleware[]; // middleware
106
- type?: 'route' | 'middleware';
98
+ type?: 'route' | 'middleware' | 'compound'; // compound表示这个 route 作为一个聚合体,没有实际的 run,而是一个 router 的聚合列表
107
99
  /**
108
100
  * $#$ will be used to split path and key
109
101
  */
@@ -146,8 +138,6 @@ export const createSkill = <T = SimpleObject>(skill: Skill<T>): Skill<T> => {
146
138
 
147
139
  export type RouteInfo = Pick<Route, (typeof pickValue)[number]>;
148
140
 
149
- type ExtractMetadata<M> = M extends { metadata?: infer Meta } ? Meta extends SimpleObject ? Meta : SimpleObject : SimpleObject;
150
-
151
141
  /**
152
142
  * @M 是 route的 metadate的类型,默认是 SimpleObject
153
143
  * @U 是 RouteContext 里 state的类型
@@ -183,7 +173,7 @@ export class Route<M extends SimpleObject = SimpleObject, U extends SimpleObject
183
173
  if (opts) {
184
174
  this.id = opts.id || randomId(12, 'rand-');
185
175
  if (!opts.id && opts.idUsePath) {
186
- const delimiter = opts.delimiter ?? '$#$';
176
+ const delimiter = opts.delimiter ?? '$$';
187
177
  this.id = path + delimiter + key;
188
178
  }
189
179
  this.run = opts.run as Run<BuildRouteContext<M, U>>;
@@ -789,8 +779,44 @@ export class QueryRouterServer<C extends SimpleObject = SimpleObject> extends Qu
789
779
  }
790
780
  return super.run(msg, ctx as RouteContext<C>);
791
781
  }
782
+
783
+ async runAction<T extends { id?: string; path?: string; key?: string; metadata?: { args?: any } } = {}>(
784
+ api: T,
785
+ payload: RunActionPayload<T>,
786
+ ctx?: RouteContext<C>
787
+ ) {
788
+ const { path, key, id } = api as any;
789
+ return this.run({ path, key, id, payload }, ctx);
790
+ }
792
791
  }
793
792
 
794
793
 
795
794
  export class Mini extends QueryRouterServer { }
796
795
 
796
+ /** JSON Schema 基本类型映射到 TypeScript 类型 */
797
+ type JsonSchemaTypeToTS<T> =
798
+ T extends { type: "string" } ? string :
799
+ T extends { type: "boolean" } ? boolean :
800
+ T extends { type: "number" } ? number :
801
+ T extends { type: "integer" } ? number :
802
+ T extends { type: "object" } ? object :
803
+ T extends { type: "array" } ? any[] :
804
+ any;
805
+
806
+ /** 将 args shape(key -> JSON Schema 类型)转换为 payload 类型,支持 optional: true 的字段为可选 */
807
+ type ArgsShapeToPayload<T> =
808
+ { [K in keyof T as T[K] extends { optional: true } ? never : K]: JsonSchemaTypeToTS<T[K]> } &
809
+ { [K in keyof T as T[K] extends { optional: true } ? K : never]?: JsonSchemaTypeToTS<T[K]> };
810
+
811
+ /** 处理两种 args 格式:完整 JSON Schema(含 properties)或简单 key->type 映射 */
812
+ type ArgsToPayload<T> =
813
+ T extends { type: "object"; properties: infer P }
814
+ ? ArgsShapeToPayload<P>
815
+ : ArgsShapeToPayload<T>;
816
+
817
+ /** 从 API 定义中提取 metadata.args */
818
+ type ExtractArgs<T> =
819
+ T extends { metadata: { args: infer A } } ? A : {};
820
+
821
+ /** runAction 第二个参数的类型,根据第一个参数的 metadata.args 推断 */
822
+ export type RunActionPayload<T> = ArgsToPayload<ExtractArgs<T>>;
@@ -284,7 +284,7 @@ export class ServerBase implements ServerType {
284
284
  * @param ws
285
285
  */
286
286
  async onWsClose(ws: WS) {
287
- const id = ws?.data?.id || '';
287
+ const id = ws?.wsId || '';
288
288
  if (id) {
289
289
  this.emitter.emit('close--' + id, { type: 'close', ws, id });
290
290
  setTimeout(() => {
@@ -298,4 +298,7 @@ export class ServerBase implements ServerType {
298
298
  if (this.showConnected)
299
299
  ws.send(JSON.stringify({ type: 'connected' }));
300
300
  }
301
+ createId() {
302
+ return Math.random().toString(36).substring(2, 15);
303
+ }
301
304
  }
@@ -4,7 +4,7 @@
4
4
  * @tags bun, server, websocket, http
5
5
  * @createdAt 2025-12-20
6
6
  */
7
- import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq } from './server-type.ts';
7
+ import { ServerType, type ServerOpts, type Cors, RouterRes, RouterReq, WS } from './server-type.ts';
8
8
  import { ServerBase } from './server-base.ts';
9
9
 
10
10
  export class BunServer extends ServerBase implements ServerType {
@@ -264,10 +264,14 @@ export class BunServer extends ServerBase implements ServerType {
264
264
  open: (ws: any) => {
265
265
  this.sendConnected(ws);
266
266
  },
267
- message: async (ws: any, message: string | Buffer) => {
267
+ message: async (bunWs: any, message: string | Buffer) => {
268
+ const ws = bunWs as WS;
268
269
  const pathname = ws.data.pathname || '';
269
270
  const token = ws.data.token || '';
270
271
  const id = ws.data.id || '';
272
+ if (!ws.wsId) {
273
+ ws.wsId = this.createId();
274
+ }
271
275
  await this.onWebSocket({ ws, message, pathname, token, id });
272
276
  },
273
277
  close: (ws: any) => {
@@ -49,16 +49,33 @@ export type OnWebSocketOptions<T = {}> = {
49
49
  message: string | Buffer;
50
50
  pathname: string,
51
51
  token?: string,
52
+ /** data 的id提取出来 */
52
53
  id?: string,
53
54
  }
54
55
  export type OnWebSocketFn = (options: OnWebSocketOptions) => Promise<void> | void;
55
56
  export type WS<T = {}> = {
56
57
  send: (data: any) => void;
57
58
  close: (code?: number, reason?: string) => void;
59
+ /**
60
+ * ws 自己生成的一个id,主要是为了区分不同的ws连接,方便在onWebSocket中使用
61
+ */
62
+ wsId?: string;
58
63
  data?: {
64
+ /**
65
+ * ws连接时的url,包含pathname和searchParams
66
+ */
59
67
  url: URL;
68
+ /**
69
+ * ws连接时的pathname
70
+ */
60
71
  pathname: string;
72
+ /**
73
+ * ws连接时的url中的token参数
74
+ */
61
75
  token?: string;
76
+ /**
77
+ * ws连接时的url中的id参数.
78
+ */
62
79
  id?: string;
63
80
  /**
64
81
  * 鉴权后的获取的信息
@@ -56,6 +56,11 @@ export class WsServerBase {
56
56
  token,
57
57
  id,
58
58
  }
59
+ // @ts-ignore
60
+ if (!ws.wsId) {
61
+ // @ts-ignore
62
+ ws.wsId = this.createId();
63
+ }
59
64
  ws.on('message', async (message: string | Buffer) => {
60
65
  await this.server.onWebSocket({ ws, message, pathname, token, id });
61
66
  });
@@ -66,7 +71,9 @@ export class WsServerBase {
66
71
  this.server.onWsClose(ws);
67
72
  });
68
73
  });
69
-
74
+ }
75
+ createId() {
76
+ return Math.random().toString(36).substring(2, 15);
70
77
  }
71
78
  }
72
79
  // TODO: ws handle and path and routerContext
@@ -0,0 +1,87 @@
1
+ import z from "zod";
2
+ import { App } from "../index.ts";
3
+
4
+ const app = new App();
5
+ const api = {
6
+ "app_domain_manager": {
7
+ /**
8
+ * 获取域名信息,可以通过id或者domain进行查询
9
+ *
10
+ * @param data - Request parameters
11
+ * @param data.data - {object}
12
+ */
13
+ "get": {
14
+ "path": "app_domain_manager",
15
+ "key": "get",
16
+ "description": "获取域名信息,可以通过id或者domain进行查询",
17
+ "metadata": {
18
+ "args": {
19
+ "data": {
20
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
21
+ "type": "object",
22
+ "properties": {
23
+ "id": {
24
+ "type": "string"
25
+ },
26
+ "domain": {
27
+ "type": "string"
28
+ }
29
+ },
30
+ "additionalProperties": false,
31
+ "required": ["id",]
32
+ }
33
+ },
34
+ "viewItem": {
35
+ "api": {
36
+ "url": "/api/router"
37
+ },
38
+ "type": "api",
39
+ "title": "路由"
40
+ },
41
+ "url": "/api/router",
42
+ "source": "query-proxy-api"
43
+ }
44
+ },
45
+ "delete": {
46
+ "path": "app_domain_manager",
47
+ "key": "delete",
48
+ "description": "删除域名",
49
+ "metadata": {
50
+ "args": {
51
+ "domainId": {
52
+ "type": "string",
53
+ "optional": true
54
+ }
55
+ }
56
+ }
57
+ }
58
+ },
59
+ "user_manager": {
60
+ "getUser": {
61
+ "path": "user_manager",
62
+ "key": "getUser",
63
+ "description": "获取用户信息",
64
+ "metadata": {
65
+ "args": {
66
+ "userId": {
67
+ "type": "string"
68
+ },
69
+ "includeProfile": {
70
+ "type": "boolean"
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+ } as const;
77
+ type API = typeof api;
78
+
79
+ // 类型推断生效:payload 根据 metadata.args 自动推断
80
+ // get 的 args.data 是 type:"object",所以 payload 需要 { data: object }
81
+ app.runAction(api.app_domain_manager.get, { data: { id: "1" } })
82
+
83
+ // delete 的 args 是 { domainId: { type: "string" } },所以 payload 需要 { domainId: string }
84
+ app.runAction(api.app_domain_manager.delete, { domainId: "d1" })
85
+
86
+ // getUser 的 args 是 { userId: string, includeProfile: boolean }
87
+ app.runAction(api.user_manager.getUser, { userId: "u1", includeProfile: true })