swgto-ts 3.0.2 → 3.1.0

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 CHANGED
@@ -81,6 +81,7 @@ export interface SwaggerTsConfig {
81
81
  fileNaming?: 'module' | 'path'
82
82
  flattenQueryParam?: boolean
83
83
  mergeParams?: boolean
84
+ flattenOnGet?: boolean
84
85
  apiDocs?: ApiDocsConfig
85
86
  }
86
87
  ```
@@ -102,6 +103,7 @@ export interface SwaggerTsConfig {
102
103
  | `cleanOutput` | `boolean` | `false` | 生成前是否清空输出目录 |
103
104
  | `flattenQueryParam` | `boolean` | `false` | 当 query 参数只有一个且为 `$ref` 引用类型时,直接用引用类型代替 `{ query: RefType }` |
104
105
  | `mergeParams` | `boolean` | `false` | 合并参数层级:`true` 时展平 `path/query/body` 嵌套,直接使用 `params: { field1, field2 }` 代替 `params: { path: {...}, query: {...} }` |
106
+ | `flattenOnGet` | `boolean` | `false` | GET 请求专用展平:将所有 body/query/path 中的 `$ref` 参数展开为顶层交叉类型,彻底消除嵌套。详见 [GET 参数展平](#get-参数展平) |
105
107
  | `apiDocs` | `ApiDocsConfig` | 见说明 | 自动生成 API 文档(HTML/Markdown),详细配置见 [API 文档生成](#api-文档生成) |
106
108
 
107
109
  ### 函数名生成规则
@@ -148,6 +150,26 @@ getUser({ path: { id: '1' }, query: { name: 'foo' } })
148
150
  getUser({ id: '1', name: 'foo' })
149
151
  ```
150
152
 
153
+ ### GET 参数展平
154
+
155
+ `flattenOnGet: true` 专门解决 GET 请求中 body 和 query 参数嵌套的问题。当 GET 请求同时包含 body 参数(如 `userDTO`)和 query 参数(如 `pageQuery`)时,会将所有 `$ref` 类型的参数展开为顶层交叉类型,实现 `{...userDTO, ...pageQuery}` 的效果。
156
+
157
+ **背景**:部分后端定义 GET 请求时,参数在 body 和 query 中分散定义,但实际入参是合并平铺的。仅靠 `mergeParams` 会保留 body 内部的属性名作为嵌套字段。
158
+
159
+ **示例**:一个 GET 请求的 body 中有 `adminUserVO: AdminUserVO`,query 中有 `pageRequest: SessionPageRequestDTO`
160
+
161
+ ```ts
162
+ // flattenOnGet: false(默认),body 属性名保留为嵌套字段
163
+ get_ai_sessions({ adminUserVO: {...}, pageRequest: {...} })
164
+ // 类型:{ adminUserVO: AdminUserVO; pageRequest: SessionPageRequestDTO; }
165
+
166
+ // flattenOnGet: true,$ref 参数全部展开到顶层
167
+ get_ai_sessions({ ...adminUserVOFields, ...pageRequestFields })
168
+ // 类型:AdminUserVO & SessionPageRequestDTO
169
+ ```
170
+
171
+ > **注意**:`flattenOnGet` 仅对 GET 请求生效,且通常与 `mergeParams` 和 `flattenQueryParam` 一起使用效果最佳。该选项会同时影响生成的类型签名和函数体实现。
172
+
151
173
  ### API 文档生成
152
174
 
153
175
  配置 `apiDocs` 可在生成代码的同时,自动输出一份人类可读的 API 文档:
