@ps-aux/api-client-gen 0.7.0-rc.2 → 0.7.0-rc.3

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.
@@ -1,12 +1,12 @@
1
- import Path$1 from 'path';
1
+ import Path from 'path';
2
2
  import * as fs from 'fs';
3
3
  import fs__default from 'fs';
4
4
  import fs$1 from 'fs/promises';
5
- import Path from 'node:path';
6
- import { readFile } from 'node:fs/promises';
7
- import fs$2 from 'node:fs';
8
5
  import { generateApi } from 'swagger-typescript-api';
9
6
  import { fileURLToPath } from 'node:url';
7
+ import { readFile } from 'node:fs/promises';
8
+ import Path$1 from 'node:path';
9
+ import fs$2 from 'node:fs';
10
10
  import { generate } from 'ts-to-zod';
11
11
 
12
12
  /** A special constant with type `never` */
@@ -551,69 +551,6 @@ function formatError(error, mapper = (issue) => issue.message) {
551
551
  processError(error);
552
552
  return fieldErrors;
553
553
  }
554
- /** Format a ZodError as a human-readable string in the following form.
555
- *
556
- * From
557
- *
558
- * ```ts
559
- * ZodError {
560
- * issues: [
561
- * {
562
- * expected: 'string',
563
- * code: 'invalid_type',
564
- * path: [ 'username' ],
565
- * message: 'Invalid input: expected string'
566
- * },
567
- * {
568
- * expected: 'number',
569
- * code: 'invalid_type',
570
- * path: [ 'favoriteNumbers', 1 ],
571
- * message: 'Invalid input: expected number'
572
- * }
573
- * ];
574
- * }
575
- * ```
576
- *
577
- * to
578
- *
579
- * ```
580
- * username
581
- * ✖ Expected number, received string at "username
582
- * favoriteNumbers[0]
583
- * ✖ Invalid input: expected number
584
- * ```
585
- */
586
- function toDotPath(_path) {
587
- const segs = [];
588
- const path = _path.map((seg) => (typeof seg === "object" ? seg.key : seg));
589
- for (const seg of path) {
590
- if (typeof seg === "number")
591
- segs.push(`[${seg}]`);
592
- else if (typeof seg === "symbol")
593
- segs.push(`[${JSON.stringify(String(seg))}]`);
594
- else if (/[^\w$]/.test(seg))
595
- segs.push(`[${JSON.stringify(seg)}]`);
596
- else {
597
- if (segs.length)
598
- segs.push(".");
599
- segs.push(seg);
600
- }
601
- }
602
- return segs.join("");
603
- }
604
- function prettifyError(error) {
605
- const lines = [];
606
- // sort by path length
607
- const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
608
- // Process each issue
609
- for (const issue of issues) {
610
- lines.push(`✖ ${issue.message}`);
611
- if (issue.path?.length)
612
- lines.push(` → at ${toDotPath(issue.path)}`);
613
- }
614
- // Convert Map to formatted string
615
- return lines.join("\n");
616
- }
617
554
 
