@kevisual/query 0.0.40 → 0.0.41

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/package.json CHANGED
@@ -1,12 +1,9 @@
1
1
  {
2
2
  "name": "@kevisual/query",
3
- "version": "0.0.40",
4
- "main": "dist/query-browser.js",
5
- "private": false,
3
+ "version": "0.0.41",
6
4
  "type": "module",
7
5
  "scripts": {
8
- "build": "npm run clean && rollup -c",
9
- "dev:lib": "rollup -c -w",
6
+ "build": "npm run clean && bun run bun.config.ts",
10
7
  "clean": "rm -rf dist"
11
8
  },
12
9
  "files": [
@@ -21,11 +18,12 @@
21
18
  "license": "ISC",
22
19
  "description": "",
23
20
  "devDependencies": {
24
- "@rollup/plugin-node-resolve": "^16.0.3",
25
- "@rollup/plugin-typescript": "^12.3.0",
26
- "rollup": "^4.57.1",
27
- "rollup-plugin-dts": "^6.3.0",
21
+ "@kevisual/code-builder": "^0.0.6",
22
+ "@kevisual/router": "^0.0.72",
23
+ "@types/node": "^25.2.3",
28
24
  "typescript": "^5.9.3",
25
+ "es-toolkit": "^1.44.0",
26
+ "zod": "^4.3.6",
29
27
  "zustand": "^5.0.11"
30
28
  },
31
29
  "publishConfig": {
@@ -36,20 +34,10 @@
36
34
  "url": "git+ssh://git@github.com/abearxiong/kevisual-query.git"
37
35
  },
38
36
  "exports": {
39
- ".": {
40
- "import": "./dist/query-browser.js",
41
- "require": "./dist/query-browser.js"
42
- },
43
- "./query": {
44
- "import": "./dist/query.js",
45
- "require": "./dist/query.js"
46
- },
47
- "./ws": {
48
- "import": "./dist/query-ws.js",
49
- "require": "./dist/query-ws.js"
50
- }
37
+ ".": "./dist/query-browser.js",
38
+ "./query": "./dist/query.js",
39
+ "./ws": "./dist/query-ws.js",
40
+ "./api": "./dist/query-api.js"
51
41
  },
52
- "dependencies": {
53
- "tslib": "^2.8.1"
54
- }
55
- }
42
+ "dependencies": {}
43
+ }
package/src/adapter.ts CHANGED
@@ -60,7 +60,7 @@ export const adapter = async (opts: AdapterOpts = {}, overloadOpts?: RequestInit
60
60
  if (opts?.url?.startsWith('http')) {
61
61
  url = new URL(opts.url);
62
62
  } else {
63
- origin = window?.location?.origin || 'http://localhost:51515';
63
+ origin = globalThis?.location?.origin || 'http://localhost:51515';
64
64
  url = new URL(opts?.url || '', origin);
65
65
  }
66
66
  const isGet = method === 'GET';
@@ -0,0 +1,130 @@
1
+
2
+ type RouteInfo = {
3
+ path: string;
4
+ key: string;
5
+ id: string;
6
+ description?: string;
7
+ metadata?: {
8
+ summary?: string;
9
+ args?: Record<string, any>;
10
+ };
11
+ }
12
+ export const createQueryByRoutes = (list: RouteInfo[]) => {
13
+ const obj: any = {};
14
+ for (const route of list) {
15
+ if (!obj[route.path]) {
16
+ obj[route.path] = {};
17
+ }
18
+ obj[route.path][route.key] = route;
19
+ }
20
+ const code = `
21
+ import { createQueryApi } from '@kevisual/query/api';
22
+ const api = ${generateApiCode(obj)} as const;
23
+ const queryApi = createQueryApi({ api });
24
+ export { queryApi };
25
+ `
26
+ return code;
27
+ }
28
+
29
+ // 生成带注释的对象字符串
30
+ function generateApiCode(obj: any): string {
31
+ let code = '{\n';
32
+ const paths = Object.keys(obj);
33
+
34
+ for (let i = 0; i < paths.length; i++) {
35
+ const path = paths[i];
36
+ const methods = obj[path];
37
+
38
+ code += ` "${path}": {\n`;
39
+
40
+ const keys = Object.keys(methods);
41
+ for (let j = 0; j < keys.length; j++) {
42
+ const key = keys[j];
43
+ const route = methods[key];
44
+ if (route?.id) {
45
+ if (route.id.startsWith('rand-')) {
46
+ delete route.id; // 删除随机生成的 ID
47
+ }
48
+ }
49
+ const description = route?.metadata?.summary || route?.description || '';
50
+ const args = route?.metadata?.args || {};
51
+
52
+ // 添加 JSDoc 注释
53
+ if (description || Object.keys(args).length > 0) {
54
+ code += ` /**\n`;
55
+
56
+ // 添加主描述
57
+ if (description) {
58
+ // 转义描述中的特殊字符
59
+ const escapedDescription = description
60
+ .replace(/\\/g, '\\\\') // 转义反斜杠
61
+ .replace(/\*/g, '\\*') // 转义星号
62
+ .replace(/\n/g, '\n * '); // 处理多行描述
63
+ code += ` * ${escapedDescription}\n`;
64
+ }
65
+
66
+ // 添加参数描述
67
+ if (Object.keys(args).length > 0) {
68
+ if (description) {
69
+ code += ` *\n`; // 添加空行分隔
70
+ }
71
+ code += ` * @param data - Request parameters\n`;
72
+
73
+ for (const [argName, schema] of Object.entries(args)) {
74
+ const argSchema = schema as any;
75
+ const argType = argSchema.type || 'unknown';
76
+ const argDesc = argSchema.description || '';
77
+
78
+ // 构建类型信息
79
+ let typeInfo = argType;
80
+ if (argType === 'string' && argSchema.enum) {
81
+ typeInfo = argSchema.enum.map((v: any) => `"${v}"`).join(' | ');
82
+ } else if (argType === 'number' || argType === 'integer') {
83
+ const constraints = [];
84
+ if (argSchema.minimum !== undefined) constraints.push(`min: ${argSchema.minimum}`);
85
+ if (argSchema.maximum !== undefined) constraints.push(`max: ${argSchema.maximum}`);
86
+ if (argSchema.exclusiveMinimum !== undefined) constraints.push(`> ${argSchema.exclusiveMinimum}`);
87
+ if (argSchema.exclusiveMaximum !== undefined) constraints.push(`< ${argSchema.exclusiveMaximum}`);
88
+ if (constraints.length > 0) typeInfo += ` (${constraints.join(', ')})`;
89
+ } else if (argType === 'string') {
90
+ const constraints = [];
91
+ if (argSchema.minLength !== undefined) constraints.push(`minLength: ${argSchema.minLength}`);
92
+ if (argSchema.maxLength !== undefined) constraints.push(`maxLength: ${argSchema.maxLength}`);
93
+ if (argSchema.format) constraints.push(`format: ${argSchema.format}`);
94
+ if (constraints.length > 0) typeInfo += ` (${constraints.join(', ')})`;
95
+ }
96
+
97
+ // 转义参数描述
98
+ const escapedArgDesc = argDesc
99
+ .replace(/\\/g, '\\\\')
100
+ .replace(/\*/g, '\\*')
101
+ .replace(/\n/g, ' ');
102
+
103
+ code += ` * @param data.${argName} - {${typeInfo}}${escapedArgDesc ? ' ' + escapedArgDesc : ''}\n`;
104
+ }
105
+ }
106
+
107
+ code += ` */\n`;
108
+ }
109
+
110
+ code += ` "${key}": ${JSON.stringify(route, null, 2).split('\n').map((line, idx) =>
111
+ idx === 0 ? line : ' ' + line
112
+ ).join('\n')}`;
113
+
114
+ if (j < keys.length - 1) {
115
+ code += ',';
116
+ }
117
+ code += '\n';
118
+ }
119
+
120
+ code += ` }`;
121
+ if (i < paths.length - 1) {
122
+ code += ',';
123
+ }
124
+ code += '\n';
125
+ }
126
+
127
+ code += '}';
128
+ return code;
129
+ }
130
+
@@ -0,0 +1,136 @@
1
+ import { DataOpts, Query } from "./query.ts";
2
+ import { z } from "zod";
3
+ import { createQueryByRoutes } from "./create-query/index.ts";
4
+ import { pick } from 'es-toolkit'
5
+ type Pos = {
6
+ path?: string;
7
+ key?: string;
8
+ id?: string;
9
+ metadata?: {
10
+ args?: Record<string, any>;
11
+ };
12
+ }
13
+
14
+ // JSON Schema 类型推断 - 使用更精确的类型匹配
15
+ type InferFromJSONSchema<T> =
16
+ T extends { type: "string"; enum: readonly (infer E)[] } ? E :
17
+ T extends { type: "string"; enum: (infer E)[] } ? E :
18
+ T extends { type: "string" } ? string :
19
+ T extends { type: "number" } ? number :
20
+ T extends { type: "integer" } ? number :
21
+ T extends { type: "boolean" } ? boolean :
22
+ T extends { type: "object"; properties: infer P }
23
+ ? { [K in keyof P]: InferFromJSONSchema<P[K]> }
24
+ : T extends { type: "array"; items: infer I }
25
+ ? Array<InferFromJSONSchema<I>>
26
+ : unknown;
27
+
28
+ // 统一类型推断:支持 Zod schema 和原始 JSON Schema
29
+ type InferType<T> =
30
+ T extends z.ZodType<infer U> ? U : // Zod schema
31
+ T extends { type: infer TType } ? InferFromJSONSchema<T> : // 任何包含 type 字段的 JSON Schema(忽略 $schema)
32
+ T;
33
+
34
+ // 提取 args 对象,将每个 Zod schema 或 JSON Schema 转换为实际类型
35
+ type ExtractArgsFromMetadata<T> = T extends { metadata?: { args?: infer A } }
36
+ ? A extends Record<string, any>
37
+ ? { [K in keyof A]: InferType<A[K]> }
38
+ : never
39
+ : never;
40
+
41
+ // 类型映射:将 API 配置转换为方法签名
42
+ type ApiMethods<P extends { [path: string]: { [key: string]: Pos } }> = {
43
+ [Path in keyof P]: {
44
+ [Key in keyof P[Path]]: (
45
+ data?: Partial<ExtractArgsFromMetadata<P[Path][Key]>>,
46
+ opts?: DataOpts
47
+ ) => ReturnType<Query['post']>
48
+ }
49
+ }
50
+ type QueryApiOpts<P extends { [path: string]: { [key: string]: Pos } } = {}> = {
51
+ query?: Query,
52
+ api?: P
53
+ }
54
+ export class QueryApi<P extends { [path: string]: { [key: string]: Pos } } = {}> {
55
+ query: Query;
56
+
57
+ constructor(opts?: QueryApiOpts<P>) {
58
+ this.query = opts?.query ?? new Query();
59
+ if (opts?.api) {
60
+ this.createApi(opts.api);
61
+ }
62
+ }
63
+
64
+ // 使用泛型来推断类型
65
+ post<T extends Pos>(
66
+ pos: T,
67
+ data?: Partial<ExtractArgsFromMetadata<T>>,
68
+ opts?: DataOpts
69
+ ) {
70
+ const _pos = pick(pos, ['path', 'key', 'id']);
71
+ return this.query.post({
72
+ ..._pos,
73
+ payload: data
74
+ }, opts)
75
+ }
76
+
77
+ createApi(api: P): asserts this is this & ApiMethods<P> {
78
+ const that = this as any;
79
+ const apiEntries = Object.entries(api);
80
+ const keepPaths = ['createApi', 'query', 'post'];
81
+
82
+ for (const [path, methods] of apiEntries) {
83
+ if (keepPaths.includes(path)) continue;
84
+
85
+ // 为每个 path 创建命名空间对象
86
+ if (!that[path]) {
87
+ that[path] = {};
88
+ }
89
+
90
+ for (const [key, pos] of Object.entries(methods)) {
91
+ that[path][key] = (data?: Partial<ExtractArgsFromMetadata<typeof pos>>, opts?: DataOpts) => {
92
+ const _pos = pick(pos, ['path', 'key', 'id']);
93
+ return that.query.post({
94
+ ..._pos,
95
+ payload: data
96
+ }, opts);
97
+ };
98
+ }
99
+ }
100
+ }
101
+ }
102
+
103
+ // 创建工厂函数,提供更好的类型推断
104
+ export function createQueryApi<P extends { [path: string]: { [key: string]: Pos } }>(
105
+ opts?: QueryApiOpts<P>
106
+ ): QueryApi<P> & ApiMethods<P> {
107
+ return new QueryApi(opts) as QueryApi<P> & ApiMethods<P>;
108
+ }
109
+
110
+ export { createQueryByRoutes };
111
+ // const demo = {
112
+ // "test_path": {
113
+ // "test_key": {
114
+ // "path": "demo",
115
+ // "key": "test",
116
+ // metadata: {
117
+ // args: {
118
+ // name: z.string(),
119
+ // age: z.number(),
120
+ // }
121
+ // }
122
+ // }
123
+ // }
124
+ // } as const;
125
+
126
+ // // 方式1: 使用工厂函数创建(推荐)
127
+ // const queryApi = createQueryApi({ query: new Query(), api: demo });
128
+
129
+ // // 现在调用时会有完整的类型推断
130
+ // // data 参数会被推断为 { name?: string, age?: number }
131
+ // queryApi.test_path.test_key({ name: "test", age: 18 });
132
+ // // 也可以不传参数
133
+ // queryApi.test_path.test_key();
134
+
135
+ // // 或者只传递 opts
136
+ // queryApi.test_path.test_key(undefined, { timeout: 5000 });
@@ -1,9 +1,9 @@
1
1
  import { adapter } from './adapter.ts';
2
2
  import { QueryWs, QueryWsOpts } from './ws.ts';
3
- import { Query, ClientQuery } from './query.ts';
3
+ import { Query } from './query.ts';
4
4
  import { BaseQuery, QueryOptions, wrapperError } from './query.ts';
5
5
 
6
- export { QueryOpts, QueryWs, ClientQuery, Query, QueryWsOpts, adapter, BaseQuery, wrapperError };
6
+ export { QueryOpts, QueryWs, Query, QueryWsOpts, adapter, BaseQuery, wrapperError };
7
7
  export { QueryOptions }
8
8
  export type { DataOpts, Result, Data } from './query.ts';
9
9
 
package/src/query.ts CHANGED
@@ -162,6 +162,7 @@ export class Query {
162
162
  */
163
163
  async post<R = any, P = any>(body: Data & P, options?: DataOpts): Promise<Result<R>> {
164
164
  const url = options?.url || this.url;
165
+ console.log('query post', url, body, options);
165
166
  const { headers, adapter, beforeRequest, afterResponse, timeout, ...rest } = options || {};
166
167
  const _headers = { ...this.headers, ...headers };
167
168
  const _adapter = adapter || this.adapter;
@@ -301,13 +302,3 @@ export class BaseQuery<T extends Query = Query, R extends { queryChain?: any; qu
301
302
  return this.query.get(data, options);
302
303
  }
303
304
  }
304
-
305
- /**
306
- * @deprecated
307
- * 前端调用后端QueryRouter, 默认路径 /client/router
308
- */
309
- export class ClientQuery extends Query {
310
- constructor(opts?: QueryOpts) {
311
- super({ ...opts, url: opts?.url || '/client/router' });
312
- }
313
- }