@@ -52,6 +52,7 @@ async function loadConfig(cwd) {
52
52
  fileNaming: rawConfig.fileNaming ?? "path",
53
53
  flattenQueryParam: rawConfig.flattenQueryParam ?? false,
54
54
  mergeParams: rawConfig.mergeParams ?? false,
55
+ flattenOnGet: rawConfig.flattenOnGet ?? false,
55
56
  apiDocs: {
56
57
  enable: rawConfig.apiDocs?.enable ?? false,
57
58
  output: rawConfig.apiDocs?.output ?? defaultOutput,
@@ -259,28 +260,60 @@ function parsePaths(document, docUrl, config) {
259
260
  const pathFields = pathParams.map((param) => `${param.name}: ${schemaToTs(param.schema)};`);
260
261
  const queryFields = queryParams.map((param) => `${param.name}${param.required ? "" : "?"}: ${schemaToTs(param.schema)};`);
261
262
  const requestTypeParts = [];
262
- if (bodyType) {
263
- requestTypeParts.push(bodyType);
264
- }
265
- if (pathFields.length) {
266
- if (config.mergeParams) {
267
- requestTypeParts.push(`{ ${pathFields.join(" ")} }`);
268
- } else {
269
- requestTypeParts.push(`{ path: { ${pathFields.join(" ")} } }`);
263
+ const isFlattenOnGet = config.flattenOnGet && method === "get";
264
+ if (isFlattenOnGet) {
265
+ const flatFields = [];
266
+ if (requestBodySchema) {
267
+ if (!requestBodySchema.$ref && requestBodySchema.properties) {
268
+ for (const [name, schema] of Object.entries(requestBodySchema.properties)) {
269
+ if (schema.$ref) {
270
+ requestTypeParts.push(schemaToTs(schema));
271
+ } else {
272
+ const required = requestBodySchema.required?.includes(name) ?? false;
273
+ flatFields.push(`${name}${required ? "" : "?"}: ${schemaToTs(schema)}`);
274
+ }
275
+ }
276
+ } else {
277
+ requestTypeParts.push(bodyType);
278
+ }
270
279
  }
271
- }
272
- if (queryFields.length) {
273
- if (config.flattenQueryParam && queryParams.length === 1 && queryParams[0].schema?.$ref) {
274
- if (config.mergeParams) {
275
- requestTypeParts.push(schemaToTs(queryParams[0].schema));
280
+ for (const param of queryParams) {
281
+ if (param.schema?.$ref) {
282
+ requestTypeParts.push(schemaToTs(param.schema));
276
283
  } else {
277
- requestTypeParts.push(`{ query: ${schemaToTs(queryParams[0].schema)} }`);
284
+ flatFields.push(`${param.name}${param.required ? "" : "?"}: ${schemaToTs(param.schema)}`);
278
285
  }
279
- } else {
286
+ }
287
+ for (const field of pathFields) {
288
+ flatFields.push(field);
289
+ }
290
+ if (flatFields.length) {
291
+ requestTypeParts.push(`{ ${flatFields.join(" ")} }`);
292
+ }
293
+ } else {
294
+ if (bodyType) {
295
+ requestTypeParts.push(bodyType);
296
+ }
297
+ if (pathFields.length) {
280
298
  if (config.mergeParams) {
281
- requestTypeParts.push(`{ ${queryFields.join(" ")} }`);
299
+ requestTypeParts.push(`{ ${pathFields.join(" ")} }`);
300
+ } else {
301
+ requestTypeParts.push(`{ path: { ${pathFields.join(" ")} } }`);
302
+ }
303
+ }
304
+ if (queryFields.length) {
305
+ if (config.flattenQueryParam && queryParams.length === 1 && queryParams[0].schema?.$ref) {
306
+ if (config.mergeParams) {
307
+ requestTypeParts.push(schemaToTs(queryParams[0].schema));
308
+ } else {
309
+ requestTypeParts.push(`{ query: ${schemaToTs(queryParams[0].schema)} }`);
310
+ }
282
311
  } else {
283
- requestTypeParts.push(`{ query: { ${queryFields.join(" ")} } }`);
312
+ if (config.mergeParams) {
313
+ requestTypeParts.push(`{ ${queryFields.join(" ")} }`);
314
+ } else {
315
+ requestTypeParts.push(`{ query: { ${queryFields.join(" ")} } }`);
316
+ }
284
317
  }
285
318
  }
286
319
  }
@@ -369,18 +402,19 @@ function buildFunctionDoc(operation, typeImportPath, requestParamType) {
369
402
  }
370
403
  return ["/**", ...lines.map((line) => ` * ${line}`), " */"].join("\n");
371
404
  }
372
- function renderFunction(operation, useGenericUrl, mergeParams) {
373
- const queryLine = operation.queryParams.length ? ` params: ${mergeParams ? "params" : "params?.query"},
405
+ function renderFunction(operation, useGenericUrl, mergeParams, flattenOnGet = false) {
406
+ const effectiveMerge = mergeParams || flattenOnGet && operation.method === "get";
407
+ const queryLine = operation.queryParams.length ? ` params: ${effectiveMerge ? "params" : "params?.query"},
374
408
  ` : "";
375
409
  const bodyOnly = Boolean(operation.requestBodySchema) && !operation.queryParams.length && !operation.pathParams.length;
376
410
  const hasBodySchema = Boolean(operation.requestBodySchema);
377
- const needsCleanData = mergeParams && hasBodySchema && operation.pathParams.length > 0;
411
+ const needsCleanData = effectiveMerge && hasBodySchema && operation.pathParams.length > 0;
378
412
  const destructureLine = needsCleanData ? ` const { ${operation.pathParams.map((p) => p.name).join(", ")}, ...requestData } = params;
379
413
  ` : "";
380
- const dataValue = needsCleanData ? "requestData" : mergeParams ? "params" : bodyOnly ? "params" : "params?.body";
414
+ const dataValue = needsCleanData ? "requestData" : effectiveMerge ? "params" : bodyOnly ? "params" : "params?.body";
381
415
  const bodyLine = hasBodySchema ? ` data: ${dataValue},
382
416
  ` : "";
383
- const pathArg = mergeParams ? "params" : "params?.path";
417
+ const pathArg = effectiveMerge ? "params" : "params?.path";
384
418
  const urlValue = useGenericUrl && operation.pathParams.length ? `buildUrl(${JSON.stringify(operation.requestPath)}, ${pathArg})` : operation.pathParams.length ? `buildUrl(${pathArg})` : JSON.stringify(operation.requestPath);
385
419
  const pathLine = ` url: ${urlValue},
386
420
  `;
@@ -391,19 +425,20 @@ ${queryLine}${bodyLine} ...config,
391
425
  });
392
426
  }`;
393
427
  }
394
- function generateJsRequestFile(operation, httpClientPath, typeImportPath, mergeParams = false) {
428
+ function generateJsRequestFile(operation, httpClientPath, typeImportPath, mergeParams = false, flattenOnGet = false) {
429
+ const effectiveMerge = mergeParams || flattenOnGet && operation.method === "get";
395
430
  const requestParamType = operation.requestTypeExpression ?? "void";
396
- const queryLine = operation.queryParams.length ? ` params: ${mergeParams ? "params" : "params?.query"},
431
+ const queryLine = operation.queryParams.length ? ` params: ${effectiveMerge ? "params" : "params?.query"},
397
432
  ` : "";
398
433
  const bodyOnly = Boolean(operation.requestBodySchema) && !operation.queryParams.length && !operation.pathParams.length;
399
434
  const hasBodySchema = Boolean(operation.requestBodySchema);
400
- const needsCleanData = mergeParams && hasBodySchema && operation.pathParams.length > 0;
435
+ const needsCleanData = effectiveMerge && hasBodySchema && operation.pathParams.length > 0;
401
436
  const destructureLine = needsCleanData ? ` const { ${operation.pathParams.map((p) => p.name).join(", ")}, ...requestData } = params;
402
437
  ` : "";
403
- const dataValue = needsCleanData ? "requestData" : mergeParams ? "params" : bodyOnly ? "params" : "params?.body";
438
+ const dataValue = needsCleanData ? "requestData" : effectiveMerge ? "params" : bodyOnly ? "params" : "params?.body";
404
439
  const bodyLine = hasBodySchema ? ` data: ${dataValue},
405
440
  ` : "";
406
- const pathArg = mergeParams ? "params" : "params?.path";
441
+ const pathArg = effectiveMerge ? "params" : "params?.path";
407
442
  const pathLine = operation.pathParams.length ? ` url: buildUrl(${pathArg}),
408
443
  ` : ` url: ${JSON.stringify(operation.requestPath)},
409
444
  `;
@@ -426,7 +461,7 @@ ${queryLine}${bodyLine} ...config,
426
461
  }
427
462
  `;
428
463
  }
429
- function generateJsModuleFile(operations, httpClientPath, typeImportPath, mergeParams = false) {
464
+ function generateJsModuleFile(operations, httpClientPath, typeImportPath, mergeParams = false, flattenOnGet = false) {
430
465
  const needsBuildUrl = operations.some((op) => op.pathParams.length > 0);
431
466
  const buildUrlHelper = needsBuildUrl ? `
432
467
  function buildUrl(url, path) {
@@ -437,7 +472,7 @@ function buildUrl(url, path) {
437
472
  const doc = buildFunctionDoc(op, typeImportPath, op.requestTypeExpression ?? "void");
438
473
  return `${doc}
439
474
 
440
- ${renderFunction(op, true, mergeParams)}`;
475
+ ${renderFunction(op, true, mergeParams, flattenOnGet)}`;
441
476
  }).join("\n\n");
442
477
  return `/* eslint-disable */
443
478
  // Auto-generated by swgto.
@@ -461,20 +496,21 @@ function buildFunctionDoc2(operation) {
461
496
  }
462
497
  return ["/**", ...lines.map((line) => ` * ${line}`), " */", ""].join("\n");
463
498
  }
464
- function renderFunction2(operation, useGenericUrl, mergeParams) {
499
+ function renderFunction2(operation, useGenericUrl, mergeParams, flattenOnGet = false) {
500
+ const effectiveMerge = mergeParams || flattenOnGet && operation.method === "get";
465
501
  const requestArg = operation.requestTypeExpression ? `params: ${operation.requestTypeExpression}, config?: RequestConfig` : "params?: void, config?: RequestConfig";
466
502
  const defaultResponseType = operation.responseTypeName ?? "unknown";
467
503
  const bodyOnly = Boolean(operation.requestBodySchema) && !operation.queryParams.length && !operation.pathParams.length;
468
504
  const hasBodySchema = Boolean(operation.requestBodySchema);
469
- const needsCleanData = mergeParams && hasBodySchema && operation.pathParams.length > 0;
505
+ const needsCleanData = effectiveMerge && hasBodySchema && operation.pathParams.length > 0;
470
506
  const destructureLine = needsCleanData ? ` const { ${operation.pathParams.map((p) => p.name).join(", ")}, ...requestData } = params;
471
507
  ` : "";
472
- const dataValue = needsCleanData ? "requestData" : mergeParams ? "params" : bodyOnly ? "params" : "params?.body";
508
+ const dataValue = needsCleanData ? "requestData" : effectiveMerge ? "params" : bodyOnly ? "params" : "params?.body";
473
509
  const bodyLine = hasBodySchema ? ` data: ${dataValue},
474
510
  ` : "";
475
- const queryLine = operation.queryParams.length ? ` params: ${mergeParams ? "params" : "params?.query"},
511
+ const queryLine = operation.queryParams.length ? ` params: ${effectiveMerge ? "params" : "params?.query"},
476
512
  ` : "";
477
- const pathArg = mergeParams ? "params" : "params?.path";
513
+ const pathArg = effectiveMerge ? "params" : "params?.path";
478
514
  const urlValue = useGenericUrl && operation.pathParams.length ? `buildUrl(${JSON.stringify(operation.requestPath)}, ${pathArg})` : operation.pathParams.length ? `buildUrl(${pathArg})` : JSON.stringify(operation.requestPath);
479
515
  const pathLine = ` url: ${urlValue},
480
516
  `;
@@ -485,7 +521,7 @@ ${queryLine}${bodyLine} ...config,
485
521
  });
486
522
  }`;
487
523
  }
488
- function generateTsRequestFile(operation, httpClientPath, typeImportPath, mergeParams = false) {
524
+ function generateTsRequestFile(operation, httpClientPath, typeImportPath, mergeParams = false, flattenOnGet = false) {
489
525
  const importTypes = [...operation.requestImportTypes, operation.responseTypeName, "RequestConfig"].filter(
490
526
  (value, index, array) => Boolean(value) && array.indexOf(value) === index
491
527
  );
@@ -501,10 +537,10 @@ function buildUrl(path?: Record<string, any>): string {
501
537
  import request from ${JSON.stringify(httpClientPath)};
502
538
  ${importLine}
503
539
  ${buildUrlHelper}
504
- ${renderFunction2(operation, false, mergeParams)}
540
+ ${renderFunction2(operation, false, mergeParams, flattenOnGet)}
505
541
  `;
506
542
  }
507
- function generateTsModuleFile(operations, httpClientPath, typeImportPath, mergeParams = false) {
543
+ function generateTsModuleFile(operations, httpClientPath, typeImportPath, mergeParams = false, flattenOnGet = false) {
508
544
  const importTypeSet = /* @__PURE__ */ new Set();
509
545
  for (const op of operations) {
510
546
  for (const t of op.requestImportTypes) importTypeSet.add(t);
@@ -519,7 +555,7 @@ function buildUrl(url: string, path?: Record<string, unknown>): string {
519
555
  return url.replace(/\\{([^}]+)\\}/g, (_, key) => String(path?.[key] ?? ''));
520
556
  }
521
557
  ` : "";
522
- const functions = operations.map((op) => renderFunction2(op, true, mergeParams)).join("\n\n");
558
+ const functions = operations.map((op) => renderFunction2(op, true, mergeParams, flattenOnGet)).join("\n\n");
523
559
  return `/* eslint-disable */
524
560
  // Auto-generated by swgto.
525
561
  import request from ${JSON.stringify(httpClientPath)};
@@ -743,7 +779,7 @@ async function generateFromConfig(cwd = process.cwd()) {
743
779
  for (const [controllerName, controllerOperations] of Object.entries(controllerMap)) {
744
780
  const relativeFile = path4.join(config.outputDir, moduleName, `${controllerName}.${config.outputType}`);
745
781
  const absoluteFile = path4.join(cwd, relativeFile);
746
- const content = config.outputType === "ts" ? generateTsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams) : generateJsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams);
782
+ const content = config.outputType === "ts" ? generateTsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams, config.flattenOnGet) : generateJsModuleFile(controllerOperations, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams, config.flattenOnGet);
747
783
  for (const op of controllerOperations) {
748
784
  op.fileBaseName = controllerName;
749
785
  }
@@ -754,7 +790,7 @@ async function generateFromConfig(cwd = process.cwd()) {
754
790
  for (const operation of moduleOperations) {
755
791
  const relativeFile = path4.join(config.outputDir, moduleName, `${operation.fileBaseName}.${config.outputType}`);
756
792
  const absoluteFile = path4.join(cwd, relativeFile);
757
- const content = config.outputType === "ts" ? generateTsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams) : generateJsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams);
793
+ const content = config.outputType === "ts" ? generateTsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams, config.flattenOnGet) : generateJsRequestFile(operation, config.httpClientPath, getRootImportPath(config.typeName), config.mergeParams, config.flattenOnGet);
758
794
  await writeTextFile(absoluteFile, content);
759
795
  files.push(relativeFile);
760
796
  }
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  generateFromConfig
4
- } from "./chunk-GTJIYNPK.js";
4
+ } from "./chunk-PAFR76SO.js";
5
5
  import "./chunk-HBBM5HFP.js";
6
6
 
7
7
  // src/cli.ts
package/dist/index.d.ts CHANGED
@@ -24,6 +24,7 @@ interface SwaggerTsConfig {
24
24
  fileNaming?: 'module' | 'path';
25
25
  flattenQueryParam?: boolean;
26
26
  mergeParams?: boolean;
27
+ flattenOnGet?: boolean;
27
28
  apiDocs?: ApiDocsConfig;
28
29
  }
29
30
 
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  generateFromConfig
3
- } from "./chunk-GTJIYNPK.js";
3
+ } from "./chunk-PAFR76SO.js";
4
4
  import "./chunk-HBBM5HFP.js";
5
5
  export {
6
6
  generateFromConfig
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "swgto-ts",
3
- "version": "3.0.2",
3
+ "version": "3.1.0",
4
4
  "description": "Generate API request files from OpenAPI 3.x documents.",
5
5
  "license": "MIT",
6
6
  "type": "module",