@vafast/cli 0.1.4 → 0.1.5
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 +64 -10
- package/bin/vafast.js +0 -0
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{sync-Bq9q17kE.js → sync-CS1-nDMt.js} +77 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -32,6 +32,18 @@ npx vafast sync --url http://localhost:3000 --endpoint /api/contract
|
|
|
32
32
|
| `--url <url>` | 服务端地址(必填) | - |
|
|
33
33
|
| `--out <path>` | 输出文件路径 | `src/api.generated.ts` |
|
|
34
34
|
| `--endpoint <path>` | 契约接口路径 | `/__contract__` |
|
|
35
|
+
| `--strip-prefix <prefix>` | 去掉路径前缀 | - |
|
|
36
|
+
|
|
37
|
+
**示例:**
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# 去掉 /restfulApi 前缀
|
|
41
|
+
npx vafast sync \
|
|
42
|
+
--url http://localhost:9002 \
|
|
43
|
+
--endpoint /restfulApi/api-spec \
|
|
44
|
+
--out src/types/api/ones.generated.ts \
|
|
45
|
+
--strip-prefix /restfulApi
|
|
46
|
+
```
|
|
35
47
|
|
|
36
48
|
## 工作流程
|
|
37
49
|
|
|
@@ -66,13 +78,23 @@ npx vafast sync --url http://localhost:3000
|
|
|
66
78
|
### 3. 使用生成的类型
|
|
67
79
|
|
|
68
80
|
```typescript
|
|
69
|
-
import {
|
|
70
|
-
import
|
|
81
|
+
import { createClient } from '@vafast/api-client'
|
|
82
|
+
import { createApiClient } from './api.generated'
|
|
71
83
|
|
|
72
|
-
|
|
84
|
+
// 创建底层客户端
|
|
85
|
+
const client = createClient({
|
|
86
|
+
baseURL: 'http://localhost:3000',
|
|
87
|
+
timeout: 30000
|
|
88
|
+
})
|
|
73
89
|
|
|
74
|
-
//
|
|
90
|
+
// 创建类型安全的 API 客户端
|
|
91
|
+
const api = createApiClient(client)
|
|
92
|
+
|
|
93
|
+
// 类型安全的调用(错误路径会被 TypeScript 检测)
|
|
75
94
|
const { data, error } = await api.users.get({ page: 1 })
|
|
95
|
+
|
|
96
|
+
// ❌ TypeScript 会报错
|
|
97
|
+
// api.nonExistent.get() // Error: Property 'nonExistent' does not exist
|
|
76
98
|
```
|
|
77
99
|
|
|
78
100
|
## 自动化
|
|
@@ -82,9 +104,12 @@ const { data, error } = await api.users.get({ page: 1 })
|
|
|
82
104
|
```json
|
|
83
105
|
{
|
|
84
106
|
"scripts": {
|
|
85
|
-
"sync": "vafast sync --url
|
|
86
|
-
"
|
|
87
|
-
"
|
|
107
|
+
"sync:auth": "vafast sync --url http://localhost:9003 --endpoint /authRestfulApi/api-spec --out src/types/api/auth.generated.ts --strip-prefix /authRestfulApi",
|
|
108
|
+
"sync:ones": "vafast sync --url http://localhost:9002 --endpoint /restfulApi/api-spec --out src/types/api/ones.generated.ts --strip-prefix /restfulApi",
|
|
109
|
+
"sync:billing": "vafast sync --url http://localhost:9004 --endpoint /billingRestfulApi/api-spec --out src/types/api/billing.generated.ts --strip-prefix /billingRestfulApi",
|
|
110
|
+
"sync:types": "npm run sync:auth && npm run sync:billing && npm run sync:ones",
|
|
111
|
+
"dev": "vite",
|
|
112
|
+
"build": "npm run sync:types && vite build"
|
|
88
113
|
}
|
|
89
114
|
}
|
|
90
115
|
```
|
|
@@ -113,28 +138,57 @@ const { data, error } = await api.users.get({ page: 1 })
|
|
|
113
138
|
生成的类型:
|
|
114
139
|
|
|
115
140
|
```typescript
|
|
141
|
+
import type { ApiResponse, RequestConfig, Client, EdenClient } from '@vafast/api-client'
|
|
142
|
+
import { eden } from '@vafast/api-client'
|
|
143
|
+
|
|
144
|
+
/** API 契约类型 */
|
|
116
145
|
export type Api = {
|
|
117
146
|
users: {
|
|
118
147
|
get: {
|
|
119
148
|
query: { page?: number }
|
|
120
|
-
return:
|
|
149
|
+
return: any
|
|
121
150
|
}
|
|
122
151
|
post: {
|
|
123
152
|
body: { name?: string }
|
|
124
|
-
return:
|
|
153
|
+
return: any
|
|
125
154
|
}
|
|
126
155
|
}
|
|
127
156
|
}
|
|
157
|
+
|
|
158
|
+
/** API 客户端类型别名 */
|
|
159
|
+
export type ApiClientType = EdenClient<Api>
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* 创建类型安全的 API 客户端
|
|
163
|
+
*/
|
|
164
|
+
export function createApiClient(client: Client): EdenClient<Api> {
|
|
165
|
+
return eden<Api>(client)
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**使用方式:**
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { createClient } from '@vafast/api-client'
|
|
173
|
+
import { createApiClient } from './api.generated'
|
|
174
|
+
|
|
175
|
+
const client = createClient({ baseURL: '/api', timeout: 30000 })
|
|
176
|
+
const api = createApiClient(client)
|
|
177
|
+
|
|
178
|
+
// 完整的类型安全
|
|
179
|
+
const { data, error } = await api.users.post({ name: 'John' })
|
|
128
180
|
```
|
|
129
181
|
|
|
130
182
|
## 注意事项
|
|
131
183
|
|
|
132
|
-
1.
|
|
184
|
+
1. **返回类型**:如果后端未定义 `response` schema,生成的返回类型为 `any`(渐进式类型安全)。建议后端添加 `response` schema 获得完整类型检查。
|
|
133
185
|
|
|
134
186
|
2. **服务器必须运行**:执行 `sync` 命令时,服务端必须在运行并暴露契约接口。
|
|
135
187
|
|
|
136
188
|
3. **不要手动修改**:生成的文件会被覆盖,请勿手动修改。
|
|
137
189
|
|
|
190
|
+
4. **类型安全**:生成的 `createApiClient` 返回 `EdenClient<Api>`,TypeScript 会检测错误的 API 路径。
|
|
191
|
+
|
|
138
192
|
## License
|
|
139
193
|
|
|
140
194
|
MIT
|
package/bin/vafast.js
CHANGED
|
File without changes
|
package/dist/cli.js
CHANGED
package/dist/index.js
CHANGED
|
@@ -113,14 +113,87 @@ function generateTypeDefinition(contract, stripPrefix) {
|
|
|
113
113
|
lines.push(" * ⚠️ 请勿手动修改此文件,使用 `vafast sync` 重新生成");
|
|
114
114
|
lines.push(" */");
|
|
115
115
|
lines.push("");
|
|
116
|
+
lines.push("import type { ApiResponse, RequestConfig, Client, EdenClient } from '@vafast/api-client'");
|
|
117
|
+
lines.push("import { eden } from '@vafast/api-client'");
|
|
118
|
+
lines.push("");
|
|
116
119
|
const routeTree = buildRouteTree(contract.routes, stripPrefix);
|
|
120
|
+
lines.push("/** API 契约类型 */");
|
|
117
121
|
lines.push("export type Api = {");
|
|
118
122
|
lines.push(generateRouteTreeType(routeTree, 1));
|
|
119
123
|
lines.push("}");
|
|
120
124
|
lines.push("");
|
|
125
|
+
lines.push("/** API 客户端类型(提供完整的 IDE 智能提示) */");
|
|
126
|
+
lines.push("export interface ApiClient {");
|
|
127
|
+
lines.push(generateClientType(routeTree, 1));
|
|
128
|
+
lines.push("}");
|
|
129
|
+
lines.push("");
|
|
130
|
+
lines.push("/** API 客户端类型别名(基于 EdenClient 推断,提供完整类型检查) */");
|
|
131
|
+
lines.push("export type ApiClientType = EdenClient<Api>");
|
|
132
|
+
lines.push("");
|
|
133
|
+
lines.push("/**");
|
|
134
|
+
lines.push(" * 创建类型安全的 API 客户端");
|
|
135
|
+
lines.push(" * ");
|
|
136
|
+
lines.push(" * @example");
|
|
137
|
+
lines.push(" * ```typescript");
|
|
138
|
+
lines.push(" * import { createClient } from '@vafast/api-client'");
|
|
139
|
+
lines.push(" * import { createApiClient } from './api.generated'");
|
|
140
|
+
lines.push(" * ");
|
|
141
|
+
lines.push(" * const client = createClient('/api').use(authMiddleware)");
|
|
142
|
+
lines.push(" * const api = createApiClient(client)");
|
|
143
|
+
lines.push(" * ");
|
|
144
|
+
lines.push(" * // 完整的 IDE 智能提示和类型检查");
|
|
145
|
+
lines.push(" * const { data, error } = await api.users.find.post({ current: 1, pageSize: 10 })");
|
|
146
|
+
lines.push(" * // ❌ 错误路径会被 TypeScript 检测到");
|
|
147
|
+
lines.push(" * // api.nonExistent.post() // Error: Property 'nonExistent' does not exist");
|
|
148
|
+
lines.push(" * ```");
|
|
149
|
+
lines.push(" */");
|
|
150
|
+
lines.push("export function createApiClient(client: Client): EdenClient<Api> {");
|
|
151
|
+
lines.push(" return eden<Api>(client)");
|
|
152
|
+
lines.push("}");
|
|
153
|
+
lines.push("");
|
|
154
|
+
return lines.join("\n");
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* 生成客户端接口类型(带完整方法签名,IDE 友好)
|
|
158
|
+
*/
|
|
159
|
+
function generateClientType(tree, indent) {
|
|
160
|
+
const lines = [];
|
|
161
|
+
const pad = " ".repeat(indent);
|
|
162
|
+
for (const [key, node] of tree) {
|
|
163
|
+
const needsQuotes = /[^a-zA-Z0-9_$]/.test(key) || /^\d/.test(key);
|
|
164
|
+
const propName = needsQuotes ? `'${key}'` : key;
|
|
165
|
+
lines.push(`${pad}${propName}: {`);
|
|
166
|
+
for (const [method, route] of node.methods) {
|
|
167
|
+
if (route.description) lines.push(`${pad} /** ${route.description} */`);
|
|
168
|
+
const methodSig = generateMethodSignature(route, method);
|
|
169
|
+
lines.push(`${pad} ${method}: ${methodSig}`);
|
|
170
|
+
}
|
|
171
|
+
if (node.children.size > 0) {
|
|
172
|
+
const childContent = generateClientType(node.children, indent + 1);
|
|
173
|
+
if (childContent) lines.push(childContent);
|
|
174
|
+
}
|
|
175
|
+
lines.push(`${pad}}`);
|
|
176
|
+
}
|
|
121
177
|
return lines.join("\n");
|
|
122
178
|
}
|
|
123
179
|
/**
|
|
180
|
+
* 生成方法签名(函数类型)
|
|
181
|
+
*/
|
|
182
|
+
function generateMethodSignature(route, method) {
|
|
183
|
+
const params = [];
|
|
184
|
+
if (route.schema?.body) {
|
|
185
|
+
const bodyType = schemaToType(route.schema.body);
|
|
186
|
+
params.push(`body: ${bodyType}`);
|
|
187
|
+
}
|
|
188
|
+
if (route.schema?.query) {
|
|
189
|
+
const queryType = schemaToType(route.schema.query);
|
|
190
|
+
params.push(`query?: ${queryType}`);
|
|
191
|
+
}
|
|
192
|
+
params.push("config?: RequestConfig");
|
|
193
|
+
const returnType = route.schema?.response ? schemaToType(route.schema.response) : "any";
|
|
194
|
+
return `(${params.join(", ")}) => Promise<ApiResponse<${returnType}>>`;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
124
197
|
* 构建路由树
|
|
125
198
|
*/
|
|
126
199
|
function buildRouteTree(routes, stripPrefix) {
|
|
@@ -188,7 +261,10 @@ function generateMethodType(route) {
|
|
|
188
261
|
const paramsType = schemaToType(route.schema.params);
|
|
189
262
|
parts.push(`params: ${paramsType}`);
|
|
190
263
|
}
|
|
191
|
-
|
|
264
|
+
if (route.schema?.response) {
|
|
265
|
+
const responseType = schemaToType(route.schema.response);
|
|
266
|
+
parts.push(`return: ${responseType}`);
|
|
267
|
+
} else parts.push("return: any");
|
|
192
268
|
if (parts.length === 1) return `{ ${parts[0]} }`;
|
|
193
269
|
return `{\n ${parts.join("\n ")}\n }`;
|
|
194
270
|
}
|