czh-api 1.0.3 → 1.0.4

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/CHANGELOG.md CHANGED
@@ -1,10 +1,22 @@
1
1
  # 更新日志
2
2
 
3
- 所有重要的版本更新都会记录在此文件中。
4
-
5
- ## [1.0.3] - 2026-03-17
6
-
7
- ### 新增
3
+ 所有重要的版本更新都会记录在此文件中。
4
+
5
+ ## [1.0.4] - 2026-03-19
6
+
7
+ ### 修复
8
+ - 修复响应最外层为数组(如 `type: array` + `items.$ref`)时类型生成异常的问题,避免出现非法接口声明(如 `export interface Xxx[]`)。
9
+ - 修复仅在“数组响应”中被引用的实体类型可能退化为 `{ [key: string]: any }` 的问题,增强该场景下的类型补全稳定性。
10
+ - 修复类型增强阶段对 `export interface` 的替换逻辑在存在内联对象字段时可能截断内容的问题,避免生成残留字段到接口外。
11
+ - 修复部分 OpenAPI `$ref` 路径在类型编译阶段解析不一致导致的类型降级问题(兼容 `#/components/schemas/*` 场景)。
12
+
13
+ ### 改进
14
+ - 优化数组响应类型推导策略:当响应为 `$ref` 数组时优先生成 `ItemType[]`,减少中间响应类型噪音。
15
+ - 增强类型生成兜底逻辑的类型名合法性校验,避免对泛型/数组等复杂类型名误生成 `interface`。
16
+
17
+ ## [1.0.3] - 2026-03-17
18
+
19
+ ### 新增
8
20
  - 添加 `pathPrefixes` 配置项,支持自定义路径前缀分组和二级分包
9
21
  - 支持配置多个路径前缀,每个前缀可指定自定义包名或自动驼峰命名
10
22
  - 自动按路径前缀后的第一级路径进行二级分包
@@ -52,64 +52,100 @@ function convertOpenApiTypeToTypeScript(openApiType) {
52
52
  }
53
53
  }
54
54
  // 改进类型定义的辅助函数
