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