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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,12 @@
1
- import Path$1 from 'path';
1
+ import Path from 'path';
2
2
  import * as fs from 'fs';
3
- import fs__default from 'fs';
3
+ import fs__default$1 from 'fs';
4
4
  import fs$1 from 'fs/promises';
5
- import Path from 'node:path';
6
- import { readFile } from 'node:fs/promises';
7
- import fs$2 from 'node:fs';
8
5
  import { generateApi } from 'swagger-typescript-api';
9
6
  import { fileURLToPath } from 'node:url';
7
+ import { readFile } from 'node:fs/promises';
8
+ import Path$1 from 'node:path';
9
+ import fs__default from 'node:fs';
10
10
  import { generate } from 'ts-to-zod';
11
11
 
12
12
  /** A special constant with type `never` */
@@ -551,69 +551,6 @@ function formatError(error, mapper = (issue) => issue.message) {
551
551
  processError(error);
552
552
  return fieldErrors;
553
553
  }
554
- /** Format a ZodError as a human-readable string in the following form.
555
- *
556
- * From
557
- *
558
- * ```ts
559
- * ZodError {
560
- * issues: [
561
- * {
562
- * expected: 'string',
563
- * code: 'invalid_type',
564
- * path: [ 'username' ],
565
- * message: 'Invalid input: expected string'
566
- * },
567
- * {
568
- * expected: 'number',
569
- * code: 'invalid_type',
570
- * path: [ 'favoriteNumbers', 1 ],
571
- * message: 'Invalid input: expected number'
572
- * }
573
- * ];
574
- * }
575
- * ```
576
- *
577
- * to
578
- *
579
- * ```
580
- * username
581
- * ✖ Expected number, received string at "username
582
- * favoriteNumbers[0]
583
- * ✖ Invalid input: expected number
584
- * ```
585
- */
586
- function toDotPath(_path) {
587
- const segs = [];
588
- const path = _path.map((seg) => (typeof seg === "object" ? seg.key : seg));
589
- for (const seg of path) {
590
- if (typeof seg === "number")
591
- segs.push(`[${seg}]`);
592
- else if (typeof seg === "symbol")
593
- segs.push(`[${JSON.stringify(String(seg))}]`);
594
- else if (/[^\w$]/.test(seg))
595
- segs.push(`[${JSON.stringify(seg)}]`);
596
- else {
597
- if (segs.length)
598
- segs.push(".");
599
- segs.push(seg);
600
- }
601
- }
602
- return segs.join("");
603
- }
604
- function prettifyError(error) {
605
- const lines = [];
606
- // sort by path length
607
- const issues = [...error.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
608
- // Process each issue
609
- for (const issue of issues) {
610
- lines.push(`✖ ${issue.message}`);
611
- if (issue.path?.length)
612
- lines.push(` → at ${toDotPath(issue.path)}`);
613
- }
614
- // Convert Map to formatted string
615
- return lines.join("\n");
616
- }
617
554
 
618
555
  const _parse = (_Err) => (schema, value, _ctx, _params) => {
619
556
  const ctx = _ctx ? Object.assign(_ctx, { async: false }) : { async: false };
@@ -764,6 +701,7 @@ const string$1 = (params) => {
764
701
  const regex = params ? `[\\s\\S]{${params?.minimum ?? 0},${params?.maximum ?? ""}}` : `[\\s\\S]*`;
765
702
  return new RegExp(`^${regex}$`);
766
703
  };
704
+ const number = /^-?\d+(?:\.\d+)?$/;
767
705
  const boolean$1 = /^(?:true|false)$/i;
768
706
  // regex for string with no uppercase letters
769
707
  const lowercase = /^[^A-Z]*$/;
@@ -1960,6 +1898,122 @@ function handleIntersectionResults(result, left, right) {
1960
1898
  result.value = merged.data;
1961
1899
  return result;
1962
1900
  }
1901
+ const $ZodRecord = /*@__PURE__*/ $constructor("$ZodRecord", (inst, def) => {
1902
+ $ZodType.init(inst, def);
1903
+ inst._zod.parse = (payload, ctx) => {
1904
+ const input = payload.value;
1905
+ if (!isPlainObject(input)) {
1906
+ payload.issues.push({
1907
+ expected: "record",
1908
+ code: "invalid_type",
1909
+ input,
1910
+ inst,
1911
+ });
1912
+ return payload;
1913
+ }
1914
+ const proms = [];
1915
+ const values = def.keyType._zod.values;
1916
+ if (values) {
1917
+ payload.value = {};
1918
+ const recordKeys = new Set();
1919
+ for (const key of values) {
1920
+ if (typeof key === "string" || typeof key === "number" || typeof key === "symbol") {
1921
+ recordKeys.add(typeof key === "number" ? key.toString() : key);
1922
+ const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
1923
+ if (result instanceof Promise) {
1924
+ proms.push(result.then((result) => {
1925
+ if (result.issues.length) {
1926
+ payload.issues.push(...prefixIssues(key, result.issues));
1927
+ }
1928
+ payload.value[key] = result.value;
1929
+ }));
1930
+ }
1931
+ else {
1932
+ if (result.issues.length) {
1933
+ payload.issues.push(...prefixIssues(key, result.issues));
1934
+ }
1935
+ payload.value[key] = result.value;
1936
+ }
1937
+ }
1938
+ }
1939
+ let unrecognized;
1940
+ for (const key in input) {
1941
+ if (!recordKeys.has(key)) {
1942
+ unrecognized = unrecognized ?? [];
1943
+ unrecognized.push(key);
1944
+ }
1945
+ }
1946
+ if (unrecognized && unrecognized.length > 0) {
1947
+ payload.issues.push({
1948
+ code: "unrecognized_keys",
1949
+ input,
1950
+ inst,
1951
+ keys: unrecognized,
1952
+ });
1953
+ }
1954
+ }
1955
+ else {
1956
+ payload.value = {};
1957
+ for (const key of Reflect.ownKeys(input)) {
1958
+ if (key === "__proto__")
1959
+ continue;
1960
+ let keyResult = def.keyType._zod.run({ value: key, issues: [] }, ctx);
1961
+ if (keyResult instanceof Promise) {
1962
+ throw new Error("Async schemas not supported in object keys currently");
1963
+ }
1964
+ // Numeric string fallback: if key is a numeric string and failed, retry with Number(key)
1965
+ // This handles z.number(), z.literal([1, 2, 3]), and unions containing numeric literals
1966
+ const checkNumericKey = typeof key === "string" && number.test(key) && keyResult.issues.length;
1967
+ if (checkNumericKey) {
1968
+ const retryResult = def.keyType._zod.run({ value: Number(key), issues: [] }, ctx);
1969
+ if (retryResult instanceof Promise) {
1970
+ throw new Error("Async schemas not supported in object keys currently");
1971
+ }
1972
+ if (retryResult.issues.length === 0) {
1973
+ keyResult = retryResult;
1974
+ }
1975
+ }
1976
+ if (keyResult.issues.length) {
1977
+ if (def.mode === "loose") {
1978
+ // Pass through unchanged
1979
+ payload.value[key] = input[key];
1980
+ }
1981
+ else {
1982
+ // Default "strict" behavior: error on invalid key
1983
+ payload.issues.push({
1984
+ code: "invalid_key",
1985
+ origin: "record",
1986
+ issues: keyResult.issues.map((iss) => finalizeIssue(iss, ctx, config())),
1987
+ input: key,
1988
+ path: [key],
1989
+ inst,
1990
+ });
1991
+ }
1992
+ continue;
1993
+ }
1994
+ const result = def.valueType._zod.run({ value: input[key], issues: [] }, ctx);
1995
+ if (result instanceof Promise) {
1996
+ proms.push(result.then((result) => {
1997
+ if (result.issues.length) {
1998
+ payload.issues.push(...prefixIssues(key, result.issues));
1999
+ }
2000
+ payload.value[keyResult.value] = result.value;
2001
+ }));
2002
+ }
2003
+ else {
2004
+ if (result.issues.length) {
2005
+ payload.issues.push(...prefixIssues(key, result.issues));
2006
+ }
2007
+ payload.value[keyResult.value] = result.value;
2008
+ }
2009
+ }
2010
+ }
2011
+ if (proms.length) {
2012
+ return Promise.all(proms).then(() => payload);
2013
+ }
2014
+ return payload;
2015
+ };
2016
+ });
1963
2017
  const $ZodEnum = /*@__PURE__*/ $constructor("$ZodEnum", (inst, def) => {
1964
2018
  $ZodType.init(inst, def);
1965
2019
  const values = getEnumValues(def.entries);
@@ -3356,6 +3410,49 @@ const intersectionProcessor = (schema, ctx, json, params) => {
3356
3410
  ];
3357
3411
  json.allOf = allOf;
3358
3412
  };
3413
+ const recordProcessor = (schema, ctx, _json, params) => {
3414
+ const json = _json;
3415
+ const def = schema._zod.def;
3416
+ json.type = "object";
3417
+ // For looseRecord with regex patterns, use patternProperties
3418
+ // This correctly represents "only validate keys matching the pattern" semantics
3419
+ // and composes well with allOf (intersections)
3420
+ const keyType = def.keyType;
3421
+ const keyBag = keyType._zod.bag;
3422
+ const patterns = keyBag?.patterns;
3423
+ if (def.mode === "loose" && patterns && patterns.size > 0) {
3424
+ // Use patternProperties for looseRecord with regex patterns
3425
+ const valueSchema = process(def.valueType, ctx, {
3426
+ ...params,
3427
+ path: [...params.path, "patternProperties", "*"],
3428
+ });
3429
+ json.patternProperties = {};
3430
+ for (const pattern of patterns) {
3431
+ json.patternProperties[pattern.source] = valueSchema;
3432
+ }
3433
+ }
3434
+ else {
3435
+ // Default behavior: use propertyNames + additionalProperties
3436
+ if (ctx.target === "draft-07" || ctx.target === "draft-2020-12") {
3437
+ json.propertyNames = process(def.keyType, ctx, {
3438
+ ...params,
3439
+ path: [...params.path, "propertyNames"],
3440
+ });
3441
+ }
3442
+ json.additionalProperties = process(def.valueType, ctx, {
3443
+ ...params,
3444
+ path: [...params.path, "additionalProperties"],
3445
+ });
3446
+ }
3447
+ // Add required for keys with discrete values (enum, literal, etc.)
3448
+ const keyValues = keyType._zod.values;
3449
+ if (keyValues) {
3450
+ const validKeyValues = [...keyValues].filter((v) => typeof v === "string" || typeof v === "number");
3451
+ if (validKeyValues.length > 0) {
3452
+ json.required = validKeyValues;
3453
+ }
3454
+ }
3455
+ };
3359
3456
  const nullableProcessor = (schema, ctx, json, params) => {
3360
3457
  const def = schema._zod.def;
3361
3458
  const inner = process(def.innerType, ctx, params);
@@ -3859,6 +3956,21 @@ function intersection(left, right) {
3859
3956
  right: right,
3860
3957
  });
3861
3958
  }
3959
+ const ZodRecord = /*@__PURE__*/ $constructor("ZodRecord", (inst, def) => {
3960
+ $ZodRecord.init(inst, def);
3961
+ ZodType.init(inst, def);
3962
+ inst._zod.processJSONSchema = (ctx, json, params) => recordProcessor(inst, ctx, json, params);
3963
+ inst.keyType = def.keyType;
3964
+ inst.valueType = def.valueType;
3965
+ });
3966
+ function record(keyType, valueType, params) {
3967
+ return new ZodRecord({
3968
+ type: "record",
3969
+ keyType,
3970
+ valueType: valueType,
3971
+ ...normalizeParams(params),
3972
+ });
3973
+ }
3862
3974
  const ZodEnum = /*@__PURE__*/ $constructor("ZodEnum", (inst, def) => {
3863
3975
  $ZodEnum.init(inst, def);
3864
3976
  ZodType.init(inst, def);
@@ -4082,6 +4194,73 @@ function superRefine(fn) {
4082
4194
  return _superRefine(fn);
4083
4195
  }
4084
4196
 
4197
+ const vNextOasGeneratorSchema = strictObject({
4198
+ selection: strictObject({
4199
+ ignoreOperationsWithTags: array(string()).default([]),
4200
+ allSchemas: boolean().default(true)
4201
+ }).optional(),
4202
+ codegen: strictObject({
4203
+ unwrap: boolean().default(true),
4204
+ withRequestParams: boolean().default(false),
4205
+ pathParamsStyle: _enum(["object", "positional"]).default("positional"),
4206
+ enumStyle: _enum(["enum", "union"]).default("enum"),
4207
+ comments: strictObject({
4208
+ enabled: boolean().default(true),
4209
+ operation: strictObject({
4210
+ tags: boolean().default(true),
4211
+ summary: boolean().default(true),
4212
+ description: boolean().default(true)
4213
+ }).prefault({}),
4214
+ schema: strictObject({
4215
+ metadata: boolean().default(true)
4216
+ }).prefault({})
4217
+ }).prefault({})
4218
+ }).prefault({}),
4219
+ compat: strictObject({
4220
+ uppercaseEnumKeys: boolean(),
4221
+ swaggerTsApiRequiredBooleans: boolean()
4222
+ }).optional()
4223
+ });
4224
+ const legacyOasGeneratorSchema = strictObject({
4225
+ legacy: strictObject({
4226
+ ignoreOperationsWithTags: array(string()).optional()
4227
+ })
4228
+ });
4229
+ const profileConfigSchema = strictObject({
4230
+ srcSpec: string().trim().min(1),
4231
+ dstDir: string().trim().min(1),
4232
+ apiName: string().trim().min(1),
4233
+ openApiGenerator: union([
4234
+ vNextOasGeneratorSchema,
4235
+ legacyOasGeneratorSchema
4236
+ ]),
4237
+ responseWrapper: strictObject({
4238
+ symbol: string().trim().min(1),
4239
+ import: string().trim().min(1).optional(),
4240
+ unwrapExpr: string().optional()
4241
+ }).optional(),
4242
+ zodSchemas: strictObject({
4243
+ enabled: boolean().optional(),
4244
+ localDateTimes: boolean().optional()
4245
+ }).optional()
4246
+ });
4247
+ const configSchema = record(string(), profileConfigSchema);
4248
+ const defineConfig = (config) => config;
4249
+ const parseConfig = (userConfig, filePath) => {
4250
+ const parsed = configSchema.safeParse(userConfig);
4251
+ if (parsed.success) return parsed.data;
4252
+ throw new Error(`Invalid config at ${filePath}: ${parsed.error.message}`, {
4253
+ cause: parsed.error
4254
+ });
4255
+ };
4256
+ const parseProfileConfig = (userConfig, filePath) => {
4257
+ const parsed = profileConfigSchema.safeParse(userConfig);
4258
+ if (parsed.success) return parsed.data;
4259
+ throw new Error(`Invalid config at ${filePath}: ${parsed.error.message}`, {
4260
+ cause: parsed.error
4261
+ });
4262
+ };
4263
+
4085
4264
  const downloadSpec = async (path, url) => {
4086
4265
  let response;
4087
4266
  try {
@@ -4101,6 +4280,86 @@ const downloadSpec = async (path, url) => {
4101
4280
  await fs$1.writeFile(path, content);
4102
4281
  };
4103
4282
 
4283
+ const toPascalCaseIdentifier = (str) => {
4284
+ const words = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
4285
+ const pascalCase = words.map((word) => word[0].toUpperCase() + word.substring(1)).join("");
4286
+ if (!pascalCase.length) return "Api";
4287
+ if (/^[0-9]/.test(pascalCase)) return `Api${pascalCase}`;
4288
+ return pascalCase;
4289
+ };
4290
+ const groupBy = (values, getKey) => {
4291
+ const groups = /* @__PURE__ */ new Map();
4292
+ for (const value of values) {
4293
+ const key = getKey(value);
4294
+ const existing = groups.get(key);
4295
+ if (existing) {
4296
+ existing.push(value);
4297
+ } else {
4298
+ groups.set(key, [value]);
4299
+ }
4300
+ }
4301
+ return groups;
4302
+ };
4303
+
4304
+ const generateOpenApiClient = async (inputFile, {
4305
+ name,
4306
+ outputDir,
4307
+ ignoreOperationsWithTags
4308
+ }, log) => {
4309
+ log(`Will generate API client name=${name} to ${outputDir}`);
4310
+ const fileName = "index";
4311
+ const dstFile = Path.join(outputDir, `${fileName}.ts`);
4312
+ const prettier = await import('prettier');
4313
+ const prettierConfig = await prettier.resolveConfig(dstFile) ?? {};
4314
+ const prettierConfigForGenerator = {
4315
+ ...prettierConfig,
4316
+ // swagger-typescript-api defaults to printWidth=120; keep Prettier default 80 unless explicitly configured.
4317
+ printWidth: prettierConfig.printWidth ?? 80
4318
+ };
4319
+ const ignoreTags = ignoreOperationsWithTags && new Set(ignoreOperationsWithTags);
4320
+ await generateApi({
4321
+ name: "index",
4322
+ output: outputDir,
4323
+ input: inputFile,
4324
+ // modular: true,
4325
+ httpClientType: "axios",
4326
+ templates: getTemplatesDir(),
4327
+ defaultResponseAsSuccess: true,
4328
+ sortTypes: true,
4329
+ // generateRouteTypes: true,
4330
+ singleHttpClient: true,
4331
+ // extractRequestBody: true,
4332
+ prettier: prettierConfigForGenerator,
4333
+ moduleNameFirstTag: true,
4334
+ apiClassName: toPascalCaseIdentifier(name),
4335
+ hooks: {
4336
+ onCreateRoute: (routeData) => {
4337
+ if (ignoreTags) {
4338
+ const ignore = routeData.raw.tags?.find(
4339
+ (t) => ignoreTags.has(t)
4340
+ );
4341
+ if (ignore) return false;
4342
+ }
4343
+ return routeData;
4344
+ }
4345
+ },
4346
+ // @ts-ignore
4347
+ codeGenConstructs: () => ({
4348
+ Keyword: {}
4349
+ })
4350
+ // extractRequestParams: true,
4351
+ });
4352
+ return { types: dstFile, promiseWrapper: [dstFile] };
4353
+ };
4354
+ const getThisScriptDirname = () => {
4355
+ return Path.dirname(fileURLToPath(import.meta.url));
4356
+ };
4357
+ const getTemplatesDir = () => {
4358
+ const currentDir = getThisScriptDirname();
4359
+ const templatesRelativePath = Path.basename(currentDir) === "dist" ? "../templates" : "../../../templates";
4360
+ return Path.resolve(currentDir, templatesRelativePath);
4361
+ };
4362
+
4104
4363
  const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
4105
4364
  const asRecord = (value) => {
4106
4365
  return isRecord(value) ? value : void 0;
@@ -4155,9 +4414,9 @@ const resolveLocalRef = (root, ref) => {
4155
4414
  return current;
4156
4415
  };
4157
4416
 
4158
- var __defProp$9 = Object.defineProperty;
4159
- var __defNormalProp$9 = (obj, key, value) => key in obj ? __defProp$9(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4160
- var __publicField$9 = (obj, key, value) => __defNormalProp$9(obj, key + "" , value);
4417
+ var __defProp$a = Object.defineProperty;
4418
+ var __defNormalProp$a = (obj, key, value) => key in obj ? __defProp$a(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4419
+ var __publicField$a = (obj, key, value) => __defNormalProp$a(obj, key + "" , value);
4161
4420
  const PARAMETER_LOCATIONS = [
4162
4421
  "path",
4163
4422
  "query",
@@ -4183,7 +4442,7 @@ const mergeParameters = (base, override) => {
4183
4442
  class OpenApiNormalizer {
4184
4443
  constructor(doc) {
4185
4444
  this.doc = doc;
4186
- __publicField$9(this, "problems", []);
4445
+ __publicField$a(this, "problems", []);
4187
4446
  }
4188
4447
  load() {
4189
4448
  const info = readRecord(this.doc, "info");
@@ -4526,44 +4785,24 @@ const syntheticOperationId = (method, path) => {
4526
4785
  return `_${compact}`;
4527
4786
  };
4528
4787
 
4529
- const toPascalCaseIdentifier = (str) => {
4530
- const words = str.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
4531
- const pascalCase = words.map((word) => word[0].toUpperCase() + word.substring(1)).join("");
4532
- if (!pascalCase.length) return "Api";
4533
- if (/^[0-9]/.test(pascalCase)) return `Api${pascalCase}`;
4534
- return pascalCase;
4535
- };
4536
- const groupBy = (values, getKey) => {
4537
- const groups = /* @__PURE__ */ new Map();
4538
- for (const value of values) {
4539
- const key = getKey(value);
4540
- const existing = groups.get(key);
4541
- if (existing) {
4542
- existing.push(value);
4543
- } else {
4544
- groups.set(key, [value]);
4545
- }
4546
- }
4547
- return groups;
4548
- };
4549
-
4550
- var httpClientCommonTypesSource = "export type KnownRequestContentType =\n | 'application/json'\n | 'multipart/form-data'\n | 'application/x-www-form-urlencoded'\n\nexport type RequestContentType = KnownRequestContentType | string\n\nexport type QueryValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | QueryValue[]\n | Record<string, any>\n // Empty schema support\n | unknown\n\nexport type QueryParams = Record<string, QueryValue>\n\nexport type Request = {\n path: string\n method: 'GET' | 'POST' | 'PUT' | 'DELETE'\n format?: 'json' | 'document'\n headers?: Record<string, string>\n query?: QueryParams\n body?: any\n requestContentType?: RequestContentType\n}\n\nexport type QuerySerializer = (params: QueryParams) => string\n";
4551
-
4552
- var httpClientPromiseTypesSource = "import type { Request } from './common-types'\n\nexport type HttpClient<RequestParams = never> = {\n request: <Data>(req: Request, params?: RequestParams) => Promise<Data>\n}\n";
4553
-
4554
- var __defProp$8 = Object.defineProperty;
4555
- var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4556
- var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
4788
+ var __defProp$9 = Object.defineProperty;
4789
+ var __defNormalProp$9 = (obj, key, value) => key in obj ? __defProp$9(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4790
+ var __publicField$9 = (obj, key, value) => __defNormalProp$9(obj, typeof key !== "symbol" ? key + "" : key, value);
4557
4791
  class TsCodegen {
4558
- constructor(typeExprCodegen) {
4792
+ constructor(typeExprCodegen, docRenderer) {
4559
4793
  this.typeExprCodegen = typeExprCodegen;
4560
- __publicField$8(this, "usedTypeRefs", []);
4561
- __publicField$8(this, "usedTypeRefKeys", /* @__PURE__ */ new Set());
4562
- __publicField$8(this, "toCode", (typeExpr) => {
4794
+ __publicField$9(this, "usedTypeRefs", []);
4795
+ __publicField$9(this, "usedTypeRefKeys", /* @__PURE__ */ new Set());
4796
+ __publicField$9(this, "docRenderer");
4797
+ __publicField$9(this, "typeExpr", (typeExpr) => {
4563
4798
  this.collectTypeRefsFromExpr(typeExpr);
4564
4799
  return this.typeExprCodegen.toCode(typeExpr).code;
4565
4800
  });
4566
- __publicField$8(this, "toChunk", (name, code, exports$1) => {
4801
+ __publicField$9(this, "declaration", (name, declaration, options = {}) => {
4802
+ this.collectTypeRefsFromDeclaration(declaration);
4803
+ return this.toDeclarationCode(name, declaration, options);
4804
+ });
4805
+ __publicField$9(this, "toChunk", (name, code, exports$1) => {
4567
4806
  const exportedSymbols = new Set(exports$1);
4568
4807
  const refs = this.getTypeRefs().filter((ref) => {
4569
4808
  return ref.kind !== "internal" || !exportedSymbols.has(ref.name);
@@ -4575,9 +4814,44 @@ class TsCodegen {
4575
4814
  refs
4576
4815
  };
4577
4816
  });
4578
- __publicField$8(this, "getTypeRefs", () => {
4817
+ __publicField$9(this, "getTypeRefs", () => {
4579
4818
  return this.usedTypeRefs.map((ref) => ({ ...ref }));
4580
4819
  });
4820
+ __publicField$9(this, "toDeclarationCode", (name, d, o) => {
4821
+ let code = this.toDeclarationBodyCode(name, d);
4822
+ if (o.exported) {
4823
+ code = `export ${code}`;
4824
+ }
4825
+ return this.addDoc(code, d.doc);
4826
+ });
4827
+ __publicField$9(this, "toDeclarationBodyCode", (name, declaration) => {
4828
+ switch (declaration.kind) {
4829
+ case "typeAlias":
4830
+ return `type ${name} = ${this.typeExprCodegen.toCode(declaration.typeExpr).code}`;
4831
+ case "enum": {
4832
+ const members = declaration.members.map((member) => {
4833
+ const code = `${member.name} = ${JSON.stringify(member.value)}`;
4834
+ return this.addDoc(code, member.doc);
4835
+ }).join(",\n");
4836
+ return `enum ${name} {
4837
+ ${members}
4838
+ }`;
4839
+ }
4840
+ default: {
4841
+ const exhaustive = declaration;
4842
+ throw new Error(
4843
+ `Unsupported TypeScript declaration: ${String(exhaustive)}`
4844
+ );
4845
+ }
4846
+ }
4847
+ });
4848
+ __publicField$9(this, "addDoc", (code, doc) => {
4849
+ if (!doc) return code;
4850
+ const rendered = this.docRenderer.render(doc);
4851
+ return rendered ? `${rendered}
4852
+ ${code}` : code;
4853
+ });
4854
+ this.docRenderer = docRenderer;
4581
4855
  }
4582
4856
  collectTypeRef(typeRef) {
4583
4857
  if (typeRef.kind === "builtin") return;
@@ -4586,6 +4860,11 @@ class TsCodegen {
4586
4860
  this.usedTypeRefKeys.add(key);
4587
4861
  this.usedTypeRefs.push({ ...typeRef });
4588
4862
  }
4863
+ collectTypeRefsFromDeclaration(declaration) {
4864
+ if (declaration.kind === "typeAlias") {
4865
+ this.collectTypeRefsFromExpr(declaration.typeExpr);
4866
+ }
4867
+ }
4589
4868
  collectTypeRefsFromExpr(typeExpr) {
4590
4869
  if (typeExpr.kind === "reference") {
4591
4870
  this.collectTypeRef(typeExpr.ref);
@@ -4666,20 +4945,45 @@ const RESERVED_WORDS = /* @__PURE__ */ new Set([
4666
4945
  "with",
4667
4946
  "yield"
4668
4947
  ]);
4669
- const TS_IDENTIFIER_PATTERN = /^[$A-Z_][0-9A-Z_$]*$/i;
4948
+ const TS_IDENTIFIER_PATTERN$1 = /^[$A-Z_][0-9A-Z_$]*$/i;
4670
4949
  const isTsIdentifier = (value) => {
4671
- return TS_IDENTIFIER_PATTERN.test(value);
4950
+ return TS_IDENTIFIER_PATTERN$1.test(value);
4951
+ };
4952
+ const splitIdentifierWords = (value) => {
4953
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^a-zA-Z0-9]+/).filter(Boolean);
4954
+ };
4955
+ const toTsCamelIdentifier = (value, opts = {}) => {
4956
+ const words = splitIdentifierWords(value);
4957
+ if (!words.length) {
4958
+ throw new Error(
4959
+ `Could not derive a TypeScript identifier from "${value}".`
4960
+ );
4961
+ }
4962
+ const [firstWord, ...restWords] = words;
4963
+ let identifier = [
4964
+ firstWord[0].toLowerCase() + firstWord.substring(1),
4965
+ ...restWords.map((word) => word[0].toUpperCase() + word.substring(1))
4966
+ ].join("");
4967
+ if (/^[0-9]/.test(identifier)) {
4968
+ const prefix = opts.leadingDigitPrefix ?? "value";
4969
+ identifier = `${prefix}${identifier[0].toUpperCase()}${identifier.substring(1)}`;
4970
+ }
4971
+ if (RESERVED_WORDS.has(identifier)) {
4972
+ identifier = `${identifier}_`;
4973
+ }
4974
+ return identifier;
4672
4975
  };
4673
4976
  const toTsPropertyKey = (value) => {
4674
4977
  return isTsIdentifier(value) ? value : JSON.stringify(value);
4675
4978
  };
4676
4979
 
4677
- var __defProp$7 = Object.defineProperty;
4678
- var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4679
- var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
4980
+ var __defProp$8 = Object.defineProperty;
4981
+ var __defNormalProp$8 = (obj, key, value) => key in obj ? __defProp$8(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4982
+ var __publicField$8 = (obj, key, value) => __defNormalProp$8(obj, typeof key !== "symbol" ? key + "" : key, value);
4680
4983
  class TypeExprCodegen {
4681
- constructor() {
4682
- __publicField$7(this, "toCode", (typeExpr) => {
4984
+ constructor(docRenderer) {
4985
+ __publicField$8(this, "docRenderer");
4986
+ __publicField$8(this, "toCode", (typeExpr) => {
4683
4987
  switch (typeExpr.kind) {
4684
4988
  case "reference":
4685
4989
  return this.toReferenceCode(typeExpr);
@@ -4695,7 +4999,7 @@ class TypeExprCodegen {
4695
4999
  }
4696
5000
  }
4697
5001
  });
4698
- __publicField$7(this, "toReferenceCode", (typeExpr) => {
5002
+ __publicField$8(this, "toReferenceCode", (typeExpr) => {
4699
5003
  if (!typeExpr.typeArgs?.length) {
4700
5004
  return {
4701
5005
  code: typeExpr.ref.name,
@@ -4708,7 +5012,7 @@ class TypeExprCodegen {
4708
5012
  ref: typeExpr.ref
4709
5013
  };
4710
5014
  });
4711
- __publicField$7(this, "toInlineCode", (expr) => {
5015
+ __publicField$8(this, "toInlineCode", (expr) => {
4712
5016
  switch (expr.node) {
4713
5017
  case "scalar":
4714
5018
  return expr.name === "integer" ? "number" : expr.name;
@@ -4733,1054 +5037,1583 @@ class TypeExprCodegen {
4733
5037
  }
4734
5038
  }
4735
5039
  });
4736
- __publicField$7(this, "toIntersectionMemberCode", (typeExpr) => {
5040
+ __publicField$8(this, "toIntersectionMemberCode", (typeExpr) => {
4737
5041
  const rendered = this.toCode(typeExpr).code;
4738
5042
  return this.isUnion(typeExpr) ? `(${rendered})` : rendered;
4739
5043
  });
4740
- __publicField$7(this, "toArrayElementCode", (typeExpr) => {
5044
+ __publicField$8(this, "toArrayElementCode", (typeExpr) => {
4741
5045
  const rendered = this.toCode(typeExpr).code;
4742
5046
  return this.needsGroupingForArrayElement(typeExpr) ? `(${rendered})` : rendered;
4743
5047
  });
4744
- __publicField$7(this, "isUnion", (typeExpr) => {
5048
+ __publicField$8(this, "isUnion", (typeExpr) => {
4745
5049
  return typeExpr.kind === "inline" && typeExpr.expr.node === "union";
4746
5050
  });
4747
- __publicField$7(this, "needsGroupingForArrayElement", (typeExpr) => {
5051
+ __publicField$8(this, "needsGroupingForArrayElement", (typeExpr) => {
4748
5052
  return typeExpr.kind === "inline" && (typeExpr.expr.node === "union" || typeExpr.expr.node === "intersection");
4749
5053
  });
4750
- __publicField$7(this, "toLiteralCode", (value) => {
5054
+ __publicField$8(this, "toLiteralCode", (value) => {
4751
5055
  if (value === null) return "null";
4752
5056
  return JSON.stringify(value);
4753
5057
  });
4754
- __publicField$7(this, "toObjectCode", (properties, additionalProperties) => {
5058
+ __publicField$8(this, "toObjectCode", (properties, additionalProperties) => {
4755
5059
  const members = properties.map(
4756
5060
  (property) => this.toPropertyCode(property)
4757
5061
  );
4758
5062
  if (additionalProperties === true) {
4759
- members.push("[key: string]: unknown");
5063
+ members.push("[key: string]: unknown;");
4760
5064
  } else if (additionalProperties !== void 0 && additionalProperties !== false) {
4761
5065
  members.push(
4762
- `[key: string]: ${this.toCode(additionalProperties).code}`
5066
+ `[key: string]: ${this.toCode(additionalProperties).code};`
4763
5067
  );
4764
5068
  }
4765
- if (!members.length) return "{}";
4766
- return `{ ${members.join("; ")} }`;
5069
+ return `{
5070
+ ${members.join("\n")}
5071
+ }`;
4767
5072
  });
4768
- __publicField$7(this, "toPropertyCode", (property) => {
5073
+ __publicField$8(this, "toPropertyCode", (property) => {
4769
5074
  const name = toTsPropertyKey(property.name);
4770
5075
  const optional = property.required ? "" : "?";
4771
- return `${name}${optional}: ${this.toCode(property.typeExpr).code}`;
4772
- });
5076
+ const propertyCode = `${name}${optional}: ${this.toCode(property.typeExpr).code}`;
5077
+ const docCode = property.doc ? this.docRenderer.render(property.doc, {
5078
+ compactText: true
5079
+ }) : "";
5080
+ const renderedProperty = `${propertyCode};`;
5081
+ return docCode ? `${docCode}
5082
+ ${renderedProperty}` : renderedProperty;
5083
+ });
5084
+ this.docRenderer = docRenderer;
4773
5085
  }
4774
5086
  }
4775
5087
 
4776
- var __defProp$6 = Object.defineProperty;
4777
- var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4778
- var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
5088
+ var __defProp$7 = Object.defineProperty;
5089
+ var __defNormalProp$7 = (obj, key, value) => key in obj ? __defProp$7(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5090
+ var __publicField$7 = (obj, key, value) => __defNormalProp$7(obj, typeof key !== "symbol" ? key + "" : key, value);
4779
5091
  class TsCodegenFactory {
4780
- constructor() {
4781
- __publicField$6(this, "typeExprCodegen", new TypeExprCodegen());
4782
- __publicField$6(this, "forNewChunk", () => {
4783
- return new TsCodegen(this.typeExprCodegen);
4784
- });
5092
+ constructor(docRenderer) {
5093
+ __publicField$7(this, "typeExprCodegen");
5094
+ __publicField$7(this, "docRenderer");
5095
+ __publicField$7(this, "forNewChunk", () => {
5096
+ return new TsCodegen(this.typeExprCodegen, this.docRenderer);
5097
+ });
5098
+ this.docRenderer = docRenderer;
5099
+ this.typeExprCodegen = new TypeExprCodegen(this.docRenderer);
4785
5100
  }
4786
5101
  }
4787
5102
 
4788
- var __defProp$5 = Object.defineProperty;
4789
- var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4790
- var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
4791
- class ToTypeExprConverter {
4792
- constructor(schemas = {}, options = {}) {
4793
- this.schemas = schemas;
4794
- this.options = options;
4795
- __publicField$5(this, "schemaCache", /* @__PURE__ */ new WeakMap());
4796
- __publicField$5(this, "refCache", /* @__PURE__ */ new Map());
4797
- __publicField$5(this, "schemaDefinitions", []);
4798
- __publicField$5(this, "schemaDefinitionsById", /* @__PURE__ */ new Map());
4799
- __publicField$5(this, "schemaDefinitionsInProgress", /* @__PURE__ */ new Set());
4800
- __publicField$5(this, "getSchemaDefinitions", () => {
4801
- return this.schemaDefinitions.map((definition) => ({
4802
- ...definition
4803
- }));
4804
- });
4805
- __publicField$5(this, "fromSchema", (schema) => {
4806
- if (!schema) {
4807
- return this.scalar("unknown");
4808
- }
4809
- const bySchema = this.schemaCache.get(schema);
4810
- if (bySchema) {
4811
- return bySchema;
4812
- }
4813
- if (typeof schema.$ref === "string") {
4814
- const byRef = this.refCache.get(this.refCacheKey(schema));
4815
- if (byRef) {
4816
- this.schemaCache.set(schema, byRef);
4817
- return byRef;
4818
- }
4819
- }
4820
- const projected = this.fromSchemaCore(schema);
4821
- const result = schema.nullable === true ? this.withNullable(projected) : projected;
4822
- this.schemaCache.set(schema, result);
4823
- if (typeof schema.$ref === "string") {
4824
- this.refCache.set(this.refCacheKey(schema), result);
4825
- }
4826
- return result;
4827
- });
4828
- __publicField$5(this, "scalar", (name) => ({
4829
- kind: "inline",
4830
- expr: {
4831
- node: "scalar",
4832
- name
4833
- }
4834
- }));
4835
- __publicField$5(this, "union", (members) => {
4836
- const flatMembers = [];
4837
- for (const member of members) {
4838
- if (member.kind === "inline" && member.expr.node === "union") {
4839
- flatMembers.push(...member.expr.members);
4840
- } else {
4841
- flatMembers.push(member);
4842
- }
4843
- }
4844
- if (flatMembers.length === 1) {
4845
- return flatMembers[0];
4846
- }
4847
- return {
4848
- kind: "inline",
4849
- expr: {
4850
- node: "union",
4851
- members: flatMembers
4852
- }
4853
- };
4854
- });
4855
- __publicField$5(this, "literal", (value) => ({
4856
- kind: "inline",
4857
- expr: {
4858
- node: "literal",
4859
- value
4860
- }
4861
- }));
4862
- __publicField$5(this, "ref", (id) => {
4863
- const name = this.refNameFromPath(id);
5103
+ const buildPathParamBindings = (paramNames) => {
5104
+ const usedBindings = /* @__PURE__ */ new Set();
5105
+ return paramNames.reduce((acc, paramName) => {
5106
+ acc[paramName] = toUniqueBindingName(paramName, usedBindings);
5107
+ return acc;
5108
+ }, {});
5109
+ };
5110
+ const toUniqueBindingName = (paramName, usedBindings) => {
5111
+ let base = toBindingBase(paramName);
5112
+ if (isReservedWord(base)) {
5113
+ base = `${base}_param`;
5114
+ }
5115
+ if (!usedBindings.has(base)) {
5116
+ usedBindings.add(base);
5117
+ return base;
5118
+ }
5119
+ let suffix = 2;
5120
+ let candidate = `${base}_${suffix}`;
5121
+ while (usedBindings.has(candidate)) {
5122
+ suffix += 1;
5123
+ candidate = `${base}_${suffix}`;
5124
+ }
5125
+ usedBindings.add(candidate);
5126
+ return candidate;
5127
+ };
5128
+ const toBindingBase = (paramName) => {
5129
+ if (isIdentifier(paramName)) {
5130
+ return paramName;
5131
+ }
5132
+ const sanitized = paramName.replace(/[^0-9A-Za-z_$]/g, "_");
5133
+ if (!sanitized.length) {
5134
+ return "pathParam";
5135
+ }
5136
+ if (/^[A-Za-z_$]/.test(sanitized)) {
5137
+ return sanitized;
5138
+ }
5139
+ return `_${sanitized}`;
5140
+ };
5141
+ const isIdentifier = (value) => {
5142
+ return /^[$A-Z_][0-9A-Z_$]*$/i.test(value);
5143
+ };
5144
+ const isReservedWord = (value) => {
5145
+ return RESERVED_WORDS.has(value);
5146
+ };
5147
+
5148
+ var __defProp$6 = Object.defineProperty;
5149
+ var __defNormalProp$6 = (obj, key, value) => key in obj ? __defProp$6(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5150
+ var __publicField$6 = (obj, key, value) => __defNormalProp$6(obj, typeof key !== "symbol" ? key + "" : key, value);
5151
+ class ParamsConstructor {
5152
+ constructor(options) {
5153
+ this.options = options;
5154
+ __publicField$6(this, "process", (params, httpPath) => {
5155
+ const pathParamSpec = validatePathParams(params, httpPath);
5156
+ const pathParamBindings = pathParamSpec?.bindings;
5157
+ const funParams = params.flatMap(
5158
+ (param) => this.renderOperationParams(param, pathParamSpec)
5159
+ );
5160
+ const hasBody = params.some((p) => p.kind === "body");
5161
+ const hasQuery = params.some((p) => p.kind === "query");
5162
+ const path = this.renderPathTemplateLiteral(
5163
+ httpPath,
5164
+ this.options.paramNames.path,
5165
+ pathParamBindings
5166
+ );
4864
5167
  return {
4865
- kind: "reference",
4866
- ref: {
4867
- kind: "internal",
4868
- name
4869
- }
5168
+ funParams,
5169
+ path,
5170
+ hasBody,
5171
+ hasQuery
4870
5172
  };
4871
5173
  });
4872
- __publicField$5(this, "builtinRef", (name) => ({
4873
- kind: "reference",
4874
- ref: {
4875
- kind: "builtin",
4876
- name
5174
+ __publicField$6(this, "renderOperationParams", (param, pathParamSpec) => {
5175
+ if (param.kind !== "path" || !pathParamSpec) {
5176
+ return [
5177
+ {
5178
+ identifier: this.paramName(param),
5179
+ typeExpr: param.typeExpr
5180
+ }
5181
+ ];
4877
5182
  }
4878
- }));
4879
- __publicField$5(this, "refNameFromPath", (ref) => {
4880
- const parts = ref.split("/");
4881
- const name = parts[parts.length - 1];
4882
- if (!name) {
4883
- throw new Error(`Unsupported schema reference: ${ref}`);
5183
+ if (this.options.pathParamsStyle === "positional") {
5184
+ return pathParamSpec.placeholderNames.map((name) => ({
5185
+ identifier: pathParamSpec.bindings[name] ?? name,
5186
+ typeExpr: pathParamSpec.propertiesByName[name].typeExpr
5187
+ }));
4884
5188
  }
4885
- return name;
4886
- });
4887
- __publicField$5(this, "refCacheKey", (schema) => {
4888
- return `${schema.$ref}|nullable:${schema.nullable === true}`;
5189
+ return [
5190
+ {
5191
+ identifier: this.renderPathParamDestructuring(
5192
+ pathParamSpec.bindings
5193
+ ),
5194
+ typeExpr: param.typeExpr
5195
+ }
5196
+ ];
4889
5197
  });
4890
- __publicField$5(this, "isUnconstrainedSchema", (schema) => {
4891
- const meaningfulKeys = Object.keys(schema).filter(
4892
- (key) => key !== "nullable"
5198
+ __publicField$6(this, "renderPathTemplateLiteral", (path, pathParamVarName, pathParamBindings) => {
5199
+ const escapedPath = path.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
5200
+ const pathWithParams = escapedPath.replace(
5201
+ /\{([^}]+)\}/g,
5202
+ (_, paramName) => {
5203
+ const binding = pathParamBindings?.[paramName];
5204
+ if (binding !== void 0) {
5205
+ return `\${${binding}}`;
5206
+ }
5207
+ return `\${${pathParamVarName}[${JSON.stringify(paramName)}]}`;
5208
+ }
4893
5209
  );
4894
- return meaningfulKeys.length === 0;
4895
- });
4896
- __publicField$5(this, "ensureSchemaDefinition", (refId) => {
4897
- if (this.schemaDefinitionsById.has(refId) || this.schemaDefinitionsInProgress.has(refId)) {
4898
- return;
4899
- }
4900
- const schemaName = this.refNameFromPath(refId);
4901
- const schema = this.schemas[schemaName];
4902
- if (!schema) {
4903
- return;
4904
- }
4905
- this.schemaDefinitionsInProgress.add(refId);
4906
- try {
4907
- const definition = {
4908
- name: schemaName,
4909
- typeExpr: this.fromSchema(schema)
4910
- };
4911
- this.schemaDefinitionsById.set(refId, definition);
4912
- this.schemaDefinitions.push(definition);
4913
- } finally {
4914
- this.schemaDefinitionsInProgress.delete(refId);
4915
- }
5210
+ return `\`${pathWithParams}\``;
4916
5211
  });
4917
- __publicField$5(this, "isSchemaNode", (value) => {
4918
- return Boolean(
4919
- value && typeof value === "object" && !Array.isArray(value)
5212
+ __publicField$6(this, "renderPathParamDestructuring", (pathParamBindings) => {
5213
+ const members = Object.entries(pathParamBindings).map(
5214
+ ([paramName, binding]) => {
5215
+ const key = toTsPropertyKey(paramName);
5216
+ return binding === paramName ? key : `${key}: ${binding}`;
5217
+ }
4920
5218
  );
5219
+ return `{ ${members.join(", ")} }`;
4921
5220
  });
4922
- __publicField$5(this, "toLiteralValue", (value) => {
4923
- if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
4924
- return value;
4925
- }
4926
- throw new Error(`Unsupported literal value: ${String(value)}`);
4927
- });
4928
- __publicField$5(this, "isScalarName", (value) => {
4929
- switch (value) {
4930
- case "string":
4931
- case "number":
4932
- case "boolean":
4933
- case "integer":
4934
- case "null":
4935
- case "unknown":
4936
- case "any":
4937
- case "never":
4938
- case "void":
4939
- return true;
4940
- default:
4941
- return false;
4942
- }
4943
- });
4944
- __publicField$5(this, "hasNullMember", (expr) => {
4945
- if (expr.kind !== "inline") return false;
4946
- if (expr.expr.node === "scalar") {
4947
- return expr.expr.name === "null";
4948
- }
4949
- if (expr.expr.node === "union") {
4950
- return expr.expr.members.some((member) => this.hasNullMember(member));
4951
- }
4952
- return false;
4953
- });
4954
- __publicField$5(this, "withNullable", (expr) => {
4955
- if (this.hasNullMember(expr)) {
4956
- return expr;
5221
+ __publicField$6(this, "paramName", (p) => {
5222
+ switch (p.kind) {
5223
+ case "body":
5224
+ return this.options.paramNames.body;
5225
+ case "query":
5226
+ return this.options.paramNames.query;
5227
+ case "path":
5228
+ return this.options.paramNames.path;
5229
+ default: {
5230
+ const exhaustive = p;
5231
+ throw new Error(
5232
+ `Unsupported operation parameter kind: ${String(exhaustive)}`
5233
+ );
5234
+ }
4957
5235
  }
4958
- return this.union([expr, this.scalar("null")]);
4959
- });
4960
- __publicField$5(this, "projectObjectType", (schema) => {
4961
- const requiredRaw = schema.required;
4962
- if (requiredRaw !== void 0 && !Array.isArray(requiredRaw)) {
4963
- throw new Error(
4964
- "Unsupported object schema: required must be an array"
4965
- );
4966
- }
4967
- const required = new Set(
4968
- (requiredRaw ?? []).map((item) => {
4969
- if (typeof item !== "string") {
4970
- throw new Error(
4971
- "Unsupported object schema: required items must be strings"
4972
- );
4973
- }
4974
- return item;
4975
- })
4976
- );
4977
- const propertiesRaw = schema.properties;
4978
- if (propertiesRaw !== void 0 && (!propertiesRaw || typeof propertiesRaw !== "object" || Array.isArray(propertiesRaw))) {
4979
- throw new Error(
4980
- "Unsupported object schema: properties must be an object"
4981
- );
4982
- }
4983
- const properties = Object.entries(propertiesRaw ?? {}).map(
4984
- ([name, propertySchema]) => {
4985
- if (!this.isSchemaNode(propertySchema)) {
4986
- throw new Error(
4987
- `Unsupported object property schema for "${name}"`
4988
- );
4989
- }
4990
- return {
4991
- name,
4992
- required: this.isRequiredProperty(
4993
- name,
4994
- required,
4995
- propertySchema
4996
- ),
4997
- typeExpr: this.fromSchema(propertySchema)
4998
- };
4999
- }
5000
- );
5001
- let additionalProperties;
5002
- if (schema.additionalProperties !== void 0) {
5003
- if (typeof schema.additionalProperties === "boolean") {
5004
- additionalProperties = schema.additionalProperties;
5005
- } else if (this.isSchemaNode(schema.additionalProperties)) {
5006
- additionalProperties = this.fromSchema(
5007
- schema.additionalProperties
5008
- );
5009
- } else {
5010
- throw new Error(
5011
- "Unsupported object schema: additionalProperties must be boolean or schema object"
5012
- );
5013
- }
5014
- }
5015
- return {
5016
- kind: "inline",
5017
- expr: {
5018
- node: "object",
5019
- properties,
5020
- additionalProperties
5021
- }
5022
- };
5023
- });
5024
- __publicField$5(this, "isRequiredProperty", (name, required, propertySchema) => {
5025
- if (required.has(name)) {
5026
- return true;
5027
- }
5028
- if (!this.options.quirks?.swaggerTsApiRequiredBooleans) {
5029
- return false;
5030
- }
5031
- return propertySchema.required === true;
5032
- });
5033
- __publicField$5(this, "mapSchemaArrayMembers", (value, kind) => {
5034
- if (!Array.isArray(value) || !value.length) {
5035
- throw new Error(
5036
- `Unsupported schema: ${kind} must be a non-empty array`
5037
- );
5038
- }
5039
- return value.map((entry, index) => {
5040
- if (!this.isSchemaNode(entry)) {
5041
- throw new Error(
5042
- `Unsupported schema: ${kind}[${index}] must be a schema object`
5043
- );
5044
- }
5045
- return this.fromSchema(entry);
5046
- });
5047
- });
5048
- __publicField$5(this, "isBinaryFileSchema", (schema) => {
5049
- return schema.type === "string" && schema.format === "binary";
5050
- });
5051
- __publicField$5(this, "fromSchemaCore", (schema) => {
5052
- if (this.isUnconstrainedSchema(schema)) {
5053
- return this.scalar("unknown");
5054
- }
5055
- if (typeof schema.$ref === "string") {
5056
- this.ensureSchemaDefinition(schema.$ref);
5057
- return this.ref(schema.$ref);
5058
- }
5059
- if (schema.const !== void 0) {
5060
- return this.literal(this.toLiteralValue(schema.const));
5061
- }
5062
- if (Array.isArray(schema.enum)) {
5063
- if (!schema.enum.length) {
5064
- throw new Error(
5065
- "Unsupported enum schema: enum must be non-empty"
5066
- );
5067
- }
5068
- return this.union(
5069
- schema.enum.map(
5070
- (value) => this.literal(this.toLiteralValue(value))
5071
- )
5072
- );
5073
- }
5074
- if (schema.type === "array") {
5075
- if (schema.items === void 0) {
5076
- return {
5077
- kind: "inline",
5078
- expr: {
5079
- node: "array",
5080
- element: this.scalar("unknown")
5081
- }
5082
- };
5083
- }
5084
- if (Array.isArray(schema.items)) {
5085
- throw new Error("Unsupported array schema: tuple-style items");
5086
- }
5087
- if (!this.isSchemaNode(schema.items)) {
5088
- throw new Error(
5089
- "Unsupported array schema: items must be a schema"
5090
- );
5091
- }
5092
- return {
5093
- kind: "inline",
5094
- expr: {
5095
- node: "array",
5096
- element: this.fromSchema(schema.items)
5097
- }
5098
- };
5099
- }
5100
- if (schema.type === "object") {
5101
- return this.projectObjectType(schema);
5102
- }
5103
- if (schema.oneOf !== void 0) {
5104
- return this.union(this.mapSchemaArrayMembers(schema.oneOf, "oneOf"));
5105
- }
5106
- if (schema.anyOf !== void 0) {
5107
- return this.union(this.mapSchemaArrayMembers(schema.anyOf, "anyOf"));
5108
- }
5109
- if (schema.allOf !== void 0) {
5110
- return {
5111
- kind: "inline",
5112
- expr: {
5113
- node: "intersection",
5114
- members: this.mapSchemaArrayMembers(schema.allOf, "allOf")
5115
- }
5116
- };
5117
- }
5118
- if (this.isBinaryFileSchema(schema)) {
5119
- return this.builtinRef("File");
5120
- }
5121
- if (this.isScalarName(schema.type)) {
5122
- return this.scalar(schema.type);
5123
- }
5124
- throw new Error(
5125
- `Unsupported schema construct: ${JSON.stringify(schema, null, 2)}`
5126
- );
5127
5236
  });
5128
5237
  }
5129
5238
  }
5130
-
5131
- var __defProp$4 = Object.defineProperty;
5132
- var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5133
- var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
5134
- const pickPreferredMediaType = (mediaTypes) => {
5135
- if (!mediaTypes.length) return void 0;
5136
- const exactJson = mediaTypes.find((media) => media === "application/json");
5137
- if (exactJson) return exactJson;
5138
- const jsonLike = mediaTypes.find((media) => /\+json$/i.test(media));
5139
- if (jsonLike) return jsonLike;
5140
- const wildcardJson = mediaTypes.find(
5141
- (media) => /application\/\*\+json/i.test(media)
5142
- );
5143
- if (wildcardJson) return wildcardJson;
5144
- return mediaTypes[0];
5239
+ const extractPathPlaceholderNames = (pathTemplate) => {
5240
+ const seen = /* @__PURE__ */ new Set();
5241
+ const names = [];
5242
+ const matcher = /\{([^}]+)\}/g;
5243
+ let match = matcher.exec(pathTemplate);
5244
+ while (match) {
5245
+ const name = match[1];
5246
+ if (!seen.has(name)) {
5247
+ seen.add(name);
5248
+ names.push(name);
5249
+ }
5250
+ match = matcher.exec(pathTemplate);
5251
+ }
5252
+ return names;
5145
5253
  };
5146
- const isSuccessStatusCode = (statusCode) => {
5147
- const upper = statusCode.toUpperCase();
5148
- if (/^2XX$/.test(upper)) {
5149
- return true;
5254
+ const isPathParam = (param) => {
5255
+ return param.kind === "path";
5256
+ };
5257
+ const isInlineObjectType = (typeExpr) => typeExpr.kind === "inline" && typeExpr.expr.node === "object";
5258
+ const renderNameList = (items) => {
5259
+ const values = [...items];
5260
+ if (!values.length) {
5261
+ return "(none)";
5150
5262
  }
5151
- const numericCode = Number(statusCode);
5152
- return Number.isFinite(numericCode) && numericCode >= 200 && numericCode < 300;
5263
+ return values.map((v) => JSON.stringify(v)).join(", ");
5153
5264
  };
5154
- const selectReturnResponses = (responses) => {
5155
- const entries = Object.entries(responses);
5156
- if (!entries.length) {
5157
- return [];
5265
+ const validatePathParams = (params, httpPath) => {
5266
+ const placeholders = extractPathPlaceholderNames(httpPath);
5267
+ const placeholderSet = new Set(placeholders);
5268
+ const pathParams = params.filter(isPathParam);
5269
+ if (pathParams.length > 1) {
5270
+ throw new Error(
5271
+ `Path "${httpPath}" has ${pathParams.length} path parameter objects; expected at most one.`
5272
+ );
5158
5273
  }
5159
- const success = entries.filter(([statusCode]) => isSuccessStatusCode(statusCode)).map(([, response]) => response);
5160
- if (success.length) {
5161
- return success;
5274
+ const pathParam = pathParams[0];
5275
+ if (!pathParam) {
5276
+ if (placeholderSet.size > 0) {
5277
+ throw new Error(
5278
+ `Path "${httpPath}" is missing a path parameter object for placeholders: ${renderNameList(
5279
+ placeholderSet
5280
+ )}.`
5281
+ );
5282
+ }
5283
+ return void 0;
5162
5284
  }
5163
- const defaultResponse = entries.find(
5164
- ([statusCode]) => statusCode.toLowerCase() === "default"
5285
+ const pathParamExpr = pathParam.typeExpr.expr;
5286
+ if (pathParamExpr.additionalProperties !== void 0 && pathParamExpr.additionalProperties !== false) {
5287
+ throw new Error(
5288
+ `Path "${httpPath}" path parameter object must not declare additionalProperties.`
5289
+ );
5290
+ }
5291
+ const nestedObjectProps = pathParamExpr.properties.filter((prop) => isInlineObjectType(prop.typeExpr)).map((prop) => prop.name);
5292
+ if (nestedObjectProps.length > 0) {
5293
+ throw new Error(
5294
+ `Path "${httpPath}" path parameters must be depth-1. Nested object properties: ${renderNameList(
5295
+ nestedObjectProps
5296
+ )}.`
5297
+ );
5298
+ }
5299
+ const optionalPathProps = pathParamExpr.properties.filter((prop) => !prop.required).map((prop) => prop.name);
5300
+ if (optionalPathProps.length > 0) {
5301
+ throw new Error(
5302
+ `Path "${httpPath}" path parameters must all be required. Optional properties: ${renderNameList(
5303
+ optionalPathProps
5304
+ )}.`
5305
+ );
5306
+ }
5307
+ const declaredNames = pathParamExpr.properties.map((prop) => prop.name);
5308
+ const declaredNameSet = new Set(declaredNames);
5309
+ const missingInObject = [...placeholderSet].filter(
5310
+ (name) => !declaredNameSet.has(name)
5165
5311
  );
5166
- if (defaultResponse) {
5167
- return [defaultResponse[1]];
5312
+ const missingInPath = declaredNames.filter(
5313
+ (name) => !placeholderSet.has(name)
5314
+ );
5315
+ if (missingInObject.length > 0 || missingInPath.length > 0) {
5316
+ throw new Error(
5317
+ `Path "${httpPath}" placeholders and path parameter object keys must match exactly. Missing in object: ${renderNameList(
5318
+ missingInObject
5319
+ )}. Missing in path: ${renderNameList(missingInPath)}.`
5320
+ );
5168
5321
  }
5169
- return entries.map(([, response]) => response);
5170
- };
5171
- const getPreferredMediaTypeEntry = (content) => {
5172
- const mediaType = pickPreferredMediaType(Object.keys(content));
5173
- if (!mediaType) return {};
5322
+ const bindings = buildPathParamBindings(placeholders);
5323
+ const propertiesByName = Object.fromEntries(
5324
+ pathParamExpr.properties.map((prop) => [prop.name, prop])
5325
+ );
5174
5326
  return {
5175
- mediaType,
5176
- media: content[mediaType]
5327
+ placeholderNames: placeholders,
5328
+ bindings,
5329
+ propertiesByName
5177
5330
  };
5178
5331
  };
5179
- const isBinaryFileResponse = (response) => {
5180
- const { mediaType, media } = getPreferredMediaTypeEntry(response.content);
5181
- return media?.schema?.type === "string" && media.schema?.format === "binary" && mediaType !== "multipart/form-data";
5182
- };
5183
- const isJsonLikeMediaType = (mediaType) => {
5184
- return mediaType === "application/json" || typeof mediaType === "string" && /\+json$/i.test(mediaType);
5185
- };
5186
- const toHttpMethodUpper = (method) => {
5187
- switch (method) {
5188
- case "get":
5189
- return "GET";
5190
- case "post":
5191
- return "POST";
5192
- case "put":
5193
- return "PUT";
5194
- case "delete":
5195
- return "DELETE";
5196
- case "patch":
5197
- return "PATCH";
5198
- case "options":
5199
- return "OPTIONS";
5200
- case "head":
5201
- return "HEAD";
5202
- case "trace":
5203
- return "TRACE";
5204
- default: {
5205
- const exhaustive = method;
5206
- throw new Error(`Unsupported HTTP method: ${String(exhaustive)}`);
5207
- }
5332
+
5333
+ const indentBlock = (value, indent) => {
5334
+ if (!value || !indent) {
5335
+ return value;
5208
5336
  }
5337
+ return value.split("\n").map((line) => `${indent}${line}`).join("\n");
5209
5338
  };
5210
- class OperationProjector {
5211
- constructor(options = {}) {
5212
- this.options = options;
5213
- __publicField$4(this, "seenOperationIds", /* @__PURE__ */ new Set());
5214
- __publicField$4(this, "typeExprConverter");
5215
- __publicField$4(this, "project", (openApi) => {
5216
- this.seenOperationIds.clear();
5217
- this.typeExprConverter = new ToTypeExprConverter(openApi.schemas, {
5218
- quirks: this.options.quirks
5339
+
5340
+ const EMPTY_LINE_MARKER = "/*__EMPTY_LINE_MARKER__*/";
5341
+
5342
+ var __defProp$5 = Object.defineProperty;
5343
+ var __defNormalProp$5 = (obj, key, value) => key in obj ? __defProp$5(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5344
+ var __publicField$5 = (obj, key, value) => __defNormalProp$5(obj, typeof key !== "symbol" ? key + "" : key, value);
5345
+ const REQUEST_PARAMS_TYPE = "RequestParams";
5346
+ const REQUEST_PARAMS_DEFAULT = "never";
5347
+ const REQUEST_PARAMS_ARG = "params";
5348
+ class ApiClientCodegen {
5349
+ constructor(opts, tsCodegenFactory, docRenderer) {
5350
+ this.opts = opts;
5351
+ this.tsCodegenFactory = tsCodegenFactory;
5352
+ this.docRenderer = docRenderer;
5353
+ __publicField$5(this, "generate", (apiName, operations, doc) => {
5354
+ const tsCodegen = this.tsCodegenFactory.forNewChunk();
5355
+ const httpClientType = tsCodegen.typeExpr(this.opts.httpClient.typeName);
5356
+ let clientType = httpClientType;
5357
+ let classTypeParams = "";
5358
+ if (this.opts.withRequestParams) {
5359
+ clientType = `${httpClientType}<${REQUEST_PARAMS_TYPE}>`;
5360
+ classTypeParams = `<${REQUEST_PARAMS_TYPE} = ${REQUEST_PARAMS_DEFAULT}>`;
5361
+ }
5362
+ const methodsCode = Array.isArray(operations) ? this.opsCode(tsCodegen, operations) : this.groupsOpsCode(tsCodegen, operations);
5363
+ const classDocCode = this.renderApiClientDoc(doc, "");
5364
+ const code = `
5365
+ ${classDocCode ? `${classDocCode}
5366
+ ` : ""}export class ${apiName}${classTypeParams} {
5367
+ constructor(private readonly http: ${clientType}) {}
5368
+
5369
+ ${methodsCode}
5370
+ }
5371
+ `;
5372
+ return tsCodegen.toChunk("api", code, [apiName]);
5373
+ });
5374
+ __publicField$5(this, "groupsOpsCode", (codegen, groupedOps) => Object.entries(groupedOps).map(([groupName, ops]) => {
5375
+ const methodsCode = this.opsCode(codegen, ops, "object");
5376
+ return `
5377
+ ${toTsPropertyKey(groupName)} = {
5378
+ ${methodsCode}
5379
+ }
5380
+ `;
5381
+ }).join("\n\n"));
5382
+ __publicField$5(this, "opsCode", (codegen, operations, target = "class") => {
5383
+ const separator = target === "object" ? `,
5384
+ ${EMPTY_LINE_MARKER}
5385
+ ` : "\n\n";
5386
+ const methodsCode = operations.map((op) => this.renderOp(op, codegen, target)).join(separator);
5387
+ return methodsCode;
5388
+ });
5389
+ __publicField$5(this, "renderOp", (op, tsCodegen, target) => {
5390
+ const { signature: sig, http } = op;
5391
+ const invocationVars = this.opts.httpClient.invocation.vars;
5392
+ const paramNames = {
5393
+ path: "path",
5394
+ query: invocationVars.query,
5395
+ body: invocationVars.body
5396
+ };
5397
+ const paramsHelper = new ParamsConstructor({
5398
+ paramNames,
5399
+ pathParamsStyle: this.opts.pathParamsStyle
5219
5400
  });
5220
- const operations = openApi.operations.map((operation) => {
5221
- const operationName = (operation.operationId ?? syntheticOperationId(operation.method, operation.path)).trim();
5222
- if (!operationName) {
5223
- throw new Error(
5224
- `Operation id is empty for ${operation.method.toUpperCase()} ${operation.path}`
5225
- );
5226
- }
5227
- if (this.seenOperationIds.has(operationName)) {
5228
- throw new Error(`Duplicate operation id: ${operationName}`);
5229
- }
5230
- return this.projectOne(operationName, operation);
5401
+ const { path, funParams, hasBody, hasQuery } = paramsHelper.process(
5402
+ sig.parameters,
5403
+ http.path
5404
+ );
5405
+ const params = funParams.map(
5406
+ (r) => `${r.identifier}: ${tsCodegen.typeExpr(r.typeExpr)}`
5407
+ );
5408
+ if (this.opts.withRequestParams) {
5409
+ params.push(`${REQUEST_PARAMS_ARG}?: ${REQUEST_PARAMS_TYPE}`);
5410
+ }
5411
+ const paramsCode = params.join(", ");
5412
+ const responseType = tsCodegen.typeExpr(sig.returnType);
5413
+ const returnType = this.wrapInResponseWrapper(sig.returnType, tsCodegen);
5414
+ return this.renderFunctionCode({
5415
+ doc: sig.doc,
5416
+ funName: sig.name,
5417
+ responseType,
5418
+ returnType,
5419
+ params: paramsCode,
5420
+ path,
5421
+ method: http.method,
5422
+ hasQuery,
5423
+ hasBody,
5424
+ requestContentType: http.requestContentType,
5425
+ responseFormat: http.responseFormat,
5426
+ requestParamsVar: this.opts.withRequestParams ? REQUEST_PARAMS_ARG : void 0,
5427
+ unwrap: this.opts.unwrap,
5428
+ target
5231
5429
  });
5232
- return {
5233
- operations,
5234
- schemaDefinitions: this.typeExprConverter.getSchemaDefinitions()
5235
- };
5236
5430
  });
5237
- __publicField$4(this, "projectOne", (operationName, operation) => {
5238
- this.seenOperationIds.add(operationName);
5239
- const parameters = [];
5240
- const pathParameter = this.projectPathParameter(operation);
5241
- const queryParameter = this.projectQueryParameter(operation);
5242
- const bodyParameter = this.projectBodyParameter(operation);
5243
- if (pathParameter) parameters.push(pathParameter);
5244
- if (queryParameter) parameters.push(queryParameter);
5245
- if (bodyParameter) parameters.push(bodyParameter);
5246
- return {
5247
- op: {
5248
- signature: {
5249
- name: operationName,
5250
- parameters,
5251
- returnType: this.projectReturnType(operation)
5252
- },
5253
- http: {
5254
- method: toHttpMethodUpper(operation.method),
5255
- path: operation.path,
5256
- requestContentType: this.projectRequestContentType(
5257
- operation.requestBody
5258
- ),
5259
- responseFormat: this.projectResponseFormat(operation)
5260
- }
5261
- },
5262
- tags: [...operation.tags]
5263
- };
5431
+ __publicField$5(this, "wrapInResponseWrapper", (typeExpr, tsCodegen) => {
5432
+ return tsCodegen.typeExpr({
5433
+ ...this.opts.httpClient.responseWrapper,
5434
+ typeArgs: [typeExpr]
5435
+ });
5264
5436
  });
5265
- __publicField$4(this, "projectRequestContentType", (requestBody) => {
5266
- const { mediaType } = getPreferredMediaTypeEntry(
5267
- requestBody?.content ?? {}
5437
+ __publicField$5(this, "renderFunctionCode", ({
5438
+ doc,
5439
+ funName,
5440
+ responseType,
5441
+ returnType,
5442
+ params,
5443
+ path,
5444
+ method,
5445
+ hasQuery,
5446
+ hasBody,
5447
+ requestContentType,
5448
+ responseFormat,
5449
+ requestParamsVar,
5450
+ unwrap,
5451
+ target
5452
+ }) => {
5453
+ let invocation = this.opts.httpClient.invocation.expr({
5454
+ funName,
5455
+ returnType,
5456
+ responseType,
5457
+ params,
5458
+ path,
5459
+ method,
5460
+ hasQuery,
5461
+ hasBody,
5462
+ requestContentType,
5463
+ responseFormat,
5464
+ requestParamsVar,
5465
+ unwrap
5466
+ });
5467
+ invocation = invocation.replaceAll("\n", "");
5468
+ const operator = target === "object" ? ":" : "=";
5469
+ const returnTypeCode = this.opts.httpClient.inferMethodReturnType ? "" : `:${returnType}`;
5470
+ const functionCode = `${funName} ${operator} (${params})${returnTypeCode} =>
5471
+ this.http${invocation}
5472
+ `;
5473
+ const docCode = this.renderOperationDoc(
5474
+ doc,
5475
+ this.opts.comments,
5476
+ target === "object" ? " " : " "
5477
+ );
5478
+ return docCode ? `${docCode}
5479
+ ${functionCode}` : functionCode;
5480
+ });
5481
+ __publicField$5(this, "renderOperationDoc", (doc, comments, indent = "") => {
5482
+ if (!doc) return null;
5483
+ return indentBlock(this.docRenderer.render(this.toTsDoc(doc)), indent);
5484
+ });
5485
+ __publicField$5(this, "renderApiClientDoc", (doc, indent = "") => {
5486
+ if (!doc) return "";
5487
+ return indentBlock(
5488
+ this.docRenderer.render({
5489
+ nodes: [
5490
+ {
5491
+ key: "title",
5492
+ value: doc.title
5493
+ },
5494
+ {
5495
+ key: "version",
5496
+ value: doc.version
5497
+ },
5498
+ ...doc.description ? [doc.description] : []
5499
+ ]
5500
+ }),
5501
+ indent
5268
5502
  );
5269
- switch (mediaType) {
5270
- case "application/json":
5271
- case "multipart/form-data":
5272
- case "application/x-www-form-urlencoded":
5273
- return mediaType;
5274
- default:
5275
- return void 0;
5276
- }
5277
5503
  });
5278
- __publicField$4(this, "projectResponseFormat", (operation) => {
5279
- const selectedResponses = selectReturnResponses(operation.responses);
5280
- if (!selectedResponses.length) {
5281
- return void 0;
5282
- }
5283
- if (selectedResponses.some((response) => isBinaryFileResponse(response))) {
5284
- return "document";
5504
+ __publicField$5(this, "toTsDoc", (doc) => {
5505
+ const nodes = [
5506
+ {
5507
+ key: "id",
5508
+ value: doc.id
5509
+ }
5510
+ ];
5511
+ const comms = this.opts.comments;
5512
+ if ((comms.summary ?? true) && doc.summary) {
5513
+ nodes.push({
5514
+ key: "summary",
5515
+ value: doc.summary
5516
+ });
5285
5517
  }
5286
- if (selectedResponses.some(
5287
- (response) => isJsonLikeMediaType(
5288
- getPreferredMediaTypeEntry(response.content).mediaType
5289
- )
5290
- )) {
5291
- return "json";
5518
+ if ((comms.description ?? true) && doc.description) {
5519
+ nodes.push({
5520
+ key: "description",
5521
+ value: doc.description
5522
+ });
5292
5523
  }
5293
- return void 0;
5294
- });
5295
- __publicField$4(this, "projectParameterGroupTypeExpr", (params) => {
5296
- const properties = Object.entries(params).map(([name, parameter]) => ({
5297
- name,
5298
- required: parameter.required,
5299
- typeExpr: this.typeExprConverter.fromSchema(
5300
- this.selectParameterSchema(parameter)
5301
- )
5302
- }));
5303
- return {
5304
- kind: "inline",
5305
- expr: {
5306
- node: "object",
5307
- properties
5308
- }
5309
- };
5310
- });
5311
- __publicField$4(this, "projectPathParameter", (operation) => {
5312
- const params = operation.parameters.path;
5313
- const entries = Object.values(params);
5314
- if (!entries.length) return null;
5315
- if (entries.some((parameter) => !parameter.required)) {
5316
- throw new Error("Unsupported path parameters: all must be required");
5524
+ nodes.push({
5525
+ key: "request",
5526
+ value: doc.request
5527
+ });
5528
+ if ((comms.tags ?? true) && doc.tags.length) {
5529
+ nodes.push({
5530
+ key: "tags",
5531
+ value: doc.tags.join(", ")
5532
+ });
5317
5533
  }
5318
5534
  return {
5319
- kind: "path",
5320
- optional: false,
5321
- typeExpr: this.projectParameterGroupTypeExpr(params)
5322
- };
5323
- });
5324
- __publicField$4(this, "projectQueryParameter", (operation) => {
5325
- const params = operation.parameters.query;
5326
- const entries = Object.values(params);
5327
- if (!entries.length) return null;
5328
- return {
5329
- kind: "query",
5330
- optional: !entries.some((parameter) => parameter.required),
5331
- typeExpr: this.projectParameterGroupTypeExpr(params)
5535
+ deprecated: doc.deprecated,
5536
+ nodes
5332
5537
  };
5333
5538
  });
5334
- __publicField$4(this, "projectBodyParameter", (operation) => {
5335
- const requestBody = operation.requestBody;
5336
- if (!requestBody) return null;
5337
- return {
5338
- kind: "body",
5339
- optional: !requestBody.required,
5340
- typeExpr: this.typeExprConverter.fromSchema(
5341
- this.selectRequestBodySchema(requestBody)
5342
- )
5343
- };
5344
- });
5345
- __publicField$4(this, "projectReturnType", (operation) => {
5346
- const selectedResponses = selectReturnResponses(operation.responses);
5347
- if (!selectedResponses.length) {
5348
- return this.typeExprConverter.scalar("void");
5349
- }
5350
- const responseTypes = selectedResponses.map((response) => {
5351
- const schema = this.selectResponseSchema(response);
5352
- if (!schema) {
5353
- return this.typeExprConverter.scalar("void");
5354
- }
5355
- return this.typeExprConverter.fromSchema(schema);
5356
- });
5357
- return this.typeExprConverter.union(responseTypes);
5358
- });
5359
- __publicField$4(this, "selectParameterSchema", (parameter) => {
5360
- if (parameter.schema) return parameter.schema;
5361
- return getPreferredMediaTypeEntry(parameter.content).media?.schema;
5362
- });
5363
- __publicField$4(this, "selectRequestBodySchema", (requestBody) => {
5364
- return getPreferredMediaTypeEntry(requestBody.content).media?.schema;
5365
- });
5366
- __publicField$4(this, "selectResponseSchema", (response) => {
5367
- return getPreferredMediaTypeEntry(response.content).media?.schema;
5368
- });
5369
- this.typeExprConverter = new ToTypeExprConverter(void 0, {
5370
- quirks: this.options.quirks
5371
- });
5372
5539
  }
5373
5540
  }
5374
5541
 
5375
- const buildPathParamBindings = (paramNames) => {
5376
- const usedBindings = /* @__PURE__ */ new Set();
5377
- return paramNames.reduce((acc, paramName) => {
5378
- acc[paramName] = toUniqueBindingName(paramName, usedBindings);
5379
- return acc;
5380
- }, {});
5381
- };
5382
- const toUniqueBindingName = (paramName, usedBindings) => {
5383
- let base = toBindingBase(paramName);
5384
- if (isReservedWord(base)) {
5385
- base = `${base}_param`;
5542
+ const promiseHttpClientCodegenSpec = () => ({
5543
+ typeName: {
5544
+ kind: "reference",
5545
+ ref: {
5546
+ kind: "internal",
5547
+ name: "HttpClient"
5548
+ }
5549
+ },
5550
+ responseWrapper: {
5551
+ kind: "reference",
5552
+ ref: {
5553
+ kind: "builtin",
5554
+ name: "Promise"
5555
+ }
5556
+ },
5557
+ inferMethodReturnType: true,
5558
+ invocation: {
5559
+ vars: {
5560
+ body: "body",
5561
+ query: "query"
5562
+ },
5563
+ expr: ({
5564
+ responseType,
5565
+ path,
5566
+ method,
5567
+ hasBody,
5568
+ hasQuery,
5569
+ requestContentType,
5570
+ responseFormat,
5571
+ requestParamsVar,
5572
+ unwrap
5573
+ }) => `.request<${responseType}>({
5574
+ method: '${method}',
5575
+ path: ${path}
5576
+ ${hasQuery ? `,
5577
+ query` : ""}
5578
+ ${hasBody ? `,
5579
+ body` : ""}
5580
+ ${requestContentType ? `,
5581
+ requestContentType: '${requestContentType}'` : ""}
5582
+ ${responseFormat ? `,
5583
+ format: '${responseFormat}'` : ""}
5584
+ }${requestParamsVar ? `,
5585
+ ${requestParamsVar}` : ""})${unwrap ? `.then(res => res.body)` : ""}
5586
+ `
5386
5587
  }
5387
- if (!usedBindings.has(base)) {
5388
- usedBindings.add(base);
5389
- return base;
5588
+ });
5589
+
5590
+ const mergeChunks = (name, chunks, options = {}) => {
5591
+ if (chunks.length === 0) {
5592
+ if (options.allowEmpty) {
5593
+ return {
5594
+ name,
5595
+ code: "export {}",
5596
+ exports: [],
5597
+ refs: []
5598
+ };
5599
+ }
5600
+ throw new Error("Cannot merge empty chunk list.");
5390
5601
  }
5391
- let suffix = 2;
5392
- let candidate = `${base}_${suffix}`;
5393
- while (usedBindings.has(candidate)) {
5394
- suffix += 1;
5395
- candidate = `${base}_${suffix}`;
5602
+ const orderedChunks = options.preserveChunkOrder ? chunks : orderByInternalDependencies(chunks);
5603
+ const mergedCode = orderedChunks.map((chunk) => chunk.code).join("\n\n");
5604
+ const exportedSymbols = /* @__PURE__ */ new Set();
5605
+ const mergedExports = [];
5606
+ for (const chunk of orderedChunks) {
5607
+ for (const symbol of chunk.exports) {
5608
+ if (exportedSymbols.has(symbol)) continue;
5609
+ exportedSymbols.add(symbol);
5610
+ mergedExports.push(symbol);
5611
+ }
5396
5612
  }
5397
- usedBindings.add(candidate);
5398
- return candidate;
5613
+ const seenRefKeys = /* @__PURE__ */ new Set();
5614
+ const mergedRefs = orderedChunks.flatMap((chunk) => chunk.refs).filter((ref) => {
5615
+ if (ref.kind === "internal" && exportedSymbols.has(ref.name)) {
5616
+ return false;
5617
+ }
5618
+ const key = ref.kind === "internal" ? `internal:${ref.name}` : ref.kind === "external" ? `external:${ref.package}:${ref.name}` : `builtin:${ref.name}`;
5619
+ if (seenRefKeys.has(key)) return false;
5620
+ seenRefKeys.add(key);
5621
+ return true;
5622
+ });
5623
+ return {
5624
+ name,
5625
+ code: mergedCode,
5626
+ exports: mergedExports,
5627
+ refs: mergedRefs
5628
+ };
5399
5629
  };
5400
- const toBindingBase = (paramName) => {
5401
- if (isIdentifier(paramName)) {
5402
- return paramName;
5630
+ const orderByInternalDependencies = (chunks) => {
5631
+ const chunkIndex = /* @__PURE__ */ new Map();
5632
+ const exportOwnerBySymbol = /* @__PURE__ */ new Map();
5633
+ chunks.forEach((chunk, index) => {
5634
+ chunkIndex.set(chunk.name, index);
5635
+ for (const symbol of chunk.exports) {
5636
+ if (exportOwnerBySymbol.has(symbol)) {
5637
+ throw new Error(
5638
+ `Assertion error. Symbol "${symbol}" exported multiple times while ordering chunks.`
5639
+ );
5640
+ }
5641
+ exportOwnerBySymbol.set(symbol, chunk);
5642
+ }
5643
+ });
5644
+ const dependentsByProvider = /* @__PURE__ */ new Map();
5645
+ const inDegreeByChunk = /* @__PURE__ */ new Map();
5646
+ for (const chunk of chunks) {
5647
+ dependentsByProvider.set(chunk, /* @__PURE__ */ new Set());
5648
+ inDegreeByChunk.set(chunk, 0);
5403
5649
  }
5404
- const sanitized = paramName.replace(/[^0-9A-Za-z_$]/g, "_");
5405
- if (!sanitized.length) {
5406
- return "pathParam";
5650
+ for (const chunk of chunks) {
5651
+ const providers = /* @__PURE__ */ new Set();
5652
+ for (const ref of chunk.refs) {
5653
+ if (ref.kind !== "internal") continue;
5654
+ const provider = exportOwnerBySymbol.get(ref.name);
5655
+ if (!provider || provider === chunk) continue;
5656
+ providers.add(provider);
5657
+ }
5658
+ for (const provider of providers) {
5659
+ dependentsByProvider.get(provider).add(chunk);
5660
+ inDegreeByChunk.set(chunk, inDegreeByChunk.get(chunk) + 1);
5661
+ }
5407
5662
  }
5408
- if (/^[A-Za-z_$]/.test(sanitized)) {
5409
- return sanitized;
5663
+ const ready = chunks.filter((chunk) => inDegreeByChunk.get(chunk) === 0).sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5664
+ const ordered = [];
5665
+ while (ready.length > 0) {
5666
+ const provider = ready.shift();
5667
+ ordered.push(provider);
5668
+ for (const dependent of dependentsByProvider.get(provider)) {
5669
+ const nextInDegree = inDegreeByChunk.get(dependent) - 1;
5670
+ inDegreeByChunk.set(dependent, nextInDegree);
5671
+ if (nextInDegree === 0) {
5672
+ ready.push(dependent);
5673
+ }
5674
+ }
5675
+ ready.sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5410
5676
  }
5411
- return `_${sanitized}`;
5412
- };
5413
- const isIdentifier = (value) => {
5414
- return /^[$A-Z_][0-9A-Z_$]*$/i.test(value);
5415
- };
5416
- const isReservedWord = (value) => {
5417
- return RESERVED_WORDS.has(value);
5677
+ if (ordered.length !== chunks.length) {
5678
+ const cycleChunkNames = chunks.filter((chunk) => inDegreeByChunk.get(chunk) > 0).map((chunk) => chunk.name).join(", ");
5679
+ throw new Error(
5680
+ `Cannot order chunks with cyclic internal dependencies: ${cycleChunkNames}`
5681
+ );
5682
+ }
5683
+ return ordered;
5418
5684
  };
5419
5685
 
5420
- var __defProp$3 = Object.defineProperty;
5421
- var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5422
- var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
5423
- class ParamsConstructor {
5686
+ 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";
5687
+
5688
+ 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";
5689
+
5690
+ const commonTypesCode = httpClientCommonTypesSource;
5691
+ const promiseHttpClientCode = httpClientPromiseTypesSource;
5692
+ const stripImports = (code) => code.replace(/^import[\s\S]*?from\s+['"][^'"]+['"]\s*;?\n?/gm, "").trim();
5693
+ const stripRequestParams = (code) => code.replace(/<RequestParams\s*=\s*never>/g, "").replace(/,\s*params\?:\s*RequestParams/g, "");
5694
+ const getHttpClientCode = (opts) => [
5695
+ commonTypesCode,
5696
+ opts.withRequestParams ? promiseHttpClientCode : stripRequestParams(promiseHttpClientCode)
5697
+ ].map(stripImports).join("\n\n");
5698
+ const provideHttpClientCode = (gen, opts) => gen.toChunk(
5699
+ "HttpClient",
5700
+ getHttpClientCode({
5701
+ withRequestParams: opts.withRequestParams
5702
+ }),
5703
+ ["HttpRequest", "HttpResponse", "HttpClient"]
5704
+ );
5705
+
5706
+ var __defProp$4 = Object.defineProperty;
5707
+ var __defNormalProp$4 = (obj, key, value) => key in obj ? __defProp$4(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5708
+ var __publicField$4 = (obj, key, value) => __defNormalProp$4(obj, typeof key !== "symbol" ? key + "" : key, value);
5709
+ class TsDocRenderer {
5424
5710
  constructor(options) {
5425
5711
  this.options = options;
5426
- __publicField$3(this, "process", (params, httpPath) => {
5427
- const pathParamSpec = validatePathParams(params, httpPath);
5428
- const pathParamBindings = pathParamSpec?.bindings;
5429
- const funParams = params.flatMap(
5430
- (param) => this.renderOperationParams(param, pathParamSpec)
5431
- );
5432
- const hasBody = params.some((p) => p.kind === "body");
5433
- const hasQuery = params.some((p) => p.kind === "query");
5434
- const path = this.renderPathTemplateLiteral(
5435
- httpPath,
5436
- this.options.paramNames.path,
5437
- pathParamBindings
5438
- );
5439
- return {
5440
- funParams,
5441
- path,
5442
- hasBody,
5443
- hasQuery
5444
- };
5712
+ __publicField$4(this, "render", (doc, options = {}) => {
5713
+ if (!this.options.enabled) return "";
5714
+ const lines = [];
5715
+ this.appendNodes(lines, doc.nodes, options);
5716
+ if (doc.deprecated) {
5717
+ this.appendDeprecated(lines, { separate: lines.length > 0 });
5718
+ }
5719
+ return this.renderComment(lines);
5445
5720
  });
5446
- __publicField$3(this, "renderOperationParams", (param, pathParamSpec) => {
5447
- if (param.kind !== "path" || !pathParamSpec) {
5448
- return [
5449
- {
5450
- identifier: this.paramName(param),
5451
- typeExpr: param.typeExpr
5452
- }
5453
- ];
5721
+ __publicField$4(this, "renderComment", (lines) => {
5722
+ if (!this.options.enabled) return "";
5723
+ if (!lines.length) return "";
5724
+ if (lines.length === 1) {
5725
+ return `/** ${lines[0]} */`;
5454
5726
  }
5455
- if (this.options.pathParamsStyle === "positional") {
5456
- return pathParamSpec.placeholderNames.map((name) => ({
5457
- identifier: pathParamSpec.bindings[name] ?? name,
5458
- typeExpr: pathParamSpec.propertiesByName[name].typeExpr
5459
- }));
5727
+ const body = lines.map((line) => line ? ` * ${line}` : " *").join("\n");
5728
+ return `/**
5729
+ ${body}
5730
+ */`;
5731
+ });
5732
+ __publicField$4(this, "appendDocText", (lines, value) => {
5733
+ const normalized = this.normalizeLines(value);
5734
+ if (!normalized.length) return;
5735
+ if (lines.length > 0) {
5736
+ lines.push("");
5460
5737
  }
5461
- return [
5462
- {
5463
- identifier: this.renderPathParamDestructuring(
5464
- pathParamSpec.bindings
5465
- ),
5466
- typeExpr: param.typeExpr
5467
- }
5468
- ];
5738
+ lines.push(...normalized);
5469
5739
  });
5470
- __publicField$3(this, "renderPathTemplateLiteral", (path, pathParamVarName, pathParamBindings) => {
5471
- const escapedPath = path.replace(/\\/g, "\\\\").replace(/`/g, "\\`").replace(/\$\{/g, "\\${");
5472
- const pathWithParams = escapedPath.replace(
5473
- /\{([^}]+)\}/g,
5474
- (_, paramName) => {
5475
- const binding = pathParamBindings?.[paramName];
5476
- if (binding !== void 0) {
5477
- return `\${${binding}}`;
5740
+ __publicField$4(this, "appendField", (lines, key, value) => {
5741
+ const normalized = this.normalizeLines(value);
5742
+ if (!normalized.length) return;
5743
+ lines.push(`@${key} ${normalized[0]}`.trimEnd());
5744
+ lines.push(...normalized.slice(1));
5745
+ });
5746
+ __publicField$4(this, "appendDeprecated", (lines, options) => {
5747
+ if (options.separate) {
5748
+ lines.push("");
5749
+ }
5750
+ lines.push("@deprecated");
5751
+ });
5752
+ __publicField$4(this, "appendNodes", (lines, nodes = [], options = {}) => {
5753
+ nodes.forEach((node) => {
5754
+ if (typeof node === "string") {
5755
+ if (options.compactText) {
5756
+ lines.push(...this.normalizeNonEmptyLines(node));
5757
+ return;
5478
5758
  }
5479
- return `\${${pathParamVarName}[${JSON.stringify(paramName)}]}`;
5480
- }
5481
- );
5482
- return `\`${pathWithParams}\``;
5483
- });
5484
- __publicField$3(this, "renderPathParamDestructuring", (pathParamBindings) => {
5485
- const members = Object.entries(pathParamBindings).map(
5486
- ([paramName, binding]) => {
5487
- const key = toTsPropertyKey(paramName);
5488
- return binding === paramName ? key : `${key}: ${binding}`;
5759
+ this.appendDocText(lines, node);
5760
+ return;
5489
5761
  }
5490
- );
5491
- return `{ ${members.join(", ")} }`;
5762
+ this.appendField(lines, node.key, node.value);
5763
+ });
5492
5764
  });
5493
- __publicField$3(this, "paramName", (p) => {
5494
- switch (p.kind) {
5495
- case "body":
5496
- return this.options.paramNames.body;
5497
- case "query":
5498
- return this.options.paramNames.query;
5499
- case "path":
5500
- return this.options.paramNames.path;
5501
- default: {
5502
- const exhaustive = p;
5503
- throw new Error(
5504
- `Unsupported operation parameter kind: ${String(exhaustive)}`
5505
- );
5506
- }
5507
- }
5765
+ __publicField$4(this, "normalizeLines", (value) => {
5766
+ const trimmed = value?.trim();
5767
+ if (!trimmed) return [];
5768
+ return trimmed.split(/\r?\n/u).map((line) => line.trimEnd().replaceAll("*/", "*\\/"));
5508
5769
  });
5770
+ __publicField$4(this, "normalizeNonEmptyLines", (value) => this.normalizeLines(value).filter((line) => line.length > 0));
5509
5771
  }
5510
5772
  }
5511
- const extractPathPlaceholderNames = (pathTemplate) => {
5512
- const seen = /* @__PURE__ */ new Set();
5513
- const names = [];
5514
- const matcher = /\{([^}]+)\}/g;
5515
- let match = matcher.exec(pathTemplate);
5516
- while (match) {
5517
- const name = match[1];
5518
- if (!seen.has(name)) {
5519
- seen.add(name);
5520
- names.push(name);
5521
- }
5522
- match = matcher.exec(pathTemplate);
5523
- }
5524
- return names;
5525
- };
5526
- const isPathParam = (param) => {
5527
- return param.kind === "path";
5528
- };
5529
- const isInlineObjectType = (typeExpr) => typeExpr.kind === "inline" && typeExpr.expr.node === "object";
5530
- const renderNameList = (items) => {
5531
- const values = [...items];
5532
- if (!values.length) {
5533
- return "(none)";
5534
- }
5535
- return values.map((v) => JSON.stringify(v)).join(", ");
5536
- };
5537
- const validatePathParams = (params, httpPath) => {
5538
- const placeholders = extractPathPlaceholderNames(httpPath);
5539
- const placeholderSet = new Set(placeholders);
5540
- const pathParams = params.filter(isPathParam);
5541
- if (pathParams.length > 1) {
5542
- throw new Error(
5543
- `Path "${httpPath}" has ${pathParams.length} path parameter objects; expected at most one.`
5544
- );
5545
- }
5546
- const pathParam = pathParams[0];
5547
- if (!pathParam) {
5548
- if (placeholderSet.size > 0) {
5549
- throw new Error(
5550
- `Path "${httpPath}" is missing a path parameter object for placeholders: ${renderNameList(
5551
- placeholderSet
5552
- )}.`
5553
- );
5554
- }
5555
- return void 0;
5556
- }
5557
- const pathParamExpr = pathParam.typeExpr.expr;
5558
- if (pathParamExpr.additionalProperties !== void 0 && pathParamExpr.additionalProperties !== false) {
5559
- throw new Error(
5560
- `Path "${httpPath}" path parameter object must not declare additionalProperties.`
5561
- );
5562
- }
5563
- const nestedObjectProps = pathParamExpr.properties.filter((prop) => isInlineObjectType(prop.typeExpr)).map((prop) => prop.name);
5564
- if (nestedObjectProps.length > 0) {
5565
- throw new Error(
5566
- `Path "${httpPath}" path parameters must be depth-1. Nested object properties: ${renderNameList(
5567
- nestedObjectProps
5568
- )}.`
5569
- );
5570
- }
5571
- const optionalPathProps = pathParamExpr.properties.filter((prop) => !prop.required).map((prop) => prop.name);
5572
- if (optionalPathProps.length > 0) {
5573
- throw new Error(
5574
- `Path "${httpPath}" path parameters must all be required. Optional properties: ${renderNameList(
5575
- optionalPathProps
5576
- )}.`
5577
- );
5578
- }
5579
- const declaredNames = pathParamExpr.properties.map((prop) => prop.name);
5580
- const declaredNameSet = new Set(declaredNames);
5581
- const missingInObject = [...placeholderSet].filter(
5582
- (name) => !declaredNameSet.has(name)
5773
+
5774
+ const generateTsCode = (clientName, projectResult, opts) => {
5775
+ const docRenderer = new TsDocRenderer({
5776
+ enabled: opts.comments.enabled
5777
+ });
5778
+ const tsCodegenFac = new TsCodegenFactory(docRenderer);
5779
+ const { schemaDefinitions } = projectResult;
5780
+ const types = [...schemaDefinitions].sort((a, b) => a.name.localeCompare(b.name)).map((def) => {
5781
+ const cg = tsCodegenFac.forNewChunk();
5782
+ const code = cg.declaration(def.name, def.declaration, {
5783
+ exported: true
5784
+ });
5785
+ return cg.toChunk(def.name, code, [def.name]);
5786
+ });
5787
+ const clientCodegen = new ApiClientCodegen(
5788
+ {
5789
+ httpClient: promiseHttpClientCodegenSpec(),
5790
+ comments: opts.comments.operation,
5791
+ unwrap: opts.unwrap,
5792
+ pathParamsStyle: opts.pathParamsStyle,
5793
+ withRequestParams: opts.withRequestParams
5794
+ },
5795
+ tsCodegenFac,
5796
+ docRenderer
5583
5797
  );
5584
- const missingInPath = declaredNames.filter(
5585
- (name) => !placeholderSet.has(name)
5798
+ const generated = clientCodegen.generate(
5799
+ clientName,
5800
+ groupsOpsByTag(projectResult.operations),
5801
+ projectResult.api
5586
5802
  );
5587
- if (missingInObject.length > 0 || missingInPath.length > 0) {
5588
- throw new Error(
5589
- `Path "${httpPath}" placeholders and path parameter object keys must match exactly. Missing in object: ${renderNameList(
5590
- missingInObject
5591
- )}. Missing in path: ${renderNameList(missingInPath)}.`
5592
- );
5593
- }
5594
- const bindings = buildPathParamBindings(placeholders);
5595
- const propertiesByName = Object.fromEntries(
5596
- pathParamExpr.properties.map((prop) => [prop.name, prop])
5803
+ const clientChunk = {
5804
+ ...generated,
5805
+ name: `${clientName}.client`
5806
+ };
5807
+ const httpClientChunk = provideHttpClientCode(
5808
+ tsCodegenFac.forNewChunk(),
5809
+ opts
5597
5810
  );
5811
+ const typesChunk = mergeChunks(`${clientName}.types`, types, {
5812
+ preserveChunkOrder: true,
5813
+ allowEmpty: true
5814
+ });
5815
+ const indexChunk = createIndexChunk(clientName);
5816
+ const all = [typesChunk, httpClientChunk, clientChunk, indexChunk];
5598
5817
  return {
5599
- placeholderNames: placeholders,
5600
- bindings,
5601
- propertiesByName
5818
+ all,
5819
+ typesName: typesChunk.name,
5820
+ promiseWrappersNames: [httpClientChunk.name, clientChunk.name]
5602
5821
  };
5603
5822
  };
5604
-
5605
- const EMPTY_LINE_MARKER = "/*__EMPTY_LINE_MARKER__*/";
5606
-
5607
- var __defProp$2 = Object.defineProperty;
5608
- var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5609
- var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
5610
- const REQUEST_PARAMS_TYPE = "RequestParams";
5611
- const REQUEST_PARAMS_DEFAULT = "never";
5612
- const REQUEST_PARAMS_ARG = "params";
5613
- class ApiClientCodegen {
5614
- constructor(opts, tsCodegenFactory) {
5615
- this.opts = opts;
5616
- this.tsCodegenFactory = tsCodegenFactory;
5617
- __publicField$2(this, "generate", (apiName, operations) => {
5618
- const tsCodegen = this.tsCodegenFactory.forNewChunk();
5619
- const httpClientType = tsCodegen.toCode(this.opts.httpClient.typeName);
5620
- let clientType = httpClientType;
5621
- let classTypeParams = "";
5622
- if (this.opts.requestParams) {
5623
- clientType = `${httpClientType}<${REQUEST_PARAMS_TYPE}>`;
5624
- classTypeParams = `<${REQUEST_PARAMS_TYPE} = ${REQUEST_PARAMS_DEFAULT}>`;
5625
- }
5626
- const methodsCode = Array.isArray(operations) ? this.opsCode(tsCodegen, operations) : this.groupsOpsCode(tsCodegen, operations);
5627
- const code = `
5628
- export class ${apiName}${classTypeParams} {
5629
- constructor(private readonly client: ${clientType}) {}
5630
-
5631
- ${methodsCode}
5632
- }
5633
- `;
5634
- return tsCodegen.toChunk("api", code, [apiName]);
5635
- });
5636
- __publicField$2(this, "groupsOpsCode", (codegen, groupedOps) => Object.entries(groupedOps).map(([groupName, ops]) => {
5637
- const methodsCode = this.opsCode(codegen, ops, "object");
5638
- return `
5639
- ${toTsPropertyKey(groupName)} = {
5640
- ${methodsCode}
5641
- }
5642
- `;
5643
- }).join("\n\n"));
5644
- __publicField$2(this, "opsCode", (codegen, operations, target = "class") => {
5645
- const separator = target === "object" ? `,
5646
- ${EMPTY_LINE_MARKER}
5647
- ` : "\n\n";
5648
- const methodsCode = operations.map((op) => this.renderOp(op, codegen, target)).join(separator);
5649
- return methodsCode;
5650
- });
5651
- __publicField$2(this, "renderOp", (op, tsCodegen, target) => {
5652
- const { signature: sig, http } = op;
5653
- const invocationVars = this.opts.httpClient.invocation.vars;
5654
- const paramNames = {
5655
- path: "path",
5656
- query: invocationVars.query,
5657
- body: invocationVars.body
5658
- };
5659
- const paramsHelper = new ParamsConstructor({
5660
- paramNames,
5661
- pathParamsStyle: this.opts.pathParamsStyle
5823
+ const createIndexChunk = (clientName) => ({
5824
+ name: "index",
5825
+ code: [
5826
+ `export { ${clientName} } from './${clientName}.client'`,
5827
+ `export * from './${clientName}.types'`,
5828
+ "export * from './HttpClient'"
5829
+ ].join("\n"),
5830
+ exports: [],
5831
+ refs: []
5832
+ });
5833
+ const groupsOpsByTag = (operations) => {
5834
+ const groupedOps = {};
5835
+ const rawTagsByNormalizedName = /* @__PURE__ */ new Map();
5836
+ operations.forEach((operation) => {
5837
+ const rawTag = operation.tags.find((tag) => tag.trim())?.trim() || "root";
5838
+ let groupName;
5839
+ try {
5840
+ groupName = toTsCamelIdentifier(rawTag, {
5841
+ leadingDigitPrefix: "tag"
5662
5842
  });
5663
- const { path, funParams, hasBody, hasQuery } = paramsHelper.process(
5664
- sig.parameters,
5665
- http.path
5843
+ } catch {
5844
+ throw new Error(
5845
+ `Invalid OpenAPI tag "${rawTag}": could not derive a client group name.`
5666
5846
  );
5667
- const params = funParams.map(
5668
- (r) => `${r.identifier}: ${tsCodegen.toCode(r.typeExpr)}`
5847
+ }
5848
+ const existingRawTag = rawTagsByNormalizedName.get(groupName);
5849
+ if (existingRawTag && existingRawTag !== rawTag) {
5850
+ throw new Error(
5851
+ `Tag naming collision: "${existingRawTag}" and "${rawTag}" both normalize to "${groupName}". Rename one of these OpenAPI tags to continue.`
5669
5852
  );
5670
- if (this.opts.requestParams) {
5671
- params.push(`${REQUEST_PARAMS_ARG}?: ${REQUEST_PARAMS_TYPE}`);
5672
- }
5673
- const paramsCode = params.join(", ");
5674
- const responseType = tsCodegen.toCode(sig.returnType);
5675
- const returnType = this.wrapInResponseWrapper(sig.returnType, tsCodegen);
5676
- return this.renderFunctionCode({
5677
- funName: sig.name,
5678
- responseType,
5679
- returnType,
5680
- params: paramsCode,
5681
- path,
5682
- method: http.method,
5683
- hasQuery,
5684
- hasBody,
5685
- requestContentType: http.requestContentType,
5686
- responseFormat: http.responseFormat,
5687
- requestParamsVar: this.opts.requestParams ? REQUEST_PARAMS_ARG : void 0,
5688
- unwrap: this.opts.unwrap ?? false,
5689
- target
5853
+ }
5854
+ rawTagsByNormalizedName.set(groupName, rawTag);
5855
+ const bucket = groupedOps[groupName] ?? [];
5856
+ bucket.push(operation.op);
5857
+ groupedOps[groupName] = bucket;
5858
+ });
5859
+ return groupedOps;
5860
+ };
5861
+
5862
+ var __defProp$3 = Object.defineProperty;
5863
+ var __defNormalProp$3 = (obj, key, value) => key in obj ? __defProp$3(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
5864
+ var __publicField$3 = (obj, key, value) => __defNormalProp$3(obj, typeof key !== "symbol" ? key + "" : key, value);
5865
+ const TS_IDENTIFIER_PATTERN = /^[$A-Z_][0-9A-Z_$]*$/i;
5866
+ class ToTypeExprConverter {
5867
+ constructor(schemas = {}, options) {
5868
+ this.schemas = schemas;
5869
+ this.options = options;
5870
+ __publicField$3(this, "schemaCache", /* @__PURE__ */ new WeakMap());
5871
+ __publicField$3(this, "refCache", /* @__PURE__ */ new Map());
5872
+ __publicField$3(this, "schemaDefinitions", []);
5873
+ __publicField$3(this, "schemaDefinitionsById", /* @__PURE__ */ new Map());
5874
+ __publicField$3(this, "schemaDefinitionsInProgress", /* @__PURE__ */ new Set());
5875
+ __publicField$3(this, "getSchemaDefinitions", () => this.schemaDefinitions);
5876
+ __publicField$3(this, "registerAllSchemaDefinitions", () => {
5877
+ Object.keys(this.schemas).forEach((schemaName) => {
5878
+ this.ensureSchemaDefinition(`#/components/schemas/${schemaName}`);
5690
5879
  });
5691
5880
  });
5692
- __publicField$2(this, "wrapInResponseWrapper", (typeExpr, tsCodegen) => {
5693
- return tsCodegen.toCode({
5694
- ...this.opts.httpClient.responseWrapper,
5695
- typeArgs: [typeExpr]
5696
- });
5881
+ __publicField$3(this, "fromTypeExpr", (schema) => {
5882
+ if (!schema) {
5883
+ return this.scalar("unknown");
5884
+ }
5885
+ const bySchema = this.schemaCache.get(schema);
5886
+ if (bySchema) {
5887
+ return bySchema;
5888
+ }
5889
+ if (typeof schema.$ref === "string") {
5890
+ const byRef = this.refCache.get(this.refCacheKey(schema));
5891
+ if (byRef) {
5892
+ this.schemaCache.set(schema, byRef);
5893
+ return byRef;
5894
+ }
5895
+ }
5896
+ const projected = this.fromSchemaCore(schema);
5897
+ const result = schema.nullable === true ? this.withNullable(projected) : projected;
5898
+ this.schemaCache.set(schema, result);
5899
+ if (typeof schema.$ref === "string") {
5900
+ this.refCache.set(this.refCacheKey(schema), result);
5901
+ }
5902
+ return result;
5697
5903
  });
5698
- __publicField$2(this, "renderFunctionCode", ({
5699
- funName,
5700
- responseType,
5701
- returnType,
5702
- params,
5703
- path,
5704
- method,
5705
- hasQuery,
5706
- hasBody,
5707
- requestContentType,
5708
- responseFormat,
5709
- requestParamsVar,
5710
- unwrap,
5711
- target
5712
- }) => {
5713
- let invocation = this.opts.httpClient.invocation.expr({
5714
- funName,
5715
- returnType,
5716
- responseType,
5717
- params,
5718
- path,
5719
- method,
5720
- hasQuery,
5721
- hasBody,
5722
- requestContentType,
5723
- responseFormat,
5724
- requestParamsVar,
5725
- unwrap
5904
+ __publicField$3(this, "scalar", (name) => ({
5905
+ kind: "inline",
5906
+ expr: {
5907
+ node: "scalar",
5908
+ name
5909
+ }
5910
+ }));
5911
+ __publicField$3(this, "union", (members) => {
5912
+ const flatMembers = [];
5913
+ for (const member of members) {
5914
+ if (member.kind === "inline" && member.expr.node === "union") {
5915
+ flatMembers.push(...member.expr.members);
5916
+ } else {
5917
+ flatMembers.push(member);
5918
+ }
5919
+ }
5920
+ if (flatMembers.length === 1) {
5921
+ return flatMembers[0];
5922
+ }
5923
+ return {
5924
+ kind: "inline",
5925
+ expr: {
5926
+ node: "union",
5927
+ members: flatMembers
5928
+ }
5929
+ };
5930
+ });
5931
+ __publicField$3(this, "literal", (value) => ({
5932
+ kind: "inline",
5933
+ expr: {
5934
+ node: "literal",
5935
+ value
5936
+ }
5937
+ }));
5938
+ __publicField$3(this, "ref", (id) => {
5939
+ const name = this.refNameFromPath(id);
5940
+ return {
5941
+ kind: "reference",
5942
+ ref: {
5943
+ kind: "internal",
5944
+ name
5945
+ }
5946
+ };
5947
+ });
5948
+ __publicField$3(this, "builtinRef", (name) => ({
5949
+ kind: "reference",
5950
+ ref: {
5951
+ kind: "builtin",
5952
+ name
5953
+ }
5954
+ }));
5955
+ __publicField$3(this, "refNameFromPath", (ref) => {
5956
+ const parts = ref.split("/");
5957
+ const name = parts[parts.length - 1];
5958
+ if (!name) {
5959
+ throw new Error(`Unsupported schema reference: ${ref}`);
5960
+ }
5961
+ return name;
5962
+ });
5963
+ __publicField$3(this, "refCacheKey", (schema) => {
5964
+ return `${schema.$ref}|nullable:${schema.nullable === true}`;
5965
+ });
5966
+ __publicField$3(this, "isUnconstrainedSchema", (schema) => {
5967
+ const meaningfulKeys = Object.keys(schema).filter(
5968
+ (key) => key !== "nullable"
5969
+ );
5970
+ return meaningfulKeys.length === 0;
5971
+ });
5972
+ __publicField$3(this, "ensureSchemaDefinition", (refId) => {
5973
+ if (this.schemaDefinitionsById.has(refId) || this.schemaDefinitionsInProgress.has(refId)) {
5974
+ return;
5975
+ }
5976
+ const schemaName = this.refNameFromPath(refId);
5977
+ const schema = this.schemas[schemaName];
5978
+ if (!schema) {
5979
+ return;
5980
+ }
5981
+ this.schemaDefinitionsInProgress.add(refId);
5982
+ try {
5983
+ const definition = {
5984
+ name: schemaName,
5985
+ declaration: this.toDeclaration(schema)
5986
+ };
5987
+ this.schemaDefinitionsById.set(refId, definition);
5988
+ this.schemaDefinitions.push(definition);
5989
+ } finally {
5990
+ this.schemaDefinitionsInProgress.delete(refId);
5991
+ }
5992
+ });
5993
+ __publicField$3(this, "isSchemaNode", (value) => {
5994
+ return Boolean(
5995
+ value && typeof value === "object" && !Array.isArray(value)
5996
+ );
5997
+ });
5998
+ __publicField$3(this, "toLiteralValue", (value) => {
5999
+ if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
6000
+ return value;
6001
+ }
6002
+ throw new Error(`Unsupported literal value: ${String(value)}`);
6003
+ });
6004
+ __publicField$3(this, "toDeclaration", (schema) => {
6005
+ const enumDeclaration = this.toEnumDeclaration(schema);
6006
+ if (enumDeclaration) {
6007
+ return enumDeclaration;
6008
+ }
6009
+ return {
6010
+ kind: "typeAlias",
6011
+ doc: this.getSchemaDoc(schema),
6012
+ typeExpr: this.fromTypeExpr(schema)
6013
+ };
6014
+ });
6015
+ __publicField$3(this, "toEnumDeclaration", (schema) => {
6016
+ if (this.options.enumStyle !== "enum" || !Array.isArray(schema.enum)) {
6017
+ return void 0;
6018
+ }
6019
+ const valueType = this.getEnumValueType(schema.enum);
6020
+ if (!valueType) {
6021
+ return void 0;
6022
+ }
6023
+ return {
6024
+ kind: "enum",
6025
+ doc: this.getSchemaDoc(schema),
6026
+ valueType,
6027
+ members: this.toEnumMembers(schema.enum)
6028
+ };
6029
+ });
6030
+ __publicField$3(this, "getEnumValueType", (values) => {
6031
+ if (!values.length) {
6032
+ return void 0;
6033
+ }
6034
+ if (values.every((value) => typeof value === "string")) {
6035
+ return "string";
6036
+ }
6037
+ if (values.every((value) => typeof value === "number")) {
6038
+ return "number";
6039
+ }
6040
+ return void 0;
6041
+ });
6042
+ __publicField$3(this, "toEnumMembers", (values) => {
6043
+ const usedNames = /* @__PURE__ */ new Map();
6044
+ return values.map((value, index) => {
6045
+ const enumValue = value;
6046
+ const baseName = this.toEnumMemberBaseName(enumValue, index);
6047
+ const nextCount = (usedNames.get(baseName) ?? 0) + 1;
6048
+ usedNames.set(baseName, nextCount);
6049
+ return {
6050
+ name: nextCount === 1 ? baseName : `${baseName}${nextCount}`,
6051
+ value: enumValue
6052
+ };
5726
6053
  });
5727
- invocation = invocation.replaceAll("\n", "");
5728
- const operator = target === "object" ? ":" : "=";
5729
- const returnTypeCode = this.opts.httpClient.inferMethodReturnType ? "" : `:${returnType}`;
5730
- return `${funName} ${operator} (${params})${returnTypeCode} =>
5731
- this.client${invocation}
5732
- `;
6054
+ });
6055
+ __publicField$3(this, "toEnumMemberBaseName", (value, index) => {
6056
+ if (typeof value === "string" && this.options.uppercaseEnumKeys) {
6057
+ const compatName = this.toCompatEnumMemberName(value);
6058
+ if (compatName) {
6059
+ return compatName;
6060
+ }
6061
+ }
6062
+ if (typeof value === "string" && TS_IDENTIFIER_PATTERN.test(value) && !RESERVED_WORDS.has(value)) {
6063
+ return value;
6064
+ }
6065
+ if (typeof value === "number" && Number.isInteger(value) && value >= 0) {
6066
+ return `Value${value}`;
6067
+ }
6068
+ const pascalName = toPascalCaseIdentifier(String(value));
6069
+ return pascalName === "Api" ? `Value${index + 1}` : pascalName;
6070
+ });
6071
+ __publicField$3(this, "toCompatEnumMemberName", (value) => {
6072
+ if (/^([A-Z_]{1,})$/g.test(value)) {
6073
+ return value;
6074
+ }
6075
+ const pascalName = toPascalCaseIdentifier(value);
6076
+ return pascalName === "Api" ? void 0 : pascalName;
6077
+ });
6078
+ __publicField$3(this, "isScalarName", (value) => {
6079
+ switch (value) {
6080
+ case "string":
6081
+ case "number":
6082
+ case "boolean":
6083
+ case "integer":
6084
+ case "null":
6085
+ case "unknown":
6086
+ case "any":
6087
+ case "never":
6088
+ case "void":
6089
+ return true;
6090
+ default:
6091
+ return false;
6092
+ }
6093
+ });
6094
+ __publicField$3(this, "hasNullMember", (expr) => {
6095
+ if (expr.kind !== "inline") return false;
6096
+ if (expr.expr.node === "scalar") {
6097
+ return expr.expr.name === "null";
6098
+ }
6099
+ if (expr.expr.node === "union") {
6100
+ return expr.expr.members.some((member) => this.hasNullMember(member));
6101
+ }
6102
+ return false;
6103
+ });
6104
+ __publicField$3(this, "withNullable", (expr) => {
6105
+ if (this.hasNullMember(expr)) {
6106
+ return expr;
6107
+ }
6108
+ return this.union([expr, this.scalar("null")]);
6109
+ });
6110
+ __publicField$3(this, "projectObjectType", (schema) => {
6111
+ const requiredRaw = schema.required;
6112
+ if (requiredRaw !== void 0 && !Array.isArray(requiredRaw)) {
6113
+ throw new Error(
6114
+ "Unsupported object schema: required must be an array"
6115
+ );
6116
+ }
6117
+ const required = new Set(
6118
+ (requiredRaw ?? []).map((item) => {
6119
+ if (typeof item !== "string") {
6120
+ throw new Error(
6121
+ "Unsupported object schema: required items must be strings"
6122
+ );
6123
+ }
6124
+ return item;
6125
+ })
6126
+ );
6127
+ const propertiesRaw = schema.properties;
6128
+ if (propertiesRaw !== void 0 && (!propertiesRaw || typeof propertiesRaw !== "object" || Array.isArray(propertiesRaw))) {
6129
+ throw new Error(
6130
+ "Unsupported object schema: properties must be an object"
6131
+ );
6132
+ }
6133
+ const properties = Object.entries(propertiesRaw ?? {}).map(
6134
+ ([name, propertySchema]) => {
6135
+ if (!this.isSchemaNode(propertySchema)) {
6136
+ throw new Error(
6137
+ `Unsupported object property schema for "${name}"`
6138
+ );
6139
+ }
6140
+ return {
6141
+ doc: this.getSchemaDoc(propertySchema),
6142
+ name,
6143
+ required: this.isRequiredProperty(
6144
+ name,
6145
+ required,
6146
+ propertySchema
6147
+ ),
6148
+ typeExpr: this.fromTypeExpr(propertySchema)
6149
+ };
6150
+ }
6151
+ );
6152
+ let additionalProperties;
6153
+ if (schema.additionalProperties !== void 0) {
6154
+ if (typeof schema.additionalProperties === "boolean") {
6155
+ additionalProperties = schema.additionalProperties;
6156
+ } else if (this.isSchemaNode(schema.additionalProperties)) {
6157
+ additionalProperties = this.fromTypeExpr(
6158
+ schema.additionalProperties
6159
+ );
6160
+ } else {
6161
+ throw new Error(
6162
+ "Unsupported object schema: additionalProperties must be boolean or schema object"
6163
+ );
6164
+ }
6165
+ }
6166
+ return {
6167
+ kind: "inline",
6168
+ expr: {
6169
+ node: "object",
6170
+ properties,
6171
+ additionalProperties
6172
+ }
6173
+ };
6174
+ });
6175
+ __publicField$3(this, "isRequiredProperty", (name, required, propertySchema) => {
6176
+ if (required.has(name)) {
6177
+ return true;
6178
+ }
6179
+ if (!this.options.swaggerTsApiRequiredBooleans) {
6180
+ return false;
6181
+ }
6182
+ return propertySchema.required === true;
6183
+ });
6184
+ __publicField$3(this, "mapSchemaArrayMembers", (value, kind) => {
6185
+ if (!Array.isArray(value) || !value.length) {
6186
+ throw new Error(
6187
+ `Unsupported schema: ${kind} must be a non-empty array`
6188
+ );
6189
+ }
6190
+ return value.map((entry, index) => {
6191
+ if (!this.isSchemaNode(entry)) {
6192
+ throw new Error(
6193
+ `Unsupported schema: ${kind}[${index}] must be a schema object`
6194
+ );
6195
+ }
6196
+ return this.fromTypeExpr(entry);
6197
+ });
6198
+ });
6199
+ __publicField$3(this, "isBinaryFileSchema", (schema) => {
6200
+ return schema.type === "string" && schema.format === "binary";
6201
+ });
6202
+ __publicField$3(this, "getSchemaDoc", (schema) => {
6203
+ const summary = typeof schema.summary === "string" ? schema.summary : void 0;
6204
+ const description = typeof schema.description === "string" ? schema.description : void 0;
6205
+ const deprecated = typeof schema.deprecated === "boolean" ? schema.deprecated : void 0;
6206
+ const annotations = this.getSchemaAnnotations(schema);
6207
+ if (!summary && !description && !deprecated && !annotations.length) {
6208
+ return void 0;
6209
+ }
6210
+ const nodes = [];
6211
+ if (summary) {
6212
+ nodes.push(summary);
6213
+ }
6214
+ if (description) {
6215
+ nodes.push(description);
6216
+ }
6217
+ nodes.push(...annotations);
6218
+ return {
6219
+ deprecated,
6220
+ nodes
6221
+ };
6222
+ });
6223
+ __publicField$3(this, "getSchemaAnnotations", (schema) => {
6224
+ if (this.options.comments?.metadata === false) return [];
6225
+ return [
6226
+ this.annotationNode("format", schema.format),
6227
+ this.annotationNode("min", schema.minimum),
6228
+ this.annotationNode("multipleOf", schema.multipleOf),
6229
+ this.annotationNode("exclusiveMin", schema.exclusiveMinimum),
6230
+ this.annotationNode("max", schema.maximum),
6231
+ this.annotationNode("minLength", schema.minLength),
6232
+ this.annotationNode("maxLength", schema.maxLength),
6233
+ this.annotationNode("exclusiveMax", schema.exclusiveMaximum),
6234
+ this.annotationNode("maxItems", schema.maxItems),
6235
+ this.annotationNode("minItems", schema.minItems),
6236
+ this.annotationNode("uniqueItems", schema.uniqueItems),
6237
+ this.annotationNode(
6238
+ "default",
6239
+ this.stringifyAnnotationValue(schema.default)
6240
+ ),
6241
+ this.annotationNode("pattern", schema.pattern),
6242
+ this.annotationNode(
6243
+ "example",
6244
+ this.stringifyAnnotationValue(schema.example)
6245
+ )
6246
+ ].filter(
6247
+ (value) => value !== void 0
6248
+ );
6249
+ });
6250
+ __publicField$3(this, "annotationNode", (name, value) => {
6251
+ if (value === void 0) return void 0;
6252
+ return {
6253
+ key: name,
6254
+ value: String(value)
6255
+ };
6256
+ });
6257
+ __publicField$3(this, "stringifyAnnotationValue", (value) => {
6258
+ if (value === void 0) return void 0;
6259
+ if (value && typeof value === "object") {
6260
+ return JSON.stringify(value);
6261
+ }
6262
+ if (typeof value === "string") {
6263
+ return JSON.stringify(value);
6264
+ }
6265
+ return String(value);
6266
+ });
6267
+ __publicField$3(this, "fromSchemaCore", (schema) => {
6268
+ if (this.isUnconstrainedSchema(schema)) {
6269
+ return this.scalar("unknown");
6270
+ }
6271
+ if (typeof schema.$ref === "string") {
6272
+ this.ensureSchemaDefinition(schema.$ref);
6273
+ return this.ref(schema.$ref);
6274
+ }
6275
+ if (schema.const !== void 0) {
6276
+ return this.literal(this.toLiteralValue(schema.const));
6277
+ }
6278
+ if (Array.isArray(schema.enum)) {
6279
+ if (!schema.enum.length) {
6280
+ throw new Error(
6281
+ "Unsupported enum schema: enum must be non-empty"
6282
+ );
6283
+ }
6284
+ return this.union(
6285
+ schema.enum.map(
6286
+ (value) => this.literal(this.toLiteralValue(value))
6287
+ )
6288
+ );
6289
+ }
6290
+ if (schema.type === "array") {
6291
+ if (schema.items === void 0) {
6292
+ return {
6293
+ kind: "inline",
6294
+ expr: {
6295
+ node: "array",
6296
+ element: this.scalar("unknown")
6297
+ }
6298
+ };
6299
+ }
6300
+ if (Array.isArray(schema.items)) {
6301
+ throw new Error("Unsupported array schema: tuple-style items");
6302
+ }
6303
+ if (!this.isSchemaNode(schema.items)) {
6304
+ throw new Error(
6305
+ "Unsupported array schema: items must be a schema"
6306
+ );
6307
+ }
6308
+ return {
6309
+ kind: "inline",
6310
+ expr: {
6311
+ node: "array",
6312
+ element: this.fromTypeExpr(schema.items)
6313
+ }
6314
+ };
6315
+ }
6316
+ if (schema.type === "object") {
6317
+ return this.projectObjectType(schema);
6318
+ }
6319
+ if (schema.oneOf !== void 0) {
6320
+ return this.union(this.mapSchemaArrayMembers(schema.oneOf, "oneOf"));
6321
+ }
6322
+ if (schema.anyOf !== void 0) {
6323
+ return this.union(this.mapSchemaArrayMembers(schema.anyOf, "anyOf"));
6324
+ }
6325
+ if (schema.allOf !== void 0) {
6326
+ return {
6327
+ kind: "inline",
6328
+ expr: {
6329
+ node: "intersection",
6330
+ members: this.mapSchemaArrayMembers(schema.allOf, "allOf")
6331
+ }
6332
+ };
6333
+ }
6334
+ if (this.isBinaryFileSchema(schema)) {
6335
+ return this.builtinRef("File");
6336
+ }
6337
+ if (this.isScalarName(schema.type)) {
6338
+ return this.scalar(schema.type);
6339
+ }
6340
+ throw new Error(
6341
+ `Unsupported schema construct: ${JSON.stringify(schema, null, 2)}`
6342
+ );
5733
6343
  });
5734
6344
  }
5735
6345
  }
5736
6346
 
5737
- const promiseHttpClientCodegenSpec = () => ({
5738
- typeName: {
5739
- kind: "reference",
5740
- ref: {
5741
- kind: "internal",
5742
- name: "HttpClient"
5743
- }
5744
- },
5745
- responseWrapper: {
5746
- kind: "reference",
5747
- ref: {
5748
- kind: "builtin",
5749
- name: "Promise"
6347
+ var __defProp$2 = Object.defineProperty;
6348
+ var __defNormalProp$2 = (obj, key, value) => key in obj ? __defProp$2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6349
+ var __publicField$2 = (obj, key, value) => __defNormalProp$2(obj, typeof key !== "symbol" ? key + "" : key, value);
6350
+ const pickPreferredMediaType = (mediaTypes) => {
6351
+ if (!mediaTypes.length) return void 0;
6352
+ const exactJson = mediaTypes.find((media) => media === "application/json");
6353
+ if (exactJson) return exactJson;
6354
+ const jsonLike = mediaTypes.find((media) => /\+json$/i.test(media));
6355
+ if (jsonLike) return jsonLike;
6356
+ const wildcardJson = mediaTypes.find(
6357
+ (media) => /application\/\*\+json/i.test(media)
6358
+ );
6359
+ if (wildcardJson) return wildcardJson;
6360
+ return mediaTypes[0];
6361
+ };
6362
+ const isSuccessStatusCode = (statusCode) => {
6363
+ const upper = statusCode.toUpperCase();
6364
+ if (/^2XX$/.test(upper)) {
6365
+ return true;
6366
+ }
6367
+ const numericCode = Number(statusCode);
6368
+ return Number.isFinite(numericCode) && numericCode >= 200 && numericCode < 300;
6369
+ };
6370
+ const selectReturnResponses = (responses) => {
6371
+ const entries = Object.entries(responses);
6372
+ if (!entries.length) {
6373
+ return [];
6374
+ }
6375
+ const success = entries.filter(([statusCode]) => isSuccessStatusCode(statusCode)).map(([, response]) => response);
6376
+ if (success.length) {
6377
+ return success;
6378
+ }
6379
+ const defaultResponse = entries.find(
6380
+ ([statusCode]) => statusCode.toLowerCase() === "default"
6381
+ );
6382
+ if (defaultResponse) {
6383
+ return [defaultResponse[1]];
6384
+ }
6385
+ return entries.map(([, response]) => response);
6386
+ };
6387
+ const getPreferredMediaTypeEntry = (content) => {
6388
+ const mediaType = pickPreferredMediaType(Object.keys(content));
6389
+ if (!mediaType) return {};
6390
+ return {
6391
+ mediaType,
6392
+ media: content[mediaType]
6393
+ };
6394
+ };
6395
+ const isBinaryFileResponse = (response) => {
6396
+ const { mediaType, media } = getPreferredMediaTypeEntry(response.content);
6397
+ return media?.schema?.type === "string" && media.schema?.format === "binary" && mediaType !== "multipart/form-data";
6398
+ };
6399
+ const isJsonLikeMediaType = (mediaType) => {
6400
+ return mediaType === "application/json" || typeof mediaType === "string" && /\+json$/i.test(mediaType);
6401
+ };
6402
+ const toHttpMethodUpper = (method) => {
6403
+ switch (method) {
6404
+ case "get":
6405
+ return "GET";
6406
+ case "post":
6407
+ return "POST";
6408
+ case "put":
6409
+ return "PUT";
6410
+ case "delete":
6411
+ return "DELETE";
6412
+ case "patch":
6413
+ return "PATCH";
6414
+ case "options":
6415
+ return "OPTIONS";
6416
+ case "head":
6417
+ return "HEAD";
6418
+ case "trace":
6419
+ return "TRACE";
6420
+ default: {
6421
+ const exhaustive = method;
6422
+ throw new Error(`Unsupported HTTP method: ${String(exhaustive)}`);
5750
6423
  }
5751
- },
5752
- inferMethodReturnType: true,
5753
- invocation: {
5754
- vars: {
5755
- body: "body",
5756
- query: "query"
5757
- },
5758
- expr: ({
5759
- responseType,
5760
- path,
5761
- method,
5762
- hasBody,
5763
- hasQuery,
5764
- requestContentType,
5765
- responseFormat,
5766
- requestParamsVar,
5767
- unwrap
5768
- }) => `.request<${responseType}>({
5769
- method: '${method}',
5770
- path: ${path}
5771
- ${hasQuery ? `,
5772
- query` : ""}
5773
- ${hasBody ? `,
5774
- body` : ""}
5775
- ${requestContentType ? `,
5776
- requestContentType: '${requestContentType}'` : ""}
5777
- ${responseFormat ? `,
5778
- format: '${responseFormat}'` : ""}
5779
- }${requestParamsVar ? `,
5780
- ${requestParamsVar}` : ""})${unwrap ? `.then(res => res.body)` : ""}
5781
- `
5782
6424
  }
5783
- });
6425
+ };
6426
+ class OperationProjector {
6427
+ constructor(options) {
6428
+ this.options = options;
6429
+ __publicField$2(this, "seenOperationIds", /* @__PURE__ */ new Set());
6430
+ __publicField$2(this, "typeExprConverter");
6431
+ __publicField$2(this, "project", (openApi) => {
6432
+ this.seenOperationIds.clear();
6433
+ this.typeExprConverter = new ToTypeExprConverter(
6434
+ openApi.schemas,
6435
+ this.options.typeExtraction
6436
+ );
6437
+ if (this.options.includeAllSchema) {
6438
+ this.typeExprConverter.registerAllSchemaDefinitions();
6439
+ }
6440
+ const operations = openApi.operations.map((operation) => {
6441
+ const operationName = (operation.operationId ?? syntheticOperationId(operation.method, operation.path)).trim();
6442
+ if (!operationName) {
6443
+ throw new Error(
6444
+ `Operation id is empty for ${operation.method.toUpperCase()} ${operation.path}`
6445
+ );
6446
+ }
6447
+ if (this.seenOperationIds.has(operationName)) {
6448
+ throw new Error(`Duplicate operation id: ${operationName}`);
6449
+ }
6450
+ return this.projectOne(operationName, operation);
6451
+ });
6452
+ return {
6453
+ api: {
6454
+ title: openApi.info.title,
6455
+ version: openApi.info.version,
6456
+ description: openApi.info.description
6457
+ },
6458
+ operations,
6459
+ schemaDefinitions: this.typeExprConverter.getSchemaDefinitions()
6460
+ };
6461
+ });
6462
+ __publicField$2(this, "projectOne", (operationName, operation) => {
6463
+ this.seenOperationIds.add(operationName);
6464
+ const parameters = [];
6465
+ const pathParameter = this.projectPathParameter(operation);
6466
+ const queryParameter = this.projectQueryParameter(operation);
6467
+ const bodyParameter = this.projectBodyParameter(operation);
6468
+ if (pathParameter) parameters.push(pathParameter);
6469
+ if (queryParameter) parameters.push(queryParameter);
6470
+ if (bodyParameter) parameters.push(bodyParameter);
6471
+ return {
6472
+ op: {
6473
+ signature: {
6474
+ doc: this.toOperationDoc(operationName, operation),
6475
+ name: operationName,
6476
+ parameters,
6477
+ returnType: this.projectReturnType(operation)
6478
+ },
6479
+ http: {
6480
+ method: toHttpMethodUpper(operation.method),
6481
+ path: operation.path,
6482
+ requestContentType: this.projectRequestContentType(
6483
+ operation.requestBody
6484
+ ),
6485
+ responseFormat: this.projectResponseFormat(operation)
6486
+ }
6487
+ },
6488
+ tags: [...operation.tags]
6489
+ };
6490
+ });
6491
+ __publicField$2(this, "projectRequestContentType", (requestBody) => {
6492
+ const { mediaType } = getPreferredMediaTypeEntry(
6493
+ requestBody?.content ?? {}
6494
+ );
6495
+ switch (mediaType) {
6496
+ case "application/json":
6497
+ case "multipart/form-data":
6498
+ case "application/x-www-form-urlencoded":
6499
+ return mediaType;
6500
+ default:
6501
+ return void 0;
6502
+ }
6503
+ });
6504
+ __publicField$2(this, "projectResponseFormat", (operation) => {
6505
+ const selectedResponses = selectReturnResponses(operation.responses);
6506
+ if (!selectedResponses.length) {
6507
+ return void 0;
6508
+ }
6509
+ if (selectedResponses.some((response) => isBinaryFileResponse(response))) {
6510
+ return "document";
6511
+ }
6512
+ if (selectedResponses.some(
6513
+ (response) => isJsonLikeMediaType(
6514
+ getPreferredMediaTypeEntry(response.content).mediaType
6515
+ )
6516
+ )) {
6517
+ return "json";
6518
+ }
6519
+ return void 0;
6520
+ });
6521
+ __publicField$2(this, "projectParameterGroupTypeExpr", (params) => {
6522
+ const properties = Object.entries(params).map(([name, parameter]) => ({
6523
+ doc: this.toParameterDoc(parameter),
6524
+ name,
6525
+ required: parameter.required,
6526
+ typeExpr: this.typeExprConverter.fromTypeExpr(
6527
+ this.selectParameterSchema(parameter)
6528
+ )
6529
+ }));
6530
+ return {
6531
+ kind: "inline",
6532
+ expr: {
6533
+ node: "object",
6534
+ properties
6535
+ }
6536
+ };
6537
+ });
6538
+ __publicField$2(this, "projectPathParameter", (operation) => {
6539
+ const params = operation.parameters.path;
6540
+ const entries = Object.values(params);
6541
+ if (!entries.length) return null;
6542
+ if (entries.some((parameter) => !parameter.required)) {
6543
+ throw new Error("Unsupported path parameters: all must be required");
6544
+ }
6545
+ return {
6546
+ kind: "path",
6547
+ optional: false,
6548
+ typeExpr: this.projectParameterGroupTypeExpr(params)
6549
+ };
6550
+ });
6551
+ __publicField$2(this, "projectQueryParameter", (operation) => {
6552
+ const params = operation.parameters.query;
6553
+ const entries = Object.values(params);
6554
+ if (!entries.length) return null;
6555
+ return {
6556
+ kind: "query",
6557
+ optional: !entries.some((parameter) => parameter.required),
6558
+ typeExpr: this.projectParameterGroupTypeExpr(params)
6559
+ };
6560
+ });
6561
+ __publicField$2(this, "projectBodyParameter", (operation) => {
6562
+ const requestBody = operation.requestBody;
6563
+ if (!requestBody) return null;
6564
+ return {
6565
+ kind: "body",
6566
+ optional: !requestBody.required,
6567
+ typeExpr: this.typeExprConverter.fromTypeExpr(
6568
+ this.selectRequestBodySchema(requestBody)
6569
+ )
6570
+ };
6571
+ });
6572
+ __publicField$2(this, "projectReturnType", (operation) => {
6573
+ const selectedResponses = selectReturnResponses(operation.responses);
6574
+ if (!selectedResponses.length) {
6575
+ return this.typeExprConverter.scalar("void");
6576
+ }
6577
+ const responseTypes = selectedResponses.map((response) => {
6578
+ const schema = this.selectResponseSchema(response);
6579
+ if (!schema) {
6580
+ return this.typeExprConverter.scalar("void");
6581
+ }
6582
+ return this.typeExprConverter.fromTypeExpr(schema);
6583
+ });
6584
+ return this.typeExprConverter.union(responseTypes);
6585
+ });
6586
+ __publicField$2(this, "selectParameterSchema", (parameter) => {
6587
+ if (parameter.schema) return parameter.schema;
6588
+ return getPreferredMediaTypeEntry(parameter.content).media?.schema;
6589
+ });
6590
+ __publicField$2(this, "selectRequestBodySchema", (requestBody) => {
6591
+ return getPreferredMediaTypeEntry(requestBody.content).media?.schema;
6592
+ });
6593
+ __publicField$2(this, "selectResponseSchema", (response) => {
6594
+ return getPreferredMediaTypeEntry(response.content).media?.schema;
6595
+ });
6596
+ __publicField$2(this, "toParameterDoc", (parameter) => {
6597
+ if (!parameter.description && !parameter.deprecated) {
6598
+ return void 0;
6599
+ }
6600
+ return {
6601
+ deprecated: parameter.deprecated,
6602
+ nodes: parameter.description ? [parameter.description] : []
6603
+ };
6604
+ });
6605
+ __publicField$2(this, "toOperationDoc", (operationName, operation) => {
6606
+ return {
6607
+ id: operationName,
6608
+ request: `${toHttpMethodUpper(operation.method)}:${operation.path}`,
6609
+ summary: operation.summary,
6610
+ description: operation.description,
6611
+ deprecated: operation.deprecated,
6612
+ tags: operation.tags.map((tag) => tag.trim()).filter(Boolean)
6613
+ };
6614
+ });
6615
+ }
6616
+ }
5784
6617
 
5785
6618
  var __defProp$1 = Object.defineProperty;
5786
6619
  var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
@@ -5796,7 +6629,7 @@ class CodeFormatter {
5796
6629
  constructor(opts) {
5797
6630
  this.opts = opts;
5798
6631
  __publicField$1(this, "format", async (code) => {
5799
- const filePath = Path.resolve(this.opts.resolveConfFrom, "generated.ts");
6632
+ const filePath = Path$1.resolve(this.opts.resolveConfFrom, "generated.ts");
5800
6633
  const prettier = await import('prettier');
5801
6634
  const prettierConfig = await prettier.resolveConfig(filePath) ?? {};
5802
6635
  const formattedCode = await prettier.format(code, {
@@ -5817,7 +6650,7 @@ class CodeWriter {
5817
6650
  __publicField(this, "symbolChunkMap", /* @__PURE__ */ new Map());
5818
6651
  __publicField(this, "write", async (dir, chunks) => {
5819
6652
  this.symbolChunkMap = this.createSymbolChunkMap(chunks);
5820
- fs$2.mkdirSync(dir, { recursive: true });
6653
+ fs__default.mkdirSync(dir, { recursive: true });
5821
6654
  for (const chunk of chunks) {
5822
6655
  const imports = this.collectImports(chunk);
5823
6656
  const importStatements = this.genImportStatements(imports);
@@ -5827,7 +6660,7 @@ class CodeWriter {
5827
6660
  let code = importCode + chunk.code;
5828
6661
  code = await this.formatter.format(code);
5829
6662
  code = ensureSingleTrailingNewline(code);
5830
- fs$2.writeFileSync(`${dir}/${chunk.name}.ts`, code, "utf8");
6663
+ fs__default.writeFileSync(`${dir}/${chunk.name}.ts`, code, "utf8");
5831
6664
  }
5832
6665
  });
5833
6666
  __publicField(this, "createSymbolChunkMap", (chunks) => {
@@ -5890,185 +6723,77 @@ class CodeWriter {
5890
6723
  }
5891
6724
  const ensureSingleTrailingNewline = (code) => code.replace(/\n+$/u, "\n");
5892
6725
 
5893
- const mergeChunks = (name, chunks) => {
5894
- if (chunks.length === 0) {
5895
- throw new Error("Cannot merge empty chunk list.");
5896
- }
5897
- const orderedChunks = orderByInternalDependencies(chunks);
5898
- const mergedCode = orderedChunks.map((chunk) => chunk.code).join("\n\n");
5899
- const exportedSymbols = /* @__PURE__ */ new Set();
5900
- const mergedExports = [];
5901
- for (const chunk of orderedChunks) {
5902
- for (const symbol of chunk.exports) {
5903
- if (exportedSymbols.has(symbol)) continue;
5904
- exportedSymbols.add(symbol);
5905
- mergedExports.push(symbol);
5906
- }
5907
- }
5908
- const seenRefKeys = /* @__PURE__ */ new Set();
5909
- const mergedRefs = orderedChunks.flatMap((chunk) => chunk.refs).filter((ref) => {
5910
- if (ref.kind === "internal" && exportedSymbols.has(ref.name)) {
5911
- return false;
5912
- }
5913
- const key = ref.kind === "internal" ? `internal:${ref.name}` : ref.kind === "external" ? `external:${ref.package}:${ref.name}` : `builtin:${ref.name}`;
5914
- if (seenRefKeys.has(key)) return false;
5915
- seenRefKeys.add(key);
5916
- return true;
5917
- });
5918
- return {
5919
- name,
5920
- code: mergedCode,
5921
- exports: mergedExports,
5922
- refs: mergedRefs
5923
- };
5924
- };
5925
- const orderByInternalDependencies = (chunks) => {
5926
- const chunkIndex = /* @__PURE__ */ new Map();
5927
- const exportOwnerBySymbol = /* @__PURE__ */ new Map();
5928
- chunks.forEach((chunk, index) => {
5929
- chunkIndex.set(chunk.name, index);
5930
- for (const symbol of chunk.exports) {
5931
- if (exportOwnerBySymbol.has(symbol)) {
5932
- throw new Error(
5933
- `Assertion error. Symbol "${symbol}" exported multiple times while ordering chunks.`
5934
- );
5935
- }
5936
- exportOwnerBySymbol.set(symbol, chunk);
5937
- }
5938
- });
5939
- const dependentsByProvider = /* @__PURE__ */ new Map();
5940
- const inDegreeByChunk = /* @__PURE__ */ new Map();
5941
- for (const chunk of chunks) {
5942
- dependentsByProvider.set(chunk, /* @__PURE__ */ new Set());
5943
- inDegreeByChunk.set(chunk, 0);
5944
- }
5945
- for (const chunk of chunks) {
5946
- const providers = /* @__PURE__ */ new Set();
5947
- for (const ref of chunk.refs) {
5948
- if (ref.kind !== "internal") continue;
5949
- const provider = exportOwnerBySymbol.get(ref.name);
5950
- if (!provider || provider === chunk) continue;
5951
- providers.add(provider);
5952
- }
5953
- for (const provider of providers) {
5954
- dependentsByProvider.get(provider).add(chunk);
5955
- inDegreeByChunk.set(chunk, inDegreeByChunk.get(chunk) + 1);
5956
- }
6726
+ class VNextOasClientGenerator {
6727
+ constructor(options, log) {
6728
+ this.options = options;
6729
+ this.log = log;
5957
6730
  }
5958
- const ready = chunks.filter((chunk) => inDegreeByChunk.get(chunk) === 0).sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5959
- const ordered = [];
5960
- while (ready.length > 0) {
5961
- const provider = ready.shift();
5962
- ordered.push(provider);
5963
- for (const dependent of dependentsByProvider.get(provider)) {
5964
- const nextInDegree = inDegreeByChunk.get(dependent) - 1;
5965
- inDegreeByChunk.set(dependent, nextInDegree);
5966
- if (nextInDegree === 0) {
5967
- ready.push(dependent);
6731
+ async generate(cmd) {
6732
+ const { inputFile, outputDir, apiName } = cmd;
6733
+ this.log(`Will generate API client name=${apiName} to ${outputDir}`);
6734
+ const sourceSchema = await loadSourceSchema(inputFile);
6735
+ const normalizedSchema = this.normalizeSchema(sourceSchema, inputFile);
6736
+ const openApiIr = this.filterOperations(normalizedSchema);
6737
+ const projectResult = new OperationProjector({
6738
+ includeAllSchema: this.options.selection?.allSchemas === true,
6739
+ typeExtraction: {
6740
+ comments: this.options.codegen.comments.schema,
6741
+ enumStyle: this.options.codegen.enumStyle,
6742
+ uppercaseEnumKeys: this.options.compat?.uppercaseEnumKeys,
6743
+ swaggerTsApiRequiredBooleans: this.options.compat?.swaggerTsApiRequiredBooleans
5968
6744
  }
5969
- }
5970
- ready.sort((a, b) => chunkIndex.get(a.name) - chunkIndex.get(b.name));
5971
- }
5972
- if (ordered.length !== chunks.length) {
5973
- const cycleChunkNames = chunks.filter((chunk) => inDegreeByChunk.get(chunk) > 0).map((chunk) => chunk.name).join(", ");
5974
- throw new Error(
5975
- `Cannot order chunks with cyclic internal dependencies: ${cycleChunkNames}`
6745
+ }).project(openApiIr);
6746
+ const clientName = toPascalCaseIdentifier(apiName);
6747
+ const chunks = generateTsCode(
6748
+ clientName,
6749
+ projectResult,
6750
+ this.options.codegen
5976
6751
  );
6752
+ const codeFormatter = new CodeFormatter({
6753
+ resolveConfFrom: outputDir
6754
+ });
6755
+ const writer = new CodeWriter(codeFormatter);
6756
+ await writer.write(outputDir, chunks.all);
6757
+ const tsFilePath = (name) => Path.join(outputDir, `${name}.ts`);
6758
+ return {
6759
+ promiseWrapper: chunks.promiseWrappersNames.map(tsFilePath),
6760
+ types: tsFilePath(chunks.typesName)
6761
+ };
5977
6762
  }
5978
- return ordered;
5979
- };
5980
-
5981
- const generateFromIr = async ({
5982
- openApiIr,
5983
- clientName,
5984
- outputDir,
5985
- outputFilename,
5986
- codeFormattingConfigDir,
5987
- quirks,
5988
- requestParams: withRequestParams
5989
- }) => {
5990
- const tsCodegenFac = new TsCodegenFactory();
5991
- const projectResult = new OperationProjector({
5992
- quirks
5993
- }).project(openApiIr);
5994
- const { schemaDefinitions } = projectResult;
5995
- const requestParams = withRequestParams;
5996
- const types = schemaDefinitions.map((def) => {
5997
- const cg = tsCodegenFac.forNewChunk();
5998
- const typeName = def.name;
5999
- const code = `export type ${typeName} = ${cg.toCode(def.typeExpr)}`;
6000
- return cg.toChunk(typeName, code, [typeName]);
6001
- });
6002
- const clientCodegen = new ApiClientCodegen(
6003
- {
6004
- httpClient: promiseHttpClientCodegenSpec(),
6005
- requestParams,
6006
- // TODO make configurable
6007
- pathParamsStyle: "positional"
6008
- },
6009
- tsCodegenFac
6010
- );
6011
- const generated = clientCodegen.generate(
6012
- clientName,
6013
- groupsOpsByTag(projectResult.operations)
6014
- );
6015
- const httpClientChunk = tsCodegenFac.forNewChunk().toChunk(
6016
- "HttpClient",
6017
- getHttpClientCode(),
6018
- ["HttpRequest", "HttpResponse", "HttpClient"]
6019
- );
6020
- const codeFormatter = new CodeFormatter({
6021
- resolveConfFrom: codeFormattingConfigDir
6022
- });
6023
- const writer = new CodeWriter(codeFormatter);
6024
- let chunks = [...types, httpClientChunk, generated];
6025
- chunks = [mergeChunks(outputFilename, chunks)];
6026
- await writer.write(outputDir, chunks);
6027
- };
6028
- const groupsOpsByTag = (operations) => {
6029
- const groupedOps = {};
6030
- operations.forEach((operation) => {
6031
- const groupName = operation.tags[0]?.trim() || "root";
6032
- const bucket = groupedOps[groupName] ?? [];
6033
- bucket.push(operation.op);
6034
- groupedOps[groupName] = bucket;
6035
- });
6036
- return groupedOps;
6037
- };
6038
- const commonTypesCode = httpClientCommonTypesSource;
6039
- const promiseHttpClientCode = httpClientPromiseTypesSource;
6040
- const stripImports = (code) => code.replace(/^import[\s\S]*?from\s+['"][^'"]+['"]\s*;?\n?/gm, "").trim();
6041
- const getHttpClientCode = (opts) => [
6042
- commonTypesCode,
6043
- promiseHttpClientCode
6044
- ].map(stripImports).join("\n\n");
6045
-
6046
- const generateOpenApiClient$1 = async ({ input, name, outputDir, quirks, ignoreOperationsWithTags }, log) => {
6047
- log(`Will generate API client name=${name} to ${outputDir}`);
6048
- const sourceSchema = await loadSourceSchema(input);
6049
- const { result, problems } = new OpenApiNormalizer(sourceSchema).load();
6050
- problems.forEach((problem) => {
6051
- log(`[open-api:${problem.level}] ${problem.path}: ${problem.message}`);
6052
- });
6053
- const errors = problems.filter((problem) => problem.level === "error");
6054
- if (errors.length) {
6055
- throw new Error(
6056
- `Failed to load OpenAPI IR from ${input}: ${errors.length} error(s).`
6763
+ filterOperations(openApiIr) {
6764
+ const ignoreTags = new Set(
6765
+ this.options.selection?.ignoreOperationsWithTags || []
6057
6766
  );
6767
+ return {
6768
+ ...openApiIr,
6769
+ operations: openApiIr.operations.filter((operation) => {
6770
+ const hasUsableTags = operation.tags.some((tag) => tag.trim());
6771
+ if (!hasUsableTags) {
6772
+ this.log(
6773
+ `[open-api:warning] Ignoring operation ${operation.method.toUpperCase()} ${operation.path} (${operation.operationId ?? "<missing operationId>"}): no tags`
6774
+ );
6775
+ return false;
6776
+ }
6777
+ return !operation.tags.some((tag) => ignoreTags.has(tag));
6778
+ })
6779
+ };
6058
6780
  }
6059
- const openApiIr = filterOperations(result, log, ignoreOperationsWithTags);
6060
- const outputFilename = "index";
6061
- await generateFromIr({
6062
- openApiIr,
6063
- clientName: toPascalCaseIdentifier(name),
6064
- outputDir,
6065
- outputFilename,
6066
- codeFormattingConfigDir: outputDir,
6067
- quirks,
6068
- requestParams: true
6069
- });
6070
- return Path.join(outputDir, `${outputFilename}.ts`);
6071
- };
6781
+ normalizeSchema(rawSchema, inputFile) {
6782
+ const { result, problems } = new OpenApiNormalizer(rawSchema).load();
6783
+ problems.forEach((problem) => {
6784
+ this.log(
6785
+ `[open-api:${problem.level}] ${problem.path}: ${problem.message}`
6786
+ );
6787
+ });
6788
+ const errors = problems.filter((problem) => problem.level === "error");
6789
+ if (errors.length) {
6790
+ throw new Error(
6791
+ `Failed to load OpenAPI IR from ${inputFile}: ${errors.length} error(s).`
6792
+ );
6793
+ }
6794
+ return result;
6795
+ }
6796
+ }
6072
6797
  const loadSourceSchema = async (input) => {
6073
6798
  let rawContent;
6074
6799
  try {
@@ -6091,109 +6816,60 @@ const loadSourceSchema = async (input) => {
6091
6816
  }
6092
6817
  return parsed;
6093
6818
  };
6094
- const filterOperations = (openApiIr, log, ignoreOperationsWithTags) => {
6095
- const ignoreTags = new Set(ignoreOperationsWithTags);
6096
- return {
6097
- ...openApiIr,
6098
- operations: openApiIr.operations.filter((operation) => {
6099
- const hasUsableTags = operation.tags.some((tag) => tag.trim());
6100
- if (!hasUsableTags) {
6101
- log(
6102
- `[open-api:warning] Ignoring operation ${operation.method.toUpperCase()} ${operation.path} (${operation.operationId ?? "<missing operationId>"}): no tags`
6103
- );
6104
- return false;
6105
- }
6106
- return !operation.tags.some((tag) => ignoreTags.has(tag));
6107
- })
6108
- };
6109
- };
6110
6819
  const toErrorMessage = (error) => {
6111
6820
  return error instanceof Error ? error.message : String(error);
6112
6821
  };
6113
6822
 
6114
- const generateOpenApiClient = async ({ input, name, outputDir, ignoreOperationsWithTags }, log) => {
6115
- log(`Will generate API client name=${name} to ${outputDir}`);
6116
- const fileName = "index";
6117
- const dstFile = Path$1.join(outputDir, `${fileName}.ts`);
6118
- const prettier = await import('prettier');
6119
- const prettierConfig = await prettier.resolveConfig(dstFile) ?? {};
6120
- const prettierConfigForGenerator = {
6121
- ...prettierConfig,
6122
- // swagger-typescript-api defaults to printWidth=120; keep Prettier default 80 unless explicitly configured.
6123
- printWidth: prettierConfig.printWidth ?? 80
6124
- };
6125
- const ignoreTags = ignoreOperationsWithTags && new Set(ignoreOperationsWithTags);
6126
- await generateApi({
6127
- name: "index",
6128
- output: outputDir,
6129
- input,
6130
- // modular: true,
6131
- httpClientType: "axios",
6132
- templates: getTemplatesDir(),
6133
- defaultResponseAsSuccess: true,
6134
- sortTypes: true,
6135
- // generateRouteTypes: true,
6136
- singleHttpClient: true,
6137
- // extractRequestBody: true,
6138
- prettier: prettierConfigForGenerator,
6139
- moduleNameFirstTag: true,
6140
- apiClassName: toPascalCaseIdentifier(name),
6141
- hooks: {
6142
- onCreateRoute: (routeData) => {
6143
- if (ignoreTags) {
6144
- const ignore = routeData.raw.tags?.find(
6145
- (t) => ignoreTags.has(t)
6146
- );
6147
- if (ignore) return false;
6148
- }
6149
- return routeData;
6150
- }
6151
- },
6152
- // @ts-ignore
6153
- codeGenConstructs: () => ({
6154
- Keyword: {}
6155
- })
6156
- // extractRequestParams: true,
6157
- });
6158
- return dstFile;
6159
- };
6160
- const getThisScriptDirname = () => {
6161
- return Path$1.dirname(fileURLToPath(import.meta.url));
6162
- };
6163
- const getTemplatesDir = () => {
6164
- const currentDir = getThisScriptDirname();
6165
- const templatesRelativePath = Path$1.basename(currentDir) === "dist" ? "../templates" : "../../../templates";
6166
- return Path$1.resolve(currentDir, templatesRelativePath);
6167
- };
6168
-
6169
- const SUPPORTED_GENERATORS = [
6170
- "swagger-ts-api",
6171
- "api-client-generator"
6172
- ];
6173
- const OPEN_API_MODEL_GENERATORS = {
6174
- "swagger-ts-api": generateOpenApiClient,
6175
- "api-client-generator": generateOpenApiClient$1
6176
- };
6177
- const generateOpenApiModel = async (generator, cmd, log) => {
6178
- const generatorFun = OPEN_API_MODEL_GENERATORS[generator];
6179
- const outputPath = await generatorFun(cmd, log);
6180
- const outputModifiers = getOutputModifiers(cmd, log);
6181
- log(`Will modify the outputs ${JSON.stringify(outputModifiers)}`);
6182
- await modifyOutput(outputPath, outputModifiers);
6183
- return outputPath;
6823
+ const generateOpenApiModel = async (spectPath, config, log) => {
6824
+ let outFiles;
6825
+ const { openApiGenerator, responseWrapper } = config;
6826
+ if ("legacy" in openApiGenerator) {
6827
+ outFiles = await generateOpenApiClient(
6828
+ spectPath,
6829
+ {
6830
+ ignoreOperationsWithTags: openApiGenerator.legacy.ignoreOperationsWithTags,
6831
+ name: config.apiName,
6832
+ outputDir: config.dstDir
6833
+ },
6834
+ log
6835
+ );
6836
+ } else {
6837
+ outFiles = await new VNextOasClientGenerator(
6838
+ openApiGenerator,
6839
+ log
6840
+ ).generate({
6841
+ inputFile: spectPath,
6842
+ apiName: config.apiName,
6843
+ outputDir: config.dstDir
6844
+ });
6845
+ }
6846
+ if (responseWrapper) {
6847
+ await modifyHttpClientAsyncWrapper(
6848
+ outFiles.promiseWrapper,
6849
+ responseWrapper,
6850
+ log
6851
+ );
6852
+ }
6853
+ return { typesFilePath: outFiles.types };
6184
6854
  };
6185
- const getOutputModifiers = ({ responseWrapper }, log) => {
6855
+ const modifyHttpClientAsyncWrapper = async (filePaths, responseWrapper, log) => {
6856
+ log(
6857
+ `Will use response wrapper '${JSON.stringify(responseWrapper)}' on file ${filePaths.join(", ")}`
6858
+ );
6186
6859
  const addImports = [];
6187
- const replaces = [];
6188
- if (responseWrapper) {
6189
- log(`Will use response wrapper '${JSON.stringify(responseWrapper)}'`);
6190
- if (responseWrapper.import) addImports.push(responseWrapper.import);
6191
- replaces.push(["Promise", responseWrapper.symbol]);
6860
+ if (responseWrapper.import) {
6861
+ addImports.push(responseWrapper.import);
6862
+ }
6863
+ const replaces = [["Promise", responseWrapper.symbol]];
6864
+ if (responseWrapper.unwrapExpr) {
6865
+ replaces.push([".then(res => res.body)", responseWrapper.unwrapExpr]);
6866
+ }
6867
+ for (const filePath of filePaths) {
6868
+ await modifyOutput(filePath, {
6869
+ addImports,
6870
+ replaces
6871
+ });
6192
6872
  }
6193
- return {
6194
- addImports,
6195
- replaces
6196
- };
6197
6873
  };
6198
6874
  const modifyOutput = async (path, cmd) => {
6199
6875
  let content = await fs$1.readFile(path).then((r) => r.toString());
@@ -6255,13 +6931,11 @@ const findMatchingBrace = (code, openBraceIndex) => {
6255
6931
  };
6256
6932
 
6257
6933
  const generateSchemas = async (inputFile, outputFile, opts = {}) => {
6258
- const code = fs__default.readFileSync(inputFile).toString();
6934
+ const code = fs__default$1.readFileSync(inputFile).toString();
6259
6935
  const { getZodSchemasFile } = generate({
6260
6936
  sourceText: removeGenericTypes(code)
6261
6937
  });
6262
- const fileName = Path$1.basename(inputFile);
6263
- let f = getZodSchemasFile(`./${fileName.replace(".ts", "")}`);
6264
- f = f.replaceAll('"./index";', '"./client"');
6938
+ let f = getZodSchemasFile(getModuleImportPath(inputFile, outputFile));
6265
6939
  f = f.replaceAll(".optional()", ".nullable()");
6266
6940
  if (opts.localDateTimes) {
6267
6941
  f = f.replaceAll(
@@ -6275,52 +6949,30 @@ const generateSchemas = async (inputFile, outputFile, opts = {}) => {
6275
6949
  ...prettierConfig,
6276
6950
  filepath: outputFile
6277
6951
  });
6278
- fs__default.writeFileSync(outputFile, formatted);
6952
+ fs__default$1.writeFileSync(outputFile, formatted);
6953
+ };
6954
+ const getModuleImportPath = (inputFile, outputFile) => {
6955
+ const sourceDir = Path.dirname(outputFile);
6956
+ const relativePath = Path.relative(sourceDir, inputFile);
6957
+ const withoutExtension = relativePath.replace(/\.ts$/u, "");
6958
+ const normalizedPath = withoutExtension.split(Path.sep).join("/");
6959
+ return normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
6279
6960
  };
6280
6961
 
6281
- const DEFAULT_GENERATOR = "api-client-generator";
6282
- const generateApiClientParamsSchema = strictObject({
6283
- srcSpec: string().trim().min(1),
6284
- dstDir: string().trim().min(1),
6285
- apiName: string().trim().min(1),
6286
- generator: _enum(SUPPORTED_GENERATORS).default(DEFAULT_GENERATOR),
6287
- quirks: strictObject({
6288
- swaggerTsApiRequiredBooleans: boolean().optional()
6289
- }).optional(),
6290
- responseWrapper: strictObject({
6291
- symbol: string().trim().min(1),
6292
- import: string().trim().min(1).optional()
6293
- }).optional(),
6294
- ignoreOperationsWithTags: array(string()).optional(),
6295
- zodSchemas: strictObject({
6296
- enabled: boolean().optional(),
6297
- localDateTimes: boolean().optional()
6298
- }).optional()
6299
- });
6300
- const parseGenerateApiClientParams = (params) => {
6301
- const parsed = generateApiClientParamsSchema.safeParse(params);
6302
- if (parsed.success) return parsed.data;
6303
- throw new Error(prettifyError(parsed.error));
6962
+ const generateApiClient = async (params, configFilePath, log = console.log) => {
6963
+ const config = parseProfileConfig(params, configFilePath);
6964
+ return generateApiClientFromConfig(config, log);
6304
6965
  };
6305
- const generateApiClient = async (params, log = console.log) => {
6306
- const {
6307
- dstDir,
6308
- apiName,
6309
- srcSpec,
6310
- quirks,
6311
- responseWrapper,
6312
- ignoreOperationsWithTags,
6313
- zodSchemas,
6314
- generator
6315
- } = parseGenerateApiClientParams(params);
6316
- const dir = Path$1.resolve(dstDir, apiName);
6966
+ const generateApiClientFromConfig = async (config, log = console.log) => {
6967
+ const { dstDir, apiName, srcSpec, zodSchemas } = config;
6968
+ const dir = Path.resolve(dstDir, apiName);
6317
6969
  if (!fs.existsSync(dir)) {
6318
6970
  log(`Creating dir ${dir}`);
6319
6971
  fs.mkdirSync(dir, {
6320
6972
  recursive: true
6321
6973
  });
6322
6974
  }
6323
- const clientDir = Path$1.resolve(dir, "client");
6975
+ const clientDir = Path.resolve(dir, "client");
6324
6976
  const specPath = await getSpecPath(srcSpec, dir, log);
6325
6977
  log(`Cleaning client output dir ${clientDir}`);
6326
6978
  fs.rmSync(clientDir, {
@@ -6330,23 +6982,19 @@ const generateApiClient = async (params, log = console.log) => {
6330
6982
  fs.mkdirSync(clientDir, {
6331
6983
  recursive: true
6332
6984
  });
6333
- const clientFile = await generateOpenApiModel(
6334
- generator,
6985
+ const { typesFilePath } = await generateOpenApiModel(
6986
+ specPath,
6335
6987
  {
6336
- name: apiName,
6337
- input: specPath,
6338
- outputDir: clientDir,
6339
- quirks,
6340
- ignoreOperationsWithTags,
6341
- responseWrapper
6988
+ ...config,
6989
+ dstDir: clientDir
6342
6990
  },
6343
6991
  log
6344
6992
  );
6345
6993
  if (zodSchemas?.enabled === false) return;
6346
6994
  log("Generating Zod schemas");
6347
6995
  await generateSchemas(
6348
- Path$1.resolve(dir, clientFile),
6349
- Path$1.resolve(dir, "./zod.ts"),
6996
+ typesFilePath,
6997
+ Path.resolve(dir, "./zod.ts"),
6350
6998
  zodSchemas
6351
6999
  );
6352
7000
  };
@@ -6356,10 +7004,10 @@ const getSpecPath = async (urlOrPath, dir, log) => {
6356
7004
  throw new Error(`Spec file ${urlOrPath} does not exists`);
6357
7005
  return urlOrPath;
6358
7006
  }
6359
- const specPath = Path$1.resolve(dir, `spec.json`);
7007
+ const specPath = Path.resolve(dir, `spec.json`);
6360
7008
  log(`Will download the API spec from ${urlOrPath} to ${specPath}`);
6361
7009
  await downloadSpec(specPath, urlOrPath);
6362
7010
  return specPath;
6363
7011
  };
6364
7012
 
6365
- export { generateApiClient as g };
7013
+ export { generateApiClientFromConfig as a, defineConfig as d, generateApiClient as g, parseConfig as p };