618
555
  const _parse = (_Err) => (schema, value, _ctx, _params) => {
619
556
  const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
@@ -4082,6 +4019,53 @@ function superRefine(fn) {
4082
4019
  return _superRefine(fn);
4083
4020
  }
4084
4021
 
4022
+ const vNextOasGeneratorSchema = strictObject({
4023
+ selection: strictObject({
4024
+ ignoreOperationsWithTags: array(string()).default([]),
4025
+ allSchemas: boolean().default(true)
4026
+ }).optional(),
4027
+ codegen: strictObject({
4028
+ unwrap: boolean().default(true),
4029
+ withRequestParams: boolean().default(false),
4030
+ pathParamsStyle: _enum(["object", "positional"]).default("positional"),
4031
+ enumStyle: _enum(["enum", "union"]).default("enum")
4032
+ }),
4033
+ compat: strictObject({
4034
+ uppercaseEnumKeys: boolean(),
4035
+ swaggerTsApiRequiredBooleans: boolean()
4036
+ }).optional()
4037
+ });
4038
+ const legacyOasGeneratorSchema = strictObject({
4039
+ legacy: strictObject({
4040
+ ignoreOperationsWithTags: array(string()).optional()
4041
+ })
4042
+ });
4043
+ const configSchema = strictObject({
4044
+ srcSpec: string().trim().min(1),
4045
+ dstDir: string().trim().min(1),
4046
+ apiName: string().trim().min(1),
4047
+ openApiGenerator: union([
4048
+ vNextOasGeneratorSchema,
4049
+ legacyOasGeneratorSchema
4050
+ ]),
4051
+ responseWrapper: strictObject({
4052
+ symbol: string().trim().min(1),
4053
+ import: string().trim().min(1).optional(),
4054
+ unwrapExpr: string().optional()
4055
+ }).optional(),
4056
+ zodSchemas: strictObject({
4057
+ enabled: boolean().optional(),
4058
+ localDateTimes: boolean().optional()
4059
+ }).optional()
4060
+ });
4061
+ const parseConfig = (userConfig, filePath) => {
4062
+ const parsed = configSchema.safeParse(userConfig);
4063
+ if (parsed.success) return parsed.data;
4064
+ throw new Error(`Invalid config at ${filePath}: ${parsed.error.message}`, {
4065
+ cause: parsed.error
4066
+ });
4067
+ };
4068
+
4085
4069
  const downloadSpec = async (path, url) => {
4086
4070
  let response;
4087
4071
  try {
@@ -4101,6 +4085,86 @@ const downloadSpec = async (path, url) => {
4101
4085
  await fs$1.writeFile(path, content);
4102
4086
  };
4103
4087
 
4088
+ const toPascalCaseIdentifier = (str) => {
4089
+ const words = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
4090
+ const pascalCase = words.map((word) => word[0].toUpperCase() + word.substring(1)).join("");
4091
+ if (!pascalCase.length) return "Api";
4092
+ if (/^[0-9]/.test(pascalCase)) return `Api${pascalCase}`;
4093
+ return pascalCase;
4094
+ };
4095
+ const groupBy = (values, getKey) => {
4096
+ const groups = /* @__PURE__ */ new Map();
4097
+ for (const value of values) {
4098
+ const key = getKey(value);
4099
+ const existing = groups.get(key);
4100
+ if (existing) {
4101
+ existing.push(value);
4102
+ } else {
4103
+ groups.set(key, [value]);
4104
+ }
4105
+ }
4106
+ return groups;
4107
+ };
4108
+
4109
+ const generateOpenApiClient = async (inputFile, {
4110
+ name,
4111
+ outputDir,
4112
+ ignoreOperationsWithTags
4113
+ }, log) => {
4114
+ log(`Will generate API client name=${name} to ${outputDir}`);
4115
+ const fileName = "index";
4116
+ const dstFile = Path.join(outputDir, `${fileName}.ts`);
4117
+ const prettier = await import('prettier');
4118
+ const prettierConfig = await prettier.resolveConfig(dstFile) ?? {};
4119
+ const prettierConfigForGenerator = {
4120
+ ...prettierConfig,
4121
+ // swagger-typescript-api defaults to printWidth=120; keep Prettier default 80 unless explicitly configured.
4122
+ printWidth: prettierConfig.printWidth ?? 80
4123
+ };
4124
+ const ignoreTags = ignoreOperationsWithTags && new Set(ignoreOperationsWithTags);
4125
+ await generateApi({
4126
+ name: "index",
4127
+ output: outputDir,
4128
+ input: inputFile,
4129
+ // modular: true,
4130
+ httpClientType: "axios",
4131
+ templates: getTemplatesDir(),
4132
+ defaultResponseAsSuccess: true,
4133
+ sortTypes: true,
4134
+ // generateRouteTypes: true,
4135
+ singleHttpClient: true,
4136
+ // extractRequestBody: true,
4137
+ prettier: prettierConfigForGenerator,
4138
+ moduleNameFirstTag: true,
4139
+ apiClassName: toPascalCaseIdentifier(name),
4140
+ hooks: {
4141
+ onCreateRoute: (routeData) => {
4142
+ if (ignoreTags) {
4143
+ const ignore = routeData.raw.tags?.find(
4144
+ (t) => ignoreTags.has(t)
4145
+ );
4146
+ if (ignore) return false;
4147
+ }
4148
+ return routeData;
4149
+ }
4150
+ },
4151
+ // @ts-ignore
4152
+ codeGenConstructs: () => ({
4153
+ Keyword: {}
4154
+ })
4155
+ // extractRequestParams: true,
4156
+ });
4157
+ return { types: dstFile, promiseWrapper: [dstFile] };
4158
+ };
4159
+ const getThisScriptDirname = () => {
4160
+ return Path.dirname(fileURLToPath(import.meta.url));
4161
+ };
4162
+ const getTemplatesDir = () => {
4163
+ const currentDir = getThisScriptDirname();
4164
+ const templatesRelativePath = Path.basename(currentDir) === "dist" ? "../templates" : "../../../templates";
4165
+ return Path.resolve(currentDir, templatesRelativePath);
4166
+ };
4167
+
4104
4168
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4105
4169
  const asRecord = (value) => {
4106
4170
  return isRecord(value) ? value : void 0;
@@ -4526,31 +4590,6 @@ const syntheticOperationId = (method, path) => {
4526
4590
  return `_${compact}`;
4527
4591
  };
4528
4592
 
4529
- const toPascalCaseIdentifier = (str) => {
4530
- const words = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
4531
- const pascalCase = words.map((word) => word[0].toUpperCase() + word.substring(1)).join("");
4532
- if (!pascalCase.length) return "Api";
4533
- if (/^[0-9]/.test(pascalCase)) return `Api${pascalCase}`;
4534
- return pascalCase;
4535
- };
4536
- const groupBy = (values, getKey) => {
4537
- const groups = /* @__PURE__ */ new Map();
4538
- for (const value of values) {
4539
- const key = getKey(value);
4540
- const existing = groups.get(key);
4541
- if (existing) {
4542
- existing.push(value);
4543
- } else {
4544
- groups.set(key, [value]);
4545
- }
4546
- }
4547
- return groups;
4548
- };
4549
-
4550
- var httpClientCommonTypesSource = "export type KnownRequestContentType =\n | 'application/json'\n | 'multipart/form-data'\n | 'application/x-www-form-urlencoded'\n\nexport type RequestContentType = KnownRequestContentType | string\n\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | QueryValue[]\n | Record<string, any>\n // Empty schema support\n | unknown\n\nexport type QueryParams = Record<string, QueryValue>\n\nexport type Request = {\n path: string\n method: 'GET' | 'POST' | 'PUT' | 'DELETE'\n format?: 'json' | 'document'\n headers?: Record<string, string>\n query?: QueryParams\n body?: any\n requestContentType?: RequestContentType\n}\n\nexport type QuerySerializer = (params: QueryParams) => string\n";
4551
-
4552
- var httpClientPromiseTypesSource = "import type { Request } from './common-types'\n\nexport type HttpClient<RequestParams = never> = {\n request: <Data>(req: Request, params?: RequestParams) => Promise<Data>\n}\n";
4553
-
4554
4593
  var __defProp$8 = Object.defineProperty;
4555
4594
  var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4556
4595
  var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
@@ -4559,10 +4598,14 @@ class TsCodegen {
4559
4598
  this.typeExprCodegen = typeExprCodegen;
4560
4599
  __publicField$8(this, "usedTypeRefs", []);
4561
4600
  __publicField$8(this, "usedTypeRefKeys", /* @__PURE__ */ new Set());
4562
- __publicField$8(this, "toCode", (typeExpr) => {
4601
+ __publicField$8(this, "typeExpr", (typeExpr) => {
4563
4602
  this.collectTypeRefsFromExpr(typeExpr);
4564
4603
  return this.typeExprCodegen.toCode(typeExpr).code;
4565
4604
  });
4605
+ __publicField$8(this, "declaration", (name, declaration) => {
4606
+ this.collectTypeRefsFromDeclaration(declaration);
4607
+ return this.toDeclarationCode(name, declaration);
4608
+ });
4566
4609
  __publicField$8(this, "toChunk", (name, code, exports$1) => {
4567
4610
  const exportedSymbols = new Set(exports$1);
4568
4611
  const refs = this.getTypeRefs().filter((ref) => {
@@ -4578,6 +4621,26 @@ class TsCodegen {
4578
4621
  __publicField$8(this, "getTypeRefs", () => {
4579
4622
  return this.usedTypeRefs.map((ref) => ({ ...ref }));
4580
4623
  });
4624
+ __publicField$8(this, "toDeclarationCode", (name, declaration) => {
4625
+ switch (declaration.kind) {
4626
+ case "typeAlias":
4627
+ return `type ${name} = ${this.typeExprCodegen.toCode(declaration.typeExpr).code}`;
4628
+ case "enum": {
4629
+ const members = declaration.members.map(
4630
+ (member) => ` ${member.name} = ${JSON.stringify(member.value)}`
4631
+ ).join(",\n");
4632
+ return `enum ${name} {
4633
+ ${members}
4634
+ }`;
4635
+ }
4636
+ default: {
4637
+ const exhaustive = declaration;
4638
+ throw new Error(
4639
+ `Unsupported TypeScript declaration: ${String(exhaustive)}`
4640
+ );
4641
+ }
4642
+ }
4643
+ });
4581
4644
  }
4582
4645
  collectTypeRef(typeRef) {
4583
4646
  if (typeRef.kind === "builtin") return;
@@ -4586,6 +4649,11 @@ class TsCodegen {
4586
4649
  this.usedTypeRefKeys.add(key);
4587
4650
  this.usedTypeRefs.push({ ...typeRef });
4588
4651
  }
4652
+ collectTypeRefsFromDeclaration(declaration) {
4653
+ if (declaration.kind === "typeAlias") {
4654
+ this.collectTypeRefsFromExpr(declaration.typeExpr);
4655
+ }
4656
+ }
4589
4657
  collectTypeRefsFromExpr(typeExpr) {
4590
4658
  if (typeExpr.kind === "reference") {
4591
4659
  this.collectTypeRef(typeExpr.ref);
@@ -4666,9 +4734,33 @@ const RESERVED_WORDS = /* @__PURE__ */ new Set([
4666
4734
  "with",
4667
4735
  "yield"
4668
4736
  ]);
4669
- const TS_IDENTIFIER_PATTERN = /^[$A-Z_][0-9A-Z_$]*$/i;
4737
+ const TS_IDENTIFIER_PATTERN$1 = /^[$A-Z_][0-9A-Z_$]*$/i;
4670
4738
  const isTsIdentifier = (value) => {
4671
- return TS_IDENTIFIER_PATTERN.test(value);
4739
+ return TS_IDENTIFIER_PATTERN$1.test(value);
4740
+ };
4741
+ const splitIdentifierWords = (value) => {
4742
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
4743
+ };
4744
+ const toTsCamelIdentifier = (value, opts = {}) => {
4745
+ const words = splitIdentifierWords(value);
4746
+ if (!words.length) {
4747
+ throw new Error(
4748
+ `Could not derive a TypeScript identifier from "${value}".`
4749
+ );
4750
+ }
4751
+ const [firstWord, ...restWords] = words;
4752
+ let identifier = [
4753
+ firstWord[0].toLowerCase() + firstWord.substring(1),
4754
+ ...restWords.map((word) => word[0].toUpperCase() + word.substring(1))
4755
+ ].join("");
4756
+ if (/^[0-9]/.test(identifier)) {
4757
+ const prefix = opts.leadingDigitPrefix ?? "value";
4758
+ identifier = `${prefix}${identifier[0].toUpperCase()}${identifier.substring(1)}`;
4759
+ }
4760
+ if (RESERVED_WORDS.has(identifier)) {
4761
+ identifier = `${identifier}_`;
4762
+ }
4763
+ return identifier;
4672
4764
  };
4673
4765
  const toTsPropertyKey = (value) => {
4674
4766
  return isTsIdentifier(value) ? value : JSON.stringify(value);
@@ -4785,1002 +4877,1268 @@ class TsCodegenFactory {
4785
4877
  }
4786
4878
  }
4787
4879
 
4880
+ const buildPathParamBindings = (paramNames) => {
4881
+ const usedBindings = /* @__PURE__ */ new Set();
4882
+ return paramNames.reduce((acc, paramName) => {
4883
+ acc[paramName] = toUniqueBindingName(paramName, usedBindings);
4884
+ return acc;
4885
+ }, {});
4886
+ };
4887
+ const toUniqueBindingName = (paramName, usedBindings) => {
4888
+ let base = toBindingBase(paramName);
4889
+ if (isReservedWord(base)) {
4890
+ base = `${base}_param`;
4891
+ }
4892
+ if (!usedBindings.has(base)) {
4893
+ usedBindings.add(base);
4894
+ return base;
4895
+ }
4896
+ let suffix = 2;
4897
+ let candidate = `${base}_${suffix}`;
4898
+ while (usedBindings.has(candidate)) {
4899
+ suffix += 1;
4900
+ candidate = `${base}_${suffix}`;
4901
+ }
4902
+ usedBindings.add(candidate);
4903
+ return candidate;
4904
+ };
4905
+ const toBindingBase = (paramName) => {
4906
+ if (isIdentifier(paramName)) {
4907
+ return paramName;
4908
+ }
4909
+ const sanitized = paramName.replace(/[^0-9A-Za-z_$]/g, "_");
4910
+ if (!sanitized.length) {
4911
+ return "pathParam";
4912
+ }
4913
+ if (/^[A-Za-z_$]/.test(sanitized)) {
4914
+ return sanitized;
4915
+ }
4916
+ return `_${sanitized}`;
4917
+ };
4918
+ const isIdentifier = (value) => {
4919
+ return /^[$A-Z_][0-9A-Z_$]*$/i.test(value);
4920
+ };
4921
+ const isReservedWord = (value) => {
4922
+ return RESERVED_WORDS.has(value);
4923
+ };
4924
+
4788
4925
  var __defProp$5 = Object.defineProperty;
4789
4926
  var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4790
4927
  var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
4791
- class ToTypeExprConverter {
4792
- constructor(schemas = {}, options = {}) {
4793
- this.schemas = schemas;
4928
+ class ParamsConstructor {
4929
+ constructor(options) {
4794
4930
  this.options = options;
4795
- __publicField$5(this, "schemaCache", /* @__PURE__ */ new WeakMap());
4796
- __publicField$5(this, "refCache", /* @__PURE__ */ new Map());
4797
- __publicField$5(this, "schemaDefinitions", []);
4798
- __publicField$5(this, "schemaDefinitionsById", /* @__PURE__ */ new Map());
4799
- __publicField$5(this, "schemaDefinitionsInProgress", /* @__PURE__ */ new Set());
4800
- __publicField$5(this, "getSchemaDefinitions", () => {
4801
- return this.schemaDefinitions.map((definition) => ({
4802
- ...definition
4803
- }));
4931
+ __publicField$5(this, "process", (params, httpPath) => {
4932
+ const pathParamSpec = validatePathParams(params, httpPath);
4933
+ const pathParamBindings = pathParamSpec?.bindings;
4934
+ const funParams = params.flatMap(
4935
+ (param) => this.renderOperationParams(param, pathParamSpec)
4936
+ );
4937
+ const hasBody = params.some((p) => p.kind === "body");
4938
+ const hasQuery = params.some((p) => p.kind === "query");
4939
+ const path = this.renderPathTemplateLiteral(
4940
+ httpPath,
4941
+ this.options.paramNames.path,
4942
+ pathParamBindings
4943
+ );
4944
+ return {
4945
+ funParams,
4946
+ path,
4947
+ hasBody,
4948
+ hasQuery
4949
+ };
4804
4950
  });
4805
- __publicField$5(this, "fromSchema", (schema) => {
4806
- if (!schema) {
4807
- return this.scalar("unknown");
4951
+ __publicField$5(this, "renderOperationParams", (param, pathParamSpec) => {
4952
+ if (param.kind !== "path" || !pathParamSpec) {
4953
+ return [
4954
+ {
4955
+ identifier: this.paramName(param),
4956
+ typeExpr: param.typeExpr
4957
+ }
4958
+ ];
4808
4959
  }
4809
- const bySchema = this.schemaCache.get(schema);
4810
- if (bySchema) {
4811
- return bySchema;
4960
+ if (this.options.pathParamsStyle === "positional") {
4961
+ return pathParamSpec.placeholderNames.map((name) => ({
4962
+ identifier: pathParamSpec.bindings[name] ?? name,
4963
+ typeExpr: pathParamSpec.propertiesByName[name].typeExpr
4964
+ }));
4812
4965
  }
4813
- if (typeof schema.$ref === "string") {
4814
- const byRef = this.refCache.get(this.refCacheKey(schema));
4815
- if (byRef) {
4816
- this.schemaCache.set(schema, byRef);
4817
- return byRef;
4966
+ return [
4967
+ {
4968
+ identifier: this.renderPathParamDestructuring(
4969
+ pathParamSpec.bindings
4970
+ ),
4971
+ typeExpr: param.typeExpr
4818
4972
  }
4819
- }
4820
- const projected = this.fromSchemaCore(schema);
4821
- const result = schema.nullable === true ? this.withNullable(projected) : projected;
4822
- this.schemaCache.set(schema, result);
4823
- if (typeof schema.$ref === "string") {
4824
- this.refCache.set(this.refCacheKey(schema), result);
4825
- }
4826
- return result;
4973
+ ];
4827
4974
  });
4828
- __publicField$5(this, "scalar", (name) => ({
4829
- kind: "inline",
4830
- expr: {
4831
- node: "scalar",
4832
- name
4833
- }
4834
- }));
4835
- __publicField$5(this, "union", (members) => {
4836
- const flatMembers = [];
4837
- for (const member of members) {
4838
- if (member.kind === "inline" && member.expr.node === "union") {
4839
- flatMembers.push(...member.expr.members);
4840
- } else {
4841
- flatMembers.push(member);
4842
- }
4843
- }
4844
- if (flatMembers.length === 1) {
4845
- return flatMembers[0];
4846
- }
4847
- return {
4848
- kind: "inline",
4849
- expr: {
4850
- node: "union",
4851
- members: flatMembers
4975
+ __publicField$5(this, "renderPathTemplateLiteral", (path, pathParamVarName, pathParamBindings) => {
4976
+ const escapedPath = path.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
4977
+ const pathWithParams = escapedPath.replace(
4978
+ /\{([^}]+)\}/g,
4979
+ (_, paramName) => {
4980
+ const binding = pathParamBindings?.[paramName];
4981
+ if (binding !== void 0) {
4982
+ return `\${${binding}}`;
4983
+ }
4984
+ return `\${${pathParamVarName}[${JSON.stringify(paramName)}]}`;
4852
4985
  }
4853
- };
4986
+ );
4987
+ return `\`${pathWithParams}\``;
4854
4988
  });
4855
- __publicField$5(this, "literal", (value) => ({
4856
- kind: "inline",
4857
- expr: {
4858
- node: "literal",
4859
- value
4860
- }
4861
- }));
4862
- __publicField$5(this, "ref", (id) => {
4863
- const name = this.refNameFromPath(id);
4864
- return {
4865
- kind: "reference",
4866
- ref: {
4867
- kind: "internal",
4868
- name
4989
+ __publicField$5(this, "renderPathParamDestructuring", (pathParamBindings) => {
4990
+ const members = Object.entries(pathParamBindings).map(
4991
+ ([paramName, binding]) => {
4992
+ const key = toTsPropertyKey(paramName);
4993
+ return binding === paramName ? key : `${key}: ${binding}`;
4869
4994
  }
4870
- };
4871
- });
4872
- __publicField$5(this, "builtinRef", (name) => ({
4873
- kind: "reference",
4874
- ref: {
4875
- kind: "builtin",
4876
- name
4877
- }
4878
- }));
4879
- __publicField$5(this, "refNameFromPath", (ref) => {
4880
- const parts = ref.split("/");
4881
- const name = parts[parts.length - 1];
4882
- if (!name) {
4883
- throw new Error(`Unsupported schema reference: ${ref}`);
4884
- }
4885
- return name;
4886
- });
4887
- __publicField$5(this, "refCacheKey", (schema) => {
4888
- return `${schema.$ref}|nullable:${schema.nullable === true}`;
4889
- });
4890
- __publicField$5(this, "isUnconstrainedSchema", (schema) => {
4891
- const meaningfulKeys = Object.keys(schema).filter(
4892
- (key) => key !== "nullable"
4893
4995
  );
4894
- return meaningfulKeys.length === 0;
4996
+ return `{ ${members.join(", ")} }`;
4895
4997
  });
4896
- __publicField$5(this, "ensureSchemaDefinition", (refId) => {
4897
- if (this.schemaDefinitionsById.has(refId) || this.schemaDefinitionsInProgress.has(refId)) {
4898
- return;
4899
- }
4900
- const schemaName = this.refNameFromPath(refId);
4901
- const schema = this.schemas[schemaName];
4902
- if (!schema) {
4903
- return;
4904
- }
4905
- this.schemaDefinitionsInProgress.add(refId);
4906
- try {
4907
- const definition = {
4908
- name: schemaName,
4909
- typeExpr: this.fromSchema(schema)
4910
- };
4911
- this.schemaDefinitionsById.set(refId, definition);
4912
- this.schemaDefinitions.push(definition);
4913
- } finally {
4914
- this.schemaDefinitionsInProgress.delete(refId);
4998
+ __publicField$5(this, "paramName", (p) => {
4999
+ switch (p.kind) {
5000
+ case "body":
5001
+ return this.options.paramNames.body;
5002
+ case "query":
5003
+ return this.options.paramNames.query;
5004
+ case "path":
5005
+ return this.options.paramNames.path;
5006
+ default: {
5007
+ const exhaustive = p;
5008
+ throw new Error(
5009
+ `Unsupported operation parameter kind: ${String(exhaustive)}`
5010
+ );
5011
+ }
4915
5012
  }
4916
5013
  });
4917
- __publicField$5(this, "isSchemaNode", (value) => {
4918
- return Boolean(
4919
- value && typeof value === "object" && !Array.isArray(value)
5014
+ }
5015
+ }
5016
+ const extractPathPlaceholderNames = (pathTemplate) => {
5017
+ const seen = /* @__PURE__ */ new Set();
5018
+ const names = [];
5019
+ const matcher = /\{([^}]+)\}/g;
5020
+ let match = matcher.exec(pathTemplate);
5021
+ while (match) {
5022
+ const name = match[1];
5023
+ if (!seen.has(name)) {
5024
+ seen.add(name);
5025
+ names.push(name);
5026
+ }
5027
+ match = matcher.exec(pathTemplate);
5028
+ }
5029
+ return names;
5030
+ };
5031
+ const isPathParam = (param) => {
5032
+ return param.kind === "path";
5033
+ };
5034
+ const isInlineObjectType = (typeExpr) => typeExpr.kind === "inline" && typeExpr.expr.node === "object";
5035
+ const renderNameList = (items) => {
5036
+ const values = [...items];
5037
+ if (!values.length) {
5038
+ return "(none)";
5039
+ }
5040
+ return values.map((v) => JSON.stringify(v)).join(", ");
5041
+ };
5042
+ const validatePathParams = (params, httpPath) => {
5043
+ const placeholders = extractPathPlaceholderNames(httpPath);
5044
+ const placeholderSet = new Set(placeholders);
5045
+ const pathParams = params.filter(isPathParam);
5046
+ if (pathParams.length > 1) {
5047
+ throw new Error(
5048
+ `Path "${httpPath}" has ${pathParams.length} path parameter objects; expected at most one.`
5049
+ );
5050
+ }
5051
+ const pathParam = pathParams[0];
5052
+ if (!pathParam) {
5053
+ if (placeholderSet.size > 0) {
5054
+ throw new Error(
5055
+ `Path "${httpPath}" is missing a path parameter object for placeholders: ${renderNameList(
5056
+ placeholderSet
5057
+ )}.`
4920
5058
  );
4921
- });
4922
- __publicField$5(this, "toLiteralValue", (value) => {
4923
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4924
- return value;
5059
+ }
5060
+ return void 0;
5061
+ }
5062
+ const pathParamExpr = pathParam.typeExpr.expr;
5063
+ if (pathParamExpr.additionalProperties !== void 0 && pathParamExpr.additionalProperties !== false) {
5064
+ throw new Error(
5065
+ `Path "${httpPath}" path parameter object must not declare additionalProperties.`
5066
+ );
5067
+ }
5068
+ const nestedObjectProps = pathParamExpr.properties.filter((prop) => isInlineObjectType(prop.typeExpr)).map((prop) => prop.name);
5069
+ if (nestedObjectProps.length > 0) {
5070
+ throw new Error(
5071
+ `Path "${httpPath}" path parameters must be depth-1. Nested object properties: ${renderNameList(
5072
+ nestedObjectProps
5073
+ )}.`
5074
+ );
5075
+ }
5076
+ const optionalPathProps = pathParamExpr.properties.filter((prop) => !prop.required).map((prop) => prop.name);
5077
+ if (optionalPathProps.length > 0) {
5078
+ throw new Error(
5079
+ `Path "${httpPath}" path parameters must all be required. Optional properties: ${renderNameList(
5080
+ optionalPathProps
5081
+ )}.`
5082
+ );
5083
+ }
5084
+ const declaredNames = pathParamExpr.properties.map((prop) => prop.name);
5085
+ const declaredNameSet = new Set(declaredNames);
5086
+ const missingInObject = [...placeholderSet].filter(
5087
+ (name) => !declaredNameSet.has(name)
5088
+ );
5089
+ const missingInPath = declaredNames.filter(
5090
+ (name) => !placeholderSet.has(name)
5091
+ );
5092
+ if (missingInObject.length > 0 || missingInPath.length > 0) {
5093
+ throw new Error(
5094
+ `Path "${httpPath}" placeholders and path parameter object keys must match exactly. Missing in object: ${renderNameList(
5095
+ missingInObject
5096
+ )}. Missing in path: ${renderNameList(missingInPath)}.`
5097
+ );
5098
+ }
5099
+ const bindings = buildPathParamBindings(placeholders);
5100
+ const propertiesByName = Object.fromEntries(
5101
+ pathParamExpr.properties.map((prop) => [prop.name, prop])
5102
+ );
5103
+ return {
5104
+ placeholderNames: placeholders,
5105
+ bindings,
5106
+ propertiesByName
5107
+ };
5108
+ };
5109
+
5110
+ const EMPTY_LINE_MARKER = "/*__EMPTY_LINE_MARKER__*/";
5111
+
5112
+ var __defProp$4 = Object.defineProperty;
5113
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5114
+ var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
5115
+ const REQUEST_PARAMS_TYPE = "RequestParams";
5116
+ const REQUEST_PARAMS_DEFAULT = "never";
5117
+ const REQUEST_PARAMS_ARG = "params";
5118
+ class ApiClientCodegen {
5119
+ constructor(opts, tsCodegenFactory) {
5120
+ this.opts = opts;
5121
+ this.tsCodegenFactory = tsCodegenFactory;
5122
+ __publicField$4(this, "generate", (apiName, operations) => {
5123
+ const tsCodegen = this.tsCodegenFactory.forNewChunk();
5124
+ const httpClientType = tsCodegen.typeExpr(this.opts.httpClient.typeName);
5125
+ let clientType = httpClientType;
5126
+ let classTypeParams = "";
5127
+ if (this.opts.withRequestParams) {
5128
+ clientType = `${httpClientType}<${REQUEST_PARAMS_TYPE}>`;
5129
+ classTypeParams = `<${REQUEST_PARAMS_TYPE} = ${REQUEST_PARAMS_DEFAULT}>`;
4925
5130
  }
4926
- throw new Error(`Unsupported literal value: ${String(value)}`);
5131
+ const methodsCode = Array.isArray(operations) ? this.opsCode(tsCodegen, operations) : this.groupsOpsCode(tsCodegen, operations);
5132
+ const code = `
5133
+ export class ${apiName}${classTypeParams} {
5134
+ constructor(private readonly client: ${clientType}) {}
5135
+
5136
+ ${methodsCode}
5137
+ }
5138
+ `;
5139
+ return tsCodegen.toChunk("api", code, [apiName]);
4927
5140
  });
4928
- __publicField$5(this, "isScalarName", (value) => {
4929
- switch (value) {
4930
- case "string":
4931
- case "number":
4932
- case "boolean":
4933
- case "integer":
4934
- case "null":
4935
- case "unknown":
4936
- case "any":
4937
- case "never":
4938
- case "void":
4939
- return true;
4940
- default:
4941
- return false;
4942
- }
5141
+ __publicField$4(this, "groupsOpsCode", (codegen, groupedOps) => Object.entries(groupedOps).map(([groupName, ops]) => {
5142
+ const methodsCode = this.opsCode(codegen, ops, "object");
5143
+ return `
5144
+ ${toTsPropertyKey(groupName)} = {
5145
+ ${methodsCode}
5146
+ }
5147
+ `;
5148
+ }).join("\n\n"));
5149
+ __publicField$4(this, "opsCode", (codegen, operations, target = "class") => {
5150
+ const separator = target === "object" ? `,
5151
+ ${EMPTY_LINE_MARKER}
5152
+ ` : "\n\n";
5153
+ const methodsCode = operations.map((op) => this.renderOp(op, codegen, target)).join(separator);
5154
+ return methodsCode;
4943
5155
  });
4944
- __publicField$5(this, "hasNullMember", (expr) => {
4945
- if (expr.kind !== "inline") return false;
4946
- if (expr.expr.node === "scalar") {
4947
- return expr.expr.name === "null";
4948
- }
4949
- if (expr.expr.node === "union") {
4950
- return expr.expr.members.some((member) => this.hasNullMember(member));
4951
- }
4952
- return false;
4953
- });
4954
- __publicField$5(this, "withNullable", (expr) => {
4955
- if (this.hasNullMember(expr)) {
4956
- return expr;
4957
- }
4958
- return this.union([expr, this.scalar("null")]);
4959
- });
4960
- __publicField$5(this, "projectObjectType", (schema) => {
4961
- const requiredRaw = schema.required;
4962
- if (requiredRaw !== void 0 && !Array.isArray(requiredRaw)) {
4963
- throw new Error(
4964
- "Unsupported object schema: required must be an array"
4965
- );
4966
- }
4967
- const required = new Set(
4968
- (requiredRaw ?? []).map((item) => {
4969
- if (typeof item !== "string") {
4970
- throw new Error(
4971
- "Unsupported object schema: required items must be strings"
4972
- );
4973
- }
4974
- return item;
4975
- })
5156
+ __publicField$4(this, "renderOp", (op, tsCodegen, target) => {
5157
+ const { signature: sig, http } = op;
5158
+ const invocationVars = this.opts.httpClient.invocation.vars;
5159
+ const paramNames = {
5160
+ path: "path",
5161
+ query: invocationVars.query,
5162
+ body: invocationVars.body
5163
+ };
5164
+ const paramsHelper = new ParamsConstructor({
5165
+ paramNames,
5166
+ pathParamsStyle: this.opts.pathParamsStyle
5167
+ });
5168
+ const { path, funParams, hasBody, hasQuery } = paramsHelper.process(
5169
+ sig.parameters,
5170
+ http.path
4976
5171
  );
4977
- const propertiesRaw = schema.properties;
4978
- if (propertiesRaw !== void 0 && (!propertiesRaw || typeof propertiesRaw !== "object" || Array.isArray(propertiesRaw))) {
4979
- throw new Error(
4980
- "Unsupported object schema: properties must be an object"
4981
- );
4982
- }
4983
- const properties = Object.entries(propertiesRaw ?? {}).map(
4984
- ([name, propertySchema]) => {
4985
- if (!this.isSchemaNode(propertySchema)) {
4986
- throw new Error(
4987
- `Unsupported object property schema for "${name}"`
4988
- );
4989
- }
4990
- return {
4991
- name,
4992
- required: this.isRequiredProperty(
4993
- name,
4994
- required,
4995
- propertySchema
4996
- ),
4997
- typeExpr: this.fromSchema(propertySchema)
4998
- };
4999
- }
5172
+ const params = funParams.map(
5173
+ (r) => `${r.identifier}: ${tsCodegen.typeExpr(r.typeExpr)}`
5000
5174
  );
5001
- let additionalProperties;
5002
- if (schema.additionalProperties !== void 0) {
5003
- if (typeof schema.additionalProperties === "boolean") {
5004
- additionalProperties = schema.additionalProperties;
5005
- } else if (this.isSchemaNode(schema.additionalProperties)) {
5006
- additionalProperties = this.fromSchema(
5007
- schema.additionalProperties
5008
- );
5009
- } else {
5010
- throw new Error(
5011
- "Unsupported object schema: additionalProperties must be boolean or schema object"
5012
- );
5013
- }
5014
- }
5015
- return {
5016
- kind: "inline",
5017
- expr: {
5018
- node: "object",
5019
- properties,
5020
- additionalProperties
5021
- }
5022
- };
5023
- });
5024
- __publicField$5(this, "isRequiredProperty", (name, required, propertySchema) => {
5025
- if (required.has(name)) {
5026
- return true;
5027
- }
5028
- if (!this.options.quirks?.swaggerTsApiRequiredBooleans) {
5029
- return false;
5030
- }
5031
- return propertySchema.required === true;
5032
- });
5033
- __publicField$5(this, "mapSchemaArrayMembers", (value, kind) => {
5034
- if (!Array.isArray(value) || !value.length) {
5035
- throw new Error(
5036
- `Unsupported schema: ${kind} must be a non-empty array`
5037
- );
5175
+ if (this.opts.withRequestParams) {
5176
+ params.push(`${REQUEST_PARAMS_ARG}?: ${REQUEST_PARAMS_TYPE}`);
5038
5177
  }
5039
- return value.map((entry, index) => {
5040
- if (!this.isSchemaNode(entry)) {
5041
- throw new Error(
5042
- `Unsupported schema: ${kind}[${index}] must be a schema object`
5043
- );
5044
- }
5045
- return this.fromSchema(entry);
5178
+ const paramsCode = params.join(", ");
5179
+ const responseType = tsCodegen.typeExpr(sig.returnType);
5180
+ const returnType = this.wrapInResponseWrapper(sig.returnType, tsCodegen);
5181
+ return this.renderFunctionCode({
5182
+ funName: sig.name,
5183
+ responseType,
5184
+ returnType,
5185
+ params: paramsCode,
5186
+ path,
5187
+ method: http.method,
5188
+ hasQuery,
5189
+ hasBody,
5190
+ requestContentType: http.requestContentType,
5191
+ responseFormat: http.responseFormat,
5192
+ requestParamsVar: this.opts.withRequestParams ? REQUEST_PARAMS_ARG : void 0,
5193
+ unwrap: this.opts.unwrap,
5194
+ target
5046
5195
  });
5047
5196
  });
5048
- __publicField$5(this, "isBinaryFileSchema", (schema) => {
5049
- return schema.type === "string" && schema.format === "binary";
5197
+ __publicField$4(this, "wrapInResponseWrapper", (typeExpr, tsCodegen) => {
5198
+ return tsCodegen.typeExpr({
5199
+ ...this.opts.httpClient.responseWrapper,
5200
+ typeArgs: [typeExpr]
5201
+ });
5050
5202
  });
5051
- __publicField$5(this, "fromSchemaCore", (schema) => {
5052
- if (this.isUnconstrainedSchema(schema)) {
5053
- return this.scalar("unknown");
5054
- }
5055
- if (typeof schema.$ref === "string") {
5056
- this.ensureSchemaDefinition(schema.$ref);
5057
- return this.ref(schema.$ref);
5058
- }
5059
- if (schema.const !== void 0) {
5060
- return this.literal(this.toLiteralValue(schema.const));
5061
- }
5062
- if (Array.isArray(schema.enum)) {
5063
- if (!schema.enum.length) {
5064
- throw new Error(
5065
- "Unsupported enum schema: enum must be non-empty"
5066
- );
5067
- }
5068
- return this.union(
5069
- schema.enum.map(
5070
- (value) => this.literal(this.toLiteralValue(value))
5071
- )
5072
- );
5073
- }
5074
- if (schema.type === "array") {
5075
- if (schema.items === void 0) {
5076
- return {
5077
- kind: "inline",
5078
- expr: {
5079
- node: "array",
5080
- element: this.scalar("unknown")
5081
- }
5082
- };
5083
- }
5084
- if (Array.isArray(schema.items)) {
5085
- throw new Error("Unsupported array schema: tuple-style items");
5086
- }
5087
- if (!this.isSchemaNode(schema.items)) {
5088
- throw new Error(
5089
- "Unsupported array schema: items must be a schema"
5090
- );
5091
- }
5092
- return {
5093
- kind: "inline",
5094
- expr: {
5095
- node: "array",
5096
- element: this.fromSchema(schema.items)
5097
- }
5098
- };
5099
- }
5100
- if (schema.type === "object") {
5101
- return this.projectObjectType(schema);
5102
- }
5103
- if (schema.oneOf !== void 0) {
5104
- return this.union(this.mapSchemaArrayMembers(schema.oneOf, "oneOf"));
5105
- }
5106
- if (schema.anyOf !== void 0) {
5107
- return this.union(this.mapSchemaArrayMembers(schema.anyOf, "anyOf"));
5108
- }
5109
- if (schema.allOf !== void 0) {
5110
- return {
5111
- kind: "inline",
5112
- expr: {
5113
- node: "intersection",
5114
- members: this.mapSchemaArrayMembers(schema.allOf, "allOf")
5115
- }
5116
- };
5117
- }
5118
- if (this.isBinaryFileSchema(schema)) {
5119
- return this.builtinRef("File");
5120
- }
5121
- if (this.isScalarName(schema.type)) {
5122
- return this.scalar(schema.type);
5123
- }
5124
- throw new Error(
5125
- `Unsupported schema construct: ${JSON.stringify(schema, null, 2)}`
5126
- );
5203
+ __publicField$4(this, "renderFunctionCode", ({
5204
+ funName,
5205
+ responseType,
5206
+ returnType,
5207
+ params,
5208
+ path,
5209
+ method,
5210
+ hasQuery,
5211
+ hasBody,
5212
+ requestContentType,
5213
+ responseFormat,
5214
+ requestParamsVar,
5215
+ unwrap,
5216
+ target
5217
+ }) => {
5218
+ let invocation = this.opts.httpClient.invocation.expr({
5219
+ funName,
5220
+ returnType,
5221
+ responseType,
5222
+ params,
5223
+ path,
5224
+ method,
5225
+ hasQuery,
5226
+ hasBody,
5227
+ requestContentType,
5228
+ responseFormat,
5229
+ requestParamsVar,
5230
+ unwrap
5231
+ });
5232
+ invocation = invocation.replaceAll("\n", "");
5233
+ const operator = target === "object" ? ":" : "=";
5234
+ const returnTypeCode = this.opts.httpClient.inferMethodReturnType ? "" : `:${returnType}`;
5235
+ return `${funName} ${operator} (${params})${returnTypeCode} =>
5236
+ this.client${invocation}
5237
+ `;
5127
5238
  });
5128
5239
  }
5129
5240
  }
5130
5241
 
5131
- var __defProp$4 = Object.defineProperty;
5132
- var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5133
- var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
5134
- const pickPreferredMediaType = (mediaTypes) => {
5135
- if (!mediaTypes.length) return void 0;
5136
- const exactJson = mediaTypes.find((media) => media === "application/json");
5137
- if (exactJson) return exactJson;
5138
- const jsonLike = mediaTypes.find((media) => /\+json$/i.test(media));
5139
- if (jsonLike) return jsonLike;
5140
- const wildcardJson = mediaTypes.find(
5141
- (media) => /application\/\*\+json/i.test(media)
5142
- );
5143
- if (wildcardJson) return wildcardJson;
5144
- return mediaTypes[0];
5145
- };
5146
- const isSuccessStatusCode = (statusCode) => {
5147
- const upper = statusCode.toUpperCase();
5148
- if (/^2XX$/.test(upper)) {
5149
- return true;
5150
- }
5151
- const numericCode = Number(statusCode);
5152
- return Number.isFinite(numericCode) && numericCode >= 200 && numericCode < 300;
5153
- };
5154
- const selectReturnResponses = (responses) => {
5155
- const entries = Object.entries(responses);
5156
- if (!entries.length) {
5157
- return [];
5158
- }
5159
- const success = entries.filter(([statusCode]) => isSuccessStatusCode(statusCode)).map(([, response]) => response);
5160
- if (success.length) {
5161
- return success;
5162
- }
5163
- const defaultResponse = entries.find(
5164
- ([statusCode]) => statusCode.toLowerCase() === "default"
5165
- );
5166
- if (defaultResponse) {
5167
- return [defaultResponse[1]];
5242
+ const promiseHttpClientCodegenSpec = () => ({
5243
+ typeName: {
5244
+ kind: "reference",
5245
+ ref: {
5246
+ kind: "internal",
5247
+ name: "HttpClient"
5248
+ }
5249
+ },
5250
+ responseWrapper: {
5251
+ kind: "reference",
5252
+ ref: {
5253
+ kind: "builtin",
5254
+ name: "Promise"
5255
+ }
5256
+ },
5257
+ inferMethodReturnType: true,
5258
+ invocation: {
5259
+ vars: {
5260
+ body: "body",
5261
+ query: "query"
5262
+ },
5263
+ expr: ({
5264
+ responseType,
5265
+ path,
5266
+ method,
5267
+ hasBody,
5268
+ hasQuery,
5269
+ requestContentType,
5270
+ responseFormat,
5271
+ requestParamsVar,
5272
+ unwrap
5273
+ }) => `.request<${responseType}>({
5274
+ method: '${method}',
5275
+ path: ${path}
5276
+ ${hasQuery ? `,
5277
+ query` : ""}
5278
+ ${hasBody ? `,
5279
+ body` : ""}
5280
+ ${requestContentType ? `,
5281
+ requestContentType: '${requestContentType}'` : ""}
5282
+ ${responseFormat ? `,
5283
+ format: '${responseFormat}'` : ""}
5284
+ }${requestParamsVar ? `,
5285
+ ${requestParamsVar}` : ""})${unwrap ? `.then(res => res.body)` : ""}
5286
+ `
5168
5287
  }
5169
- return entries.map(([, response]) => response);
5170
- };
5171
- const getPreferredMediaTypeEntry = (content) => {
5172
- const mediaType = pickPreferredMediaType(Object.keys(content));
5173
- if (!mediaType) return {};
5288
+ });
5289
+
5290
+ const mergeChunks = (name, chunks, options = {}) => {
5291
+ if (chunks.length === 0) {
5292
+ if (options.allowEmpty) {
5293
+ return {
5294
+ name,
5295
+ code: "export {}",
5296
+ exports: [],
5297
+ refs: []
5298
+ };
5299
+ }
5300
+ throw new Error("Cannot merge empty chunk list.");
5301
+ }
5302
+ const orderedChunks = options.preserveChunkOrder ? chunks : orderByInternalDependencies(chunks);
5303
+ const mergedCode = orderedChunks.map((chunk) => chunk.code).join("\n\n");
5304
+ const exportedSymbols = /* @__PURE__ */ new Set();
5305
+ const mergedExports = [];
5306
+ for (const chunk of orderedChunks) {
5307
+ for (const symbol of chunk.exports) {
5308
+ if (exportedSymbols.has(symbol)) continue;
5309
+ exportedSymbols.add(symbol);
5310
+ mergedExports.push(symbol);
5311
+ }
5312
+ }
5313
+ const seenRefKeys = /* @__PURE__ */ new Set();
5314
+ const mergedRefs = orderedChunks.flatMap((chunk) => chunk.refs).filter((ref) => {
5315
+ if (ref.kind === "internal" && exportedSymbols.has(ref.name)) {
5316
+ return false;
5317
+ }
5318
+ const key = ref.kind === "internal" ? `internal:${ref.name}` : ref.kind === "external" ? `external:${ref.package}:${ref.name}` : `builtin:${ref.name}`;
5319
+ if (seenRefKeys.has(key)) return false;
5320
+ seenRefKeys.add(key);
5321
+ return true;
5322
+ });
5174
5323
  return {
5175
- mediaType,
5176
- media: content[mediaType]
5324
+ name,
5325
+ code: mergedCode,
5326
+ exports: mergedExports,
5327
+ refs: mergedRefs
5177
5328
  };
5178
5329
  };
5179
- const isBinaryFileResponse = (response) => {
5180
- const { mediaType, media } = getPreferredMediaTypeEntry(response.content);
5181
- return media?.schema?.type === "string" && media.schema?.format === "binary" && mediaType !== "multipart/form-data";
5330
+ const orderByInternalDependencies = (chunks) => {
5331
+ const chunkIndex = /* @__PURE__ */ new Map();
5332
+ const exportOwnerBySymbol = /* @__PURE__ */ new Map();
5333
+ chunks.forEach((chunk, index) => {
5334
+ chunkIndex.set(chunk.name, index);
5335
+ for (const symbol of chunk.exports) {
5336
+ if (exportOwnerBySymbol.has(symbol)) {
5337
+ throw new Error(
5338
+ `Assertion error. Symbol "${symbol}" exported multiple times while ordering chunks.`
5339
+ );
5340
+ }
5341
+ exportOwnerBySymbol.set(symbol, chunk);
5342
+ }
5343
+ });
5344
+ const dependentsByProvider = /* @__PURE__ */ new Map();
5345
+ const inDegreeByChunk = /* @__PURE__ */ new Map();
5346
+ for (const chunk of chunks) {
5347
+ dependentsByProvider.set(chunk, /* @__PURE__ */ new Set());
5348
+ inDegreeByChunk.set(chunk, 0);
5349
+ }
5350
+ for (const chunk of chunks) {
5351
+ const providers = /* @__PURE__ */ new Set();
5352
+ for (const ref of chunk.refs) {
5353
+ if (ref.kind !== "internal") continue;
5354
+ const provider = exportOwnerBySymbol.get(ref.name);
5355
+ if (!provider || provider === chunk) continue;
5356
+ providers.add(provider);
5357
+ }
5358
+ for (const provider of providers) {
5359
+ dependentsByProvider.get(provider).add(chunk);
5360
+ inDegreeByChunk.set(chunk, inDegreeByChunk.get(chunk) + 1);
5361
+ }
5362
+ }
5363
+ const ready = chunks.filter((chunk) => inDegreeByChunk.get(chunk) === 0).sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5364
+ const ordered = [];
5365
+ while (ready.length > 0) {
5366
+ const provider = ready.shift();
5367
+ ordered.push(provider);
5368
+ for (const dependent of dependentsByProvider.get(provider)) {
5369
+ const nextInDegree = inDegreeByChunk.get(dependent) - 1;
5370
+ inDegreeByChunk.set(dependent, nextInDegree);
5371
+ if (nextInDegree === 0) {
5372
+ ready.push(dependent);
5373
+ }
5374
+ }
5375
+ ready.sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5376
+ }
5377
+ if (ordered.length !== chunks.length) {
5378
+ const cycleChunkNames = chunks.filter((chunk) => inDegreeByChunk.get(chunk) > 0).map((chunk) => chunk.name).join(", ");
5379
+ throw new Error(
5380
+ `Cannot order chunks with cyclic internal dependencies: ${cycleChunkNames}`
5381
+ );
5382
+ }
5383
+ return ordered;
5182
5384
  };
5183
- const isJsonLikeMediaType = (mediaType) => {
5184
- return mediaType === "application/json" || typeof mediaType === "string" && /\+json$/i.test(mediaType);
5385
+
5386
+ var httpClientCommonTypesSource = "export type KnownRequestContentType =\n | 'application/json'\n | 'multipart/form-data'\n | 'application/x-www-form-urlencoded'\n\nexport type RequestContentType = KnownRequestContentType | string\n\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | QueryValue[]\n | Record<string, any>\n // Empty schema support\n | unknown\n\nexport type QueryParams = Record<string, QueryValue>\n\nexport type Request = {\n path: string\n method: 'GET' | 'POST' | 'PUT' | 'DELETE'\n format?: 'json' | 'document'\n headers?: Record<string, string>\n query?: QueryParams\n body?: any\n requestContentType?: RequestContentType\n}\n\nexport type HttpResponse<Data> = {\n body: Data\n headers: Record<string, string | string[]>\n status: number\n}\n\nexport type QuerySerializer = (params: QueryParams) => string\n";
5387
+
5388
+ var httpClientPromiseTypesSource = "import type { HttpResponse, Request } from './common-types'\n\nexport type HttpClient<RequestParams = never> = {\n request: <Data>(\n req: Request,\n params?: RequestParams\n ) => Promise<HttpResponse<Data>>\n}\n";
5389
+
5390
+ const commonTypesCode = httpClientCommonTypesSource;
5391
+ const promiseHttpClientCode = httpClientPromiseTypesSource;
5392
+ const stripImports = (code) => code.replace(/^import[\s\S]*?from\s+['"][^'"]+['"]\s*;?\n?/gm, "").trim();
5393
+ const stripRequestParams = (code) => code.replace(/<RequestParams\s*=\s*never>/g, "").replace(/,\s*params\?:\s*RequestParams/g, "");
5394
+ const getHttpClientCode = (opts) => [
5395
+ commonTypesCode,
5396
+ opts.withRequestParams ? promiseHttpClientCode : stripRequestParams(promiseHttpClientCode)
5397
+ ].map(stripImports).join("\n\n");
5398
+ const provideHttpClientCode = (gen, opts) => gen.toChunk(
5399
+ "HttpClient",
5400
+ getHttpClientCode({
5401
+ withRequestParams: opts.withRequestParams
5402
+ }),
5403
+ ["HttpRequest", "HttpResponse", "HttpClient"]
5404
+ );
5405
+
5406
+ const generateTsCode = (clientName, projectResult, opts) => {
5407
+ const tsCodegenFac = new TsCodegenFactory();
5408
+ const { schemaDefinitions } = projectResult;
5409
+ const types = [...schemaDefinitions].sort((a, b) => a.name.localeCompare(b.name)).map((def) => {
5410
+ const cg = tsCodegenFac.forNewChunk();
5411
+ const code = `export ${cg.declaration(def.name, def.declaration)}`;
5412
+ return cg.toChunk(def.name, code, [def.name]);
5413
+ });
5414
+ const clientCodegen = new ApiClientCodegen(
5415
+ { httpClient: promiseHttpClientCodegenSpec(), ...opts },
5416
+ tsCodegenFac
5417
+ );
5418
+ const generated = clientCodegen.generate(
5419
+ clientName,
5420
+ groupsOpsByTag(projectResult.operations)
5421
+ );
5422
+ const clientChunk = {
5423
+ ...generated,
5424
+ name: `${clientName}.client`
5425
+ };
5426
+ const httpClientChunk = provideHttpClientCode(
5427
+ tsCodegenFac.forNewChunk(),
5428
+ opts
5429
+ );
5430
+ const typesChunk = mergeChunks(`${clientName}.types`, types, {
5431
+ preserveChunkOrder: true,
5432
+ allowEmpty: true
5433
+ });
5434
+ const indexChunk = createIndexChunk(clientName);
5435
+ const all = [typesChunk, httpClientChunk, clientChunk, indexChunk];
5436
+ return {
5437
+ all,
5438
+ typesName: typesChunk.name,
5439
+ promiseWrappersNames: [httpClientChunk.name, clientChunk.name]
5440
+ };
5185
5441
  };
5186
- const toHttpMethodUpper = (method) => {
5187
- switch (method) {
5188
- case "get":
5189
- return "GET";
5190
- case "post":
5191
- return "POST";
5192
- case "put":
5193
- return "PUT";
5194
- case "delete":
5195
- return "DELETE";
5196
- case "patch":
5197
- return "PATCH";
5198
- case "options":
5199
- return "OPTIONS";
5200
- case "head":
5201
- return "HEAD";
5202
- case "trace":
5203
- return "TRACE";
5204
- default: {
5205
- const exhaustive = method;
5206
- throw new Error(`Unsupported HTTP method: ${String(exhaustive)}`);
5442
+ const createIndexChunk = (clientName) => ({
5443
+ name: "index",
5444
+ code: [
5445
+ `export { ${clientName} } from './${clientName}.client'`,
5446
+ `export * from './${clientName}.types'`,
5447
+ "export * from './HttpClient'"
5448
+ ].join("\n"),
5449
+ exports: [],
5450
+ refs: []
5451
+ });
5452
+ const groupsOpsByTag = (operations) => {
5453
+ const groupedOps = {};
5454
+ const rawTagsByNormalizedName = /* @__PURE__ */ new Map();
5455
+ operations.forEach((operation) => {
5456
+ const rawTag = operation.tags.find((tag) => tag.trim())?.trim() || "root";
5457
+ let groupName;
5458
+ try {
5459
+ groupName = toTsCamelIdentifier(rawTag, {
5460
+ leadingDigitPrefix: "tag"
5461
+ });
5462
+ } catch {
5463
+ throw new Error(
5464
+ `Invalid OpenAPI tag "${rawTag}": could not derive a client group name.`
5465
+ );
5207
5466
  }
5208
- }
5467
+ const existingRawTag = rawTagsByNormalizedName.get(groupName);
5468
+ if (existingRawTag && existingRawTag !== rawTag) {
5469
+ throw new Error(
5470
+ `Tag naming collision: "${existingRawTag}" and "${rawTag}" both normalize to "${groupName}". Rename one of these OpenAPI tags to continue.`
5471
+ );
5472
+ }
5473
+ rawTagsByNormalizedName.set(groupName, rawTag);
5474
+ const bucket = groupedOps[groupName] ?? [];
5475
+ bucket.push(operation.op);
5476
+ groupedOps[groupName] = bucket;
5477
+ });
5478
+ return groupedOps;
5209
5479
  };
5210
- class OperationProjector {
5211
- constructor(options = {}) {
5480
+
5481
+ var __defProp$3 = Object.defineProperty;
5482
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5483
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
5484
+ const TS_IDENTIFIER_PATTERN = /^[$A-Z_][0-9A-Z_$]*$/i;
5485
+ class ToTypeExprConverter {
5486
+ constructor(schemas = {}, options) {
5487
+ this.schemas = schemas;
5212
5488
  this.options = options;
5213
- __publicField$4(this, "seenOperationIds", /* @__PURE__ */ new Set());
5214
- __publicField$4(this, "typeExprConverter");
5215
- __publicField$4(this, "project", (openApi) => {
5216
- this.seenOperationIds.clear();
5217
- this.typeExprConverter = new ToTypeExprConverter(openApi.schemas, {
5218
- quirks: this.options.quirks
5219
- });
5220
- const operations = openApi.operations.map((operation) => {
5221
- const operationName = (operation.operationId ?? syntheticOperationId(operation.method, operation.path)).trim();
5222
- if (!operationName) {
5223
- throw new Error(
5224
- `Operation id is empty for ${operation.method.toUpperCase()} ${operation.path}`
5225
- );
5226
- }
5227
- if (this.seenOperationIds.has(operationName)) {
5228
- throw new Error(`Duplicate operation id: ${operationName}`);
5229
- }
5230
- return this.projectOne(operationName, operation);
5489
+ __publicField$3(this, "schemaCache", /* @__PURE__ */ new WeakMap());
5490
+ __publicField$3(this, "refCache", /* @__PURE__ */ new Map());
5491
+ __publicField$3(this, "schemaDefinitions", []);
5492
+ __publicField$3(this, "schemaDefinitionsById", /* @__PURE__ */ new Map());
5493
+ __publicField$3(this, "schemaDefinitionsInProgress", /* @__PURE__ */ new Set());
5494
+ __publicField$3(this, "getSchemaDefinitions", () => this.schemaDefinitions);
5495
+ __publicField$3(this, "registerAllSchemaDefinitions", () => {
5496
+ Object.keys(this.schemas).forEach((schemaName) => {
5497
+ this.ensureSchemaDefinition(`#/components/schemas/${schemaName}`);
5231
5498
  });
5232
- return {
5233
- operations,
5234
- schemaDefinitions: this.typeExprConverter.getSchemaDefinitions()
5235
- };
5236
5499
  });
5237
- __publicField$4(this, "projectOne", (operationName, operation) => {
5238
- this.seenOperationIds.add(operationName);
5239
- const parameters = [];
5240
- const pathParameter = this.projectPathParameter(operation);
5241
- const queryParameter = this.projectQueryParameter(operation);
5242
- const bodyParameter = this.projectBodyParameter(operation);
5243
- if (pathParameter) parameters.push(pathParameter);
5244
- if (queryParameter) parameters.push(queryParameter);
5245
- if (bodyParameter) parameters.push(bodyParameter);
5246
- return {
5247
- op: {
5248
- signature: {
5249
- name: operationName,
5250
- parameters,
5251
- returnType: this.projectReturnType(operation)
5252
- },
5253
- http: {
5254
- method: toHttpMethodUpper(operation.method),
5255
- path: operation.path,
5256
- requestContentType: this.projectRequestContentType(
5257
- operation.requestBody
5258
- ),
5259
- responseFormat: this.projectResponseFormat(operation)
5260
- }
5261
- },
5262
- tags: [...operation.tags]
5263
- };
5264
- });
5265
- __publicField$4(this, "projectRequestContentType", (requestBody) => {
5266
- const { mediaType } = getPreferredMediaTypeEntry(
5267
- requestBody?.content ?? {}
5268
- );
5269
- switch (mediaType) {
5270
- case "application/json":
5271
- case "multipart/form-data":
5272
- case "application/x-www-form-urlencoded":
5273
- return mediaType;
5274
- default:
5275
- return void 0;
5500
+ __publicField$3(this, "fromTypeExpr", (schema) => {
5501
+ if (!schema) {
5502
+ return this.scalar("unknown");
5276
5503
  }
5277
- });
5278
- __publicField$4(this, "projectResponseFormat", (operation) => {
5279
- const selectedResponses = selectReturnResponses(operation.responses);
5280
- if (!selectedResponses.length) {
5281
- return void 0;
5504
+ const bySchema = this.schemaCache.get(schema);
5505
+ if (bySchema) {
5506
+ return bySchema;
5282
5507
  }
5283
- if (selectedResponses.some((response) => isBinaryFileResponse(response))) {
5284
- return "document";
5508
+ if (typeof schema.$ref === "string") {
5509
+ const byRef = this.refCache.get(this.refCacheKey(schema));
5510
+ if (byRef) {
5511
+ this.schemaCache.set(schema, byRef);
5512
+ return byRef;
5513
+ }
5285
5514
  }
5286
- if (selectedResponses.some(
5287
- (response) => isJsonLikeMediaType(
5288
- getPreferredMediaTypeEntry(response.content).mediaType
5289
- )
5290
- )) {
5291
- return "json";
5515
+ const projected = this.fromSchemaCore(schema);
5516
+ const result = schema.nullable === true ? this.withNullable(projected) : projected;
5517
+ this.schemaCache.set(schema, result);
5518
+ if (typeof schema.$ref === "string") {
5519
+ this.refCache.set(this.refCacheKey(schema), result);
5292
5520
  }
5293
- return void 0;
5521
+ return result;
5294
5522
  });
5295
- __publicField$4(this, "projectParameterGroupTypeExpr", (params) => {
5296
- const properties = Object.entries(params).map(([name, parameter]) => ({
5297
- name,
5298
- required: parameter.required,
5299
- typeExpr: this.typeExprConverter.fromSchema(
5300
- this.selectParameterSchema(parameter)
5301
- )
5302
- }));
5523
+ __publicField$3(this, "scalar", (name) => ({
5524
+ kind: "inline",
5525
+ expr: {
5526
+ node: "scalar",
5527
+ name
5528
+ }
5529
+ }));
5530
+ __publicField$3(this, "union", (members) => {
5531
+ const flatMembers = [];
5532
+ for (const member of members) {
5533
+ if (member.kind === "inline" && member.expr.node === "union") {
5534
+ flatMembers.push(...member.expr.members);
5535
+ } else {
5536
+ flatMembers.push(member);
5537
+ }
5538
+ }
5539
+ if (flatMembers.length === 1) {
5540
+ return flatMembers[0];
5541
+ }
5303
5542
  return {
5304
5543
  kind: "inline",
5305
5544
  expr: {
5306
- node: "object",
5307
- properties
5545
+ node: "union",
5546
+ members: flatMembers
5308
5547
  }
5309
5548
  };
5310
5549
  });
5311
- __publicField$4(this, "projectPathParameter", (operation) => {
5312
- const params = operation.parameters.path;
5313
- const entries = Object.values(params);
5314
- if (!entries.length) return null;
5315
- if (entries.some((parameter) => !parameter.required)) {
5316
- throw new Error("Unsupported path parameters: all must be required");
5550
+ __publicField$3(this, "literal", (value) => ({
5551
+ kind: "inline",
5552
+ expr: {
5553
+ node: "literal",
5554
+ value
5317
5555
  }
5556
+ }));
5557
+ __publicField$3(this, "ref", (id) => {
5558
+ const name = this.refNameFromPath(id);
5318
5559
  return {
5319
- kind: "path",
5320
- optional: false,
5321
- typeExpr: this.projectParameterGroupTypeExpr(params)
5560
+ kind: "reference",
5561
+ ref: {
5562
+ kind: "internal",
5563
+ name
5564
+ }
5322
5565
  };
5323
5566
  });
5324
- __publicField$4(this, "projectQueryParameter", (operation) => {
5325
- const params = operation.parameters.query;
5326
- const entries = Object.values(params);
5327
- if (!entries.length) return null;
5567
+ __publicField$3(this, "builtinRef", (name) => ({
5568
+ kind: "reference",
5569
+ ref: {
5570
+ kind: "builtin",
5571
+ name
5572
+ }
5573
+ }));
5574
+ __publicField$3(this, "refNameFromPath", (ref) => {
5575
+ const parts = ref.split("/");
5576
+ const name = parts[parts.length - 1];
5577
+ if (!name) {
5578
+ throw new Error(`Unsupported schema reference: ${ref}`);
5579
+ }
5580
+ return name;
5581
+ });
5582
+ __publicField$3(this, "refCacheKey", (schema) => {
5583
+ return `${schema.$ref}|nullable:${schema.nullable === true}`;
5584
+ });
5585
+ __publicField$3(this, "isUnconstrainedSchema", (schema) => {
5586
+ const meaningfulKeys = Object.keys(schema).filter(
5587
+ (key) => key !== "nullable"
5588
+ );
5589
+ return meaningfulKeys.length === 0;
5590
+ });
5591
+ __publicField$3(this, "ensureSchemaDefinition", (refId) => {
5592
+ if (this.schemaDefinitionsById.has(refId) || this.schemaDefinitionsInProgress.has(refId)) {
5593
+ return;
5594
+ }
5595
+ const schemaName = this.refNameFromPath(refId);
5596
+ const schema = this.schemas[schemaName];
5597
+ if (!schema) {
5598
+ return;
5599
+ }
5600
+ this.schemaDefinitionsInProgress.add(refId);
5601
+ try {
5602
+ const definition = {
5603
+ name: schemaName,
5604
+ declaration: this.toDeclaration(schema)
5605
+ };
5606
+ this.schemaDefinitionsById.set(refId, definition);
5607
+ this.schemaDefinitions.push(definition);
5608
+ } finally {
5609
+ this.schemaDefinitionsInProgress.delete(refId);
5610
+ }
5611
+ });
5612
+ __publicField$3(this, "isSchemaNode", (value) => {
5613
+ return Boolean(
5614
+ value && typeof value === "object" && !Array.isArray(value)
5615
+ );
5616
+ });
5617
+ __publicField$3(this, "toLiteralValue", (value) => {
5618
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
5619
+ return value;
5620
+ }
5621
+ throw new Error(`Unsupported literal value: ${String(value)}`);
5622
+ });
5623
+ __publicField$3(this, "toDeclaration", (schema) => {
5624
+ const enumDeclaration = this.toEnumDeclaration(schema);
5625
+ if (enumDeclaration) {
5626
+ return enumDeclaration;
5627
+ }
5328
5628
  return {
5329
- kind: "query",
5330
- optional: !entries.some((parameter) => parameter.required),
5331
- typeExpr: this.projectParameterGroupTypeExpr(params)
5629
+ kind: "typeAlias",
5630
+ typeExpr: this.fromTypeExpr(schema)
5332
5631
  };
5333
5632
  });
5334
- __publicField$4(this, "projectBodyParameter", (operation) => {
5335
- const requestBody = operation.requestBody;
5336
- if (!requestBody) return null;
5633
+ __publicField$3(this, "toEnumDeclaration", (schema) => {
5634
+ if (this.options.enumStyle !== "enum" || !Array.isArray(schema.enum)) {
5635
+ return void 0;
5636
+ }
5637
+ const valueType = this.getEnumValueType(schema.enum);
5638
+ if (!valueType) {
5639
+ return void 0;
5640
+ }
5337
5641
  return {
5338
- kind: "body",
5339
- optional: !requestBody.required,
5340
- typeExpr: this.typeExprConverter.fromSchema(
5341
- this.selectRequestBodySchema(requestBody)
5342
- )
5642
+ kind: "enum",
5643
+ valueType,
5644
+ members: this.toEnumMembers(schema.enum)
5343
5645
  };
5344
5646
  });
5345
- __publicField$4(this, "projectReturnType", (operation) => {
5346
- const selectedResponses = selectReturnResponses(operation.responses);
5347
- if (!selectedResponses.length) {
5348
- return this.typeExprConverter.scalar("void");
5647
+ __publicField$3(this, "getEnumValueType", (values) => {
5648
+ if (!values.length) {
5649
+ return void 0;
5349
5650
  }
5350
- const responseTypes = selectedResponses.map((response) => {
5351
- const schema = this.selectResponseSchema(response);
5352
- if (!schema) {
5353
- return this.typeExprConverter.scalar("void");
5354
- }
5355
- return this.typeExprConverter.fromSchema(schema);
5651
+ if (values.every((value) => typeof value === "string")) {
5652
+ return "string";
5653
+ }
5654
+ if (values.every((value) => typeof value === "number")) {
5655
+ return "number";
5656
+ }
5657
+ return void 0;
5658
+ });
5659
+ __publicField$3(this, "toEnumMembers", (values) => {
5660
+ const usedNames = /* @__PURE__ */ new Map();
5661
+ return values.map((value, index) => {
5662
+ const enumValue = value;
5663
+ const baseName = this.toEnumMemberBaseName(enumValue, index);
5664
+ const nextCount = (usedNames.get(baseName) ?? 0) + 1;
5665
+ usedNames.set(baseName, nextCount);
5666
+ return {
5667
+ name: nextCount === 1 ? baseName : `${baseName}${nextCount}`,
5668
+ value: enumValue
5669
+ };
5356
5670
  });
5357
- return this.typeExprConverter.union(responseTypes);
5358
5671
  });
5359
- __publicField$4(this, "selectParameterSchema", (parameter) => {
5360
- if (parameter.schema) return parameter.schema;
5361
- return getPreferredMediaTypeEntry(parameter.content).media?.schema;
5672
+ __publicField$3(this, "toEnumMemberBaseName", (value, index) => {
5673
+ if (typeof value === "string" && this.options.uppercaseEnumKeys) {
5674
+ const compatName = this.toCompatEnumMemberName(value);
5675
+ if (compatName) {
5676
+ return compatName;
5677
+ }
5678
+ }
5679
+ if (typeof value === "string" && TS_IDENTIFIER_PATTERN.test(value) && !RESERVED_WORDS.has(value)) {
5680
+ return value;
5681
+ }
5682
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
5683
+ return `Value${value}`;
5684
+ }
5685
+ const pascalName = toPascalCaseIdentifier(String(value));
5686
+ return pascalName === "Api" ? `Value${index + 1}` : pascalName;
5362
5687
  });
5363
- __publicField$4(this, "selectRequestBodySchema", (requestBody) => {
5364
- return getPreferredMediaTypeEntry(requestBody.content).media?.schema;
5688
+ __publicField$3(this, "toCompatEnumMemberName", (value) => {
5689
+ if (/^([A-Z_]{1,})$/g.test(value)) {
5690
+ return value;
5691
+ }
5692
+ const pascalName = toPascalCaseIdentifier(value);
5693
+ return pascalName === "Api" ? void 0 : pascalName;
5365
5694
  });
5366
- __publicField$4(this, "selectResponseSchema", (response) => {
5367
- return getPreferredMediaTypeEntry(response.content).media?.schema;
5695
+ __publicField$3(this, "isScalarName", (value) => {
5696
+ switch (value) {
5697
+ case "string":
5698
+ case "number":
5699
+ case "boolean":
5700
+ case "integer":
5701
+ case "null":
5702
+ case "unknown":
5703
+ case "any":
5704
+ case "never":
5705
+ case "void":
5706
+ return true;
5707
+ default:
5708
+ return false;
5709
+ }
5368
5710
  });
5369
- this.typeExprConverter = new ToTypeExprConverter(void 0, {
5370
- quirks: this.options.quirks
5711
+ __publicField$3(this, "hasNullMember", (expr) => {
5712
+ if (expr.kind !== "inline") return false;
5713
+ if (expr.expr.node === "scalar") {
5714
+ return expr.expr.name === "null";
5715
+ }
5716
+ if (expr.expr.node === "union") {
5717
+ return expr.expr.members.some((member) => this.hasNullMember(member));
5718
+ }
5719
+ return false;
5371
5720
  });
5372
- }
5373
- }
5374
-
5375
- const buildPathParamBindings = (paramNames) => {
5376
- const usedBindings = /* @__PURE__ */ new Set();
5377
- return paramNames.reduce((acc, paramName) => {
5378
- acc[paramName] = toUniqueBindingName(paramName, usedBindings);
5379
- return acc;
5380
- }, {});
5381
- };
5382
- const toUniqueBindingName = (paramName, usedBindings) => {
5383
- let base = toBindingBase(paramName);
5384
- if (isReservedWord(base)) {
5385
- base = `${base}_param`;
5386
- }
5387
- if (!usedBindings.has(base)) {
5388
- usedBindings.add(base);
5389
- return base;
5390
- }
5391
- let suffix = 2;
5392
- let candidate = `${base}_${suffix}`;
5393
- while (usedBindings.has(candidate)) {
5394
- suffix += 1;
5395
- candidate = `${base}_${suffix}`;
5396
- }
5397
- usedBindings.add(candidate);
5398
- return candidate;
5399
- };
5400
- const toBindingBase = (paramName) => {
5401
- if (isIdentifier(paramName)) {
5402
- return paramName;
5403
- }
5404
- const sanitized = paramName.replace(/[^0-9A-Za-z_$]/g, "_");
5405
- if (!sanitized.length) {
5406
- return "pathParam";
5407
- }
5408
- if (/^[A-Za-z_$]/.test(sanitized)) {
5409
- return sanitized;
5410
- }
5411
- return `_${sanitized}`;
5412
- };
5413
- const isIdentifier = (value) => {
5414
- return /^[$A-Z_][0-9A-Z_$]*$/i.test(value);
5415
- };
5416
- const isReservedWord = (value) => {
5417
- return RESERVED_WORDS.has(value);
5418
- };
5419
-
5420
- var __defProp$3 = Object.defineProperty;
5421
- var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5422
- var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
5423
- class ParamsConstructor {
5424
- constructor(options) {
5425
- this.options = options;
5426
- __publicField$3(this, "process", (params, httpPath) => {
5427
- const pathParamSpec = validatePathParams(params, httpPath);
5428
- const pathParamBindings = pathParamSpec?.bindings;
5429
- const funParams = params.flatMap(
5430
- (param) => this.renderOperationParams(param, pathParamSpec)
5721
+ __publicField$3(this, "withNullable", (expr) => {
5722
+ if (this.hasNullMember(expr)) {
5723
+ return expr;
5724
+ }
5725
+ return this.union([expr, this.scalar("null")]);
5726
+ });
5727
+ __publicField$3(this, "projectObjectType", (schema) => {
5728
+ const requiredRaw = schema.required;
5729
+ if (requiredRaw !== void 0 && !Array.isArray(requiredRaw)) {
5730
+ throw new Error(
5731
+ "Unsupported object schema: required must be an array"
5732
+ );
5733
+ }
5734
+ const required = new Set(
5735
+ (requiredRaw ?? []).map((item) => {
5736
+ if (typeof item !== "string") {
5737
+ throw new Error(
5738
+ "Unsupported object schema: required items must be strings"
5739
+ );
5740
+ }
5741
+ return item;
5742
+ })
5431
5743
  );
5432
- const hasBody = params.some((p) => p.kind === "body");
5433
- const hasQuery = params.some((p) => p.kind === "query");
5434
- const path = this.renderPathTemplateLiteral(
5435
- httpPath,
5436
- this.options.paramNames.path,
5437
- pathParamBindings
5744
+ const propertiesRaw = schema.properties;
5745
+ if (propertiesRaw !== void 0 && (!propertiesRaw || typeof propertiesRaw !== "object" || Array.isArray(propertiesRaw))) {
5746
+ throw new Error(
5747
+ "Unsupported object schema: properties must be an object"
5748
+ );
5749
+ }
5750
+ const properties = Object.entries(propertiesRaw ?? {}).map(
5751
+ ([name, propertySchema]) => {
5752
+ if (!this.isSchemaNode(propertySchema)) {
5753
+ throw new Error(
5754
+ `Unsupported object property schema for "${name}"`
5755
+ );
5756
+ }
5757
+ return {
5758
+ name,
5759
+ required: this.isRequiredProperty(
5760
+ name,
5761
+ required,
5762
+ propertySchema
5763
+ ),
5764
+ typeExpr: this.fromTypeExpr(propertySchema)
5765
+ };
5766
+ }
5438
5767
  );
5768
+ let additionalProperties;
5769
+ if (schema.additionalProperties !== void 0) {
5770
+ if (typeof schema.additionalProperties === "boolean") {
5771
+ additionalProperties = schema.additionalProperties;
5772
+ } else if (this.isSchemaNode(schema.additionalProperties)) {
5773
+ additionalProperties = this.fromTypeExpr(
5774
+ schema.additionalProperties
5775
+ );
5776
+ } else {
5777
+ throw new Error(
5778
+ "Unsupported object schema: additionalProperties must be boolean or schema object"
5779
+ );
5780
+ }
5781
+ }
5439
5782
  return {
5440
- funParams,
5441
- path,
5442
- hasBody,
5443
- hasQuery
5783
+ kind: "inline",
5784
+ expr: {
5785
+ node: "object",
5786
+ properties,
5787
+ additionalProperties
5788
+ }
5444
5789
  };
5445
5790
  });
5446
- __publicField$3(this, "renderOperationParams", (param, pathParamSpec) => {
5447
- if (param.kind !== "path" || !pathParamSpec) {
5448
- return [
5449
- {
5450
- identifier: this.paramName(param),
5451
- typeExpr: param.typeExpr
5452
- }
5453
- ];
5791
+ __publicField$3(this, "isRequiredProperty", (name, required, propertySchema) => {
5792
+ if (required.has(name)) {
5793
+ return true;
5454
5794
  }
5455
- if (this.options.pathParamsStyle === "positional") {
5456
- return pathParamSpec.placeholderNames.map((name) => ({
5457
- identifier: pathParamSpec.bindings[name] ?? name,
5458
- typeExpr: pathParamSpec.propertiesByName[name].typeExpr
5459
- }));
5795
+ if (!this.options.swaggerTsApiRequiredBooleans) {
5796
+ return false;
5460
5797
  }
5461
- return [
5462
- {
5463
- identifier: this.renderPathParamDestructuring(
5464
- pathParamSpec.bindings
5465
- ),
5466
- typeExpr: param.typeExpr
5467
- }
5468
- ];
5798
+ return propertySchema.required === true;
5469
5799
  });
5470
- __publicField$3(this, "renderPathTemplateLiteral", (path, pathParamVarName, pathParamBindings) => {
5471
- const escapedPath = path.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
5472
- const pathWithParams = escapedPath.replace(
5473
- /\{([^}]+)\}/g,
5474
- (_, paramName) => {
5475
- const binding = pathParamBindings?.[paramName];
5476
- if (binding !== void 0) {
5477
- return `\${${binding}}`;
5478
- }
5479
- return `\${${pathParamVarName}[${JSON.stringify(paramName)}]}`;
5800
+ __publicField$3(this, "mapSchemaArrayMembers", (value, kind) => {
5801
+ if (!Array.isArray(value) || !value.length) {
5802
+ throw new Error(
5803
+ `Unsupported schema: ${kind} must be a non-empty array`
5804
+ );
5805
+ }
5806
+ return value.map((entry, index) => {
5807
+ if (!this.isSchemaNode(entry)) {
5808
+ throw new Error(
5809
+ `Unsupported schema: ${kind}[${index}] must be a schema object`
5810
+ );
5480
5811
  }
5481
- );
5482
- return `\`${pathWithParams}\``;
5812
+ return this.fromTypeExpr(entry);
5813
+ });
5483
5814
  });
5484
- __publicField$3(this, "renderPathParamDestructuring", (pathParamBindings) => {
5485
- const members = Object.entries(pathParamBindings).map(
5486
- ([paramName, binding]) => {
5487
- const key = toTsPropertyKey(paramName);
5488
- return binding === paramName ? key : `${key}: ${binding}`;
5489
- }
5490
- );
5491
- return `{ ${members.join(", ")} }`;
5815
+ __publicField$3(this, "isBinaryFileSchema", (schema) => {
5816
+ return schema.type === "string" && schema.format === "binary";
5492
5817
  });
5493
- __publicField$3(this, "paramName", (p) => {
5494
- switch (p.kind) {
5495
- case "body":
5496
- return this.options.paramNames.body;
5497
- case "query":
5498
- return this.options.paramNames.query;
5499
- case "path":
5500
- return this.options.paramNames.path;
5501
- default: {
5502
- const exhaustive = p;
5818
+ __publicField$3(this, "fromSchemaCore", (schema) => {
5819
+ if (this.isUnconstrainedSchema(schema)) {
5820
+ return this.scalar("unknown");
5821
+ }
5822
+ if (typeof schema.$ref === "string") {
5823
+ this.ensureSchemaDefinition(schema.$ref);
5824
+ return this.ref(schema.$ref);
5825
+ }
5826
+ if (schema.const !== void 0) {
5827
+ return this.literal(this.toLiteralValue(schema.const));
5828
+ }
5829
+ if (Array.isArray(schema.enum)) {
5830
+ if (!schema.enum.length) {
5503
5831
  throw new Error(
5504
- `Unsupported operation parameter kind: ${String(exhaustive)}`
5832
+ "Unsupported enum schema: enum must be non-empty"
5833
+ );
5834
+ }
5835
+ return this.union(
5836
+ schema.enum.map(
5837
+ (value) => this.literal(this.toLiteralValue(value))
5838
+ )
5839
+ );
5840
+ }
5841
+ if (schema.type === "array") {
5842
+ if (schema.items === void 0) {
5843
+ return {
5844
+ kind: "inline",
5845
+ expr: {
5846
+ node: "array",
5847
+ element: this.scalar("unknown")
5848
+ }
5849
+ };
5850
+ }
5851
+ if (Array.isArray(schema.items)) {
5852
+ throw new Error("Unsupported array schema: tuple-style items");
5853
+ }
5854
+ if (!this.isSchemaNode(schema.items)) {
5855
+ throw new Error(
5856
+ "Unsupported array schema: items must be a schema"
5505
5857
  );
5506
5858
  }
5859
+ return {
5860
+ kind: "inline",
5861
+ expr: {
5862
+ node: "array",
5863
+ element: this.fromTypeExpr(schema.items)
5864
+ }
5865
+ };
5866
+ }
5867
+ if (schema.type === "object") {
5868
+ return this.projectObjectType(schema);
5869
+ }
5870
+ if (schema.oneOf !== void 0) {
5871
+ return this.union(this.mapSchemaArrayMembers(schema.oneOf, "oneOf"));
5872
+ }
5873
+ if (schema.anyOf !== void 0) {
5874
+ return this.union(this.mapSchemaArrayMembers(schema.anyOf, "anyOf"));
5875
+ }
5876
+ if (schema.allOf !== void 0) {
5877
+ return {
5878
+ kind: "inline",
5879
+ expr: {
5880
+ node: "intersection",
5881
+ members: this.mapSchemaArrayMembers(schema.allOf, "allOf")
5882
+ }
5883
+ };
5884
+ }
5885
+ if (this.isBinaryFileSchema(schema)) {
5886
+ return this.builtinRef("File");
5507
5887
  }
5888
+ if (this.isScalarName(schema.type)) {
5889
+ return this.scalar(schema.type);
5890
+ }
5891
+ throw new Error(
5892
+ `Unsupported schema construct: ${JSON.stringify(schema, null, 2)}`
5893
+ );
5508
5894
  });
5509
5895
  }
5510
5896
  }
5511
- const extractPathPlaceholderNames = (pathTemplate) => {
5512
- const seen = /* @__PURE__ */ new Set();
5513
- const names = [];
5514
- const matcher = /\{([^}]+)\}/g;
5515
- let match = matcher.exec(pathTemplate);
5516
- while (match) {
5517
- const name = match[1];
5518
- if (!seen.has(name)) {
5519
- seen.add(name);
5520
- names.push(name);
5521
- }
5522
- match = matcher.exec(pathTemplate);
5523
- }
5524
- return names;
5897
+
5898
+ var __defProp$2 = Object.defineProperty;
5899
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5900
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
5901
+ const pickPreferredMediaType = (mediaTypes) => {
5902
+ if (!mediaTypes.length) return void 0;
5903
+ const exactJson = mediaTypes.find((media) => media === "application/json");
5904
+ if (exactJson) return exactJson;
5905
+ const jsonLike = mediaTypes.find((media) => /\+json$/i.test(media));
5906
+ if (jsonLike) return jsonLike;
5907
+ const wildcardJson = mediaTypes.find(
5908
+ (media) => /application\/\*\+json/i.test(media)
5909
+ );
5910
+ if (wildcardJson) return wildcardJson;
5911
+ return mediaTypes[0];
5525
5912
  };
5526
- const isPathParam = (param) => {
5527
- return param.kind === "path";
5913
+ const isSuccessStatusCode = (statusCode) => {
5914
+ const upper = statusCode.toUpperCase();
5915
+ if (/^2XX$/.test(upper)) {
5916
+ return true;
5917
+ }
5918
+ const numericCode = Number(statusCode);
5919
+ return Number.isFinite(numericCode) && numericCode >= 200 && numericCode < 300;
5528
5920
  };
5529
- const isInlineObjectType = (typeExpr) => typeExpr.kind === "inline" && typeExpr.expr.node === "object";
5530
- const renderNameList = (items) => {
5531
- const values = [...items];
5532
- if (!values.length) {
5533
- return "(none)";
5534
- }
5535
- return values.map((v) => JSON.stringify(v)).join(", ");
5536
- };
5537
- const validatePathParams = (params, httpPath) => {
5538
- const placeholders = extractPathPlaceholderNames(httpPath);
5539
- const placeholderSet = new Set(placeholders);
5540
- const pathParams = params.filter(isPathParam);
5541
- if (pathParams.length > 1) {
5542
- throw new Error(
5543
- `Path "${httpPath}" has ${pathParams.length} path parameter objects; expected at most one.`
5544
- );
5545
- }
5546
- const pathParam = pathParams[0];
5547
- if (!pathParam) {
5548
- if (placeholderSet.size > 0) {
5549
- throw new Error(
5550
- `Path "${httpPath}" is missing a path parameter object for placeholders: ${renderNameList(
5551
- placeholderSet
5552
- )}.`
5553
- );
5554
- }
5555
- return void 0;
5556
- }
5557
- const pathParamExpr = pathParam.typeExpr.expr;
5558
- if (pathParamExpr.additionalProperties !== void 0 && pathParamExpr.additionalProperties !== false) {
5559
- throw new Error(
5560
- `Path "${httpPath}" path parameter object must not declare additionalProperties.`
5561
- );
5562
- }
5563
- const nestedObjectProps = pathParamExpr.properties.filter((prop) => isInlineObjectType(prop.typeExpr)).map((prop) => prop.name);
5564
- if (nestedObjectProps.length > 0) {
5565
- throw new Error(
5566
- `Path "${httpPath}" path parameters must be depth-1. Nested object properties: ${renderNameList(
5567
- nestedObjectProps
5568
- )}.`
5569
- );
5921
+ const selectReturnResponses = (responses) => {
5922
+ const entries = Object.entries(responses);
5923
+ if (!entries.length) {
5924
+ return [];
5570
5925
  }
5571
- const optionalPathProps = pathParamExpr.properties.filter((prop) => !prop.required).map((prop) => prop.name);
5572
- if (optionalPathProps.length > 0) {
5573
- throw new Error(
5574
- `Path "${httpPath}" path parameters must all be required. Optional properties: ${renderNameList(
5575
- optionalPathProps
5576
- )}.`
5577
- );
5926
+ const success = entries.filter(([statusCode]) => isSuccessStatusCode(statusCode)).map(([, response]) => response);
5927
+ if (success.length) {
5928
+ return success;
5578
5929
  }
5579
- const declaredNames = pathParamExpr.properties.map((prop) => prop.name);
5580
- const declaredNameSet = new Set(declaredNames);
5581
- const missingInObject = [...placeholderSet].filter(
5582
- (name) => !declaredNameSet.has(name)
5583
- );
5584
- const missingInPath = declaredNames.filter(
5585
- (name) => !placeholderSet.has(name)
5930
+ const defaultResponse = entries.find(
5931
+ ([statusCode]) => statusCode.toLowerCase() === "default"
5586
5932
  );
5587
- if (missingInObject.length > 0 || missingInPath.length > 0) {
5588
- throw new Error(
5589
- `Path "${httpPath}" placeholders and path parameter object keys must match exactly. Missing in object: ${renderNameList(
5590
- missingInObject
5591
- )}. Missing in path: ${renderNameList(missingInPath)}.`
5592
- );
5933
+ if (defaultResponse) {
5934
+ return [defaultResponse[1]];
5593
5935
  }
5594
- const bindings = buildPathParamBindings(placeholders);
5595
- const propertiesByName = Object.fromEntries(
5596
- pathParamExpr.properties.map((prop) => [prop.name, prop])
5597
- );
5936
+ return entries.map(([, response]) => response);
5937
+ };
5938
+ const getPreferredMediaTypeEntry = (content) => {
5939
+ const mediaType = pickPreferredMediaType(Object.keys(content));
5940
+ if (!mediaType) return {};
5598
5941
  return {
5599
- placeholderNames: placeholders,
5600
- bindings,
5601
- propertiesByName
5942
+ mediaType,
5943
+ media: content[mediaType]
5602
5944
  };
5603
5945
  };
5604
-
5605
- const EMPTY_LINE_MARKER = "/*__EMPTY_LINE_MARKER__*/";
5606
-
5607
- var __defProp$2 = Object.defineProperty;
5608
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5609
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
5610
- const REQUEST_PARAMS_TYPE = "RequestParams";
5611
- const REQUEST_PARAMS_DEFAULT = "never";
5612
- const REQUEST_PARAMS_ARG = "params";
5613
- class ApiClientCodegen {
5614
- constructor(opts, tsCodegenFactory) {
5615
- this.opts = opts;
5616
- this.tsCodegenFactory = tsCodegenFactory;
5617
- __publicField$2(this, "generate", (apiName, operations) => {
5618
- const tsCodegen = this.tsCodegenFactory.forNewChunk();
5619
- const httpClientType = tsCodegen.toCode(this.opts.httpClient.typeName);
5620
- let clientType = httpClientType;
5621
- let classTypeParams = "";
5622
- if (this.opts.requestParams) {
5623
- clientType = `${httpClientType}<${REQUEST_PARAMS_TYPE}>`;
5624
- classTypeParams = `<${REQUEST_PARAMS_TYPE} = ${REQUEST_PARAMS_DEFAULT}>`;
5946
+ const isBinaryFileResponse = (response) => {
5947
+ const { mediaType, media } = getPreferredMediaTypeEntry(response.content);
5948
+ return media?.schema?.type === "string" && media.schema?.format === "binary" && mediaType !== "multipart/form-data";
5949
+ };
5950
+ const isJsonLikeMediaType = (mediaType) => {
5951
+ return mediaType === "application/json" || typeof mediaType === "string" && /\+json$/i.test(mediaType);
5952
+ };
5953
+ const toHttpMethodUpper = (method) => {
5954
+ switch (method) {
5955
+ case "get":
5956
+ return "GET";
5957
+ case "post":
5958
+ return "POST";
5959
+ case "put":
5960
+ return "PUT";
5961
+ case "delete":
5962
+ return "DELETE";
5963
+ case "patch":
5964
+ return "PATCH";
5965
+ case "options":
5966
+ return "OPTIONS";
5967
+ case "head":
5968
+ return "HEAD";
5969
+ case "trace":
5970
+ return "TRACE";
5971
+ default: {
5972
+ const exhaustive = method;
5973
+ throw new Error(`Unsupported HTTP method: ${String(exhaustive)}`);
5974
+ }
5975
+ }
5976
+ };
5977
+ class OperationProjector {
5978
+ constructor(options) {
5979
+ this.options = options;
5980
+ __publicField$2(this, "seenOperationIds", /* @__PURE__ */ new Set());
5981
+ __publicField$2(this, "typeExprConverter");
5982
+ __publicField$2(this, "project", (openApi) => {
5983
+ this.seenOperationIds.clear();
5984
+ this.typeExprConverter = new ToTypeExprConverter(
5985
+ openApi.schemas,
5986
+ this.options.typeExtraction
5987
+ );
5988
+ if (this.options.includeAllSchema) {
5989
+ this.typeExprConverter.registerAllSchemaDefinitions();
5625
5990
  }
5626
- const methodsCode = Array.isArray(operations) ? this.opsCode(tsCodegen, operations) : this.groupsOpsCode(tsCodegen, operations);
5627
- const code = `
5628
- export class ${apiName}${classTypeParams} {
5629
- constructor(private readonly client: ${clientType}) {}
5630
-
5631
- ${methodsCode}
5632
- }
5633
- `;
5634
- return tsCodegen.toChunk("api", code, [apiName]);
5635
- });
5636
- __publicField$2(this, "groupsOpsCode", (codegen, groupedOps) => Object.entries(groupedOps).map(([groupName, ops]) => {
5637
- const methodsCode = this.opsCode(codegen, ops, "object");
5638
- return `
5639
- ${toTsPropertyKey(groupName)} = {
5640
- ${methodsCode}
5641
- }
5642
- `;
5643
- }).join("\n\n"));
5644
- __publicField$2(this, "opsCode", (codegen, operations, target = "class") => {
5645
- const separator = target === "object" ? `,
5646
- ${EMPTY_LINE_MARKER}
5647
- ` : "\n\n";
5648
- const methodsCode = operations.map((op) => this.renderOp(op, codegen, target)).join(separator);
5649
- return methodsCode;
5991
+ const operations = openApi.operations.map((operation) => {
5992
+ const operationName = (operation.operationId ?? syntheticOperationId(operation.method, operation.path)).trim();
5993
+ if (!operationName) {
5994
+ throw new Error(
5995
+ `Operation id is empty for ${operation.method.toUpperCase()} ${operation.path}`
5996
+ );
5997
+ }
5998
+ if (this.seenOperationIds.has(operationName)) {
5999
+ throw new Error(`Duplicate operation id: ${operationName}`);
6000
+ }
6001
+ return this.projectOne(operationName, operation);
6002
+ });
6003
+ return {
6004
+ operations,
6005
+ schemaDefinitions: this.typeExprConverter.getSchemaDefinitions()
6006
+ };
5650
6007
  });
5651
- __publicField$2(this, "renderOp", (op, tsCodegen, target) => {
5652
- const { signature: sig, http } = op;
5653
- const invocationVars = this.opts.httpClient.invocation.vars;
5654
- const paramNames = {
5655
- path: "path",
5656
- query: invocationVars.query,
5657
- body: invocationVars.body
6008
+ __publicField$2(this, "projectOne", (operationName, operation) => {
6009
+ this.seenOperationIds.add(operationName);
6010
+ const parameters = [];
6011
+ const pathParameter = this.projectPathParameter(operation);
6012
+ const queryParameter = this.projectQueryParameter(operation);
6013
+ const bodyParameter = this.projectBodyParameter(operation);
6014
+ if (pathParameter) parameters.push(pathParameter);
6015
+ if (queryParameter) parameters.push(queryParameter);
6016
+ if (bodyParameter) parameters.push(bodyParameter);
6017
+ return {
6018
+ op: {
6019
+ signature: {
6020
+ name: operationName,
6021
+ parameters,
6022
+ returnType: this.projectReturnType(operation)
6023
+ },
6024
+ http: {
6025
+ method: toHttpMethodUpper(operation.method),
6026
+ path: operation.path,
6027
+ requestContentType: this.projectRequestContentType(
6028
+ operation.requestBody
6029
+ ),
6030
+ responseFormat: this.projectResponseFormat(operation)
6031
+ }
6032
+ },
6033
+ tags: [...operation.tags]
5658
6034
  };
5659
- const paramsHelper = new ParamsConstructor({
5660
- paramNames,
5661
- pathParamsStyle: this.opts.pathParamsStyle
5662
- });
5663
- const { path, funParams, hasBody, hasQuery } = paramsHelper.process(
5664
- sig.parameters,
5665
- http.path
5666
- );
5667
- const params = funParams.map(
5668
- (r) => `${r.identifier}: ${tsCodegen.toCode(r.typeExpr)}`
6035
+ });
6036
+ __publicField$2(this, "projectRequestContentType", (requestBody) => {
6037
+ const { mediaType } = getPreferredMediaTypeEntry(
6038
+ requestBody?.content ?? {}
5669
6039
  );
5670
- if (this.opts.requestParams) {
5671
- params.push(`${REQUEST_PARAMS_ARG}?: ${REQUEST_PARAMS_TYPE}`);
6040
+ switch (mediaType) {
6041
+ case "application/json":
6042
+ case "multipart/form-data":
6043
+ case "application/x-www-form-urlencoded":
6044
+ return mediaType;
6045
+ default:
6046
+ return void 0;
5672
6047
  }
5673
- const paramsCode = params.join(", ");
5674
- const responseType = tsCodegen.toCode(sig.returnType);
5675
- const returnType = this.wrapInResponseWrapper(sig.returnType, tsCodegen);
5676
- return this.renderFunctionCode({
5677
- funName: sig.name,
5678
- responseType,
5679
- returnType,
5680
- params: paramsCode,
5681
- path,
5682
- method: http.method,
5683
- hasQuery,
5684
- hasBody,
5685
- requestContentType: http.requestContentType,
5686
- responseFormat: http.responseFormat,
5687
- requestParamsVar: this.opts.requestParams ? REQUEST_PARAMS_ARG : void 0,
5688
- unwrap: this.opts.unwrap ?? false,
5689
- target
5690
- });
5691
6048
  });
5692
- __publicField$2(this, "wrapInResponseWrapper", (typeExpr, tsCodegen) => {
5693
- return tsCodegen.toCode({
5694
- ...this.opts.httpClient.responseWrapper,
5695
- typeArgs: [typeExpr]
5696
- });
6049
+ __publicField$2(this, "projectResponseFormat", (operation) => {
6050
+ const selectedResponses = selectReturnResponses(operation.responses);
6051
+ if (!selectedResponses.length) {
6052
+ return void 0;
6053
+ }
6054
+ if (selectedResponses.some((response) => isBinaryFileResponse(response))) {
6055
+ return "document";
6056
+ }
6057
+ if (selectedResponses.some(
6058
+ (response) => isJsonLikeMediaType(
6059
+ getPreferredMediaTypeEntry(response.content).mediaType
6060
+ )
6061
+ )) {
6062
+ return "json";
6063
+ }
6064
+ return void 0;
5697
6065
  });
5698
- __publicField$2(this, "renderFunctionCode", ({
5699
- funName,
5700
- responseType,
5701
- returnType,
5702
- params,
5703
- path,
5704
- method,
5705
- hasQuery,
5706
- hasBody,
5707
- requestContentType,
5708
- responseFormat,
5709
- requestParamsVar,
5710
- unwrap,
5711
- target
5712
- }) => {
5713
- let invocation = this.opts.httpClient.invocation.expr({
5714
- funName,
5715
- returnType,
5716
- responseType,
5717
- params,
5718
- path,
5719
- method,
5720
- hasQuery,
5721
- hasBody,
5722
- requestContentType,
5723
- responseFormat,
5724
- requestParamsVar,
5725
- unwrap
5726
- });
5727
- invocation = invocation.replaceAll("\n", "");
5728
- const operator = target === "object" ? ":" : "=";
5729
- const returnTypeCode = this.opts.httpClient.inferMethodReturnType ? "" : `:${returnType}`;
5730
- return `${funName} ${operator} (${params})${returnTypeCode} =>
5731
- this.client${invocation}
5732
- `;
6066
+ __publicField$2(this, "projectParameterGroupTypeExpr", (params) => {
6067
+ const properties = Object.entries(params).map(([name, parameter]) => ({
6068
+ name,
6069
+ required: parameter.required,
6070
+ typeExpr: this.typeExprConverter.fromTypeExpr(
6071
+ this.selectParameterSchema(parameter)
6072
+ )
6073
+ }));
6074
+ return {
6075
+ kind: "inline",
6076
+ expr: {
6077
+ node: "object",
6078
+ properties
6079
+ }
6080
+ };
6081
+ });
6082
+ __publicField$2(this, "projectPathParameter", (operation) => {
6083
+ const params = operation.parameters.path;
6084
+ const entries = Object.values(params);
6085
+ if (!entries.length) return null;
6086
+ if (entries.some((parameter) => !parameter.required)) {
6087
+ throw new Error("Unsupported path parameters: all must be required");
6088
+ }
6089
+ return {
6090
+ kind: "path",
6091
+ optional: false,
6092
+ typeExpr: this.projectParameterGroupTypeExpr(params)
6093
+ };
6094
+ });
6095
+ __publicField$2(this, "projectQueryParameter", (operation) => {
6096
+ const params = operation.parameters.query;
6097
+ const entries = Object.values(params);
6098
+ if (!entries.length) return null;
6099
+ return {
6100
+ kind: "query",
6101
+ optional: !entries.some((parameter) => parameter.required),
6102
+ typeExpr: this.projectParameterGroupTypeExpr(params)
6103
+ };
6104
+ });
6105
+ __publicField$2(this, "projectBodyParameter", (operation) => {
6106
+ const requestBody = operation.requestBody;
6107
+ if (!requestBody) return null;
6108
+ return {
6109
+ kind: "body",
6110
+ optional: !requestBody.required,
6111
+ typeExpr: this.typeExprConverter.fromTypeExpr(
6112
+ this.selectRequestBodySchema(requestBody)
6113
+ )
6114
+ };
6115
+ });
6116
+ __publicField$2(this, "projectReturnType", (operation) => {
6117
+ const selectedResponses = selectReturnResponses(operation.responses);
6118
+ if (!selectedResponses.length) {
6119
+ return this.typeExprConverter.scalar("void");
6120
+ }
6121
+ const responseTypes = selectedResponses.map((response) => {
6122
+ const schema = this.selectResponseSchema(response);
6123
+ if (!schema) {
6124
+ return this.typeExprConverter.scalar("void");
6125
+ }
6126
+ return this.typeExprConverter.fromTypeExpr(schema);
6127
+ });
6128
+ return this.typeExprConverter.union(responseTypes);
6129
+ });
6130
+ __publicField$2(this, "selectParameterSchema", (parameter) => {
6131
+ if (parameter.schema) return parameter.schema;
6132
+ return getPreferredMediaTypeEntry(parameter.content).media?.schema;
6133
+ });
6134
+ __publicField$2(this, "selectRequestBodySchema", (requestBody) => {
6135
+ return getPreferredMediaTypeEntry(requestBody.content).media?.schema;
6136
+ });
6137
+ __publicField$2(this, "selectResponseSchema", (response) => {
6138
+ return getPreferredMediaTypeEntry(response.content).media?.schema;
5733
6139
  });
5734
6140
  }
5735
- }
5736
-
5737
- const promiseHttpClientCodegenSpec = () => ({
5738
- typeName: {
5739
- kind: "reference",
5740
- ref: {
5741
- kind: "internal",
5742
- name: "HttpClient"
5743
- }
5744
- },
5745
- responseWrapper: {
5746
- kind: "reference",
5747
- ref: {
5748
- kind: "builtin",
5749
- name: "Promise"
5750
- }
5751
- },
5752
- inferMethodReturnType: true,
5753
- invocation: {
5754
- vars: {
5755
- body: "body",
5756
- query: "query"
5757
- },
5758
- expr: ({
5759
- responseType,
5760
- path,
5761
- method,
5762
- hasBody,
5763
- hasQuery,
5764
- requestContentType,
5765
- responseFormat,
5766
- requestParamsVar,
5767
- unwrap
5768
- }) => `.request<${responseType}>({
5769
- method: '${method}',
5770
- path: ${path}
5771
- ${hasQuery ? `,
5772
- query` : ""}
5773
- ${hasBody ? `,
5774
- body` : ""}
5775
- ${requestContentType ? `,
5776
- requestContentType: '${requestContentType}'` : ""}
5777
- ${responseFormat ? `,
5778
- format: '${responseFormat}'` : ""}
5779
- }${requestParamsVar ? `,
5780
- ${requestParamsVar}` : ""})${unwrap ? `.then(res => res.body)` : ""}
5781
- `
5782
- }
5783
- });
6141
+ }
5784
6142
 
5785
6143
  var __defProp$1 = Object.defineProperty;
5786
6144
  var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -5796,7 +6154,7 @@ class CodeFormatter {
5796
6154
  constructor(opts) {
5797
6155
  this.opts = opts;
5798
6156
  __publicField$1(this, "format", async (code) => {
5799
- const filePath = Path.resolve(this.opts.resolveConfFrom, "generated.ts");
6157
+ const filePath = Path$1.resolve(this.opts.resolveConfFrom, "generated.ts");
5800
6158
  const prettier = await import('prettier');
5801
6159
  const prettierConfig = await prettier.resolveConfig(filePath) ?? {};
5802
6160
  const formattedCode = await prettier.format(code, {
@@ -5890,185 +6248,76 @@ class CodeWriter {
5890
6248
  }
5891
6249
  const ensureSingleTrailingNewline = (code) => code.replace(/\n+$/u, "\n");
5892
6250
 
5893
- const mergeChunks = (name, chunks) => {
5894
- if (chunks.length === 0) {
5895
- throw new Error("Cannot merge empty chunk list.");
5896
- }
5897
- const orderedChunks = orderByInternalDependencies(chunks);
5898
- const mergedCode = orderedChunks.map((chunk) => chunk.code).join("\n\n");
5899
- const exportedSymbols = /* @__PURE__ */ new Set();
5900
- const mergedExports = [];
5901
- for (const chunk of orderedChunks) {
5902
- for (const symbol of chunk.exports) {
5903
- if (exportedSymbols.has(symbol)) continue;
5904
- exportedSymbols.add(symbol);
5905
- mergedExports.push(symbol);
5906
- }
5907
- }
5908
- const seenRefKeys = /* @__PURE__ */ new Set();
5909
- const mergedRefs = orderedChunks.flatMap((chunk) => chunk.refs).filter((ref) => {
5910
- if (ref.kind === "internal" && exportedSymbols.has(ref.name)) {
5911
- return false;
5912
- }
5913
- const key = ref.kind === "internal" ? `internal:${ref.name}` : ref.kind === "external" ? `external:${ref.package}:${ref.name}` : `builtin:${ref.name}`;
5914
- if (seenRefKeys.has(key)) return false;
5915
- seenRefKeys.add(key);
5916
- return true;
5917
- });
5918
- return {
5919
- name,
5920
- code: mergedCode,
5921
- exports: mergedExports,
5922
- refs: mergedRefs
5923
- };
5924
- };
5925
- const orderByInternalDependencies = (chunks) => {
5926
- const chunkIndex = /* @__PURE__ */ new Map();
5927
- const exportOwnerBySymbol = /* @__PURE__ */ new Map();
5928
- chunks.forEach((chunk, index) => {
5929
- chunkIndex.set(chunk.name, index);
5930
- for (const symbol of chunk.exports) {
5931
- if (exportOwnerBySymbol.has(symbol)) {
5932
- throw new Error(
5933
- `Assertion error. Symbol "${symbol}" exported multiple times while ordering chunks.`
5934
- );
5935
- }
5936
- exportOwnerBySymbol.set(symbol, chunk);
5937
- }
5938
- });
5939
- const dependentsByProvider = /* @__PURE__ */ new Map();
5940
- const inDegreeByChunk = /* @__PURE__ */ new Map();
5941
- for (const chunk of chunks) {
5942
- dependentsByProvider.set(chunk, /* @__PURE__ */ new Set());
5943
- inDegreeByChunk.set(chunk, 0);
5944
- }
5945
- for (const chunk of chunks) {
5946
- const providers = /* @__PURE__ */ new Set();
5947
- for (const ref of chunk.refs) {
5948
- if (ref.kind !== "internal") continue;
5949
- const provider = exportOwnerBySymbol.get(ref.name);
5950
- if (!provider || provider === chunk) continue;
5951
- providers.add(provider);
5952
- }
5953
- for (const provider of providers) {
5954
- dependentsByProvider.get(provider).add(chunk);
5955
- inDegreeByChunk.set(chunk, inDegreeByChunk.get(chunk) + 1);
5956
- }
6251
+ class VNextOasClientGenerator {
6252
+ constructor(options, log) {
6253
+ this.options = options;
6254
+ this.log = log;
5957
6255
  }
5958
- const ready = chunks.filter((chunk) => inDegreeByChunk.get(chunk) === 0).sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5959
- const ordered = [];
5960
- while (ready.length > 0) {
5961
- const provider = ready.shift();
5962
- ordered.push(provider);
5963
- for (const dependent of dependentsByProvider.get(provider)) {
5964
- const nextInDegree = inDegreeByChunk.get(dependent) - 1;
5965
- inDegreeByChunk.set(dependent, nextInDegree);
5966
- if (nextInDegree === 0) {
5967
- ready.push(dependent);
6256
+ async generate(cmd) {
6257
+ const { inputFile, outputDir, apiName } = cmd;
6258
+ this.log(`Will generate API client name=${apiName} to ${outputDir}`);
6259
+ const sourceSchema = await loadSourceSchema(inputFile);
6260
+ const normalizedSchema = this.normalizeSchema(sourceSchema, inputFile);
6261
+ const openApiIr = this.filterOperations(normalizedSchema);
6262
+ const projectResult = new OperationProjector({
6263
+ includeAllSchema: this.options.selection?.allSchemas === true,
6264
+ typeExtraction: {
6265
+ enumStyle: this.options.codegen.enumStyle,
6266
+ uppercaseEnumKeys: this.options.compat?.uppercaseEnumKeys,
6267
+ swaggerTsApiRequiredBooleans: this.options.compat?.swaggerTsApiRequiredBooleans
5968
6268
  }
5969
- }
5970
- ready.sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5971
- }
5972
- if (ordered.length !== chunks.length) {
5973
- const cycleChunkNames = chunks.filter((chunk) => inDegreeByChunk.get(chunk) > 0).map((chunk) => chunk.name).join(", ");
5974
- throw new Error(
5975
- `Cannot order chunks with cyclic internal dependencies: ${cycleChunkNames}`
6269
+ }).project(openApiIr);
6270
+ const clientName = toPascalCaseIdentifier(apiName);
6271
+ const chunks = generateTsCode(
6272
+ clientName,
6273
+ projectResult,
6274
+ this.options.codegen
5976
6275
  );
6276
+ const codeFormatter = new CodeFormatter({
6277
+ resolveConfFrom: outputDir
6278
+ });
6279
+ const writer = new CodeWriter(codeFormatter);
6280
+ await writer.write(outputDir, chunks.all);
6281
+ const tsFilePath = (name) => Path.join(outputDir, `${name}.ts`);
6282
+ return {
6283
+ promiseWrapper: chunks.promiseWrappersNames.map(tsFilePath),
6284
+ types: tsFilePath(chunks.typesName)
6285
+ };
5977
6286
  }
5978
- return ordered;
5979
- };
5980
-
5981
- const generateFromIr = async ({
5982
- openApiIr,
5983
- clientName,
5984
- outputDir,
5985
- outputFilename,
5986
- codeFormattingConfigDir,
5987
- quirks,
5988
- requestParams: withRequestParams
5989
- }) => {
5990
- const tsCodegenFac = new TsCodegenFactory();
5991
- const projectResult = new OperationProjector({
5992
- quirks
5993
- }).project(openApiIr);
5994
- const { schemaDefinitions } = projectResult;
5995
- const requestParams = withRequestParams;
5996
- const types = schemaDefinitions.map((def) => {
5997
- const cg = tsCodegenFac.forNewChunk();
5998
- const typeName = def.name;
5999
- const code = `export type ${typeName} = ${cg.toCode(def.typeExpr)}`;
6000
- return cg.toChunk(typeName, code, [typeName]);
6001
- });
6002
- const clientCodegen = new ApiClientCodegen(
6003
- {
6004
- httpClient: promiseHttpClientCodegenSpec(),
6005
- requestParams,
6006
- // TODO make configurable
6007
- pathParamsStyle: "positional"
6008
- },
6009
- tsCodegenFac
6010
- );
6011
- const generated = clientCodegen.generate(
6012
- clientName,
6013
- groupsOpsByTag(projectResult.operations)
6014
- );
6015
- const httpClientChunk = tsCodegenFac.forNewChunk().toChunk(
6016
- "HttpClient",
6017
- getHttpClientCode(),
6018
- ["HttpRequest", "HttpResponse", "HttpClient"]
6019
- );
6020
- const codeFormatter = new CodeFormatter({
6021
- resolveConfFrom: codeFormattingConfigDir
6022
- });
6023
- const writer = new CodeWriter(codeFormatter);
6024
- let chunks = [...types, httpClientChunk, generated];
6025
- chunks = [mergeChunks(outputFilename, chunks)];
6026
- await writer.write(outputDir, chunks);
6027
- };
6028
- const groupsOpsByTag = (operations) => {
6029
- const groupedOps = {};
6030
- operations.forEach((operation) => {
6031
- const groupName = operation.tags[0]?.trim() || "root";
6032
- const bucket = groupedOps[groupName] ?? [];
6033
- bucket.push(operation.op);
6034
- groupedOps[groupName] = bucket;
6035
- });
6036
- return groupedOps;
6037
- };
6038
- const commonTypesCode = httpClientCommonTypesSource;
6039
- const promiseHttpClientCode = httpClientPromiseTypesSource;
6040
- const stripImports = (code) => code.replace(/^import[\s\S]*?from\s+['"][^'"]+['"]\s*;?\n?/gm, "").trim();
6041
- const getHttpClientCode = (opts) => [
6042
- commonTypesCode,
6043
- promiseHttpClientCode
6044
- ].map(stripImports).join("\n\n");
6045
-
6046
- const generateOpenApiClient$1 = async ({ input, name, outputDir, quirks, ignoreOperationsWithTags }, log) => {
6047
- log(`Will generate API client name=${name} to ${outputDir}`);
6048
- const sourceSchema = await loadSourceSchema(input);
6049
- const { result, problems } = new OpenApiNormalizer(sourceSchema).load();
6050
- problems.forEach((problem) => {
6051
- log(`[open-api:${problem.level}] ${problem.path}: ${problem.message}`);
6052
- });
6053
- const errors = problems.filter((problem) => problem.level === "error");
6054
- if (errors.length) {
6055
- throw new Error(
6056
- `Failed to load OpenAPI IR from ${input}: ${errors.length} error(s).`
6287
+ filterOperations(openApiIr) {
6288
+ const ignoreTags = new Set(
6289
+ this.options.selection?.ignoreOperationsWithTags || []
6057
6290
  );
6291
+ return {
6292
+ ...openApiIr,
6293
+ operations: openApiIr.operations.filter((operation) => {
6294
+ const hasUsableTags = operation.tags.some((tag) => tag.trim());
6295
+ if (!hasUsableTags) {
6296
+ this.log(
6297
+ `[open-api:warning] Ignoring operation ${operation.method.toUpperCase()} ${operation.path} (${operation.operationId ?? "<missing operationId>"}): no tags`
6298
+ );
6299
+ return false;
6300
+ }
6301
+ return !operation.tags.some((tag) => ignoreTags.has(tag));
6302
+ })
6303
+ };
6058
6304
  }
6059
- const openApiIr = filterOperations(result, log, ignoreOperationsWithTags);
6060
- const outputFilename = "index";
6061
- await generateFromIr({
6062
- openApiIr,
6063
- clientName: toPascalCaseIdentifier(name),
6064
- outputDir,
6065
- outputFilename,
6066
- codeFormattingConfigDir: outputDir,
6067
- quirks,
6068
- requestParams: true
6069
- });
6070
- return Path.join(outputDir, `${outputFilename}.ts`);
6071
- };
6305
+ normalizeSchema(rawSchema, inputFile) {
6306
+ const { result, problems } = new OpenApiNormalizer(rawSchema).load();
6307
+ problems.forEach((problem) => {
6308
+ this.log(
6309
+ `[open-api:${problem.level}] ${problem.path}: ${problem.message}`
6310
+ );
6311
+ });
6312
+ const errors = problems.filter((problem) => problem.level === "error");
6313
+ if (errors.length) {
6314
+ throw new Error(
6315
+ `Failed to load OpenAPI IR from ${inputFile}: ${errors.length} error(s).`
6316
+ );
6317
+ }
6318
+ return result;
6319
+ }
6320
+ }
6072
6321
  const loadSourceSchema = async (input) => {
6073
6322
  let rawContent;
6074
6323
  try {
@@ -6091,109 +6340,60 @@ const loadSourceSchema = async (input) => {
6091
6340
  }
6092
6341
  return parsed;
6093
6342
  };
6094
- const filterOperations = (openApiIr, log, ignoreOperationsWithTags) => {
6095
- const ignoreTags = new Set(ignoreOperationsWithTags);
6096
- return {
6097
- ...openApiIr,
6098
- operations: openApiIr.operations.filter((operation) => {
6099
- const hasUsableTags = operation.tags.some((tag) => tag.trim());
6100
- if (!hasUsableTags) {
6101
- log(
6102
- `[open-api:warning] Ignoring operation ${operation.method.toUpperCase()} ${operation.path} (${operation.operationId ?? "<missing operationId>"}): no tags`
6103
- );
6104
- return false;
6105
- }
6106
- return !operation.tags.some((tag) => ignoreTags.has(tag));
6107
- })
6108
- };
6109
- };
6110
6343
  const toErrorMessage = (error) => {
6111
6344
  return error instanceof Error ? error.message : String(error);
6112
6345
  };
6113
6346
 
6114
- const generateOpenApiClient = async ({ input, name, outputDir, ignoreOperationsWithTags }, log) => {
6115
- log(`Will generate API client name=${name} to ${outputDir}`);
6116
- const fileName = "index";
6117
- const dstFile = Path$1.join(outputDir, `${fileName}.ts`);
6118
- const prettier = await import('prettier');
6119
- const prettierConfig = await prettier.resolveConfig(dstFile) ?? {};
6120
- const prettierConfigForGenerator = {
6121
- ...prettierConfig,
6122
- // swagger-typescript-api defaults to printWidth=120; keep Prettier default 80 unless explicitly configured.
6123
- printWidth: prettierConfig.printWidth ?? 80
6124
- };
6125
- const ignoreTags = ignoreOperationsWithTags && new Set(ignoreOperationsWithTags);
6126
- await generateApi({
6127
- name: "index",
6128
- output: outputDir,
6129
- input,
6130
- // modular: true,
6131
- httpClientType: "axios",
6132
- templates: getTemplatesDir(),
6133
- defaultResponseAsSuccess: true,
6134
- sortTypes: true,
6135
- // generateRouteTypes: true,
6136
- singleHttpClient: true,
6137
- // extractRequestBody: true,
6138
- prettier: prettierConfigForGenerator,
6139
- moduleNameFirstTag: true,
6140
- apiClassName: toPascalCaseIdentifier(name),
6141
- hooks: {
6142
- onCreateRoute: (routeData) => {
6143
- if (ignoreTags) {
6144
- const ignore = routeData.raw.tags?.find(
6145
- (t) => ignoreTags.has(t)
6146
- );
6147
- if (ignore) return false;
6148
- }
6149
- return routeData;
6150
- }
6151
- },
6152
- // @ts-ignore
6153
- codeGenConstructs: () => ({
6154
- Keyword: {}
6155
- })
6156
- // extractRequestParams: true,
6157
- });
6158
- return dstFile;
6159
- };
6160
- const getThisScriptDirname = () => {
6161
- return Path$1.dirname(fileURLToPath(import.meta.url));
6162
- };
6163
- const getTemplatesDir = () => {
6164
- const currentDir = getThisScriptDirname();
6165
- const templatesRelativePath = Path$1.basename(currentDir) === "dist" ? "../templates" : "../../../templates";
6166
- return Path$1.resolve(currentDir, templatesRelativePath);
6167
- };
6168
-
6169
- const SUPPORTED_GENERATORS = [
6170
- "swagger-ts-api",
6171
- "api-client-generator"
6172
- ];
6173
- const OPEN_API_MODEL_GENERATORS = {
6174
- "swagger-ts-api": generateOpenApiClient,
6175
- "api-client-generator": generateOpenApiClient$1
6176
- };
6177
- const generateOpenApiModel = async (generator, cmd, log) => {
6178
- const generatorFun = OPEN_API_MODEL_GENERATORS[generator];
6179
- const outputPath = await generatorFun(cmd, log);
6180
- const outputModifiers = getOutputModifiers(cmd, log);
6181
- log(`Will modify the outputs ${JSON.stringify(outputModifiers)}`);
6182
- await modifyOutput(outputPath, outputModifiers);
6183
- return outputPath;
6347
+ const generateOpenApiModel = async (spectPath, config, log) => {
6348
+ let outFiles;
6349
+ const { openApiGenerator, responseWrapper } = config;
6350
+ if ("legacy" in openApiGenerator) {
6351
+ outFiles = await generateOpenApiClient(
6352
+ spectPath,
6353
+ {
6354
+ ignoreOperationsWithTags: openApiGenerator.legacy.ignoreOperationsWithTags,
6355
+ name: config.apiName,
6356
+ outputDir: config.dstDir
6357
+ },
6358
+ log
6359
+ );
6360
+ } else {
6361
+ outFiles = await new VNextOasClientGenerator(
6362
+ openApiGenerator,
6363
+ log
6364
+ ).generate({
6365
+ inputFile: spectPath,
6366
+ apiName: config.apiName,
6367
+ outputDir: config.dstDir
6368
+ });
6369
+ }
6370
+ if (responseWrapper) {
6371
+ await modifyHttpClientAsyncWrapper(
6372
+ outFiles.promiseWrapper,
6373
+ responseWrapper,
6374
+ log
6375
+ );
6376
+ }
6377
+ return { typesFilePath: outFiles.types };
6184
6378
  };
6185
- const getOutputModifiers = ({ responseWrapper }, log) => {
6379
+ const modifyHttpClientAsyncWrapper = async (filePaths, responseWrapper, log) => {
6380
+ log(
6381
+ `Will use response wrapper '${JSON.stringify(responseWrapper)}' on file ${filePaths.join(", ")}`
6382
+ );
6186
6383
  const addImports = [];
6187
- const replaces = [];
6188
- if (responseWrapper) {
6189
- log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
6190
- if (responseWrapper.import) addImports.push(responseWrapper.import);
6191
- replaces.push(["Promise", responseWrapper.symbol]);
6384
+ if (responseWrapper.import) {
6385
+ addImports.push(responseWrapper.import);
6386
+ }
6387
+ const replaces = [["Promise", responseWrapper.symbol]];
6388
+ if (responseWrapper.unwrapExpr) {
6389
+ replaces.push([".then(res => res.body)", responseWrapper.unwrapExpr]);
6390
+ }
6391
+ for (const filePath of filePaths) {
6392
+ await modifyOutput(filePath, {
6393
+ addImports,
6394
+ replaces
6395
+ });
6192
6396
  }
6193
- return {
6194
- addImports,
6195
- replaces
6196
- };
6197
6397
  };
6198
6398
  const modifyOutput = async (path, cmd) => {
6199
6399
  let content = await fs$1.readFile(path).then((r) => r.toString());
@@ -6259,9 +6459,7 @@ const generateSchemas = async (inputFile, outputFile, opts = {}) => {
6259
6459
  const { getZodSchemasFile } = generate({
6260
6460
  sourceText: removeGenericTypes(code)
6261
6461
  });
6262
- const fileName = Path$1.basename(inputFile);
6263
- let f = getZodSchemasFile(`./${fileName.replace(".ts", "")}`);
6264
- f = f.replaceAll('"./index";', '"./client"');
6462
+ let f = getZodSchemasFile(getModuleImportPath(inputFile, outputFile));
6265
6463
  f = f.replaceAll(".optional()", ".nullable()");
6266
6464
  if (opts.localDateTimes) {
6267
6465
  f = f.replaceAll(
@@ -6277,50 +6475,25 @@ const generateSchemas = async (inputFile, outputFile, opts = {}) => {
6277
6475
  });
6278
6476
  fs__default.writeFileSync(outputFile, formatted);
6279
6477
  };
6280
-
6281
- const DEFAULT_GENERATOR = "api-client-generator";
6282
- const generateApiClientParamsSchema = strictObject({
6283
- srcSpec: string().trim().min(1),
6284
- dstDir: string().trim().min(1),
6285
- apiName: string().trim().min(1),
6286
- generator: _enum(SUPPORTED_GENERATORS).default(DEFAULT_GENERATOR),
6287
- quirks: strictObject({
6288
- swaggerTsApiRequiredBooleans: boolean().optional()
6289
- }).optional(),
6290
- responseWrapper: strictObject({
6291
- symbol: string().trim().min(1),
6292
- import: string().trim().min(1).optional()
6293
- }).optional(),
6294
- ignoreOperationsWithTags: array(string()).optional(),
6295
- zodSchemas: strictObject({
6296
- enabled: boolean().optional(),
6297
- localDateTimes: boolean().optional()
6298
- }).optional()
6299
- });
6300
- const parseGenerateApiClientParams = (params) => {
6301
- const parsed = generateApiClientParamsSchema.safeParse(params);
6302
- if (parsed.success) return parsed.data;
6303
- throw new Error(prettifyError(parsed.error));
6478
+ const getModuleImportPath = (inputFile, outputFile) => {
6479
+ const sourceDir = Path.dirname(outputFile);
6480
+ const relativePath = Path.relative(sourceDir, inputFile);
6481
+ const withoutExtension = relativePath.replace(/\.ts$/u, "");
6482
+ const normalizedPath = withoutExtension.split(Path.sep).join("/");
6483
+ return normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
6304
6484
  };
6305
- const generateApiClient = async (params, log = console.log) => {
6306
- const {
6307
- dstDir,
6308
- apiName,
6309
- srcSpec,
6310
- quirks,
6311
- responseWrapper,
6312
- ignoreOperationsWithTags,
6313
- zodSchemas,
6314
- generator
6315
- } = parseGenerateApiClientParams(params);
6316
- const dir = Path$1.resolve(dstDir, apiName);
6485
+
6486
+ const generateApiClient = async (params, configFilePath, log = console.log) => {
6487
+ const config = parseConfig(params, configFilePath);
6488
+ const { dstDir, apiName, srcSpec, zodSchemas } = config;
6489
+ const dir = Path.resolve(dstDir, apiName);
6317
6490
  if (!fs.existsSync(dir)) {
6318
6491
  log(`Creating dir ${dir}`);
6319
6492
  fs.mkdirSync(dir, {
6320
6493
  recursive: true
6321
6494
  });
6322
6495
  }
6323
- const clientDir = Path$1.resolve(dir, "client");
6496
+ const clientDir = Path.resolve(dir, "client");
6324
6497
  const specPath = await getSpecPath(srcSpec, dir, log);
6325
6498
  log(`Cleaning client output dir ${clientDir}`);
6326
6499
  fs.rmSync(clientDir, {
@@ -6330,23 +6503,19 @@ const generateApiClient = async (params, log = console.log) => {
6330
6503
  fs.mkdirSync(clientDir, {
6331
6504
  recursive: true
6332
6505
  });
6333
- const clientFile = await generateOpenApiModel(
6334
- generator,
6506
+ const { typesFilePath } = await generateOpenApiModel(
6507
+ specPath,
6335
6508
  {
6336
- name: apiName,
6337
- input: specPath,
6338
- outputDir: clientDir,
6339
- quirks,
6340
- ignoreOperationsWithTags,
6341
- responseWrapper
6509
+ ...config,
6510
+ dstDir: clientDir
6342
6511
  },
6343
6512
  log
6344
6513
  );
6345
6514
  if (zodSchemas?.enabled === false) return;
6346
6515
  log("Generating Zod schemas");
6347
6516
  await generateSchemas(
6348
- Path$1.resolve(dir, clientFile),
6349
- Path$1.resolve(dir, "./zod.ts"),
6517
+ typesFilePath,
6518
+ Path.resolve(dir, "./zod.ts"),
6350
6519
  zodSchemas
6351
6520
  );
6352
6521
  };
@@ -6356,7 +6525,7 @@ const getSpecPath = async (urlOrPath, dir, log) => {
6356
6525
  throw new Error(`Spec file ${urlOrPath} does not exists`);
6357
6526
  return urlOrPath;
6358
6527
  }
6359
- const specPath = Path$1.resolve(dir, `spec.json`);
6528
+ const specPath = Path.resolve(dir, `spec.json`);
6360
6529
  log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
6361
6530
  await downloadSpec(specPath, urlOrPath);
6362
6531
  return specPath;