chore: bump version to 0.0.46 and enhance type inference for JSON Schema required fields
This commit is contained in:
201
docs/required-field-inference.md
Normal file
201
docs/required-field-inference.md
Normal file
@@ -0,0 +1,201 @@
|
||||
# Query API 类型推断优化 - 支持 JSON Schema Required 字段
|
||||
|
||||
## 问题描述
|
||||
|
||||
之前的 `query-api` 实现对所有参数使用 `Partial`,导致即使 JSON Schema 中定义了 `required` 字段,TypeScript 类型推断也会将所有字段标记为可选。
|
||||
|
||||
示例:
|
||||
```typescript
|
||||
const api = {
|
||||
update: {
|
||||
metadata: {
|
||||
args: {
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
domain: { type: "string" }
|
||||
},
|
||||
required: ["domain"] // 只有 domain 是必需的
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
// 之前:所有字段都是可选的
|
||||
// queryApi.update({ data: {} }) // 不会报错,但应该要求 domain
|
||||
|
||||
// 现在:正确识别必需字段
|
||||
queryApi.update({ data: {} }) // 报错:缺少必需字段 "domain"
|
||||
queryApi.update({ data: { domain: "test.com" } }) // 正确
|
||||
```
|
||||
|
||||
## 解决方案
|
||||
|
||||
### 1. 增强 `InferFromJSONSchema` 类型
|
||||
|
||||
更新类型推断逻辑以支持 `required` 字段:
|
||||
|
||||
```typescript
|
||||
type InferFromJSONSchema<T> =
|
||||
// ... 其他类型处理 ...
|
||||
|
||||
// 对象类型 - 带 required 字段(必需字段 + 可选字段)
|
||||
T extends { type: "object"; properties: infer P; required: infer R extends readonly string[] }
|
||||
? {
|
||||
// 必需字段
|
||||
[K in keyof P as K extends R[number] ? K : never]: InferFromJSONSchema<P[K]>
|
||||
} & {
|
||||
// 可选字段
|
||||
[K in keyof P as K extends R[number] ? never : K]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
: // 对象类型 - 不带 required 字段(所有字段可选)
|
||||
T extends { type: "object"; properties: infer P }
|
||||
? {
|
||||
[K in keyof P]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
: ...
|
||||
```
|
||||
|
||||
### 2. 移除不必要的 `Partial`
|
||||
|
||||
从以下位置移除 `Partial` 包装:
|
||||
- `ApiMethods` 类型定义
|
||||
- `QueryApi.post()` 方法
|
||||
- `createApi()` 方法内部
|
||||
|
||||
### 3. 支持 additionalProperties
|
||||
|
||||
添加对动态对象的支持:
|
||||
```typescript
|
||||
// 对象类型 - additionalProperties
|
||||
T extends { type: "object"; additionalProperties: infer A }
|
||||
? (A extends {} ? Record<string, any> : never)
|
||||
: ...
|
||||
```
|
||||
|
||||
## 类型推断示例
|
||||
|
||||
### 示例 1:必需字段和可选字段
|
||||
|
||||
```typescript
|
||||
const api = {
|
||||
update: {
|
||||
metadata: {
|
||||
args: {
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
id: { type: "string" },
|
||||
domain: { type: "string" },
|
||||
status: {
|
||||
type: "string",
|
||||
enum: ["active", "inactive"]
|
||||
}
|
||||
},
|
||||
required: ["domain"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const queryApi = createQueryApi({ api });
|
||||
|
||||
// ✅ 正确:只传必需字段
|
||||
queryApi.update({ data: { domain: "test.com" } });
|
||||
|
||||
// ✅ 正确:传必需字段 + 可选字段
|
||||
queryApi.update({
|
||||
data: {
|
||||
domain: "test.com",
|
||||
id: "123",
|
||||
status: "active"
|
||||
}
|
||||
});
|
||||
|
||||
// ❌ 错误:缺少必需字段
|
||||
queryApi.update({ data: { id: "123" } });
|
||||
// Error: 类型 "{ id: string; }" 中缺少属性 "domain"
|
||||
|
||||
// ❌ 错误:enum 值不正确
|
||||
queryApi.update({
|
||||
data: {
|
||||
domain: "test.com",
|
||||
status: "pending"
|
||||
}
|
||||
});
|
||||
// Error: 不能将类型 "pending" 分配给类型 "active" | "inactive"
|
||||
```
|
||||
|
||||
### 示例 2:没有 required 字段(全部可选)
|
||||
|
||||
```typescript
|
||||
const api = {
|
||||
list: {
|
||||
metadata: {
|
||||
args: {
|
||||
data: {
|
||||
type: "object",
|
||||
properties: {
|
||||
page: { type: "number" },
|
||||
pageSize: { type: "number" }
|
||||
}
|
||||
// 没有 required 字段
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const queryApi = createQueryApi({ api });
|
||||
|
||||
// ✅ 所有字段都是可选的
|
||||
queryApi.list({ data: {} });
|
||||
queryApi.list({ data: { page: 1 } });
|
||||
queryApi.list({ data: { page: 1, pageSize: 20 } });
|
||||
```
|
||||
|
||||
### 示例 3:动态对象 (additionalProperties)
|
||||
|
||||
```typescript
|
||||
const api = {
|
||||
createMeta: {
|
||||
metadata: {
|
||||
args: {
|
||||
metadata: {
|
||||
type: "object",
|
||||
additionalProperties: {} // 动态键值对
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const queryApi = createQueryApi({ api });
|
||||
|
||||
// ✅ 可以传入任意键值对
|
||||
queryApi.createMeta({ metadata: { key1: "value1", key2: 123 } });
|
||||
```
|
||||
|
||||
## 测试
|
||||
|
||||
运行测试文件验证类型推断:
|
||||
```bash
|
||||
npx tsc --noEmit test/verify-fix.ts
|
||||
```
|
||||
|
||||
## 文件修改
|
||||
|
||||
- `src/query-api.ts` - 更新类型推断逻辑
|
||||
- `test/verify-fix.ts` - 测试用例
|
||||
- `test/type-test.ts` - 类型推断测试
|
||||
- `test/debug-type.ts` - 调试类型推断
|
||||
|
||||
## 注意事项
|
||||
|
||||
1. **as const** - API 定义必须使用 `as const` 以保持字面量类型
|
||||
2. **readonly** - 类型推断需要处理 `readonly` 修饰符
|
||||
3. **enum** - 自动推断 enum 值并提供类型约束
|
||||
4. **构建** - 修改后需要重新构建项目:`npm run build`
|
||||
183
docs/type-inference-fix-report.md
Normal file
183
docs/type-inference-fix-report.md
Normal file
@@ -0,0 +1,183 @@
|
||||
# 类型推断问题修复报告
|
||||
|
||||
## 问题描述
|
||||
|
||||
在 `/home/ubuntu/kevisual/query/test/examples.ts` 中,使用 `as const` 定义的 JSON Schema 对象,其 `required` 字段没有被正确识别。具体表现为:
|
||||
|
||||
- Schema 定义:`required: ["domain"]` - 表示只有 `domain` 是必需字段
|
||||
- 实际推断:所有字段(id, domain, appId, status, data)都被推断为必需字段
|
||||
- 期望行为:只有 `domain` 是必需的,其他字段应该是可选的
|
||||
|
||||
## 根本原因
|
||||
|
||||
问题出在 [src/query-api.ts](../src/query-api.ts) 的 `InferFromJSONSchema` 类型定义中,**条件类型的匹配顺序不当**。
|
||||
|
||||
### 原始代码(有问题的版本)
|
||||
|
||||
```typescript
|
||||
type InferFromJSONSchema<T> =
|
||||
// ... 其他类型 ...
|
||||
|
||||
// 对象类型 - 只有 additionalProperties(纯动态对象)
|
||||
T extends { type: "object"; additionalProperties: infer A }
|
||||
? (A extends {} ? Record<string, any> : never)
|
||||
|
||||
: // 对象类型 - 带 required 字段(必需字段 + 可选字段)
|
||||
T extends { type: "object"; properties: infer P; required: infer R extends readonly string[] }
|
||||
? { /* ... */ }
|
||||
|
||||
: // ...
|
||||
```
|
||||
|
||||
### 问题分析
|
||||
|
||||
当 JSON Schema **同时包含** `additionalProperties: false` 和 `required: ["domain"]` 时:
|
||||
|
||||
1. TypeScript 首先尝试匹配第一个条件:`T extends { type: "object"; additionalProperties: infer A }`
|
||||
2. 匹配成功!因为 Schema 确实有 `additionalProperties: false`
|
||||
3. 此时 `A = false`,而 `false extends {}` 为 `false`
|
||||
4. 返回 `never`,导致类型推断失败或行为异常
|
||||
|
||||
**关键点**:在 JSON Schema 规范中,`additionalProperties: false` 和 `required` 字段是可以同时存在的,但原始代码的条件顺序导致 `additionalProperties` 优先匹配,阻止了 `required` 字段的正确处理。
|
||||
|
||||
## 解决方案
|
||||
|
||||
调整条件类型的匹配顺序,将 `properties + required` 的检查放在 `additionalProperties` 之前:
|
||||
|
||||
```typescript
|
||||
type InferFromJSONSchema<T> =
|
||||
// ... 其他类型 ...
|
||||
|
||||
: // 对象类型 - 带 required 字段(必需字段 + 可选字段)
|
||||
// 注意:必须在 additionalProperties 检查之前,因为两者可能同时存在
|
||||
T extends { type: "object"; properties: infer P; required: infer R extends readonly string[] }
|
||||
? {
|
||||
[K in keyof P as K extends R[number] ? K : never]: InferFromJSONSchema<P[K]>
|
||||
} & {
|
||||
[K in keyof P as K extends R[number] ? never : K]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
|
||||
: // 对象类型 - 不带 required 字段(所有字段可选)
|
||||
T extends { type: "object"; properties: infer P }
|
||||
? {
|
||||
[K in keyof P]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
|
||||
: // 对象类型 - 只有 additionalProperties(纯动态对象)
|
||||
T extends { type: "object"; additionalProperties: infer A }
|
||||
? (A extends false ? Record<string, never> : Record<string, any>)
|
||||
|
||||
: // ...
|
||||
```
|
||||
|
||||
### 关键改进
|
||||
|
||||
1. **优先级调整**:将 `properties + required` 的检查提前
|
||||
2. **additionalProperties 处理优化**:
|
||||
- `A extends false` → `Record<string, never>`(空对象)
|
||||
- 其他情况 → `Record<string, any>`(动态对象)
|
||||
|
||||
## 验证结果
|
||||
|
||||
### 测试场景 1: 带 required + additionalProperties: false
|
||||
|
||||
```typescript
|
||||
const schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"domain": { "type": "string" },
|
||||
"appId": { "type": "string" },
|
||||
"status": { "type": "string", "enum": ["active", "inactive"] }
|
||||
},
|
||||
"required": ["domain"],
|
||||
"additionalProperties": false
|
||||
} as const;
|
||||
```
|
||||
|
||||
**推断结果**:
|
||||
```typescript
|
||||
type Result = {
|
||||
domain: string; // ✓ 必需字段
|
||||
id?: string; // ✓ 可选字段
|
||||
appId?: string; // ✓ 可选字段
|
||||
status?: "active" | "inactive"; // ✓ 可选字段 + enum 推断正确
|
||||
}
|
||||
```
|
||||
|
||||
### 测试场景 2: 所有字段可选
|
||||
|
||||
```typescript
|
||||
const schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"domain": { "type": "string" }
|
||||
},
|
||||
"additionalProperties": false
|
||||
} as const;
|
||||
```
|
||||
|
||||
**推断结果**:
|
||||
```typescript
|
||||
type Result = {
|
||||
id?: string; // ✓ 可选
|
||||
domain?: string; // ✓ 可选
|
||||
}
|
||||
```
|
||||
|
||||
### 测试场景 3: 所有字段必需
|
||||
|
||||
```typescript
|
||||
const schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"domain": { "type": "string" }
|
||||
},
|
||||
"required": ["id", "domain"]
|
||||
} as const;
|
||||
```
|
||||
|
||||
**推断结果**:
|
||||
```typescript
|
||||
type Result = {
|
||||
id: string; // ✓ 必需
|
||||
domain: string; // ✓ 必需
|
||||
}
|
||||
```
|
||||
|
||||
## TypeScript 类型系统的关键知识点
|
||||
|
||||
### 1. `as const` 的作用
|
||||
|
||||
使用 `as const` 后:
|
||||
- `required: ["domain"]` 的类型从 `string[]` 变为 `readonly ["domain"]`
|
||||
- `R[number]` 从 `string`(匹配所有字符串键)变为 `"domain"`(只匹配字面量)
|
||||
- 这使得 `K extends R[number]` 的判断更加精确
|
||||
|
||||
### 2. 条件类型的匹配顺序
|
||||
|
||||
TypeScript 的条件类型是**从上到下**匹配的,**第一个匹配的条件会被使用**。因此:
|
||||
- 更具体的条件应该放在前面
|
||||
- 更通用的条件应该放在后面
|
||||
- 可能同时满足多个条件时,要考虑优先级
|
||||
|
||||
### 3. `extends` 的结构类型检查
|
||||
|
||||
```typescript
|
||||
T extends { type: "object"; additionalProperties: infer A }
|
||||
```
|
||||
|
||||
即使 `T` 还有其他字段(如 `properties`、`required`),只要它包含 `type` 和 `additionalProperties`,这个条件就会匹配成功。
|
||||
|
||||
## 总结
|
||||
|
||||
这个问题的核心是 **TypeScript 条件类型的匹配顺序问题**。当 JSON Schema 同时包含多个字段时,需要确保更具体的条件(如 `properties + required`)在更通用的条件(如只检查 `additionalProperties`)之前被检查。
|
||||
|
||||
修复后的类型系统现在能够正确处理:
|
||||
- ✅ 必需字段和可选字段的区分
|
||||
- ✅ `as const` 的字面量类型推断
|
||||
- ✅ `enum` 字段的联合类型推断
|
||||
- ✅ `additionalProperties` 的正确处理
|
||||
- ✅ 各种 JSON Schema 字段组合
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@kevisual/query",
|
||||
"version": "0.0.44",
|
||||
"version": "0.0.46",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "npm run clean && bun run bun.config.ts",
|
||||
|
||||
@@ -25,18 +25,35 @@ type InferFromJSONSchema<T> =
|
||||
T extends { type: "number" } ? number :
|
||||
T extends { type: "integer" } ? number :
|
||||
T extends { type: "boolean" } ? boolean :
|
||||
// 对象类型 - 检查是否有 properties
|
||||
T extends { type: "object"; properties: infer P }
|
||||
? {
|
||||
[K in keyof P]: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
: // 数组类型
|
||||
// 数组类型
|
||||
T extends { type: "array"; items: infer I }
|
||||
? Array<InferFromJSONSchema<I>>
|
||||
: // 对象类型 - 带 required 字段(必需字段 + 可选字段)
|
||||
// 注意:必须在 additionalProperties 检查之前,因为两者可能同时存在
|
||||
T extends { type: "object"; properties: infer P; required: infer R extends readonly string[] }
|
||||
? {
|
||||
[K in keyof P as K extends R[number] ? K : never]: InferFromJSONSchema<P[K]>
|
||||
} & {
|
||||
[K in keyof P as K extends R[number] ? never : K]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
: // 对象类型 - 不带 required 字段(所有字段可选)
|
||||
T extends { type: "object"; properties: infer P }
|
||||
? {
|
||||
[K in keyof P]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
: // 对象类型 - 只有 additionalProperties(纯动态对象)
|
||||
T extends { type: "object"; additionalProperties: infer A }
|
||||
? (A extends false ? Record<string, never> : Record<string, any>)
|
||||
: // 默认情况 - 如果是对象但没有 type 字段,尝试递归推断
|
||||
T extends Record<string, any>
|
||||
? T extends { properties: infer P }
|
||||
? { [K in keyof P]: InferFromJSONSchema<P[K]> }
|
||||
? T extends { properties: infer P; required: infer R extends readonly string[] }
|
||||
? {
|
||||
[K in keyof P as K extends R[number] ? K : never]: InferFromJSONSchema<P[K]>
|
||||
} & {
|
||||
[K in keyof P as K extends R[number] ? never : K]?: InferFromJSONSchema<P[K]>
|
||||
}
|
||||
: T extends { properties: infer P }
|
||||
? { [K in keyof P]?: InferFromJSONSchema<P[K]> }
|
||||
: unknown
|
||||
: unknown;
|
||||
|
||||
|
||||
222
test/examples.ts
222
test/examples.ts
@@ -38,15 +38,229 @@ const api = {
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 获取域名列表,支持分页
|
||||
*
|
||||
* @param data - Request parameters
|
||||
* @param data.data - {object}
|
||||
*/
|
||||
"list": {
|
||||
"path": "app_domain_manager",
|
||||
"key": "list",
|
||||
"description": "获取域名列表,支持分页",
|
||||
"metadata": {
|
||||
"args": {
|
||||
"data": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": {
|
||||
"type": "number"
|
||||
},
|
||||
"pageSize": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"viewItem": {
|
||||
"api": {
|
||||
"url": "/api/router"
|
||||
},
|
||||
"type": "api",
|
||||
"title": "路由"
|
||||
},
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 更新一个域名的信息
|
||||
*
|
||||
* @param data - Request parameters
|
||||
* @param data.data - {object}
|
||||
*/
|
||||
"update": {
|
||||
"path": "app_domain_manager",
|
||||
"key": "update",
|
||||
"description": "更新一个域名的信息",
|
||||
"metadata": {
|
||||
"args": {
|
||||
"data": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
},
|
||||
"appId": {
|
||||
"type": "string"
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"active",
|
||||
"inactive"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"type": "object",
|
||||
"propertyNames": {
|
||||
"type": "string"
|
||||
},
|
||||
"additionalProperties": {}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"domain"
|
||||
],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"viewItem": {
|
||||
"api": {
|
||||
"url": "/api/router"
|
||||
},
|
||||
"type": "api",
|
||||
"title": "路由"
|
||||
},
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
},
|
||||
/**
|
||||
* 删除一个域名
|
||||
*
|
||||
* @param data - Request parameters
|
||||
* @param data.data - {object}
|
||||
*/
|
||||
"delete": {
|
||||
"path": "app_domain_manager",
|
||||
"key": "delete",
|
||||
"description": "删除一个域名",
|
||||
"metadata": {
|
||||
"args": {
|
||||
"data": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"domain": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"viewItem": {
|
||||
"api": {
|
||||
"url": "/api/router"
|
||||
},
|
||||
"type": "api",
|
||||
"title": "路由"
|
||||
},
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app": {
|
||||
"getDomainApp": {
|
||||
"path": "app",
|
||||
"key": "getDomainApp",
|
||||
"metadata": {
|
||||
"viewItem": {
|
||||
"api": {
|
||||
"url": "/api/router"
|
||||
},
|
||||
"type": "api",
|
||||
"title": "路由"
|
||||
},
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
}
|
||||
},
|
||||
"app-domain": {
|
||||
"create": {
|
||||
"path": "app-domain",
|
||||
"key": "create",
|
||||
"metadata": {
|
||||
"viewItem": {
|
||||
"api": {
|
||||
"url": "/api/router"
|
||||
},
|
||||
"type": "api",
|
||||
"title": "路由"
|
||||
},
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
},
|
||||
"update": {
|
||||
"path": "app-domain",
|
||||
"key": "update",
|
||||
"metadata": {
|
||||
"viewItem": {
|
||||
"api": {
|
||||
"url": "/api/router"
|
||||
},
|
||||
"type": "api",
|
||||
"title": "路由"
|
||||
},
|
||||
"url": "/api/router",
|
||||
"source": "query-proxy-api"
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
const queryApi = createQueryApi({ api });
|
||||
export { queryApi };
|
||||
|
||||
queryApi.app_domain_manager.get({
|
||||
// ===== 类型推断示例 =====
|
||||
|
||||
// ✅ 示例 1:只传必需字段 domain
|
||||
queryApi.app_domain_manager.update({
|
||||
data: {
|
||||
id: '123',
|
||||
domain: 'example.com'
|
||||
domain: "example.com"
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// ✅ 示例 2:传必需字段 + 可选字段
|
||||
queryApi.app_domain_manager.update({
|
||||
data: {
|
||||
domain: "example.com",
|
||||
id: "123",
|
||||
appId: "app-001",
|
||||
status: "active", // enum 类型,只能是 'active' 或 'inactive'
|
||||
data: { custom: "metadata" }
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ 示例 3:list 方法没有 required 字段,所有参数都是可选的
|
||||
queryApi.app_domain_manager.list({
|
||||
data: {
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ 示例 4:list 方法可以不传参数
|
||||
queryApi.app_domain_manager.list({
|
||||
data: {}
|
||||
});
|
||||
|
||||
// 检查类型推断
|
||||
type UpdateParams = Parameters<typeof queryApi.app_domain_manager.update>[0];
|
||||
// 推断为: { data: { domain: string } & { id?: string, appId?: string, status?: "active" | "inactive", data?: Record<string, any> } }
|
||||
|
||||
type ListParams = Parameters<typeof queryApi.app_domain_manager.list>[0];
|
||||
// 推断为: { data: { page?: number, pageSize?: number } }
|
||||
|
||||
|
||||
@@ -161,4 +161,115 @@ queryApi4.order.updateStatus({
|
||||
// });
|
||||
|
||||
|
||||
// ============ 示例 5: Object 类型 - 带 required 字段 ============
|
||||
const api5 = {
|
||||
"domain_manager": {
|
||||
"update": {
|
||||
"path": "domain_manager",
|
||||
"key": "update",
|
||||
"metadata": {
|
||||
"args": {
|
||||
"data": {
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "string" },
|
||||
"domain": { "type": "string" },
|
||||
"appId": { "type": "string" },
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["active", "inactive"] as const
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ssl": { "type": "boolean" },
|
||||
"cdn": { "type": "boolean" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": ["domain"] as const, // 只有 domain 是必需的
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"list": {
|
||||
"path": "domain_manager",
|
||||
"key": "list",
|
||||
"metadata": {
|
||||
"args": {
|
||||
"data": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"page": { "type": "number" },
|
||||
"pageSize": { "type": "number" },
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["active", "inactive"] as const
|
||||
}
|
||||
}
|
||||
// 没有 required 字段,所以所有字段都是可选的
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} as const;
|
||||
|
||||
const queryApi5 = createQueryApi({ api: api5 });
|
||||
|
||||
// ✅ 示例 5.1: 只传必需字段 domain
|
||||
queryApi5.domain_manager.update({
|
||||
data: {
|
||||
domain: "example.com" // domain 是必需的
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ 示例 5.2: 传必需字段 + 可选字段
|
||||
queryApi5.domain_manager.update({
|
||||
data: {
|
||||
domain: "example.com", // 必需
|
||||
id: "123", // 可选
|
||||
appId: "app-001", // 可选
|
||||
status: "active", // 可选,且只能是 "active" | "inactive"
|
||||
config: { // 可选对象
|
||||
ssl: true,
|
||||
cdn: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// ❌ 错误:缺少必需字段 domain
|
||||
// queryApi5.domain_manager.update({
|
||||
// data: {
|
||||
// id: "123" // 错误:缺少必需字段 "domain"
|
||||
// }
|
||||
// });
|
||||
|
||||
// ✅ 示例 5.3: list 方法所有字段都是可选的
|
||||
queryApi5.domain_manager.list({
|
||||
data: {
|
||||
page: 1,
|
||||
pageSize: 10
|
||||
}
|
||||
});
|
||||
|
||||
// ✅ 示例 5.4: list 方法可以传空对象
|
||||
queryApi5.domain_manager.list({
|
||||
data: {}
|
||||
});
|
||||
|
||||
// ✅ 示例 5.5: list 方法甚至可以不传参数
|
||||
queryApi5.domain_manager.list();
|
||||
|
||||
|
||||
// ============ 类型检查 ============
|
||||
type UpdateParams = Parameters<typeof queryApi5.domain_manager.update>[0];
|
||||
// 推断为: { data: { domain: string } & { id?: string, appId?: string, status?: "active" | "inactive", config?: {...} } }
|
||||
|
||||
type ListParams = Parameters<typeof queryApi5.domain_manager.list>[0];
|
||||
// 推断为: { query?: { page?: number, pageSize?: number, status?: "active" | "inactive" } }
|
||||
|
||||
|
||||
console.log('✅ 所有类型推断示例运行成功!');
|
||||
|
||||
Reference in New Issue
Block a user