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