55
+ function isValidTypeIdentifier(typeName) {
56
+ if (!typeName)
57
+ return false;
58
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(typeName);
59
+ }
60
+ function escapeRegex(value) {
61
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
62
+ }
63
+ function resolveInterfaceTargetType(typeName) {
64
+ if (typeof typeName !== 'string' || typeName.length === 0)
65
+ return null;
66
+ if (isValidTypeIdentifier(typeName))
67
+ return typeName;
68
+ const arrayMatch = typeName.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\[\]$/);
69
+ if (arrayMatch) {
70
+ return arrayMatch[1];
71
+ }
72
+ return null;
73
+ }
74
+ function findInterfaceBlockRange(content, typeName) {
75
+ const startRegex = new RegExp(`export interface ${escapeRegex(typeName)}\\s*\\{`);
76
+ const match = startRegex.exec(content);
77
+ if (!match)
78
+ return null;
79
+ const start = match.index;
80
+ const openBraceIndex = start + match[0].lastIndexOf('{');
81
+ let depth = 0;
82
+ for (let i = openBraceIndex; i < content.length; i++) {
83
+ const ch = content[i];
84
+ if (ch === '{') {
85
+ depth++;
86
+ }
87
+ else if (ch === '}') {
88
+ depth--;
89
+ if (depth === 0) {
90
+ return { start, end: i + 1 };
91
+ }
92
+ }
93
+ }
94
+ return null;
95
+ }
55
96
  function improveTypeDefinitions(typesContent, endpoints) {
56
97
  let improvedContent = typesContent;
57
98
  // 收集所有需要改进的类型定义
58
99
  const typeImprovements = {};
59
- endpoints.forEach(endpoint => {
60
- // 改进 params 类型
61
- if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
62
- const paramsFields = endpoint.jsdocParams
63
- .filter((param) => param.name && param.type)
64
- .map((param) => {
65
- const optional = !param.required ? '?' : '';
66
- let type = param.type;
67
- // 处理联合类型,确保正确的 TypeScript 语法
68
- if (type && type.includes(' | ')) {
69
- // 如果包含 null,确保格式正确
70
- if (type.includes('null')) {
71
- type = type.replace(/\s*\|\s*null/g, ' | null');
72
- }
100
+ // 辅助函数:将 jsdoc params 转换为接口字段
101
+ function buildFieldsFromJsdocParams(jsdocParams) {
102
+ return jsdocParams
103
+ .filter((param) => param.name && param.type)
104
+ .map((param) => {
105
+ const optional = !param.required ? '?' : '';
106
+ let type = param.type;
107
+ // 处理联合类型,确保正确的 TypeScript 语法
108
+ if (type && type.includes(' | ')) {
109
+ if (type.includes('null')) {
110
+ type = type.replace(/\s*\|\s*null/g, ' | null');
73
111
  }
74
- else {
75
- // 处理基本类型转换
76
- type = convertOpenApiTypeToTypeScript(type);
77
- }
78
- const comment = param.description ? ` // ${param.description}` : '';
79
- return ` ${param.name}${optional}: ${type};${comment}`;
80
- })
81
- .join('\n');
112
+ }
113
+ else {
114
+ type = convertOpenApiTypeToTypeScript(type);
115
+ }
116
+ const comment = param.description ? ` // ${param.description}` : '';
117
+ return ` ${param.name}${optional}: ${type};${comment}`;
118
+ })
119
+ .join('\n');
120
+ }
121
+ endpoints.forEach(endpoint => {
122
+ // 改进 params 类型 - 使用 paramsJsdocParams(仅包含 path/query 参数)
123
+ const requestParamsTypeName = endpoint.requestParamsTypeName;
124
+ if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
125
+ const paramsFields = buildFieldsFromJsdocParams(endpoint.paramsJsdocParams);
82
126
  if (paramsFields) {
83
- typeImprovements[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
84
- ${paramsFields}
127
+ typeImprovements[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
128
+ ${paramsFields}
85
129
  }`;
86
130
  }
87
131
  }
88
- // 改进 requestBody 类型
89
- if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
90
- const dataFields = endpoint.jsdocParams
91
- .filter((param) => param.name && param.type)
92
- .map((param) => {
93
- const optional = !param.required ? '?' : '';
94
- let type = param.type;
95
- // 处理联合类型,确保正确的 TypeScript 语法
96
- if (type && type.includes(' | ')) {
97
- // 如果包含 null,确保格式正确
98
- if (type.includes('null')) {
99
- type = type.replace(/\s*\|\s*null/g, ' | null');
100
- }
101
- }
102
- else {
103
- // 处理基本类型转换
104
- type = convertOpenApiTypeToTypeScript(type);
105
- }
106
- const comment = param.description ? ` // ${param.description}` : '';
107
- return ` ${param.name}${optional}: ${type};${comment}`;
108
- })
109
- .join('\n');
132
+ // 改进 requestBody 类型 - 使用 dataJsdocParams(仅包含 requestBody 参数)
133
+ const requestBodyTypeName = endpoint.requestBodyTypeName;
134
+ if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
135
+ const dataFields = buildFieldsFromJsdocParams(endpoint.dataJsdocParams);
110
136
  if (dataFields) {
111
- typeImprovements[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
112
- ${dataFields}
137
+ typeImprovements[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
138
+ ${dataFields}
139
+ }`;
140
+ }
141
+ }
142
+ // 改进 response 类型 - 使用 responseJsdocParams(包含响应字段信息)
143
+ const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
144
+ if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
145
+ const responseFields = buildFieldsFromJsdocParams(endpoint.responseJsdocParams);
146
+ if (responseFields) {
147
+ typeImprovements[responseTargetType] = `export interface ${responseTargetType} {
148
+ ${responseFields}
113
149
  }`;
114
150
  }
115
151
  }
@@ -117,9 +153,12 @@ ${dataFields}
117
153
  // 替换现有的类型定义
118
154
  Object.entries(typeImprovements).forEach(([typeName, newDefinition]) => {
119
155
  // 查找并替换现有的接口定义
120
- const interfaceRegex = new RegExp(`export interface ${typeName}\\s*\\{[^}]*\\}`, 'g');
121
- if (interfaceRegex.test(improvedContent)) {
122
- improvedContent = improvedContent.replace(interfaceRegex, newDefinition);
156
+ const blockRange = findInterfaceBlockRange(improvedContent, typeName);
157
+ if (blockRange) {
158
+ improvedContent =
159
+ improvedContent.slice(0, blockRange.start) +
160
+ newDefinition +
161
+ improvedContent.slice(blockRange.end);
123
162
  }
124
163
  else {
125
164
  // 如果没找到接口定义,添加到末尾
@@ -186,6 +225,33 @@ function removeUselessTypeAliases(typesContent) {
186
225
  cleanedContent = cleanedContent.replace(/\/\*\*\n \* [^\n]*\n \*\/\n(?!export)/g, '');
187
226
  return cleanedContent;
188
227
  }
228
+ function normalizeRef(refValue) {
229
+ const match = refValue.match(/^#\/components\/schemas\/([^/]+)$/);
230
+ if (match) {
231
+ return `#/definitions/${match[1]}`;
232
+ }
233
+ return refValue;
234
+ }
235
+ function normalizeSchemaRefs(value) {
236
+ if (Array.isArray(value)) {
237
+ return value.map(item => normalizeSchemaRefs(item));
238
+ }
239
+ if (value && typeof value === 'object') {
240
+ const obj = value;
241
+ const normalized = {};
242
+ for (const key in obj) {
243
+ const currentValue = obj[key];
244
+ if (key === '$ref' && typeof currentValue === 'string') {
245
+ normalized[key] = normalizeRef(currentValue);
246
+ }
247
+ else {
248
+ normalized[key] = normalizeSchemaRefs(currentValue);
249
+ }
250
+ }
251
+ return normalized;
252
+ }
253
+ return value;
254
+ }
189
255
  const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
190
256
  try {
191
257
  console.log(chalk_1.default.blue('Building API from source...'));
@@ -285,9 +351,10 @@ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
285
351
  if (Object.keys(module.schemas).length > 0) {
286
352
  console.log(chalk_1.default.blue(`正在为模块 "${moduleName}" 生成类型文件,包含 ${Object.keys(module.schemas).length} 个 schema`));
287
353
  try {
354
+ const normalizedModuleSchemas = normalizeSchemaRefs(module.schemas);
288
355
  const schemasWithTitles = {};
289
- for (const schemaName in module.schemas) {
290
- schemasWithTitles[schemaName] = Object.assign(Object.assign({}, module.schemas[schemaName]), { title: schemaName });
356
+ for (const schemaName in normalizedModuleSchemas) {
357
+ schemasWithTitles[schemaName] = Object.assign(Object.assign({}, normalizedModuleSchemas[schemaName]), { title: schemaName });
291
358
  }
292
359
  const rootSchemaForCompiler = {
293
360
  title: 'schemas',
@@ -334,7 +401,8 @@ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
334
401
  // 尝试逐个验证 schema,过滤掉有问题的
335
402
  const validSchemas = {};
336
403
  const invalidSchemas = [];
337
- for (const schemaName in module.schemas) {
404
+ const normalizedModuleSchemas = normalizeSchemaRefs(module.schemas);
405
+ for (const schemaName in normalizedModuleSchemas) {
338
406
  try {
339
407
  // 尝试单独编译每个 schema
340
408
  const testSchema = {
@@ -342,11 +410,11 @@ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
342
410
  type: 'object',
343
411
  properties: {},
344
412
  additionalProperties: false,
345
- definitions: { [schemaName]: module.schemas[schemaName] },
346
- components: { schemas: { [schemaName]: module.schemas[schemaName] } }
413
+ definitions: { [schemaName]: normalizedModuleSchemas[schemaName] },
414
+ components: { schemas: { [schemaName]: normalizedModuleSchemas[schemaName] } }
347
415
  };
348
416
  yield (0, json_schema_to_typescript_1.compile)(testSchema, 'test', { unreachableDefinitions: true });
349
- validSchemas[schemaName] = Object.assign(Object.assign({}, module.schemas[schemaName]), { title: schemaName });
417
+ validSchemas[schemaName] = Object.assign(Object.assign({}, normalizedModuleSchemas[schemaName]), { title: schemaName });
350
418
  }
351
419
  catch (schemaError) {
352
420
  invalidSchemas.push(schemaName);
@@ -415,68 +483,63 @@ const handleBuild = () => __awaiter(void 0, void 0, void 0, function* () {
415
483
  const typeDefinitions = {};
416
484
  // 从 endpoints 中提取类型定义
417
485
  module.endpoints.forEach(endpoint => {
418
- // 处理 params 类型
419
- if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
420
- const paramsFields = endpoint.jsdocParams
421
- .filter(param => param.name && param.type)
422
- .map(param => {
423
- const optional = !param.required ? '?' : '';
424
- let type = param.type;
425
- // 处理联合类型
426
- if (type && type.includes(' | ')) {
427
- // 如果包含 null,确保格式正确
428
- if (type.includes('null')) {
429
- type = type.replace(/\s*\|\s*null/g, ' | null');
430
- }
431
- }
432
- else {
433
- // 处理基本类型转换
434
- type = convertOpenApiTypeToTypeScript(type);
486
+ // 辅助函数:将 jsdoc params 转换为字段
487
+ const buildFields = (params) => params
488
+ .filter((param) => param.name && param.type)
489
+ .map((param) => {
490
+ const optional = !param.required ? '?' : '';
491
+ let type = param.type;
492
+ if (type && type.includes(' | ')) {
493
+ if (type.includes('null')) {
494
+ type = type.replace(/\s*\|\s*null/g, ' | null');
435
495
  }
436
- const comment = param.description ? ` // ${param.description}` : '';
437
- return ` ${param.name}${optional}: ${type};${comment}`;
438
- })
439
- .join('\n');
496
+ }
497
+ else {
498
+ type = convertOpenApiTypeToTypeScript(type);
499
+ }
500
+ const comment = param.description ? ` // ${param.description}` : '';
501
+ return ` ${param.name}${optional}: ${type};${comment}`;
502
+ })
503
+ .join('\n');
504
+ // 处理 params 类型 - 使用 paramsJsdocParams
505
+ const requestParamsTypeName = endpoint.requestParamsTypeName;
506
+ if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
507
+ const paramsFields = buildFields(endpoint.paramsJsdocParams);
440
508
  if (paramsFields) {
441
- typeDefinitions[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
442
- ${paramsFields}
509
+ typeDefinitions[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
510
+ ${paramsFields}
443
511
  }`;
444
512
  }
445
513
  }
446
- // 处理 data 类型 (requestBody)
447
- if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
448
- const dataFields = endpoint.jsdocParams
449
- .filter(param => param.name && param.type)
450
- .map(param => {
451
- const optional = !param.required ? '?' : '';
452
- let type = param.type;
453
- // 处理联合类型
454
- if (type && type.includes(' | ')) {
455
- // 如果包含 null,确保格式正确
456
- if (type.includes('null')) {
457
- type = type.replace(/\s*\|\s*null/g, ' | null');
458
- }
459
- }
460
- else {
461
- // 处理基本类型转换
462
- type = convertOpenApiTypeToTypeScript(type);
463
- }
464
- const comment = param.description ? ` // ${param.description}` : '';
465
- return ` ${param.name}${optional}: ${type};${comment}`;
466
- })
467
- .join('\n');
514
+ // 处理 data 类型 (requestBody) - 使用 dataJsdocParams
515
+ const requestBodyTypeName = endpoint.requestBodyTypeName;
516
+ if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
517
+ const dataFields = buildFields(endpoint.dataJsdocParams);
468
518
  if (dataFields) {
469
- typeDefinitions[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
470
- ${dataFields}
519
+ typeDefinitions[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
520
+ ${dataFields}
521
+ }`;
522
+ }
523
+ }
524
+ // 处理 response 类型 - 使用 responseJsdocParams
525
+ const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
526
+ if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
527
+ const responseFields = buildFields(endpoint.responseJsdocParams);
528
+ if (responseFields) {
529
+ typeDefinitions[responseTargetType] = `export interface ${responseTargetType} {
530
+ ${responseFields}
471
531
  }`;
472
532
  }
473
533
  }
474
534
  });
475
535
  // 为没有定义的类型添加基本定义
476
536
  allReferencedTypes.forEach(typeName => {
537
+ if (!isValidTypeIdentifier(typeName)) {
538
+ return;
539
+ }
477
540
  if (!typeDefinitions[typeName]) {
478
- typeDefinitions[typeName] = `export interface ${typeName} {
479
- [key: string]: any;
541
+ typeDefinitions[typeName] = `export interface ${typeName} {
542
+ [key: string]: any;
480
543
  }`;
481
544
  }
482
545
  });
@@ -238,6 +238,9 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
238
238
  let requestBodyTypeName = undefined;
239
239
  let contentType = undefined;
240
240
  const jsdocParams = [];
241
+ const paramsJsdocParams = [];
242
+ const dataJsdocParams = [];
243
+ const responseJsdocParams = [];
241
244
  // --- FormData Detection ---
242
245
  let isFormData = false;
243
246
  // Method 1: Check requestBody
@@ -331,13 +334,17 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
331
334
  requestBodyTypeName = typeName;
332
335
  modules[moduleName].schemas[typeName] = formDataSchema;
333
336
  referencedTypes.push(typeName);
334
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataSchema, allSchemas));
337
+ const formDataJsdoc = extractJsdocParamsFromSchema(formDataSchema, allSchemas);
338
+ jsdocParams.push(...formDataJsdoc);
339
+ dataJsdocParams.push(...formDataJsdoc);
335
340
  }
336
341
  if (Object.keys(paramsSchema.properties || {}).length > 0) {
337
342
  requestParamsTypeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Params`;
338
343
  modules[moduleName].schemas[requestParamsTypeName] = paramsSchema;
339
344
  referencedTypes.push(requestParamsTypeName);
340
- jsdocParams.push(...extractJsdocParamsFromSchema(paramsSchema, allSchemas));
345
+ const paramsJsdoc = extractJsdocParamsFromSchema(paramsSchema, allSchemas);
346
+ jsdocParams.push(...paramsJsdoc);
347
+ paramsJsdocParams.push(...paramsJsdoc);
341
348
  }
342
349
  }
343
350
  // Handle standard Request Body
@@ -350,7 +357,9 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
350
357
  requestBodyTypeName = name;
351
358
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
352
359
  referencedTypes.push(name);
353
- jsdocParams.push(...extractJsdocParamsFromSchema(jsonContent.schema, allSchemas));
360
+ const bodyJsdoc = extractJsdocParamsFromSchema(jsonContent.schema, allSchemas);
361
+ jsdocParams.push(...bodyJsdoc);
362
+ dataJsdocParams.push(...bodyJsdoc);
354
363
  }
355
364
  else {
356
365
  // Handle inline schema (including arrays)
@@ -367,11 +376,15 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
367
376
  addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
368
377
  }
369
378
  else {
370
- jsdocParams.push(...extractJsdocParamsFromSchema(schema.items, allSchemas));
379
+ const arrayJsdoc = extractJsdocParamsFromSchema(schema.items, allSchemas);
380
+ jsdocParams.push(...arrayJsdoc);
381
+ dataJsdocParams.push(...arrayJsdoc);
371
382
  }
372
383
  }
373
384
  else if (schema.type === 'object') {
374
- jsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
385
+ const objJsdoc = extractJsdocParamsFromSchema(schema, allSchemas);
386
+ jsdocParams.push(...objJsdoc);
387
+ dataJsdocParams.push(...objJsdoc);
375
388
  }
376
389
  }
377
390
  }
@@ -385,7 +398,8 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
385
398
  requestBodyTypeName = typeName;
386
399
  modules[moduleName].schemas[typeName] = formDataContent.schema;
387
400
  referencedTypes.push(typeName);
388
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataContent.schema, allSchemas));
401
+ const formJsdoc = extractJsdocParamsFromSchema(formDataContent.schema, allSchemas);
402
+ dataJsdocParams.push(...formJsdoc);
389
403
  }
390
404
  }
391
405
  // Handle Response Body
@@ -399,6 +413,35 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
399
413
  responseTypeName = name;
400
414
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
401
415
  referencedTypes.push(name);
416
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
417
+ }
418
+ else if (schema && !isReferenceObject(schema)) {
419
+ const inlineSchema = schema;
420
+ if (inlineSchema.type === 'object' || inlineSchema.properties) {
421
+ // Generate a named type for inline response schema
422
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
423
+ responseTypeName = typeName;
424
+ modules[moduleName].schemas[typeName] = inlineSchema;
425
+ referencedTypes.push(typeName);
426
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
427
+ }
428
+ else if (inlineSchema.type === 'array' && inlineSchema.items) {
429
+ if (isReferenceObject(inlineSchema.items)) {
430
+ const itemSchemaName = getSchemaName(inlineSchema.items.$ref);
431
+ // Prefer direct `ItemType[]` for array responses to avoid unnecessary alias schema compilation.
432
+ responseTypeName = `${itemSchemaName}[]`;
433
+ referencedTypes.push(itemSchemaName);
434
+ addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
435
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema.items, allSchemas));
436
+ }
437
+ else {
438
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
439
+ responseTypeName = typeName;
440
+ modules[moduleName].schemas[typeName] = inlineSchema;
441
+ referencedTypes.push(typeName);
442
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
443
+ }
444
+ }
402
445
  }
403
446
  }
404
447
  // Convert path to template literal string for path parameters
@@ -416,6 +459,9 @@ const processApi = (api, excludePaths = [], includePaths = [], pathPrefixes = []
416
459
  hasData: !!requestBodyTypeName,
417
460
  contentType,
418
461
  jsdocParams,
462
+ paramsJsdocParams,
463
+ dataJsdocParams,
464
+ responseJsdocParams,
419
465
  };
420
466
  modules[moduleName].endpoints.push(endpoint);
421
467
  }
@@ -10,12 +10,7 @@ export const {{functionName}} = ({{#if hasParams}}params: {{requestParamsTypeNam
10
10
  return http.request<{{responseTypeName}}>({
11
11
  url: `{{path}}`,
12
12
  method: '{{method}}',
13
- {{#if hasParams}}
14
- params,
15
- {{/if}}
16
- {{#if hasData}}
17
- data,
18
- {{/if}}
13
+ {{#if hasParams}}{{#unless (eq method 'put')}}{{#unless (eq method 'post')}}params,{{/unless}}{{/unless}}{{/if}}{{#if hasData}}data,{{/if}}
19
14
  {{#if contentType}}
20
15
  headers: { 'Content-Type': '{{contentType}}' },
21
16
  {{/if}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "czh-api",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "A CLI tool to generate TypeScript API clients from Swagger/OpenAPI documents.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -40,83 +40,128 @@ function convertOpenApiTypeToTypeScript(openApiType: string | undefined): string
40
40
  }
41
41
 
42
42
  // 改进类型定义的辅助函数
43
- function improveTypeDefinitions(typesContent: string, endpoints: any[]): string {
43
+ function isValidTypeIdentifier(typeName: string | undefined): boolean {
44
+ if (!typeName) return false;
45
+ return /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(typeName);
46
+ }
47
+
48
+ function escapeRegex(value: string): string {
49
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
50
+ }
51
+
52
+ function resolveInterfaceTargetType(typeName: string | undefined): string | null {
53
+ if (typeof typeName !== 'string' || typeName.length === 0) return null;
54
+ if (isValidTypeIdentifier(typeName)) return typeName;
55
+
56
+ const arrayMatch = typeName.match(/^([A-Za-z_$][A-Za-z0-9_$]*)\[\]$/);
57
+ if (arrayMatch) {
58
+ return arrayMatch[1];
59
+ }
60
+
61
+ return null;
62
+ }
63
+
64
+ function findInterfaceBlockRange(content: string, typeName: string): { start: number; end: number } | null {
65
+ const startRegex = new RegExp(`export interface ${escapeRegex(typeName)}\\s*\\{`);
66
+ const match = startRegex.exec(content);
67
+ if (!match) return null;
68
+
69
+ const start = match.index;
70
+ const openBraceIndex = start + match[0].lastIndexOf('{');
71
+ let depth = 0;
72
+
73
+ for (let i = openBraceIndex; i < content.length; i++) {
74
+ const ch = content[i];
75
+ if (ch === '{') {
76
+ depth++;
77
+ } else if (ch === '}') {
78
+ depth--;
79
+ if (depth === 0) {
80
+ return { start, end: i + 1 };
81
+ }
82
+ }
83
+ }
84
+
85
+ return null;
86
+ }
87
+
88
+ function improveTypeDefinitions(typesContent: string, endpoints: any[]): string {
44
89
  let improvedContent = typesContent;
45
90
 
46
91
  // 收集所有需要改进的类型定义
47
92
  const typeImprovements: { [typeName: string]: string } = {};
93
+
94
+ // 辅助函数:将 jsdoc params 转换为接口字段
95
+ function buildFieldsFromJsdocParams(jsdocParams: any[]): string {
96
+ return jsdocParams
97
+ .filter((param: any) => param.name && param.type)
98
+ .map((param: any) => {
99
+ const optional = !param.required ? '?' : '';
100
+ let type = param.type;
101
+
102
+ // 处理联合类型,确保正确的 TypeScript 语法
103
+ if (type && type.includes(' | ')) {
104
+ if (type.includes('null')) {
105
+ type = type.replace(/\s*\|\s*null/g, ' | null');
106
+ }
107
+ } else {
108
+ type = convertOpenApiTypeToTypeScript(type);
109
+ }
110
+
111
+ const comment = param.description ? ` // ${param.description}` : '';
112
+ return ` ${param.name}${optional}: ${type};${comment}`;
113
+ })
114
+ .join('\n');
115
+ }
48
116
 
49
117
  endpoints.forEach(endpoint => {
50
- // 改进 params 类型
51
- if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
52
- const paramsFields = endpoint.jsdocParams
53
- .filter((param: any) => param.name && param.type)
54
- .map((param: any) => {
55
- const optional = !param.required ? '?' : '';
56
- let type = param.type;
57
-
58
- // 处理联合类型,确保正确的 TypeScript 语法
59
- if (type && type.includes(' | ')) {
60
- // 如果包含 null,确保格式正确
61
- if (type.includes('null')) {
62
- type = type.replace(/\s*\|\s*null/g, ' | null');
63
- }
64
- } else {
65
- // 处理基本类型转换
66
- type = convertOpenApiTypeToTypeScript(type);
67
- }
68
-
69
- const comment = param.description ? ` // ${param.description}` : '';
70
- return ` ${param.name}${optional}: ${type};${comment}`;
71
- })
72
- .join('\n');
73
-
74
- if (paramsFields) {
75
- typeImprovements[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
76
- ${paramsFields}
77
- }`;
78
- }
79
- }
118
+ // 改进 params 类型 - 使用 paramsJsdocParams(仅包含 path/query 参数)
119
+ const requestParamsTypeName = endpoint.requestParamsTypeName;
120
+ if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
121
+ const paramsFields = buildFieldsFromJsdocParams(endpoint.paramsJsdocParams);
122
+
123
+ if (paramsFields) {
124
+ typeImprovements[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
125
+ ${paramsFields}
126
+ }`;
127
+ }
128
+ }
80
129
 
81
- // 改进 requestBody 类型
82
- if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
83
- const dataFields = endpoint.jsdocParams
84
- .filter((param: any) => param.name && param.type)
85
- .map((param: any) => {
86
- const optional = !param.required ? '?' : '';
87
- let type = param.type;
88
-
89
- // 处理联合类型,确保正确的 TypeScript 语法
90
- if (type && type.includes(' | ')) {
91
- // 如果包含 null,确保格式正确
92
- if (type.includes('null')) {
93
- type = type.replace(/\s*\|\s*null/g, ' | null');
94
- }
95
- } else {
96
- // 处理基本类型转换
97
- type = convertOpenApiTypeToTypeScript(type);
98
- }
99
-
100
- const comment = param.description ? ` // ${param.description}` : '';
101
- return ` ${param.name}${optional}: ${type};${comment}`;
102
- })
103
- .join('\n');
104
-
105
- if (dataFields) {
106
- typeImprovements[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
107
- ${dataFields}
108
- }`;
109
- }
110
- }
130
+ // 改进 requestBody 类型 - 使用 dataJsdocParams(仅包含 requestBody 参数)
131
+ const requestBodyTypeName = endpoint.requestBodyTypeName;
132
+ if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
133
+ const dataFields = buildFieldsFromJsdocParams(endpoint.dataJsdocParams);
134
+
135
+ if (dataFields) {
136
+ typeImprovements[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
137
+ ${dataFields}
138
+ }`;
139
+ }
140
+ }
141
+
142
+ // 改进 response 类型 - 使用 responseJsdocParams(包含响应字段信息)
143
+ const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
144
+ if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
145
+ const responseFields = buildFieldsFromJsdocParams(endpoint.responseJsdocParams);
146
+
147
+ if (responseFields) {
148
+ typeImprovements[responseTargetType] = `export interface ${responseTargetType} {
149
+ ${responseFields}
150
+ }`;
151
+ }
152
+ }
111
153
  });
112
154
 
113
155
  // 替换现有的类型定义
114
156
  Object.entries(typeImprovements).forEach(([typeName, newDefinition]) => {
115
157
  // 查找并替换现有的接口定义
116
- const interfaceRegex = new RegExp(`export interface ${typeName}\\s*\\{[^}]*\\}`, 'g');
117
- if (interfaceRegex.test(improvedContent)) {
118
- improvedContent = improvedContent.replace(interfaceRegex, newDefinition);
119
- } else {
158
+ const blockRange = findInterfaceBlockRange(improvedContent, typeName);
159
+ if (blockRange) {
160
+ improvedContent =
161
+ improvedContent.slice(0, blockRange.start) +
162
+ newDefinition +
163
+ improvedContent.slice(blockRange.end);
164
+ } else {
120
165
  // 如果没找到接口定义,添加到末尾
121
166
  improvedContent += '\n\n' + newDefinition;
122
167
  }
@@ -126,7 +171,7 @@ ${dataFields}
126
171
  }
127
172
 
128
173
  // 移除无用的类型别名
129
- function removeUselessTypeAliases(typesContent: string): string {
174
+ function removeUselessTypeAliases(typesContent: string): string {
130
175
  let cleanedContent = typesContent;
131
176
 
132
177
  // 收集要删除的类型别名及其对应的基础类型
@@ -193,10 +238,42 @@ function removeUselessTypeAliases(typesContent: string): string {
193
238
  // 移除孤立的类型注释(没有对应类型定义的注释)
194
239
  cleanedContent = cleanedContent.replace(/\/\*\*\n \* [^\n]*\n \*\/\n(?!export)/g, '');
195
240
 
196
- return cleanedContent;
197
- }
198
-
199
- interface IConfig {
241
+ return cleanedContent;
242
+ }
243
+
244
+ function normalizeRef(refValue: string): string {
245
+ const match = refValue.match(/^#\/components\/schemas\/([^/]+)$/);
246
+ if (match) {
247
+ return `#/definitions/${match[1]}`;
248
+ }
249
+ return refValue;
250
+ }
251
+
252
+ function normalizeSchemaRefs<T>(value: T): T {
253
+ if (Array.isArray(value)) {
254
+ return value.map(item => normalizeSchemaRefs(item)) as T;
255
+ }
256
+
257
+ if (value && typeof value === 'object') {
258
+ const obj = value as Record<string, unknown>;
259
+ const normalized: Record<string, unknown> = {};
260
+
261
+ for (const key in obj) {
262
+ const currentValue = obj[key];
263
+ if (key === '$ref' && typeof currentValue === 'string') {
264
+ normalized[key] = normalizeRef(currentValue);
265
+ } else {
266
+ normalized[key] = normalizeSchemaRefs(currentValue);
267
+ }
268
+ }
269
+
270
+ return normalized as T;
271
+ }
272
+
273
+ return value;
274
+ }
275
+
276
+ interface IConfig {
200
277
  url: string;
201
278
  outputDir: string;
202
279
  httpClientPath: string;
@@ -317,13 +394,14 @@ export const handleBuild = async () => {
317
394
  if (Object.keys(module.schemas).length > 0) {
318
395
  console.log(chalk.blue(`正在为模块 "${moduleName}" 生成类型文件,包含 ${Object.keys(module.schemas).length} 个 schema`));
319
396
  try {
320
- const schemasWithTitles: { [key: string]: OpenAPIV3.SchemaObject } = {};
321
- for (const schemaName in module.schemas) {
322
- schemasWithTitles[schemaName] = {
323
- ...module.schemas[schemaName],
324
- title: schemaName,
325
- };
326
- }
397
+ const normalizedModuleSchemas = normalizeSchemaRefs(module.schemas);
398
+ const schemasWithTitles: { [key: string]: OpenAPIV3.SchemaObject } = {};
399
+ for (const schemaName in normalizedModuleSchemas) {
400
+ schemasWithTitles[schemaName] = {
401
+ ...normalizedModuleSchemas[schemaName],
402
+ title: schemaName,
403
+ };
404
+ }
327
405
 
328
406
  const rootSchemaForCompiler = {
329
407
  title: 'schemas',
@@ -374,9 +452,10 @@ export const handleBuild = async () => {
374
452
 
375
453
  // 尝试逐个验证 schema,过滤掉有问题的
376
454
  const validSchemas: { [key: string]: OpenAPIV3.SchemaObject } = {};
377
- const invalidSchemas: string[] = [];
455
+ const invalidSchemas: string[] = [];
456
+ const normalizedModuleSchemas = normalizeSchemaRefs(module.schemas);
378
457
 
379
- for (const schemaName in module.schemas) {
458
+ for (const schemaName in normalizedModuleSchemas) {
380
459
  try {
381
460
  // 尝试单独编译每个 schema
382
461
  const testSchema = {
@@ -384,12 +463,12 @@ export const handleBuild = async () => {
384
463
  type: 'object',
385
464
  properties: {},
386
465
  additionalProperties: false,
387
- definitions: { [schemaName]: module.schemas[schemaName] },
388
- components: { schemas: { [schemaName]: module.schemas[schemaName] } }
466
+ definitions: { [schemaName]: normalizedModuleSchemas[schemaName] },
467
+ components: { schemas: { [schemaName]: normalizedModuleSchemas[schemaName] } }
389
468
  };
390
469
  await compile(testSchema as any, 'test', { unreachableDefinitions: true });
391
470
  validSchemas[schemaName] = {
392
- ...module.schemas[schemaName],
471
+ ...normalizedModuleSchemas[schemaName],
393
472
  title: schemaName
394
473
  };
395
474
  } catch (schemaError) {
@@ -467,77 +546,74 @@ export const handleBuild = async () => {
467
546
 
468
547
  // 从 endpoints 中提取类型定义
469
548
  module.endpoints.forEach(endpoint => {
470
- // 处理 params 类型
471
- if (endpoint.requestParamsTypeName && endpoint.jsdocParams) {
472
- const paramsFields = endpoint.jsdocParams
473
- .filter(param => param.name && param.type)
474
- .map(param => {
475
- const optional = !param.required ? '?' : '';
476
- let type = param.type;
477
-
478
- // 处理联合类型
479
- if (type && type.includes(' | ')) {
480
- // 如果包含 null,确保格式正确
481
- if (type.includes('null')) {
482
- type = type.replace(/\s*\|\s*null/g, ' | null');
483
- }
484
- } else {
485
- // 处理基本类型转换
486
- type = convertOpenApiTypeToTypeScript(type);
549
+ // 辅助函数:将 jsdoc params 转换为字段
550
+ const buildFields = (params: any[]) => params
551
+ .filter((param: any) => param.name && param.type)
552
+ .map((param: any) => {
553
+ const optional = !param.required ? '?' : '';
554
+ let type = param.type;
555
+
556
+ if (type && type.includes(' | ')) {
557
+ if (type.includes('null')) {
558
+ type = type.replace(/\s*\|\s*null/g, ' | null');
487
559
  }
488
-
489
- const comment = param.description ? ` // ${param.description}` : '';
490
- return ` ${param.name}${optional}: ${type};${comment}`;
491
- })
492
- .join('\n');
493
-
494
- if (paramsFields) {
495
- typeDefinitions[endpoint.requestParamsTypeName] = `export interface ${endpoint.requestParamsTypeName} {
496
- ${paramsFields}
497
- }`;
498
- }
499
- }
560
+ } else {
561
+ type = convertOpenApiTypeToTypeScript(type);
562
+ }
563
+
564
+ const comment = param.description ? ` // ${param.description}` : '';
565
+ return ` ${param.name}${optional}: ${type};${comment}`;
566
+ })
567
+ .join('\n');
568
+
569
+ // 处理 params 类型 - 使用 paramsJsdocParams
570
+ const requestParamsTypeName = endpoint.requestParamsTypeName;
571
+ if (requestParamsTypeName && isValidTypeIdentifier(requestParamsTypeName) && endpoint.paramsJsdocParams && endpoint.paramsJsdocParams.length > 0) {
572
+ const paramsFields = buildFields(endpoint.paramsJsdocParams);
573
+
574
+ if (paramsFields) {
575
+ typeDefinitions[requestParamsTypeName] = `export interface ${requestParamsTypeName} {
576
+ ${paramsFields}
577
+ }`;
578
+ }
579
+ }
500
580
 
501
- // 处理 data 类型 (requestBody)
502
- if (endpoint.requestBodyTypeName && endpoint.jsdocParams) {
503
- const dataFields = endpoint.jsdocParams
504
- .filter(param => param.name && param.type)
505
- .map(param => {
506
- const optional = !param.required ? '?' : '';
507
- let type = param.type;
508
-
509
- // 处理联合类型
510
- if (type && type.includes(' | ')) {
511
- // 如果包含 null,确保格式正确
512
- if (type.includes('null')) {
513
- type = type.replace(/\s*\|\s*null/g, ' | null');
514
- }
515
- } else {
516
- // 处理基本类型转换
517
- type = convertOpenApiTypeToTypeScript(type);
518
- }
519
-
520
- const comment = param.description ? ` // ${param.description}` : '';
521
- return ` ${param.name}${optional}: ${type};${comment}`;
522
- })
523
- .join('\n');
524
-
525
- if (dataFields) {
526
- typeDefinitions[endpoint.requestBodyTypeName] = `export interface ${endpoint.requestBodyTypeName} {
527
- ${dataFields}
528
- }`;
529
- }
530
- }
581
+ // 处理 data 类型 (requestBody) - 使用 dataJsdocParams
582
+ const requestBodyTypeName = endpoint.requestBodyTypeName;
583
+ if (requestBodyTypeName && isValidTypeIdentifier(requestBodyTypeName) && endpoint.dataJsdocParams && endpoint.dataJsdocParams.length > 0) {
584
+ const dataFields = buildFields(endpoint.dataJsdocParams);
585
+
586
+ if (dataFields) {
587
+ typeDefinitions[requestBodyTypeName] = `export interface ${requestBodyTypeName} {
588
+ ${dataFields}
589
+ }`;
590
+ }
591
+ }
592
+
593
+ // 处理 response 类型 - 使用 responseJsdocParams
594
+ const responseTargetType = resolveInterfaceTargetType(endpoint.responseTypeName);
595
+ if (responseTargetType && endpoint.responseTypeName !== 'void' && endpoint.responseJsdocParams && endpoint.responseJsdocParams.length > 0) {
596
+ const responseFields = buildFields(endpoint.responseJsdocParams);
597
+
598
+ if (responseFields) {
599
+ typeDefinitions[responseTargetType] = `export interface ${responseTargetType} {
600
+ ${responseFields}
601
+ }`;
602
+ }
603
+ }
531
604
  });
532
605
 
533
606
  // 为没有定义的类型添加基本定义
534
- allReferencedTypes.forEach(typeName => {
535
- if (!typeDefinitions[typeName]) {
536
- typeDefinitions[typeName] = `export interface ${typeName} {
537
- [key: string]: any;
538
- }`;
539
- }
540
- });
607
+ allReferencedTypes.forEach(typeName => {
608
+ if (!isValidTypeIdentifier(typeName)) {
609
+ return;
610
+ }
611
+ if (!typeDefinitions[typeName]) {
612
+ typeDefinitions[typeName] = `export interface ${typeName} {
613
+ [key: string]: any;
614
+ }`;
615
+ }
616
+ });
541
617
 
542
618
  const basicTypesContent = `/* eslint-disable */
543
619
  /**
@@ -611,4 +687,4 @@ ${Object.values(typeDefinitions).join('\n\n')}
611
687
  } catch (error) {
612
688
  console.error(chalk.red('An error occurred during build process:'), error);
613
689
  }
614
- };
690
+ };
@@ -16,6 +16,11 @@ export interface Endpoint {
16
16
  hasData: boolean;
17
17
  contentType?: string;
18
18
  jsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
19
+ // Separate jsdoc params for params and data to avoid merging
20
+ paramsJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
21
+ dataJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
22
+ // Jsdoc params for response type
23
+ responseJsdocParams: Array<{ name: string; type?: string; description?: string; required?: boolean }>;
19
24
  }
20
25
 
21
26
  export interface Module {
@@ -290,6 +295,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
290
295
  let requestBodyTypeName: string | undefined = undefined;
291
296
  let contentType: string | undefined = undefined;
292
297
  const jsdocParams: Required<Endpoint>['jsdocParams'] = [];
298
+ const paramsJsdocParams: Required<Endpoint>['paramsJsdocParams'] = [];
299
+ const dataJsdocParams: Required<Endpoint>['dataJsdocParams'] = [];
300
+ const responseJsdocParams: Required<Endpoint>['responseJsdocParams'] = [];
293
301
 
294
302
  // --- FormData Detection ---
295
303
  let isFormData = false;
@@ -378,14 +386,18 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
378
386
  requestBodyTypeName = typeName;
379
387
  modules[moduleName].schemas[typeName] = formDataSchema;
380
388
  referencedTypes.push(typeName);
381
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataSchema, allSchemas));
389
+ const formDataJsdoc = extractJsdocParamsFromSchema(formDataSchema, allSchemas);
390
+ jsdocParams.push(...formDataJsdoc);
391
+ dataJsdocParams.push(...formDataJsdoc);
382
392
  }
383
393
 
384
394
  if (Object.keys(paramsSchema.properties || {}).length > 0) {
385
395
  requestParamsTypeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Params`;
386
396
  modules[moduleName].schemas[requestParamsTypeName] = paramsSchema;
387
397
  referencedTypes.push(requestParamsTypeName);
388
- jsdocParams.push(...extractJsdocParamsFromSchema(paramsSchema, allSchemas));
398
+ const paramsJsdoc = extractJsdocParamsFromSchema(paramsSchema, allSchemas);
399
+ jsdocParams.push(...paramsJsdoc);
400
+ paramsJsdocParams.push(...paramsJsdoc);
389
401
  }
390
402
  }
391
403
 
@@ -400,7 +412,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
400
412
  requestBodyTypeName = name;
401
413
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
402
414
  referencedTypes.push(name);
403
- jsdocParams.push(...extractJsdocParamsFromSchema(jsonContent.schema, allSchemas));
415
+ const bodyJsdoc = extractJsdocParamsFromSchema(jsonContent.schema, allSchemas);
416
+ jsdocParams.push(...bodyJsdoc);
417
+ dataJsdocParams.push(...bodyJsdoc);
404
418
  } else {
405
419
  // Handle inline schema (including arrays)
406
420
  const schema = jsonContent.schema as OpenAPIV3.SchemaObject;
@@ -416,10 +430,14 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
416
430
  const itemSchemaName = getSchemaName(schema.items.$ref);
417
431
  addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
418
432
  } else {
419
- jsdocParams.push(...extractJsdocParamsFromSchema(schema.items as OpenAPIV3.SchemaObject, allSchemas));
433
+ const arrayJsdoc = extractJsdocParamsFromSchema(schema.items as OpenAPIV3.SchemaObject, allSchemas);
434
+ jsdocParams.push(...arrayJsdoc);
435
+ dataJsdocParams.push(...arrayJsdoc);
420
436
  }
421
437
  } else if (schema.type === 'object') {
422
- jsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
438
+ const objJsdoc = extractJsdocParamsFromSchema(schema, allSchemas);
439
+ jsdocParams.push(...objJsdoc);
440
+ dataJsdocParams.push(...objJsdoc);
423
441
  }
424
442
  }
425
443
  }
@@ -431,8 +449,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
431
449
  const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Data`;
432
450
  requestBodyTypeName = typeName;
433
451
  modules[moduleName].schemas[typeName] = formDataContent.schema as OpenAPIV3.SchemaObject;
434
- referencedTypes.push(typeName);
435
- jsdocParams.push(...extractJsdocParamsFromSchema(formDataContent.schema, allSchemas));
452
+ referencedTypes.push(typeName);
453
+ const formJsdoc = extractJsdocParamsFromSchema(formDataContent.schema, allSchemas);
454
+ dataJsdocParams.push(...formJsdoc);
436
455
  }
437
456
  }
438
457
 
@@ -448,8 +467,34 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
448
467
  responseTypeName = name;
449
468
  addSchemaWithDependencies(name, modules[moduleName], allSchemas);
450
469
  referencedTypes.push(name);
451
- }
452
- }
470
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(schema, allSchemas));
471
+ } else if (schema && !isReferenceObject(schema)) {
472
+ const inlineSchema = schema as OpenAPIV3.SchemaObject;
473
+ if (inlineSchema.type === 'object' || inlineSchema.properties) {
474
+ // Generate a named type for inline response schema
475
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
476
+ responseTypeName = typeName;
477
+ modules[moduleName].schemas[typeName] = inlineSchema;
478
+ referencedTypes.push(typeName);
479
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
480
+ } else if (inlineSchema.type === 'array' && inlineSchema.items) {
481
+ if (isReferenceObject(inlineSchema.items)) {
482
+ const itemSchemaName = getSchemaName(inlineSchema.items.$ref);
483
+ // Prefer direct `ItemType[]` for array responses to avoid unnecessary alias schema compilation.
484
+ responseTypeName = `${itemSchemaName}[]`;
485
+ referencedTypes.push(itemSchemaName);
486
+ addSchemaWithDependencies(itemSchemaName, modules[moduleName], allSchemas);
487
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema.items, allSchemas));
488
+ } else {
489
+ const typeName = `${functionName.charAt(0).toUpperCase() + functionName.slice(1)}Response`;
490
+ responseTypeName = typeName;
491
+ modules[moduleName].schemas[typeName] = inlineSchema;
492
+ referencedTypes.push(typeName);
493
+ responseJsdocParams.push(...extractJsdocParamsFromSchema(inlineSchema, allSchemas));
494
+ }
495
+ }
496
+ }
497
+ }
453
498
 
454
499
  // Convert path to template literal string for path parameters
455
500
  const urlTemplate = path.replace(/\{(\w+)\}/g, '${params.$1}');
@@ -467,6 +512,9 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
467
512
  hasData: !!requestBodyTypeName,
468
513
  contentType,
469
514
  jsdocParams,
515
+ paramsJsdocParams,
516
+ dataJsdocParams,
517
+ responseJsdocParams,
470
518
  };
471
519
 
472
520
  modules[moduleName].endpoints.push(endpoint);
@@ -474,4 +522,4 @@ export const processApi = (api: OpenAPI.Document, excludePaths: string[] = [], i
474
522
  }
475
523
 
476
524
  return modules;
477
- };
525
+ };
@@ -10,12 +10,7 @@ export const {{functionName}} = ({{#if hasParams}}params: {{requestParamsTypeNam
10
10
  return http.request<{{responseTypeName}}>({
11
11
  url: `{{path}}`,
12
12
  method: '{{method}}',
13
- {{#if hasParams}}
14
- params,
15
- {{/if}}
16
- {{#if hasData}}
17
- data,
18
- {{/if}}
13
+ {{#if hasParams}}{{#unless (eq method 'put')}}{{#unless (eq method 'post')}}params,{{/unless}}{{/unless}}{{/if}}{{#if hasData}}data,{{/if}}
19
14
  {{#if contentType}}
20
15
  headers: { 'Content-Type': '{{contentType}}' },
21
16
  {{/if}}