ng-openapi 0.2.22 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (5) hide show
  1. package/README.md +41 -17
  2. package/cli.cjs +1199 -857
  3. package/index.d.ts +529 -83
  4. package/index.js +1265 -906
  5. package/package.json +1 -1
package/cli.cjs CHANGED
@@ -24,6 +24,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
24
24
  mod
25
25
  ));
26
26
 
27
+ // ../shared/src/core/spec-loader.ts
28
+ var fs = __toESM(require("fs"));
29
+
27
30
  // ../shared/src/utils/functions/is-url.ts
28
31
  function isUrl(input) {
29
32
  try {
@@ -38,16 +41,197 @@ function isUrl(input) {
38
41
  }
39
42
  __name(isUrl, "isUrl");
40
43
 
41
- // src/lib/cli.ts
42
- var import_commander = require("commander");
43
- var fs4 = __toESM(require("fs"));
44
- var path13 = __toESM(require("path"));
44
+ // ../shared/src/errors.ts
45
+ var NgOpenApiError = class extends Error {
46
+ static {
47
+ __name(this, "NgOpenApiError");
48
+ }
49
+ /** The underlying error that caused this one, when there is one. */
50
+ cause;
51
+ constructor(message, cause) {
52
+ super(message);
53
+ this.name = new.target.name;
54
+ this.cause = cause;
55
+ }
56
+ };
57
+ var SpecLoadError = class extends NgOpenApiError {
58
+ static {
59
+ __name(this, "SpecLoadError");
60
+ }
61
+ /** The file path or URL that failed to load. */
62
+ source;
63
+ constructor(message, source, cause) {
64
+ super(message, cause);
65
+ this.source = source;
66
+ }
67
+ };
68
+ var SpecParseError = class extends NgOpenApiError {
69
+ static {
70
+ __name(this, "SpecParseError");
71
+ }
72
+ /** The file path or URL the content came from, when known. */
73
+ source;
74
+ constructor(message, source, cause) {
75
+ super(message, cause);
76
+ this.source = source;
77
+ }
78
+ };
45
79
 
46
- // package.json
47
- var version = "0.2.22";
80
+ // ../shared/src/core/spec-loader.ts
81
+ async function loadSpecContent(pathOrUrl) {
82
+ if (isUrl(pathOrUrl)) {
83
+ return await fetchUrlContent(pathOrUrl);
84
+ }
85
+ try {
86
+ return fs.readFileSync(pathOrUrl, "utf8");
87
+ } catch (error) {
88
+ throw new SpecLoadError(`Failed to read spec file: ${pathOrUrl}${error instanceof Error ? ` - ${error.message}` : ""}`, pathOrUrl, error);
89
+ }
90
+ }
91
+ __name(loadSpecContent, "loadSpecContent");
92
+ async function fetchUrlContent(url) {
93
+ try {
94
+ const response = await fetch(url, {
95
+ method: "GET",
96
+ headers: {
97
+ Accept: "application/json, application/yaml, text/yaml, text/plain, */*",
98
+ "User-Agent": "ng-openapi"
99
+ },
100
+ // 30 second timeout
101
+ signal: AbortSignal.timeout(3e4)
102
+ });
103
+ if (!response.ok) {
104
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
105
+ }
106
+ const content = await response.text();
107
+ if (!content || content.trim() === "") {
108
+ throw new Error(`Empty response from URL: ${url}`);
109
+ }
110
+ return content;
111
+ } catch (error) {
112
+ let errorMessage = `Failed to fetch content from URL: ${url}`;
113
+ if (error instanceof Error && (error.name === "TimeoutError" || error.name === "AbortError")) {
114
+ errorMessage += " - Request timeout (30s)";
115
+ } else if (error instanceof Error && error.message) {
116
+ errorMessage += ` - ${error.message}`;
117
+ }
118
+ throw new SpecLoadError(errorMessage, url, error);
119
+ }
120
+ }
121
+ __name(fetchUrlContent, "fetchUrlContent");
48
122
 
49
- // src/lib/core/generator.ts
50
- var import_ts_morph7 = require("ts-morph");
123
+ // ../shared/src/core/spec-format.ts
124
+ var path = __toESM(require("path"));
125
+ var yaml = __toESM(require("js-yaml"));
126
+ function parseSpecContent(content, pathOrUrl) {
127
+ let format;
128
+ if (isUrl(pathOrUrl)) {
129
+ const urlPath = new URL(pathOrUrl).pathname.toLowerCase();
130
+ if (urlPath.endsWith(".json")) {
131
+ format = "json";
132
+ } else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) {
133
+ format = "yaml";
134
+ } else {
135
+ format = detectFormat(content);
136
+ }
137
+ } else {
138
+ const extension = path.extname(pathOrUrl).toLowerCase();
139
+ switch (extension) {
140
+ case ".json":
141
+ format = "json";
142
+ break;
143
+ case ".yaml":
144
+ format = "yaml";
145
+ break;
146
+ case ".yml":
147
+ format = "yml";
148
+ break;
149
+ default:
150
+ format = detectFormat(content);
151
+ }
152
+ }
153
+ try {
154
+ switch (format) {
155
+ case "json":
156
+ return JSON.parse(content);
157
+ case "yaml":
158
+ case "yml":
159
+ return yaml.load(content);
160
+ default:
161
+ throw new Error(`Unable to determine format for: ${pathOrUrl}`);
162
+ }
163
+ } catch (error) {
164
+ throw new SpecParseError(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`, pathOrUrl, error);
165
+ }
166
+ }
167
+ __name(parseSpecContent, "parseSpecContent");
168
+ function detectFormat(content) {
169
+ const trimmed = content.trim();
170
+ if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
171
+ return "json";
172
+ }
173
+ if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) {
174
+ return "yaml";
175
+ }
176
+ return "json";
177
+ }
178
+ __name(detectFormat, "detectFormat");
179
+
180
+ // ../shared/src/utils/content-types.constants.ts
181
+ var CONTENT_TYPES = {
182
+ MULTIPART: "multipart/form-data",
183
+ FORM_URLENCODED: "application/x-www-form-urlencoded",
184
+ JSON: "application/json"
185
+ };
186
+
187
+ // ../shared/src/utils/functions/extract-paths.ts
188
+ function extractPaths(swaggerPaths = {}, methods = [
189
+ "get",
190
+ "post",
191
+ "put",
192
+ "patch",
193
+ "delete",
194
+ "options",
195
+ "head"
196
+ ]) {
197
+ const paths = [];
198
+ Object.entries(swaggerPaths).forEach(([path14, pathItem]) => {
199
+ methods.forEach((method) => {
200
+ const operation = pathItem[method];
201
+ if (operation) {
202
+ paths.push({
203
+ path: path14,
204
+ method: method.toUpperCase(),
205
+ operationId: operation.operationId,
206
+ summary: operation.summary,
207
+ description: operation.description,
208
+ tags: operation.tags || [],
209
+ parameters: parseParameters(operation.parameters || [], pathItem.parameters || []),
210
+ requestBody: operation.requestBody,
211
+ responses: operation.responses || {}
212
+ });
213
+ }
214
+ });
215
+ });
216
+ return paths;
217
+ }
218
+ __name(extractPaths, "extractPaths");
219
+ function parseParameters(operationParams, pathParams) {
220
+ const allParams = [
221
+ ...pathParams,
222
+ ...operationParams
223
+ ];
224
+ return allParams.map((param) => ({
225
+ name: param.name,
226
+ in: param.in,
227
+ required: param.required || param.in === "path",
228
+ schema: param.schema,
229
+ type: param.type,
230
+ format: param.format,
231
+ description: param.description
232
+ }));
233
+ }
234
+ __name(parseParameters, "parseParameters");
51
235
 
52
236
  // ../shared/src/utils/string.utils.ts
53
237
  function camelCase(str) {
@@ -85,7 +269,7 @@ function getTypeScriptType(schemaOrType, config, formatOrNullable, isNullable, c
85
269
  return nullableType(pascalCaseForEnums(refName), nullable);
86
270
  }
87
271
  if (schema.type === "array") {
88
- const itemType = schema.items ? getTypeScriptType(schema.items, config, void 0, void 0, context) : "unknown";
272
+ const itemType = schema.items ? Array.isArray(schema.items) ? "any" : getTypeScriptType(schema.items, config, void 0, void 0, context) : "unknown";
89
273
  return nullable ? `(Array<${itemType}> | null)` : `Array<${itemType}>`;
90
274
  }
91
275
  switch (schema.type) {
@@ -132,78 +316,6 @@ function escapeString(str) {
132
316
  }
133
317
  __name(escapeString, "escapeString");
134
318
 
135
- // ../shared/src/utils/functions/token-names.ts
136
- function getClientContextTokenName(clientName = "default") {
137
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
138
- return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
139
- }
140
- __name(getClientContextTokenName, "getClientContextTokenName");
141
- function getBasePathTokenName(clientName = "default") {
142
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
143
- return `BASE_PATH_${clientSuffix}`;
144
- }
145
- __name(getBasePathTokenName, "getBasePathTokenName");
146
- function getInterceptorsTokenName(clientName = "default") {
147
- const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
148
- return `HTTP_INTERCEPTORS_${clientSuffix}`;
149
- }
150
- __name(getInterceptorsTokenName, "getInterceptorsTokenName");
151
-
152
- // ../shared/src/utils/functions/duplicate-function-name.ts
153
- function hasDuplicateFunctionNames(arr) {
154
- return new Set(arr.map((fn) => fn.getName())).size !== arr.length;
155
- }
156
- __name(hasDuplicateFunctionNames, "hasDuplicateFunctionNames");
157
-
158
- // ../shared/src/utils/functions/extract-paths.ts
159
- function extractPaths(swaggerPaths = {}, methods = [
160
- "get",
161
- "post",
162
- "put",
163
- "patch",
164
- "delete",
165
- "options",
166
- "head"
167
- ]) {
168
- const paths = [];
169
- Object.entries(swaggerPaths).forEach(([path14, pathItem]) => {
170
- methods.forEach((method) => {
171
- if (pathItem[method]) {
172
- const operation = pathItem[method];
173
- paths.push({
174
- path: path14,
175
- method: method.toUpperCase(),
176
- operationId: operation.operationId,
177
- summary: operation.summary,
178
- description: operation.description,
179
- tags: operation.tags || [],
180
- parameters: parseParameters(operation.parameters || [], pathItem.parameters || []),
181
- requestBody: operation.requestBody,
182
- responses: operation.responses || {}
183
- });
184
- }
185
- });
186
- });
187
- return paths;
188
- }
189
- __name(extractPaths, "extractPaths");
190
- function parseParameters(operationParams, pathParams) {
191
- const allParams = [
192
- ...pathParams,
193
- ...operationParams
194
- ];
195
- return allParams.map((param) => ({
196
- name: param.name,
197
- in: param.in,
198
- required: param.required || param.in === "path",
199
- schema: param.schema,
200
- type: param.type,
201
- format: param.format,
202
- description: param.description
203
- }));
204
- }
205
- __name(parseParameters, "parseParameters");
206
-
207
319
  // ../shared/src/utils/functions/extract-swagger-response-type.ts
208
320
  function getResponseTypeFromResponse(response, responseTypeMapping) {
209
321
  const content = response.content || {};
@@ -267,7 +379,7 @@ function isPrimitiveType(schema) {
267
379
  "integer",
268
380
  "boolean"
269
381
  ];
270
- if (primitiveTypes.includes(schema.type)) {
382
+ if (schema.type && primitiveTypes.includes(schema.type)) {
271
383
  return true;
272
384
  }
273
385
  if (schema.type === "array") {
@@ -338,199 +450,204 @@ function getResponseType(response, config) {
338
450
  }
339
451
  __name(getResponseType, "getResponseType");
340
452
 
341
- // ../shared/src/utils/content-types.constants.ts
342
- var CONTENT_TYPES = {
343
- MULTIPART: "multipart/form-data",
344
- FORM_URLENCODED: "application/x-www-form-urlencoded",
345
- JSON: "application/json"
346
- };
347
-
348
- // ../shared/src/utils/functions/get-request-body-type.ts
349
- function getRequestBodyType(requestBody, config) {
350
- const content = requestBody.content || {};
351
- const jsonContent = content[CONTENT_TYPES.JSON];
352
- if (jsonContent?.schema) {
353
- return getTypeScriptType(jsonContent.schema, config, jsonContent.schema.nullable);
453
+ // ../shared/src/core/normalize.ts
454
+ function normalizeSpec(spec) {
455
+ const rawDefinitions = spec.definitions || spec.components?.schemas || {};
456
+ const definitions = Object.fromEntries(Object.entries(rawDefinitions).map(([name, definition]) => [
457
+ name,
458
+ normalizeSchema(definition)
459
+ ]));
460
+ const resolveReference = /* @__PURE__ */ __name((ref) => {
461
+ const parts = ref.split("/");
462
+ return definitions[parts[parts.length - 1]];
463
+ }, "resolveReference");
464
+ return {
465
+ version: spec.swagger ? {
466
+ type: "swagger",
467
+ version: spec.swagger
468
+ } : spec.openapi ? {
469
+ type: "openapi",
470
+ version: spec.openapi
471
+ } : null,
472
+ definitions,
473
+ operations: extractPaths(spec.paths).map((operation) => normalizeOperation(normalizeOperationSchemas(operation), resolveReference)),
474
+ resolveReference
475
+ };
476
+ }
477
+ __name(normalizeSpec, "normalizeSpec");
478
+ function normalizeSchema(schema) {
479
+ const normalized = {
480
+ ...schema
481
+ };
482
+ const rawType = normalized.type;
483
+ if (Array.isArray(rawType)) {
484
+ const types = rawType.filter((t) => t !== "null");
485
+ if (types.length < rawType.length) {
486
+ normalized.nullable = true;
487
+ }
488
+ normalized.type = types.length === 1 ? types[0] : types.length === 0 ? "null" : types;
489
+ }
490
+ if (normalized.const !== void 0 && !normalized.enum) {
491
+ const constType = typeof normalized.const;
492
+ if (constType === "string" || constType === "number") {
493
+ normalized.enum = [
494
+ normalized.const
495
+ ];
496
+ if (normalized.type === void 0) {
497
+ normalized.type = constType;
498
+ }
499
+ delete normalized.const;
500
+ } else if (constType === "boolean") {
501
+ if (normalized.type === void 0) {
502
+ normalized.type = "boolean";
503
+ }
504
+ delete normalized.const;
505
+ }
354
506
  }
355
- return "any";
507
+ if (normalized.properties) {
508
+ normalized.properties = Object.fromEntries(Object.entries(normalized.properties).map(([name, property]) => [
509
+ name,
510
+ normalizeSchema(property)
511
+ ]));
512
+ }
513
+ if (normalized.items) {
514
+ normalized.items = Array.isArray(normalized.items) ? normalized.items.map(normalizeSchema) : normalizeSchema(normalized.items);
515
+ }
516
+ if (typeof normalized.additionalProperties === "object") {
517
+ normalized.additionalProperties = normalizeSchema(normalized.additionalProperties);
518
+ }
519
+ if (normalized.allOf) {
520
+ normalized.allOf = normalized.allOf.map(normalizeSchema);
521
+ }
522
+ if (normalized.oneOf) {
523
+ normalized.oneOf = normalized.oneOf.map(normalizeSchema);
524
+ }
525
+ if (normalized.anyOf) {
526
+ normalized.anyOf = normalized.anyOf.map(normalizeSchema);
527
+ }
528
+ return normalized;
356
529
  }
357
- __name(getRequestBodyType, "getRequestBodyType");
358
-
359
- // ../shared/src/utils/functions/is-data-type-interface.ts
360
- function isDataTypeInterface(type) {
361
- const invalidTypes = [
362
- "any",
363
- "File",
364
- "string",
365
- "number",
366
- "boolean",
367
- "object",
368
- "unknown",
369
- "[]",
370
- "Array"
530
+ __name(normalizeSchema, "normalizeSchema");
531
+ function normalizeOperationSchemas(operation) {
532
+ return {
533
+ ...operation,
534
+ parameters: operation.parameters?.map((parameter) => parameter.schema ? {
535
+ ...parameter,
536
+ schema: normalizeSchema(parameter.schema)
537
+ } : parameter),
538
+ requestBody: operation.requestBody ? {
539
+ ...operation.requestBody,
540
+ content: normalizeContentSchemas(operation.requestBody.content)
541
+ } : operation.requestBody,
542
+ responses: operation.responses ? Object.fromEntries(Object.entries(operation.responses).map(([status, response]) => [
543
+ status,
544
+ {
545
+ ...response,
546
+ content: normalizeContentSchemas(response.content)
547
+ }
548
+ ])) : operation.responses
549
+ };
550
+ }
551
+ __name(normalizeOperationSchemas, "normalizeOperationSchemas");
552
+ function normalizeContentSchemas(content) {
553
+ if (!content) return content;
554
+ return Object.fromEntries(Object.entries(content).map(([contentType, mediaType]) => [
555
+ contentType,
556
+ mediaType?.schema ? {
557
+ ...mediaType,
558
+ schema: normalizeSchema(mediaType.schema)
559
+ } : mediaType
560
+ ]));
561
+ }
562
+ __name(normalizeContentSchemas, "normalizeContentSchemas");
563
+ function normalizeOperation(operation, resolveRef) {
564
+ const content = operation.requestBody?.content;
565
+ const isMultipart = !!content?.[CONTENT_TYPES.MULTIPART];
566
+ const isUrlEncoded = !!content?.[CONTENT_TYPES.FORM_URLENCODED] && !content?.[CONTENT_TYPES.JSON];
567
+ const formDataSchema = isMultipart ? resolveBodySchema(operation.requestBody, CONTENT_TYPES.MULTIPART, resolveRef) : void 0;
568
+ const urlEncodedSchema = isUrlEncoded ? resolveBodySchema(operation.requestBody, CONTENT_TYPES.FORM_URLENCODED, resolveRef) : void 0;
569
+ return {
570
+ ...operation,
571
+ pathParams: operation.parameters?.filter((p) => p.in === "path") || [],
572
+ queryParams: operation.parameters?.filter((p) => p.in === "query") || [],
573
+ hasBody: !!operation.requestBody,
574
+ isMultipart,
575
+ isUrlEncoded,
576
+ formDataSchema,
577
+ formDataFields: Object.keys(formDataSchema?.properties || {}),
578
+ urlEncodedSchema,
579
+ urlEncodedFields: Object.keys(urlEncodedSchema?.properties || {}),
580
+ responseType: determineResponseType(operation)
581
+ };
582
+ }
583
+ __name(normalizeOperation, "normalizeOperation");
584
+ function resolveBodySchema(requestBody, contentType, resolveRef) {
585
+ const schema = requestBody?.content?.[contentType]?.schema;
586
+ return schema?.$ref ? resolveRef(schema.$ref) : schema;
587
+ }
588
+ __name(resolveBodySchema, "resolveBodySchema");
589
+ function determineResponseType(operation) {
590
+ const successResponses = [
591
+ "200",
592
+ "201",
593
+ "202",
594
+ "204",
595
+ "206"
371
596
  ];
372
- return !invalidTypes.some((invalidType) => type.includes(invalidType));
597
+ for (const statusCode of successResponses) {
598
+ const response = operation.responses?.[statusCode];
599
+ if (!response) continue;
600
+ return getResponseTypeFromResponse(response);
601
+ }
602
+ return "json";
373
603
  }
374
- __name(isDataTypeInterface, "isDataTypeInterface");
375
-
376
- // ../shared/src/config/constants.ts
377
- var disableLinting = `/* @ts-nocheck */
378
- /* eslint-disable */
379
- /* @noformat */
380
- /* @formatter:off */
381
- `;
382
- var authorComment = `/**
383
- * Generated by ng-openapi
384
- `;
385
- var defaultHeaderComment = disableLinting + authorComment;
386
- var TYPE_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated TypeScript interfaces from Swagger specification
387
- * Do not edit this file manually
388
- */
389
- `;
390
- var SERVICE_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated service exports
391
- * Do not edit this file manually
392
- */
393
- `;
394
- var SERVICE_GENERATOR_HEADER_COMMENT = /* @__PURE__ */ __name((controllerName) => defaultHeaderComment + `* Generated Angular service for ${controllerName} controller
395
- * Do not edit this file manually
396
- */
397
- `, "SERVICE_GENERATOR_HEADER_COMMENT");
398
- var REQUEST_PARAMS_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated request parameter interfaces
399
- * Do not edit this file manually
400
- */
401
- `;
402
- var MAIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Entrypoint for the client
403
- * Do not edit this file manually
404
- */
405
- `;
406
- var PROVIDER_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated provider functions for easy setup
407
- * Do not edit this file manually
408
- */
409
- `;
410
- var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => defaultHeaderComment + `* Generated Base Interceptor for client ${clientName}
411
- * Do not edit this file manually
412
- */
413
- `, "BASE_INTERCEPTOR_HEADER_COMMENT");
414
- var ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated Zod Schemas exports
415
- * Do not edit this file manually
416
- */
417
- `;
604
+ __name(determineResponseType, "determineResponseType");
418
605
 
419
606
  // ../shared/src/core/swagger-parser.ts
420
- var fs = __toESM(require("fs"));
421
- var path = __toESM(require("path"));
422
- var yaml = __toESM(require("js-yaml"));
423
607
  var SwaggerParser = class _SwaggerParser {
424
608
  static {
425
609
  __name(this, "SwaggerParser");
426
610
  }
427
611
  spec;
612
+ normalized;
428
613
  constructor(spec, config) {
429
614
  const isInputValid = config.validateInput?.(spec) ?? true;
430
615
  if (!isInputValid) {
431
- throw new Error("Swagger spec is not valid. Check your `validateInput` condition.");
616
+ throw new SpecParseError("Swagger spec is not valid. Check your `validateInput` condition.");
432
617
  }
433
618
  this.spec = spec;
434
619
  }
620
+ /**
621
+ * Loads, parses and wraps a spec.
622
+ *
623
+ * @throws SpecLoadError when the file/URL cannot be read.
624
+ * @throws SpecParseError when the content cannot be parsed or the
625
+ * config's `validateInput` hook rejects the spec.
626
+ */
435
627
  static async create(swaggerPathOrUrl, config) {
436
- const swaggerContent = await _SwaggerParser.loadContent(swaggerPathOrUrl);
437
- const spec = _SwaggerParser.parseSpecContent(swaggerContent, swaggerPathOrUrl);
628
+ const swaggerContent = await loadSpecContent(swaggerPathOrUrl);
629
+ const spec = parseSpecContent(swaggerContent, swaggerPathOrUrl);
438
630
  return new _SwaggerParser(spec, config);
439
631
  }
440
- static async loadContent(pathOrUrl) {
441
- if (isUrl(pathOrUrl)) {
442
- return await _SwaggerParser.fetchUrlContent(pathOrUrl);
443
- } else {
444
- return fs.readFileSync(pathOrUrl, "utf8");
445
- }
446
- }
447
- static async fetchUrlContent(url) {
448
- try {
449
- const response = await fetch(url, {
450
- method: "GET",
451
- headers: {
452
- Accept: "application/json, application/yaml, text/yaml, text/plain, */*",
453
- "User-Agent": "ng-openapi"
454
- },
455
- // 30 second timeout
456
- signal: AbortSignal.timeout(3e4)
457
- });
458
- if (!response.ok) {
459
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
460
- }
461
- const content = await response.text();
462
- if (!content || content.trim() === "") {
463
- throw new Error(`Empty response from URL: ${url}`);
464
- }
465
- return content;
466
- } catch (error) {
467
- let errorMessage = `Failed to fetch content from URL: ${url}`;
468
- if (error.name === "AbortError") {
469
- errorMessage += " - Request timeout (30s)";
470
- } else if (error.message) {
471
- errorMessage += ` - ${error.message}`;
472
- }
473
- throw new Error(errorMessage);
474
- }
475
- }
476
- static parseSpecContent(content, pathOrUrl) {
477
- let format;
478
- if (isUrl(pathOrUrl)) {
479
- const urlPath = new URL(pathOrUrl).pathname.toLowerCase();
480
- if (urlPath.endsWith(".json")) {
481
- format = "json";
482
- } else if (urlPath.endsWith(".yaml") || urlPath.endsWith(".yml")) {
483
- format = "yaml";
484
- } else {
485
- format = _SwaggerParser.detectFormat(content);
486
- }
487
- } else {
488
- const extension = path.extname(pathOrUrl).toLowerCase();
489
- switch (extension) {
490
- case ".json":
491
- format = "json";
492
- break;
493
- case ".yaml":
494
- format = "yaml";
495
- break;
496
- case ".yml":
497
- format = "yml";
498
- break;
499
- default:
500
- format = _SwaggerParser.detectFormat(content);
501
- }
502
- }
503
- try {
504
- switch (format) {
505
- case "json":
506
- return JSON.parse(content);
507
- case "yaml":
508
- case "yml":
509
- return yaml.load(content);
510
- default:
511
- throw new Error(`Unable to determine format for: ${pathOrUrl}`);
512
- }
513
- } catch (error) {
514
- throw new Error(`Failed to parse ${format.toUpperCase()} content from: ${pathOrUrl}. Error: ${error instanceof Error ? error.message : error}`);
515
- }
516
- }
517
- static detectFormat(content) {
518
- const trimmed = content.trim();
519
- if (trimmed.startsWith("{") || trimmed.startsWith("[")) {
520
- return "json";
521
- }
522
- if (trimmed.includes("openapi:") || trimmed.includes("swagger:") || trimmed.includes("---") || /^[a-zA-Z][a-zA-Z0-9_]*\s*:/.test(trimmed)) {
523
- return "yaml";
524
- }
525
- return "json";
632
+ /**
633
+ * The version-free model generators consume. Computed once and cached —
634
+ * all generators share the same NormalizedOperation instances, so they
635
+ * can be used as Map keys across generators.
636
+ */
637
+ getNormalizedSpec() {
638
+ this.normalized ??= normalizeSpec(this.spec);
639
+ return this.normalized;
526
640
  }
641
+ /** Definition map regardless of version: 2.0 `definitions` or 3.x `components.schemas`. */
527
642
  getDefinitions() {
528
643
  return this.spec.definitions || this.spec.components?.schemas || {};
529
644
  }
645
+ /** One definition by bare name, or undefined when the spec has none by that name. */
530
646
  getDefinition(name) {
531
647
  const definitions = this.getDefinitions();
532
648
  return definitions[name];
533
649
  }
650
+ /** Resolves "#/definitions/X" / "#/components/schemas/X" style refs by their last segment. */
534
651
  resolveReference(ref) {
535
652
  const parts = ref.split("/");
536
653
  const definitionName = parts[parts.length - 1];
@@ -539,15 +656,18 @@ var SwaggerParser = class _SwaggerParser {
539
656
  getAllDefinitionNames() {
540
657
  return Object.keys(this.getDefinitions());
541
658
  }
659
+ /** The raw parsed spec — prefer getNormalizedSpec() unless raw access is the point. */
542
660
  getSpec() {
543
661
  return this.spec;
544
662
  }
545
663
  getPaths() {
546
664
  return this.spec.paths || {};
547
665
  }
666
+ /** Whether the spec declares a supported version (Swagger 2.x or OpenAPI 3.x). */
548
667
  isValidSpec() {
549
668
  return !!(this.spec.swagger && this.spec.swagger.startsWith("2.") || this.spec.openapi && this.spec.openapi.startsWith("3."));
550
669
  }
670
+ /** Detected flavor + literal version string, or null when neither field is present. */
551
671
  getSpecVersion() {
552
672
  if (this.spec.swagger) {
553
673
  return {
@@ -565,94 +685,374 @@ var SwaggerParser = class _SwaggerParser {
565
685
  }
566
686
  };
567
687
 
568
- // src/lib/generators/type/type.generator.ts
688
+ // ../shared/src/emit/headers.emit.ts
689
+ function emitHeaders(options) {
690
+ const { optionsExpression, customHeaders, contentType } = options;
691
+ let headerCode = `
692
+ let headers: HttpHeaders;
693
+ if (${optionsExpression}?.headers instanceof HttpHeaders) {
694
+ headers = ${optionsExpression}.headers;
695
+ } else {
696
+ headers = new HttpHeaders(${optionsExpression}?.headers);
697
+ }`;
698
+ if (customHeaders) {
699
+ headerCode += `
700
+ // Add default headers if not already present
701
+ ${emitDefaultHeaderGuards(customHeaders)}`;
702
+ }
703
+ if (contentType?.isMultipart) {
704
+ headerCode += `
705
+ // Remove Content-Type for multipart (browser will set it with boundary)
706
+ headers = headers.delete('Content-Type');`;
707
+ } else if (contentType?.isUrlEncoded) {
708
+ headerCode += `
709
+ // Set Content-Type for URL-encoded form data
710
+ if (!headers.has('Content-Type')) {
711
+ headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
712
+ }`;
713
+ } else if (contentType?.hasBody) {
714
+ headerCode += `
715
+ // Set Content-Type for JSON requests if not already set
716
+ if (!headers.has('Content-Type')) {
717
+ headers = headers.set('Content-Type', 'application/json');
718
+ }`;
719
+ }
720
+ return headerCode;
721
+ }
722
+ __name(emitHeaders, "emitHeaders");
723
+ function emitDefaultHeaderGuards(customHeaders) {
724
+ return Object.entries(customHeaders).map(([key, value]) => `if (!headers.has('${key}')) {
725
+ headers = headers.set('${key}', '${value}');
726
+ }`).join("\n");
727
+ }
728
+ __name(emitDefaultHeaderGuards, "emitDefaultHeaderGuards");
729
+
730
+ // ../shared/src/emit/url.emit.ts
731
+ function plainParamValue(identifier) {
732
+ return identifier;
733
+ }
734
+ __name(plainParamValue, "plainParamValue");
735
+ function emitUrlExpression(path14, pathParams, paramValue = plainParamValue) {
736
+ let urlExpression = `\`\${this.basePath}${path14}\``;
737
+ pathParams.forEach((param) => {
738
+ urlExpression = urlExpression.replace(`{${param.name}}`, `\${${paramValue(camelCase(param.name))}}`);
739
+ });
740
+ return urlExpression;
741
+ }
742
+ __name(emitUrlExpression, "emitUrlExpression");
743
+ function emitUrlConstruction(path14, pathParams) {
744
+ return `const url = ${emitUrlExpression(path14, pathParams)};`;
745
+ }
746
+ __name(emitUrlConstruction, "emitUrlConstruction");
747
+
748
+ // ../shared/src/emit/query-params.emit.ts
749
+ function emitQueryParams(queryParams) {
750
+ if (queryParams.length === 0) {
751
+ return "";
752
+ }
753
+ const paramMappings = queryParams.map((param) => `if (${camelCase(param.name)} != null) {
754
+ params = HttpParamsBuilder.addToHttpParams(params, ${camelCase(param.name)}, '${param.name}');
755
+ }`).join("\n");
756
+ return `
757
+ let params = new HttpParams();
758
+ ${paramMappings}`;
759
+ }
760
+ __name(emitQueryParams, "emitQueryParams");
761
+
762
+ // ../shared/src/emit/response-type.emit.ts
763
+ function emitResponseTypeOption(responseType) {
764
+ if (responseType === "json") {
765
+ return "";
766
+ }
767
+ return `responseType: '${responseType}'`;
768
+ }
769
+ __name(emitResponseTypeOption, "emitResponseTypeOption");
770
+ function joinRequestOptionEntries(entries) {
771
+ return entries.filter((entry) => entry && !entry.includes("undefined")).join(",\n ");
772
+ }
773
+ __name(joinRequestOptionEntries, "joinRequestOptionEntries");
774
+
775
+ // ../shared/src/utils/functions/token-names.ts
776
+ function getClientContextTokenName(clientName = "default") {
777
+ const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
778
+ return `CLIENT_CONTEXT_TOKEN_${clientSuffix}`;
779
+ }
780
+ __name(getClientContextTokenName, "getClientContextTokenName");
781
+ function getBasePathTokenName(clientName = "default") {
782
+ const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
783
+ return `BASE_PATH_${clientSuffix}`;
784
+ }
785
+ __name(getBasePathTokenName, "getBasePathTokenName");
786
+ function getInterceptorsTokenName(clientName = "default") {
787
+ const clientSuffix = clientName.toUpperCase().replace(/[^A-Z0-9]/g, "_");
788
+ return `HTTP_INTERCEPTORS_${clientSuffix}`;
789
+ }
790
+ __name(getInterceptorsTokenName, "getInterceptorsTokenName");
791
+
792
+ // ../shared/src/utils/functions/duplicate-function-name.ts
793
+ function hasDuplicateFunctionNames(arr) {
794
+ return new Set(arr.map((fn) => fn.getName())).size !== arr.length;
795
+ }
796
+ __name(hasDuplicateFunctionNames, "hasDuplicateFunctionNames");
797
+
798
+ // ../shared/src/utils/functions/get-request-body-type.ts
799
+ function getRequestBodyType(requestBody, config) {
800
+ const content = requestBody.content || {};
801
+ const jsonContent = content[CONTENT_TYPES.JSON];
802
+ if (jsonContent?.schema) {
803
+ return getTypeScriptType(jsonContent.schema, config, jsonContent.schema.nullable);
804
+ }
805
+ return "any";
806
+ }
807
+ __name(getRequestBodyType, "getRequestBodyType");
808
+
809
+ // ../shared/src/utils/functions/is-data-type-interface.ts
810
+ function isDataTypeInterface(type) {
811
+ const invalidTypes = [
812
+ "any",
813
+ "File",
814
+ "string",
815
+ "number",
816
+ "boolean",
817
+ "object",
818
+ "unknown",
819
+ "[]",
820
+ "Array"
821
+ ];
822
+ return !invalidTypes.some((invalidType) => type.includes(invalidType));
823
+ }
824
+ __name(isDataTypeInterface, "isDataTypeInterface");
825
+
826
+ // ../shared/src/config/constants.ts
827
+ var disableLinting = `/* @ts-nocheck */
828
+ /* eslint-disable */
829
+ /* @noformat */
830
+ /* @formatter:off */
831
+ `;
832
+ var authorComment = `/**
833
+ * Generated by ng-openapi
834
+ `;
835
+ var defaultHeaderComment = disableLinting + authorComment;
836
+ var TYPE_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated TypeScript interfaces from Swagger specification
837
+ * Do not edit this file manually
838
+ */
839
+ `;
840
+ var SERVICE_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated service exports
841
+ * Do not edit this file manually
842
+ */
843
+ `;
844
+ var SERVICE_GENERATOR_HEADER_COMMENT = /* @__PURE__ */ __name((controllerName) => defaultHeaderComment + `* Generated Angular service for ${controllerName} controller
845
+ * Do not edit this file manually
846
+ */
847
+ `, "SERVICE_GENERATOR_HEADER_COMMENT");
848
+ var REQUEST_PARAMS_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated request parameter interfaces
849
+ * Do not edit this file manually
850
+ */
851
+ `;
852
+ var MAIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Entrypoint for the client
853
+ * Do not edit this file manually
854
+ */
855
+ `;
856
+ var PROVIDER_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated provider functions for easy setup
857
+ * Do not edit this file manually
858
+ */
859
+ `;
860
+ var BASE_INTERCEPTOR_HEADER_COMMENT = /* @__PURE__ */ __name((clientName) => defaultHeaderComment + `* Generated Base Interceptor for client ${clientName}
861
+ * Do not edit this file manually
862
+ */
863
+ `, "BASE_INTERCEPTOR_HEADER_COMMENT");
864
+ var ZOD_PLUGIN_INDEX_GENERATOR_HEADER_COMMENT = defaultHeaderComment + `* Generated Zod Schemas exports
865
+ * Do not edit this file manually
866
+ */
867
+ `;
868
+
869
+ // src/lib/cli.ts
870
+ var import_commander = require("commander");
871
+ var fs4 = __toESM(require("fs"));
872
+ var path13 = __toESM(require("path"));
873
+
874
+ // package.json
875
+ var version = "0.3.0";
876
+
877
+ // src/lib/core/generator.ts
878
+ var import_ts_morph10 = require("ts-morph");
879
+
880
+ // src/lib/generators/type/enum-builder.ts
569
881
  var import_ts_morph = require("ts-morph");
570
- var TypeGenerator = class {
882
+
883
+ // src/lib/generators/type/type-resolver.ts
884
+ function escapeString2(str) {
885
+ return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
886
+ }
887
+ __name(escapeString2, "escapeString");
888
+ function sanitizePropertyName(name) {
889
+ if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
890
+ return `"${name}"`;
891
+ }
892
+ return name;
893
+ }
894
+ __name(sanitizePropertyName, "sanitizePropertyName");
895
+ var TypeResolver = class {
571
896
  static {
572
- __name(this, "TypeGenerator");
897
+ __name(this, "TypeResolver");
573
898
  }
574
- project;
575
- parser;
576
- sourceFile;
577
- generatedTypes = /* @__PURE__ */ new Set();
578
899
  config;
579
- // Performance caches
900
+ onWarning;
901
+ resolutionCache = /* @__PURE__ */ new WeakMap();
580
902
  pascalCaseCache = /* @__PURE__ */ new Map();
581
903
  sanitizedNameCache = /* @__PURE__ */ new Map();
582
- typeResolutionCache = /* @__PURE__ */ new Map();
583
- // Batch collection for AST operations
584
- statements = [];
585
- deferredTypes = /* @__PURE__ */ new Map();
586
- constructor(parser, project, config, outputRoot) {
904
+ constructor(config, onWarning) {
587
905
  this.config = config;
588
- this.project = project;
589
- this.parser = parser;
590
- const outputPath = outputRoot + "/models/index.ts";
591
- this.sourceFile = this.project.createSourceFile(outputPath, "", {
592
- overwrite: true
593
- });
906
+ this.onWarning = onWarning;
594
907
  }
595
- async generate() {
596
- try {
597
- const definitions = this.parser.getDefinitions();
598
- if (!definitions || Object.keys(definitions).length === 0) {
599
- console.warn("No definitions found in swagger file");
600
- }
601
- this.collectAllTypeStructures(definitions);
602
- this.collectSdkTypes();
603
- this.applyBatchUpdates();
604
- await this.finalize();
605
- } catch (error) {
606
- console.error("Error in generate():", error);
607
- throw new Error(`Failed to generate types: ${error instanceof Error ? error.message : "Unknown error"}`);
908
+ resolve(schema) {
909
+ const cached = this.resolutionCache.get(schema);
910
+ if (cached !== void 0) {
911
+ return cached;
608
912
  }
913
+ const result = this.resolveUncached(schema);
914
+ this.resolutionCache.set(schema, result);
915
+ return result;
609
916
  }
610
- collectAllTypeStructures(definitions) {
611
- Object.keys(definitions).forEach((name) => {
612
- const interfaceName = this.getCachedPascalCase(name);
613
- this.generatedTypes.add(interfaceName);
614
- });
615
- Object.entries(definitions).forEach(([name, definition]) => {
616
- this.collectTypeStructure(name, definition);
617
- });
618
- this.deferredTypes.forEach((definition, name) => {
619
- this.collectTypeStructure(name, definition);
620
- });
917
+ pascalName(str) {
918
+ if (!this.pascalCaseCache.has(str)) {
919
+ this.pascalCaseCache.set(str, pascalCaseForEnums(str));
920
+ }
921
+ return this.pascalCaseCache.get(str);
621
922
  }
622
- collectTypeStructure(name, definition) {
623
- const interfaceName = this.getCachedPascalCase(name) ?? "";
624
- if (definition.enum) {
625
- this.collectEnumStructure(interfaceName, definition);
626
- } else if (definition.allOf) {
627
- this.collectCompositeTypeStructure(interfaceName, definition);
628
- } else if (definition.items) {
629
- this.collectArrayTypeStructure(interfaceName, definition);
630
- } else if (definition.properties) {
631
- this.collectInterfaceStructure(interfaceName, definition);
923
+ sanitizeName(name) {
924
+ if (!this.sanitizedNameCache.has(name)) {
925
+ this.sanitizedNameCache.set(name, sanitizePropertyName(name));
926
+ }
927
+ return this.sanitizedNameCache.get(name);
928
+ }
929
+ getArrayItemType(items) {
930
+ if (Array.isArray(items)) {
931
+ const types = items.map((item) => this.resolve(item));
932
+ return `[${types.join(", ")}]`;
632
933
  } else {
633
- const propertyType = this.resolveSwaggerTypeCached(definition);
634
- this.statements.push({
635
- kind: import_ts_morph.StructureKind.TypeAlias,
636
- name: interfaceName,
637
- isExported: true,
638
- docs: definition.description ? [
639
- definition.description
640
- ] : void 0,
641
- type: propertyType
642
- });
934
+ return this.resolve(items);
935
+ }
936
+ }
937
+ resolveUncached(schema) {
938
+ if (schema.$ref) {
939
+ return this.resolveReference(schema.$ref);
940
+ }
941
+ if (schema.enum) {
942
+ return schema.enum.map((value) => typeof value === "string" ? `'${escapeString2(value)}'` : String(value)).join(" | ");
943
+ }
944
+ if (schema.allOf) {
945
+ return schema.allOf.map((def) => this.resolve(def)).filter((type) => type !== "any" && type !== "unknown").join(" & ") || "Record<string, unknown>";
946
+ }
947
+ if (schema.oneOf) {
948
+ return schema.oneOf.map((def) => this.resolve(def)).filter((type, index, array) => type !== "any" && type !== "unknown" && array.indexOf(type) === index).join(" | ") || "unknown";
949
+ }
950
+ if (schema.anyOf) {
951
+ return schema.anyOf.map((def) => this.resolve(def)).filter((type) => type !== "any" && type !== "unknown").join(" | ") || "unknown";
952
+ }
953
+ if (schema.type === "array") {
954
+ const itemType = schema.items ? this.getArrayItemType(schema.items) : "unknown";
955
+ return `Array<${itemType}>`;
956
+ }
957
+ if (schema.type === "object") {
958
+ if (schema.properties) {
959
+ return this.generateInlineObjectType(schema);
960
+ }
961
+ if (schema.additionalProperties) {
962
+ const valueType = typeof schema.additionalProperties === "object" ? this.resolve(schema.additionalProperties) : "unknown";
963
+ return `Record<string, ${valueType}>`;
964
+ }
965
+ return "Record<string, unknown>";
966
+ }
967
+ return this.mapSwaggerTypeToTypeScript(schema.type, schema.format, schema.nullable);
968
+ }
969
+ generateInlineObjectType(definition) {
970
+ if (!definition.properties) {
971
+ if (definition.additionalProperties) {
972
+ const additionalType = typeof definition.additionalProperties === "object" ? this.resolve(definition.additionalProperties) : "unknown";
973
+ return `Record<string, ${additionalType}>`;
974
+ }
975
+ return "Record<string, unknown>";
976
+ }
977
+ const properties = Object.entries(definition.properties).map(([key, prop]) => {
978
+ const isRequired = definition.required?.includes(key) ?? false;
979
+ const questionMark = isRequired ? "" : "?";
980
+ const sanitizedKey = this.sanitizeName(key);
981
+ return `${sanitizedKey}${questionMark}: ${this.resolve(prop)}`;
982
+ }).join("; ");
983
+ return `{ ${properties} }`;
984
+ }
985
+ resolveReference(ref) {
986
+ const refName = ref.split("/").pop();
987
+ if (!refName) {
988
+ this.onWarning?.(`Invalid reference format: ${ref}`);
989
+ return "unknown";
990
+ }
991
+ return this.pascalName(refName);
992
+ }
993
+ mapSwaggerTypeToTypeScript(type, format, isNullable) {
994
+ switch (type) {
995
+ case "string":
996
+ if (format === "date" || format === "date-time") {
997
+ const dateType = this.config.options.dateType === "Date" ? "Date" : "string";
998
+ return this.nullableType(dateType, isNullable);
999
+ }
1000
+ if (format === "binary") return "Blob";
1001
+ if (format === "uuid") return "string";
1002
+ if (format === "email") return "string";
1003
+ if (format === "uri") return "string";
1004
+ return this.nullableType("string", isNullable);
1005
+ case "number":
1006
+ case "integer":
1007
+ return this.nullableType("number", isNullable);
1008
+ case "boolean":
1009
+ return this.nullableType("boolean", isNullable);
1010
+ case "array":
1011
+ return this.nullableType("any[]", isNullable);
1012
+ case "object":
1013
+ return this.nullableType("Record<string, unknown>", isNullable);
1014
+ case "null":
1015
+ return this.nullableType("null", isNullable);
1016
+ default:
1017
+ if (Array.isArray(type)) {
1018
+ const types = type.map((t) => this.mapSwaggerTypeToTypeScript(t, void 0, isNullable));
1019
+ return this.nullableType(types.join(" | "), isNullable);
1020
+ }
1021
+ return this.nullableType("any", isNullable);
643
1022
  }
644
1023
  }
645
- collectEnumStructure(name, definition) {
646
- if (!definition.enum?.length) return;
1024
+ nullableType(type, isNullable) {
1025
+ return type + (isNullable ? " | null" : "");
1026
+ }
1027
+ };
1028
+
1029
+ // src/lib/generators/type/enum-builder.ts
1030
+ function toEnumKey(value) {
1031
+ const str = value.toString();
1032
+ const hasLeadingMinus = str.startsWith("-");
1033
+ const pascalCased = pascalCase(str);
1034
+ return hasLeadingMinus ? pascalCased.replace(/^([0-9])/, "_n$1") : pascalCased.replace(/^([0-9])/, "_$1");
1035
+ }
1036
+ __name(toEnumKey, "toEnumKey");
1037
+ var EnumBuilder = class {
1038
+ static {
1039
+ __name(this, "EnumBuilder");
1040
+ }
1041
+ config;
1042
+ onWarning;
1043
+ constructor(config, onWarning) {
1044
+ this.config = config;
1045
+ this.onWarning = onWarning;
1046
+ }
1047
+ build(name, definition) {
1048
+ if (!definition.enum?.length) return [];
647
1049
  const docs = !this.config.options.generateEnumBasedOnDescription && definition.description ? [
648
1050
  definition.description
649
1051
  ] : void 0;
650
1052
  if (this.config.options.enumStyle === "enum") {
651
- const statement = this.buildEnumAsEnum(name, definition, docs);
652
- this.statements.push(...statement);
1053
+ return this.buildEnumAsEnum(name, definition, docs);
653
1054
  } else {
654
- const statement = this.buildEnumAsUnion(name, definition, docs);
655
- this.statements.push(...statement);
1055
+ return this.buildEnumAsUnion(name, definition, docs);
656
1056
  }
657
1057
  }
658
1058
  buildEnumAsEnum(name, definition, docs) {
@@ -661,7 +1061,7 @@ var TypeGenerator = class {
661
1061
  const isStringEnum = definition.enum.some((value) => typeof value === "string");
662
1062
  if (isStringEnum) {
663
1063
  const members = definition.enum.map((value) => ({
664
- name: this.toEnumKey(value),
1064
+ name: toEnumKey(value),
665
1065
  value: `${String(value)}`
666
1066
  }));
667
1067
  statements.push({
@@ -672,7 +1072,7 @@ var TypeGenerator = class {
672
1072
  members
673
1073
  });
674
1074
  } else {
675
- const members = this.buildEnumMembers(definition);
1075
+ const members = this.buildEnumMembers(name, definition);
676
1076
  statements.push({
677
1077
  kind: import_ts_morph.StructureKind.Enum,
678
1078
  name,
@@ -688,8 +1088,8 @@ var TypeGenerator = class {
688
1088
  const statements = [];
689
1089
  const objectProperties = [];
690
1090
  const unionType = definition.enum.map((value) => {
691
- const key = this.toEnumKey(value);
692
- const val = typeof value === "string" ? `'${this.escapeString(value)}'` : isNaN(value) ? `'${value}'` : `${value}`;
1091
+ const key = toEnumKey(value);
1092
+ const val = typeof value === "string" ? `'${escapeString2(value)}'` : isNaN(value) ? `'${value}'` : `${value}`;
693
1093
  objectProperties.push(`${key}: ${val} as ${name}`);
694
1094
  return val;
695
1095
  }).join(" | ");
@@ -713,7 +1113,7 @@ var TypeGenerator = class {
713
1113
  });
714
1114
  return statements;
715
1115
  }
716
- buildEnumMembers(definition) {
1116
+ buildEnumMembers(name, definition) {
717
1117
  if (definition.description && this.config.options.generateEnumBasedOnDescription) {
718
1118
  try {
719
1119
  const enumValueObjects = JSON.parse(definition.description);
@@ -722,63 +1122,49 @@ var TypeGenerator = class {
722
1122
  value: obj.Value
723
1123
  }));
724
1124
  } catch {
1125
+ if (/^\s*[[{]/.test(definition.description)) {
1126
+ this.onWarning?.(`Enum "${name}": description looks like JSON (generateEnumBasedOnDescription) but could not be used \u2014 falling back to raw enum values`);
1127
+ }
725
1128
  }
726
1129
  }
727
1130
  return definition.enum?.map((value) => ({
728
- name: this.toEnumKey(value),
1131
+ name: toEnumKey(value),
729
1132
  value
730
1133
  }));
731
1134
  }
732
- collectCompositeTypeStructure(name, definition) {
733
- let typeExpression = "";
734
- if (definition.allOf) {
735
- const types = definition.allOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type) => type !== "any" && type !== "unknown");
736
- typeExpression = types.length > 0 ? types.join(" & ") : "Record<string, unknown>";
737
- }
738
- this.statements.push({
739
- kind: import_ts_morph.StructureKind.TypeAlias,
740
- name,
741
- type: typeExpression,
742
- isExported: true,
743
- docs: definition.description ? [
744
- definition.description
745
- ] : void 0
746
- });
1135
+ };
1136
+
1137
+ // src/lib/generators/type/interface-builder.ts
1138
+ var import_ts_morph2 = require("ts-morph");
1139
+ var InterfaceBuilder = class {
1140
+ static {
1141
+ __name(this, "InterfaceBuilder");
747
1142
  }
748
- collectArrayTypeStructure(name, definition) {
749
- const itemType = definition.items ? this.getArrayItemType(definition.items) : "unknown";
750
- this.statements.push({
751
- kind: import_ts_morph.StructureKind.TypeAlias,
752
- name,
753
- isExported: true,
754
- docs: definition.description ? [
755
- definition.description
756
- ] : void 0,
757
- type: `Array<${itemType}>`
758
- });
1143
+ resolver;
1144
+ constructor(resolver) {
1145
+ this.resolver = resolver;
759
1146
  }
760
- collectInterfaceStructure(name, definition) {
761
- const properties = this.buildInterfaceProperties(definition);
762
- this.statements.push({
763
- kind: import_ts_morph.StructureKind.Interface,
1147
+ build(name, definition) {
1148
+ return {
1149
+ kind: import_ts_morph2.StructureKind.Interface,
764
1150
  name,
765
1151
  isExported: true,
766
1152
  docs: definition.description ? [
767
1153
  definition.description
768
1154
  ] : void 0,
769
- properties,
1155
+ properties: this.buildProperties(definition),
770
1156
  indexSignatures: this.buildIndexSignatures(definition)
771
- });
1157
+ };
772
1158
  }
773
- buildInterfaceProperties(definition) {
1159
+ buildProperties(definition) {
774
1160
  if (!definition.properties) {
775
1161
  return [];
776
1162
  }
777
1163
  return Object.entries(definition.properties).map(([propertyName, property]) => {
778
1164
  const isRequired = definition.required?.includes(propertyName) ?? false;
779
1165
  const isReadOnly = property.readOnly;
780
- const propertyType = this.resolveSwaggerTypeCached(property);
781
- const sanitizedName = this.getCachedSanitizedName(propertyName);
1166
+ const propertyType = this.resolver.resolve(property);
1167
+ const sanitizedName = this.resolver.sanitizeName(propertyName);
782
1168
  return {
783
1169
  name: sanitizedName,
784
1170
  type: propertyType,
@@ -820,121 +1206,156 @@ var TypeGenerator = class {
820
1206
  }
821
1207
  return [];
822
1208
  }
823
- resolveSwaggerTypeCached(schema) {
824
- const cacheKey = JSON.stringify(schema);
825
- if (this.typeResolutionCache.has(cacheKey)) {
826
- return this.typeResolutionCache.get(cacheKey);
1209
+ };
1210
+
1211
+ // src/lib/generators/type/sdk-types.ts
1212
+ var import_ts_morph3 = require("ts-morph");
1213
+ function buildSdkTypes(config) {
1214
+ const { response } = config.options.validation ?? {};
1215
+ const typeParameters = [
1216
+ "TResponseType extends 'arraybuffer' | 'blob' | 'json' | 'text'"
1217
+ ];
1218
+ const properties = [
1219
+ {
1220
+ name: "headers",
1221
+ type: "HttpHeaders",
1222
+ hasQuestionToken: true
1223
+ },
1224
+ {
1225
+ name: "reportProgress",
1226
+ type: "boolean",
1227
+ hasQuestionToken: true
1228
+ },
1229
+ {
1230
+ name: "responseType",
1231
+ type: "TResponseType",
1232
+ hasQuestionToken: true
1233
+ },
1234
+ {
1235
+ name: "withCredentials",
1236
+ type: "boolean",
1237
+ hasQuestionToken: true
1238
+ },
1239
+ {
1240
+ name: "context",
1241
+ type: "HttpContext",
1242
+ hasQuestionToken: true
827
1243
  }
828
- const result = this.resolveSwaggerType(schema);
829
- this.typeResolutionCache.set(cacheKey, result);
830
- return result;
1244
+ ];
1245
+ if (response) {
1246
+ properties.push({
1247
+ name: "parse",
1248
+ type: "(response: unknown) => TReturnType",
1249
+ hasQuestionToken: true
1250
+ });
1251
+ typeParameters.push("TReturnType");
831
1252
  }
832
- resolveSwaggerType(schema) {
833
- if (schema.$ref) {
834
- return this.resolveReference(schema.$ref);
835
- }
836
- if (schema.enum) {
837
- return schema.enum.map((value) => typeof value === "string" ? `'${this.escapeString(value)}'` : String(value)).join(" | ");
838
- }
839
- if (schema.allOf) {
840
- return schema.allOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type) => type !== "any" && type !== "unknown").join(" & ") || "Record<string, unknown>";
841
- }
842
- if (schema.oneOf) {
843
- return schema.oneOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type, index, array) => type !== "any" && type !== "unknown" && array.indexOf(type) === index).join(" | ") || "unknown";
844
- }
845
- if (schema.anyOf) {
846
- return schema.anyOf.map((def) => this.resolveSwaggerTypeCached(def)).filter((type) => type !== "any" && type !== "unknown").join(" | ") || "unknown";
847
- }
848
- if (schema.type === "array") {
849
- const itemType = schema.items ? this.getArrayItemType(schema.items) : "unknown";
850
- return `Array<${itemType}>`;
851
- }
852
- if (schema.type === "object") {
853
- if (schema.properties) {
854
- return this.generateInlineObjectType(schema);
855
- }
856
- if (schema.additionalProperties) {
857
- const valueType = typeof schema.additionalProperties === "object" ? this.resolveSwaggerTypeCached(schema.additionalProperties) : "unknown";
858
- return `Record<string, ${valueType}>`;
859
- }
860
- return "Record<string, unknown>";
1253
+ return [
1254
+ {
1255
+ kind: import_ts_morph3.StructureKind.Interface,
1256
+ name: "RequestOptions",
1257
+ isExported: true,
1258
+ typeParameters,
1259
+ properties,
1260
+ docs: [
1261
+ "Request Options for Angular HttpClient requests"
1262
+ ]
861
1263
  }
862
- return this.mapSwaggerTypeToTypeScript(schema.type, schema.format, schema.nullable);
1264
+ ];
1265
+ }
1266
+ __name(buildSdkTypes, "buildSdkTypes");
1267
+
1268
+ // src/lib/generators/type/type.generator.ts
1269
+ var import_ts_morph4 = require("ts-morph");
1270
+ var TypeGenerator = class {
1271
+ static {
1272
+ __name(this, "TypeGenerator");
863
1273
  }
864
- generateInlineObjectType(definition) {
865
- if (!definition.properties) {
866
- if (definition.additionalProperties) {
867
- const additionalType = typeof definition.additionalProperties === "object" ? this.resolveSwaggerTypeCached(definition.additionalProperties) : "unknown";
868
- return `Record<string, ${additionalType}>`;
869
- }
870
- return "Record<string, unknown>";
871
- }
872
- const properties = Object.entries(definition.properties).map(([key, prop]) => {
873
- const isRequired = definition.required?.includes(key) ?? false;
874
- const questionMark = isRequired ? "" : "?";
875
- const sanitizedKey = this.getCachedSanitizedName(key);
876
- return `${sanitizedKey}${questionMark}: ${this.resolveSwaggerTypeCached(prop)}`;
877
- }).join("; ");
878
- return `{ ${properties} }`;
1274
+ parser;
1275
+ config;
1276
+ sourceFile;
1277
+ resolver;
1278
+ enumBuilder;
1279
+ interfaceBuilder;
1280
+ statements = [];
1281
+ onWarning;
1282
+ constructor(parser, project, config, outputRoot, onWarning) {
1283
+ this.config = config;
1284
+ this.parser = parser;
1285
+ this.onWarning = onWarning;
1286
+ const outputPath = outputRoot + "/models/index.ts";
1287
+ this.sourceFile = project.createSourceFile(outputPath, "", {
1288
+ overwrite: true
1289
+ });
1290
+ this.resolver = new TypeResolver(config, onWarning);
1291
+ this.enumBuilder = new EnumBuilder(config, onWarning);
1292
+ this.interfaceBuilder = new InterfaceBuilder(this.resolver);
879
1293
  }
880
- resolveReference(ref) {
881
- const refName = ref.split("/").pop();
882
- if (!refName) {
883
- console.warn(`Invalid reference format: ${ref}`);
884
- return "unknown";
1294
+ async generate() {
1295
+ try {
1296
+ const definitions = this.parser.getNormalizedSpec().definitions;
1297
+ if (!definitions || Object.keys(definitions).length === 0) {
1298
+ this.onWarning?.("No definitions found in swagger file");
1299
+ }
1300
+ Object.entries(definitions).forEach(([name, definition]) => {
1301
+ this.collectTypeStructure(name, definition);
1302
+ });
1303
+ this.statements.push(...buildSdkTypes(this.config));
1304
+ this.applyBatchUpdates();
1305
+ await this.finalize();
1306
+ } catch (error) {
1307
+ throw new Error(`Failed to generate types: ${error instanceof Error ? error.message : "Unknown error"}`);
885
1308
  }
886
- return this.getCachedPascalCase(refName);
887
1309
  }
888
- collectSdkTypes() {
889
- const { response } = this.config.options.validation ?? {};
890
- const typeParameters = [
891
- "TResponseType extends 'arraybuffer' | 'blob' | 'json' | 'text'"
892
- ];
893
- const properties = [
894
- {
895
- name: "headers",
896
- type: "HttpHeaders",
897
- hasQuestionToken: true
898
- },
899
- {
900
- name: "reportProgress",
901
- type: "boolean",
902
- hasQuestionToken: true
903
- },
904
- {
905
- name: "responseType",
906
- type: "TResponseType",
907
- hasQuestionToken: true
908
- },
909
- {
910
- name: "withCredentials",
911
- type: "boolean",
912
- hasQuestionToken: true
913
- },
914
- {
915
- name: "context",
916
- type: "HttpContext",
917
- hasQuestionToken: true
918
- }
919
- ];
920
- if (response) {
921
- properties.push({
922
- name: "parse",
923
- type: "(response: unknown) => TReturnType",
924
- hasQuestionToken: true
1310
+ collectTypeStructure(name, definition) {
1311
+ const typeName = this.resolver.pascalName(name);
1312
+ if (definition.enum) {
1313
+ this.statements.push(...this.enumBuilder.build(typeName, definition));
1314
+ } else if (definition.allOf) {
1315
+ this.statements.push(this.buildCompositeTypeAlias(typeName, definition));
1316
+ } else if (definition.items) {
1317
+ this.statements.push(this.buildArrayTypeAlias(typeName, definition));
1318
+ } else if (definition.properties) {
1319
+ this.statements.push(this.interfaceBuilder.build(typeName, definition));
1320
+ } else {
1321
+ this.statements.push({
1322
+ kind: import_ts_morph4.StructureKind.TypeAlias,
1323
+ name: typeName,
1324
+ isExported: true,
1325
+ docs: definition.description ? [
1326
+ definition.description
1327
+ ] : void 0,
1328
+ type: this.resolver.resolve(definition)
925
1329
  });
926
- typeParameters.push("TReturnType");
927
1330
  }
928
- this.statements.push({
929
- kind: import_ts_morph.StructureKind.Interface,
930
- name: "RequestOptions",
1331
+ }
1332
+ buildCompositeTypeAlias(name, definition) {
1333
+ let typeExpression = "";
1334
+ if (definition.allOf) {
1335
+ const types = definition.allOf.map((def) => this.resolver.resolve(def)).filter((type) => type !== "any" && type !== "unknown");
1336
+ typeExpression = types.length > 0 ? types.join(" & ") : "Record<string, unknown>";
1337
+ }
1338
+ return {
1339
+ kind: import_ts_morph4.StructureKind.TypeAlias,
1340
+ name,
1341
+ type: typeExpression,
931
1342
  isExported: true,
932
- typeParameters,
933
- properties,
934
- docs: [
935
- "Request Options for Angular HttpClient requests"
936
- ]
937
- });
1343
+ docs: definition.description ? [
1344
+ definition.description
1345
+ ] : void 0
1346
+ };
1347
+ }
1348
+ buildArrayTypeAlias(name, definition) {
1349
+ const itemType = definition.items ? this.resolver.getArrayItemType(definition.items) : "unknown";
1350
+ return {
1351
+ kind: import_ts_morph4.StructureKind.TypeAlias,
1352
+ name,
1353
+ isExported: true,
1354
+ docs: definition.description ? [
1355
+ definition.description
1356
+ ] : void 0,
1357
+ type: `Array<${itemType}>`
1358
+ };
938
1359
  }
939
1360
  applyBatchUpdates() {
940
1361
  this.sourceFile.insertText(0, TYPE_GENERATOR_HEADER_COMMENT);
@@ -953,81 +1374,10 @@ var TypeGenerator = class {
953
1374
  this.sourceFile.formatText();
954
1375
  await this.sourceFile.save();
955
1376
  }
956
- // Cached helper methods
957
- getCachedPascalCase(str) {
958
- if (!this.pascalCaseCache.has(str)) {
959
- this.pascalCaseCache.set(str, pascalCaseForEnums(str));
960
- }
961
- return this.pascalCaseCache.get(str);
962
- }
963
- getCachedSanitizedName(name) {
964
- if (!this.sanitizedNameCache.has(name)) {
965
- this.sanitizedNameCache.set(name, this.sanitizePropertyName(name));
966
- }
967
- return this.sanitizedNameCache.get(name);
968
- }
969
- // Original helper methods
970
- mapSwaggerTypeToTypeScript(type, format, isNullable) {
971
- switch (type) {
972
- case "string":
973
- if (format === "date" || format === "date-time") {
974
- const dateType = this.config.options.dateType === "Date" ? "Date" : "string";
975
- return this.nullableType(dateType, isNullable);
976
- }
977
- if (format === "binary") return "Blob";
978
- if (format === "uuid") return "string";
979
- if (format === "email") return "string";
980
- if (format === "uri") return "string";
981
- return this.nullableType("string", isNullable);
982
- case "number":
983
- case "integer":
984
- return this.nullableType("number", isNullable);
985
- case "boolean":
986
- return this.nullableType("boolean", isNullable);
987
- case "array":
988
- return this.nullableType("any[]", isNullable);
989
- case "object":
990
- return this.nullableType("Record<string, unknown>", isNullable);
991
- case "null":
992
- return this.nullableType("null", isNullable);
993
- default:
994
- if (Array.isArray(type)) {
995
- const types = type.map((t) => this.mapSwaggerTypeToTypeScript(t, void 0, isNullable));
996
- return this.nullableType(types.join(" | "), isNullable);
997
- }
998
- return this.nullableType("any", isNullable);
999
- }
1000
- }
1001
- nullableType(type, isNullable) {
1002
- return type + (isNullable ? " | null" : "");
1003
- }
1004
- sanitizePropertyName(name) {
1005
- if (!/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name)) {
1006
- return `"${name}"`;
1007
- }
1008
- return name;
1009
- }
1010
- toEnumKey(value) {
1011
- const str = value.toString();
1012
- const hasLeadingMinus = str.startsWith("-");
1013
- const pascalCased = pascalCase(str);
1014
- return hasLeadingMinus ? pascalCased.replace(/^([0-9])/, "_n$1") : pascalCased.replace(/^([0-9])/, "_$1");
1015
- }
1016
- getArrayItemType(items) {
1017
- if (Array.isArray(items)) {
1018
- const types = items.map((item) => this.resolveSwaggerTypeCached(item));
1019
- return `[${types.join(", ")}]`;
1020
- } else {
1021
- return this.resolveSwaggerTypeCached(items);
1022
- }
1023
- }
1024
- escapeString(str) {
1025
- return str.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1026
- }
1027
1377
  };
1028
1378
 
1029
1379
  // src/lib/generators/utility/token.generator.ts
1030
- var import_ts_morph2 = require("ts-morph");
1380
+ var import_ts_morph5 = require("ts-morph");
1031
1381
  var path2 = __toESM(require("path"));
1032
1382
  var TokenGenerator = class {
1033
1383
  static {
@@ -1065,7 +1415,7 @@ var TokenGenerator = class {
1065
1415
  const clientContextTokenName = this.getClientContextTokenName();
1066
1416
  sourceFile.addVariableStatement({
1067
1417
  isExported: true,
1068
- declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
1418
+ declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1069
1419
  declarations: [
1070
1420
  {
1071
1421
  name: basePathTokenName,
@@ -1082,7 +1432,7 @@ var TokenGenerator = class {
1082
1432
  });
1083
1433
  sourceFile.addVariableStatement({
1084
1434
  isExported: true,
1085
- declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
1435
+ declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1086
1436
  declarations: [
1087
1437
  {
1088
1438
  name: interceptorsTokenName,
@@ -1099,7 +1449,7 @@ var TokenGenerator = class {
1099
1449
  });
1100
1450
  sourceFile.addVariableStatement({
1101
1451
  isExported: true,
1102
- declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
1452
+ declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1103
1453
  declarations: [
1104
1454
  {
1105
1455
  name: clientContextTokenName,
@@ -1114,7 +1464,7 @@ var TokenGenerator = class {
1114
1464
  if (this.clientName === "default") {
1115
1465
  sourceFile.addVariableStatement({
1116
1466
  isExported: true,
1117
- declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
1467
+ declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1118
1468
  declarations: [
1119
1469
  {
1120
1470
  name: "BASE_PATH",
@@ -1128,7 +1478,7 @@ var TokenGenerator = class {
1128
1478
  });
1129
1479
  sourceFile.addVariableStatement({
1130
1480
  isExported: true,
1131
- declarationKind: import_ts_morph2.VariableDeclarationKind.Const,
1481
+ declarationKind: import_ts_morph5.VariableDeclarationKind.Const,
1132
1482
  declarations: [
1133
1483
  {
1134
1484
  name: "CLIENT_CONTEXT_TOKEN",
@@ -1299,7 +1649,7 @@ var FileDownloadGenerator = class {
1299
1649
  };
1300
1650
 
1301
1651
  // src/lib/generators/utility/date-transformer.generator.ts
1302
- var import_ts_morph3 = require("ts-morph");
1652
+ var import_ts_morph6 = require("ts-morph");
1303
1653
  var path4 = __toESM(require("path"));
1304
1654
  var DateTransformerGenerator = class {
1305
1655
  static {
@@ -1342,7 +1692,7 @@ var DateTransformerGenerator = class {
1342
1692
  ]);
1343
1693
  sourceFile.addVariableStatement({
1344
1694
  isExported: true,
1345
- declarationKind: import_ts_morph3.VariableDeclarationKind.Const,
1695
+ declarationKind: import_ts_morph6.VariableDeclarationKind.Const,
1346
1696
  declarations: [
1347
1697
  {
1348
1698
  name: "ISO_DATE_REGEX",
@@ -1414,7 +1764,7 @@ var DateTransformerGenerator = class {
1414
1764
  {
1415
1765
  name: "dateRegex",
1416
1766
  type: "RegExp",
1417
- scope: import_ts_morph3.Scope.Private,
1767
+ scope: import_ts_morph6.Scope.Private,
1418
1768
  isReadonly: true,
1419
1769
  initializer: "ISO_DATE_REGEX"
1420
1770
  }
@@ -1712,7 +2062,7 @@ return makeEnvironmentProviders(providers);`;
1712
2062
  };
1713
2063
 
1714
2064
  // src/lib/generators/utility/base-interceptor.generator.ts
1715
- var import_ts_morph4 = require("ts-morph");
2065
+ var import_ts_morph7 = require("ts-morph");
1716
2066
  var path7 = __toESM(require("path"));
1717
2067
  var BaseInterceptorGenerator = class {
1718
2068
  static {
@@ -1781,14 +2131,14 @@ var BaseInterceptorGenerator = class {
1781
2131
  {
1782
2132
  name: "httpInterceptors",
1783
2133
  type: "HttpInterceptor[]",
1784
- scope: import_ts_morph4.Scope.Private,
2134
+ scope: import_ts_morph7.Scope.Private,
1785
2135
  isReadonly: true,
1786
2136
  initializer: `inject(${interceptorsTokenName})`
1787
2137
  },
1788
2138
  {
1789
2139
  name: "clientContextToken",
1790
2140
  type: "HttpContextToken<string>",
1791
- scope: import_ts_morph4.Scope.Private,
2141
+ scope: import_ts_morph7.Scope.Private,
1792
2142
  isReadonly: true,
1793
2143
  initializer: clientContextTokenName
1794
2144
  }
@@ -1838,7 +2188,7 @@ var BaseInterceptorGenerator = class {
1838
2188
 
1839
2189
  // src/lib/generators/utility/http-params-builder.generator.ts
1840
2190
  var path8 = __toESM(require("path"));
1841
- var import_ts_morph5 = require("ts-morph");
2191
+ var import_ts_morph8 = require("ts-morph");
1842
2192
  var HttpParamsBuilderGenerator = class {
1843
2193
  static {
1844
2194
  __name(this, "HttpParamsBuilderGenerator");
@@ -1872,7 +2222,7 @@ var HttpParamsBuilderGenerator = class {
1872
2222
  {
1873
2223
  name: "addToHttpParams",
1874
2224
  isStatic: true,
1875
- scope: import_ts_morph5.Scope.Public,
2225
+ scope: import_ts_morph8.Scope.Public,
1876
2226
  parameters: [
1877
2227
  {
1878
2228
  name: "httpParams",
@@ -1905,7 +2255,7 @@ return this.addToHttpParamsRecursive(httpParams, value, key);`
1905
2255
  {
1906
2256
  name: "addToHttpParamsRecursive",
1907
2257
  isStatic: true,
1908
- scope: import_ts_morph5.Scope.Private,
2258
+ scope: import_ts_morph8.Scope.Private,
1909
2259
  parameters: [
1910
2260
  {
1911
2261
  name: "httpParams",
@@ -1944,7 +2294,7 @@ return this.handlePrimitive(httpParams, value, key);`
1944
2294
  {
1945
2295
  name: "handleArray",
1946
2296
  isStatic: true,
1947
- scope: import_ts_morph5.Scope.Private,
2297
+ scope: import_ts_morph8.Scope.Private,
1948
2298
  parameters: [
1949
2299
  {
1950
2300
  name: "httpParams",
@@ -1969,7 +2319,7 @@ return httpParams;`
1969
2319
  {
1970
2320
  name: "handleDate",
1971
2321
  isStatic: true,
1972
- scope: import_ts_morph5.Scope.Private,
2322
+ scope: import_ts_morph8.Scope.Private,
1973
2323
  parameters: [
1974
2324
  {
1975
2325
  name: "httpParams",
@@ -1994,7 +2344,7 @@ return httpParams.append(key, date.toISOString());`
1994
2344
  {
1995
2345
  name: "handleObject",
1996
2346
  isStatic: true,
1997
- scope: import_ts_morph5.Scope.Private,
2347
+ scope: import_ts_morph8.Scope.Private,
1998
2348
  parameters: [
1999
2349
  {
2000
2350
  name: "httpParams",
@@ -2020,7 +2370,7 @@ return httpParams;`
2020
2370
  {
2021
2371
  name: "handlePrimitive",
2022
2372
  isStatic: true,
2023
- scope: import_ts_morph5.Scope.Private,
2373
+ scope: import_ts_morph8.Scope.Private,
2024
2374
  parameters: [
2025
2375
  {
2026
2376
  name: "httpParams",
@@ -2048,7 +2398,7 @@ return httpParams.append(key, value);`
2048
2398
  };
2049
2399
 
2050
2400
  // src/lib/generators/service/service.generator.ts
2051
- var import_ts_morph6 = require("ts-morph");
2401
+ var import_ts_morph9 = require("ts-morph");
2052
2402
  var path10 = __toESM(require("path"));
2053
2403
 
2054
2404
  // src/lib/generators/service/service-method/service-method-body.generator.ts
@@ -2057,132 +2407,30 @@ var ServiceMethodBodyGenerator = class {
2057
2407
  __name(this, "ServiceMethodBodyGenerator");
2058
2408
  }
2059
2409
  config;
2060
- parser;
2061
- constructor(config, parser) {
2410
+ constructor(config) {
2062
2411
  this.config = config;
2063
- this.parser = parser;
2064
2412
  }
2065
2413
  generateMethodBody(operation) {
2066
- const context = this.createGenerationContext(operation);
2067
2414
  const bodyParts = [
2068
- this.generateUrlConstruction(operation, context),
2069
- this.generateQueryParams(context),
2070
- this.generateHeaders(context),
2071
- this.generateMultipartFormData(operation, context),
2072
- this.generateUrlEncodedFormData(operation, context),
2073
- this.generateRequestOptions(context),
2074
- this.generateHttpRequest(operation, context)
2415
+ emitUrlConstruction(operation.path, operation.pathParams),
2416
+ emitQueryParams(operation.queryParams),
2417
+ emitHeaders({
2418
+ optionsExpression: "options",
2419
+ customHeaders: this.config.options.customHeaders,
2420
+ contentType: operation
2421
+ }),
2422
+ this.generateMultipartFormData(operation),
2423
+ this.generateUrlEncodedFormData(operation),
2424
+ this.generateHttpRequest(operation)
2075
2425
  ];
2076
2426
  return bodyParts.filter(Boolean).join("\n");
2077
2427
  }
2078
- isMultipartFormData(operation) {
2079
- return !!operation.requestBody?.content?.[CONTENT_TYPES.MULTIPART];
2080
- }
2081
- isUrlEncodedFormData(operation) {
2082
- return !!operation.requestBody?.content?.[CONTENT_TYPES.FORM_URLENCODED] && !operation.requestBody?.content?.[CONTENT_TYPES.JSON];
2083
- }
2084
- getFormDataFields(operation) {
2085
- if (!this.isMultipartFormData(operation)) {
2086
- return [];
2087
- }
2088
- const schema = operation.requestBody?.content?.[CONTENT_TYPES.MULTIPART].schema;
2089
- let resolvedSchema = schema;
2090
- if (schema?.$ref) {
2091
- resolvedSchema = this.parser.resolveReference(schema.$ref);
2092
- }
2093
- const properties = resolvedSchema?.properties || {};
2094
- return Object.keys(properties);
2095
- }
2096
- getUrlEncodedFields(operation) {
2097
- if (!this.isUrlEncodedFormData(operation)) {
2098
- return [];
2099
- }
2100
- const schema = operation.requestBody?.content?.[CONTENT_TYPES.FORM_URLENCODED].schema;
2101
- let resolvedSchema = schema;
2102
- if (schema?.$ref) {
2103
- resolvedSchema = this.parser.resolveReference(schema.$ref);
2104
- }
2105
- const properties = resolvedSchema?.properties || {};
2106
- return Object.keys(properties);
2107
- }
2108
- createGenerationContext(operation) {
2109
- return {
2110
- pathParams: operation.parameters?.filter((p) => p.in === "path") || [],
2111
- queryParams: operation.parameters?.filter((p) => p.in === "query") || [],
2112
- hasBody: !!operation.requestBody,
2113
- isMultipart: this.isMultipartFormData(operation),
2114
- isUrlEncoded: this.isUrlEncodedFormData(operation),
2115
- formDataFields: this.getFormDataFields(operation),
2116
- urlEncodedFields: this.getUrlEncodedFields(operation),
2117
- responseType: this.determineResponseType(operation)
2118
- };
2119
- }
2120
- generateUrlConstruction(operation, context) {
2121
- let urlExpression = `\`\${this.basePath}${operation.path}\``;
2122
- if (context.pathParams.length > 0) {
2123
- context.pathParams.forEach((param) => {
2124
- urlExpression = urlExpression.replace(`{${param.name}}`, `\${${camelCase(param.name)}}`);
2125
- });
2126
- }
2127
- return `const url = ${urlExpression};`;
2128
- }
2129
- generateQueryParams(context) {
2130
- if (context.queryParams.length === 0) {
2131
- return "";
2132
- }
2133
- const paramMappings = context.queryParams.map((param) => `if (${camelCase(param.name)} != null) {
2134
- params = HttpParamsBuilder.addToHttpParams(params, ${camelCase(param.name)}, '${param.name}');
2135
- }`).join("\n");
2136
- return `
2137
- let params = new HttpParams();
2138
- ${paramMappings}`;
2139
- }
2140
- generateHeaders(context) {
2141
- const hasCustomHeaders = this.config.options.customHeaders;
2142
- let headerCode = `
2143
- let headers: HttpHeaders;
2144
- if (options?.headers instanceof HttpHeaders) {
2145
- headers = options.headers;
2146
- } else {
2147
- headers = new HttpHeaders(options?.headers);
2148
- }`;
2149
- if (hasCustomHeaders) {
2150
- headerCode += `
2151
- // Add default headers if not already present
2152
- ${Object.entries(this.config.options.customHeaders || {}).map(([key, value]) => `if (!headers.has('${key}')) {
2153
- headers = headers.set('${key}', '${value}');
2154
- }`).join("\n")}`;
2155
- }
2156
- if (context.isMultipart) {
2157
- headerCode += `
2158
- // Remove Content-Type for multipart (browser will set it with boundary)
2159
- headers = headers.delete('Content-Type');`;
2160
- } else if (context.isUrlEncoded) {
2161
- headerCode += `
2162
- // Set Content-Type for URL-encoded form data
2163
- if (!headers.has('Content-Type')) {
2164
- headers = headers.set('Content-Type', 'application/x-www-form-urlencoded');
2165
- }`;
2166
- } else if (context.hasBody) {
2167
- headerCode += `
2168
- // Set Content-Type for JSON requests if not already set
2169
- if (!headers.has('Content-Type')) {
2170
- headers = headers.set('Content-Type', 'application/json');
2171
- }`;
2172
- }
2173
- return headerCode;
2174
- }
2175
- generateMultipartFormData(operation, context) {
2176
- if (!context.isMultipart || context.formDataFields.length === 0) {
2428
+ generateMultipartFormData(operation) {
2429
+ if (!operation.isMultipart || operation.formDataFields.length === 0) {
2177
2430
  return "";
2178
2431
  }
2179
- const schema = operation.requestBody?.content?.[CONTENT_TYPES.MULTIPART].schema;
2180
- let resolvedSchema = schema;
2181
- if (schema?.$ref) {
2182
- resolvedSchema = this.parser.resolveReference(schema.$ref);
2183
- }
2184
- const properties = resolvedSchema?.properties || {};
2185
- const formDataAppends = context.formDataFields.map((field) => {
2432
+ const properties = operation.formDataSchema?.properties || {};
2433
+ const formDataAppends = operation.formDataFields.map((field) => {
2186
2434
  const fieldSchema = properties[field];
2187
2435
  const isFile = fieldSchema?.type === "string" && fieldSchema?.format === "binary";
2188
2436
  const isArray = fieldSchema?.type === "array";
@@ -2208,17 +2456,12 @@ if (!headers.has('Content-Type')) {
2208
2456
  const formData = new FormData();
2209
2457
  ${formDataAppends}`;
2210
2458
  }
2211
- generateUrlEncodedFormData(operation, context) {
2212
- if (!context.isUrlEncoded || context.urlEncodedFields.length === 0) {
2459
+ generateUrlEncodedFormData(operation) {
2460
+ if (!operation.isUrlEncoded || operation.urlEncodedFields.length === 0) {
2213
2461
  return "";
2214
2462
  }
2215
- const schema = operation.requestBody?.content?.[CONTENT_TYPES.FORM_URLENCODED].schema;
2216
- let resolvedSchema = schema;
2217
- if (schema?.$ref) {
2218
- resolvedSchema = this.parser.resolveReference(schema.$ref);
2219
- }
2220
- const properties = resolvedSchema?.properties || {};
2221
- const formBodyAppends = context.urlEncodedFields.map((field) => {
2463
+ const properties = operation.urlEncodedSchema?.properties || {};
2464
+ const formBodyAppends = operation.urlEncodedFields.map((field) => {
2222
2465
  const fieldSchema = properties[field];
2223
2466
  const isArray = fieldSchema?.type === "array";
2224
2467
  if (isArray) {
@@ -2239,32 +2482,13 @@ ${formDataAppends}`;
2239
2482
  const formBody = new URLSearchParams();
2240
2483
  ${formBodyAppends}`;
2241
2484
  }
2242
- generateRequestOptions(context) {
2243
- const options = [];
2244
- options.push("observe: observe as any");
2245
- options.push("headers");
2246
- if (context.queryParams.length > 0) {
2247
- options.push("params");
2248
- }
2249
- if (context.responseType !== "json") {
2250
- options.push(`responseType: '${context.responseType}' as '${context.responseType}'`);
2251
- }
2252
- options.push("reportProgress: options?.reportProgress");
2253
- options.push("withCredentials: options?.withCredentials");
2254
- options.push("context: this.createContextWithClientId(options?.context)");
2255
- const formattedOptions = options.filter((opt) => opt && !opt.includes("undefined")).join(",\n ");
2256
- return `
2257
- const requestOptions: any = {
2258
- ${formattedOptions}
2259
- };`;
2260
- }
2261
- generateHttpRequest(operation, context) {
2485
+ generateHttpRequest(operation) {
2262
2486
  const httpMethod = operation.method.toLowerCase();
2263
2487
  let bodyParam = "";
2264
- if (context.hasBody) {
2265
- if (context.isMultipart) {
2488
+ if (operation.hasBody) {
2489
+ if (operation.isMultipart) {
2266
2490
  bodyParam = "formData";
2267
- } else if (context.isUrlEncoded) {
2491
+ } else if (operation.isUrlEncoded) {
2268
2492
  bodyParam = "formBody.toString()";
2269
2493
  } else if (operation.requestBody?.content?.[CONTENT_TYPES.JSON]) {
2270
2494
  const bodyType = getRequestBodyType(operation.requestBody, this.config);
@@ -2278,28 +2502,23 @@ const requestOptions: any = {
2278
2502
  "patch"
2279
2503
  ];
2280
2504
  const parseResponse = this.config.options.validation?.response ? `.pipe(map(response => options?.parse?.(response) ?? response))` : "";
2505
+ const entries = [];
2281
2506
  if (methodsWithBody.includes(httpMethod)) {
2282
- return `
2283
- return this.httpClient.${httpMethod}(url, ${bodyParam || "null"}, requestOptions)${parseResponse};`;
2284
- } else {
2285
- return `
2286
- return this.httpClient.${httpMethod}(url, requestOptions)${parseResponse};`;
2507
+ entries.push(`body: ${bodyParam || "null"}`);
2287
2508
  }
2288
- }
2289
- determineResponseType(operation) {
2290
- const successResponses = [
2291
- "200",
2292
- "201",
2293
- "202",
2294
- "204",
2295
- "206"
2296
- ];
2297
- for (const statusCode of successResponses) {
2298
- const response = operation.responses?.[statusCode];
2299
- if (!response) continue;
2300
- return getResponseTypeFromResponse(response);
2509
+ entries.push("observe");
2510
+ entries.push("headers");
2511
+ if (operation.queryParams.length > 0) {
2512
+ entries.push("params");
2301
2513
  }
2302
- return "json";
2514
+ entries.push(emitResponseTypeOption(operation.responseType));
2515
+ entries.push("reportProgress: options?.reportProgress");
2516
+ entries.push("withCredentials: options?.withCredentials");
2517
+ entries.push("context: this.createContextWithClientId(options?.context)");
2518
+ return `
2519
+ return this.httpClient.request('${httpMethod}', url, {
2520
+ ${joinRequestOptionEntries(entries)}
2521
+ })${parseResponse};`;
2303
2522
  }
2304
2523
  };
2305
2524
 
@@ -2376,10 +2595,8 @@ var ServiceMethodParamsGenerator = class {
2376
2595
  __name(this, "ServiceMethodParamsGenerator");
2377
2596
  }
2378
2597
  config;
2379
- parser;
2380
- constructor(config, parser) {
2598
+ constructor(config) {
2381
2599
  this.config = config;
2382
- this.parser = parser;
2383
2600
  }
2384
2601
  generateMethodParameters(operation) {
2385
2602
  const params = this.generateApiParameters(operation);
@@ -2391,28 +2608,28 @@ var ServiceMethodParamsGenerator = class {
2391
2608
  }
2392
2609
  generateApiParameters(operation) {
2393
2610
  const params = [];
2394
- const pathParams = operation.parameters?.filter((p) => p.in === "path") || [];
2395
- pathParams.forEach((param) => {
2611
+ operation.pathParams.forEach((param) => {
2396
2612
  params.push({
2397
2613
  name: camelCase(param.name),
2398
- type: getTypeScriptType(param.schema || param, this.config),
2614
+ // Swagger 2.0 puts type/format/enum on the parameter itself; the
2615
+ // spread (vs passing param directly) is needed because Parameter
2616
+ // lacks TypeSchema's index signature — a fresh literal satisfies it.
2617
+ type: getTypeScriptType(param.schema || {
2618
+ ...param
2619
+ }, this.config),
2399
2620
  hasQuestionToken: !param.required
2400
2621
  });
2401
2622
  });
2402
2623
  const requestBody = operation.requestBody;
2403
2624
  if (requestBody) {
2404
- const formDataContent = requestBody.content?.[CONTENT_TYPES.MULTIPART];
2405
- const urlEncodedContent = requestBody.content?.[CONTENT_TYPES.FORM_URLENCODED];
2406
2625
  const jsonContent = requestBody.content?.[CONTENT_TYPES.JSON];
2407
- if (formDataContent) {
2408
- const formParams = this.convertObjectToSingleParams(formDataContent.schema);
2409
- params.push(...formParams);
2626
+ if (operation.isMultipart) {
2627
+ params.push(...this.convertObjectToSingleParams(operation.formDataSchema));
2410
2628
  }
2411
- if (!jsonContent && urlEncodedContent) {
2412
- const formParams = this.convertObjectToSingleParams(urlEncodedContent.schema);
2413
- params.push(...formParams);
2629
+ if (operation.isUrlEncoded) {
2630
+ params.push(...this.convertObjectToSingleParams(operation.urlEncodedSchema));
2414
2631
  }
2415
- if (jsonContent && !formDataContent) {
2632
+ if (jsonContent && !operation.isMultipart) {
2416
2633
  const bodyType = this.getRequestBodyType(requestBody);
2417
2634
  const isInterface = isDataTypeInterface(bodyType);
2418
2635
  params.push({
@@ -2422,11 +2639,12 @@ var ServiceMethodParamsGenerator = class {
2422
2639
  });
2423
2640
  }
2424
2641
  }
2425
- const queryParams = operation.parameters?.filter((p) => p.in === "query") || [];
2426
- queryParams.forEach((param) => {
2642
+ operation.queryParams.forEach((param) => {
2427
2643
  params.push({
2428
2644
  name: camelCase(param.name),
2429
- type: getTypeScriptType(param.schema || param, this.config),
2645
+ type: getTypeScriptType(param.schema || {
2646
+ ...param
2647
+ }, this.config),
2430
2648
  hasQuestionToken: !param.required
2431
2649
  });
2432
2650
  });
@@ -2465,17 +2683,14 @@ var ServiceMethodParamsGenerator = class {
2465
2683
  }
2466
2684
  return "any";
2467
2685
  }
2686
+ /** `schema` arrives ref-resolved from the normalizer (formData/urlEncoded schema). */
2468
2687
  convertObjectToSingleParams(schema) {
2469
2688
  const params = [];
2470
- let resolvedSchema = schema;
2471
- if (schema?.$ref) {
2472
- resolvedSchema = this.parser.resolveReference(schema.$ref);
2473
- }
2474
- Object.entries(resolvedSchema?.properties ?? {}).forEach(([key, value]) => {
2689
+ Object.entries(schema?.properties ?? {}).forEach(([key, value]) => {
2475
2690
  params.push({
2476
2691
  name: key,
2477
2692
  type: getTypeScriptType(value, this.config, value.nullable),
2478
- hasQuestionToken: !resolvedSchema?.required?.includes(key)
2693
+ hasQuestionToken: !schema?.required?.includes(key)
2479
2694
  });
2480
2695
  });
2481
2696
  return params;
@@ -2490,9 +2705,9 @@ var ServiceMethodOverloadsGenerator = class {
2490
2705
  config;
2491
2706
  paramsGenerator;
2492
2707
  responseDataType = "any";
2493
- constructor(config, parser) {
2708
+ constructor(config) {
2494
2709
  this.config = config;
2495
- this.paramsGenerator = new ServiceMethodParamsGenerator(config, parser);
2710
+ this.paramsGenerator = new ServiceMethodParamsGenerator(config);
2496
2711
  }
2497
2712
  generateMethodOverloads(operation, requestObject) {
2498
2713
  const observeTypes = [
@@ -2501,7 +2716,7 @@ var ServiceMethodOverloadsGenerator = class {
2501
2716
  "events"
2502
2717
  ];
2503
2718
  const overloads = [];
2504
- const responseType = this.determineResponseTypeForOperation(operation);
2719
+ const responseType = operation.responseType;
2505
2720
  observeTypes.forEach((observe) => {
2506
2721
  const overload = this.generateMethodOverload(operation, observe, responseType, requestObject);
2507
2722
  if (overload) {
@@ -2577,21 +2792,6 @@ var ServiceMethodOverloadsGenerator = class {
2577
2792
  }
2578
2793
  return `RequestOptions<'${responseType}', ${additionalTypeParameters.join(", ")}>`;
2579
2794
  }
2580
- determineResponseTypeForOperation(operation) {
2581
- const successResponses = [
2582
- "200",
2583
- "201",
2584
- "202",
2585
- "204",
2586
- "206"
2587
- ];
2588
- for (const statusCode of successResponses) {
2589
- const response = operation.responses?.[statusCode];
2590
- if (!response) continue;
2591
- return getResponseTypeFromResponse(response);
2592
- }
2593
- return "json";
2594
- }
2595
2795
  };
2596
2796
 
2597
2797
  // src/lib/generators/service/service-method.generator.ts
@@ -2603,11 +2803,11 @@ var ServiceMethodGenerator = class {
2603
2803
  bodyGenerator;
2604
2804
  overloadsGenerator;
2605
2805
  paramsGenerator;
2606
- constructor(config, parser) {
2806
+ constructor(config) {
2607
2807
  this.config = config;
2608
- this.bodyGenerator = new ServiceMethodBodyGenerator(config, parser);
2609
- this.overloadsGenerator = new ServiceMethodOverloadsGenerator(config, parser);
2610
- this.paramsGenerator = new ServiceMethodParamsGenerator(config, parser);
2808
+ this.bodyGenerator = new ServiceMethodBodyGenerator(config);
2809
+ this.overloadsGenerator = new ServiceMethodOverloadsGenerator(config);
2810
+ this.paramsGenerator = new ServiceMethodParamsGenerator(config);
2611
2811
  }
2612
2812
  addServiceMethod(serviceClass, operation, requestObject) {
2613
2813
  const methodName = this.generateMethodName(operation);
@@ -2672,9 +2872,9 @@ var RequestParamsGenerator = class {
2672
2872
  paramsGenerator;
2673
2873
  registry = /* @__PURE__ */ new Map();
2674
2874
  usedInterfaceNames = /* @__PURE__ */ new Set();
2675
- constructor(parser, project, config) {
2875
+ constructor(project, config) {
2676
2876
  this.project = project;
2677
- this.paramsGenerator = new ServiceMethodParamsGenerator(config, parser);
2877
+ this.paramsGenerator = new ServiceMethodParamsGenerator(config);
2678
2878
  }
2679
2879
  buildRegistry(controllerGroups, getMethodName) {
2680
2880
  Object.entries(controllerGroups).forEach(([controllerName, operations]) => {
@@ -2778,31 +2978,27 @@ var ServiceGenerator = class {
2778
2978
  }
2779
2979
  project;
2780
2980
  parser;
2781
- spec;
2782
2981
  config;
2783
2982
  methodGenerator;
2784
2983
  requestObjects;
2785
- constructor(parser, project, config) {
2984
+ onWarning;
2985
+ constructor(parser, project, config, onWarning) {
2786
2986
  this.config = config;
2787
2987
  this.project = project;
2788
2988
  this.parser = parser;
2789
- this.spec = this.parser.getSpec();
2790
- if (!this.parser.isValidSpec()) {
2791
- const versionInfo = this.parser.getSpecVersion();
2792
- throw new Error(`Invalid or unsupported specification format. Expected OpenAPI 3.x or Swagger 2.x. ${versionInfo ? `Found: ${versionInfo.type} ${versionInfo.version}` : "No version info found"}`);
2793
- }
2794
- this.methodGenerator = new ServiceMethodGenerator(config, parser);
2989
+ this.onWarning = onWarning;
2990
+ this.methodGenerator = new ServiceMethodGenerator(config);
2795
2991
  }
2796
2992
  async generate(outputRoot) {
2797
2993
  const outputDir = path10.join(outputRoot, "services");
2798
- const paths = extractPaths(this.spec.paths);
2994
+ const paths = this.parser.getNormalizedSpec().operations;
2799
2995
  if (paths.length === 0) {
2800
- console.warn("No API paths found in the specification");
2996
+ this.onWarning?.("No API paths found in the specification");
2801
2997
  return;
2802
2998
  }
2803
2999
  const controllerGroups = this.groupPathsByController(paths);
2804
3000
  if (this.config.options.useSingleRequestParameter) {
2805
- const requestParamsGenerator = new RequestParamsGenerator(this.parser, this.project, this.config);
3001
+ const requestParamsGenerator = new RequestParamsGenerator(this.project, this.config);
2806
3002
  this.requestObjects = requestParamsGenerator.buildRegistry(controllerGroups, (operation) => this.methodGenerator.generateMethodName(operation));
2807
3003
  requestParamsGenerator.generate(outputRoot);
2808
3004
  }
@@ -2898,27 +3094,27 @@ var ServiceGenerator = class {
2898
3094
  serviceClass.addProperty({
2899
3095
  name: "httpClient",
2900
3096
  type: "HttpClient",
2901
- scope: import_ts_morph6.Scope.Private,
3097
+ scope: import_ts_morph9.Scope.Private,
2902
3098
  isReadonly: true,
2903
3099
  initializer: "inject(HttpClient)"
2904
3100
  });
2905
3101
  serviceClass.addProperty({
2906
3102
  name: "basePath",
2907
3103
  type: "string",
2908
- scope: import_ts_morph6.Scope.Private,
3104
+ scope: import_ts_morph9.Scope.Private,
2909
3105
  isReadonly: true,
2910
3106
  initializer: `inject(${basePathTokenName})`
2911
3107
  });
2912
3108
  serviceClass.addProperty({
2913
3109
  name: "clientContextToken",
2914
3110
  type: "HttpContextToken<string>",
2915
- scope: import_ts_morph6.Scope.Private,
3111
+ scope: import_ts_morph9.Scope.Private,
2916
3112
  isReadonly: true,
2917
3113
  initializer: clientContextTokenName
2918
3114
  });
2919
3115
  serviceClass.addMethod({
2920
3116
  name: "createContextWithClientId",
2921
- scope: import_ts_morph6.Scope.Private,
3117
+ scope: import_ts_morph9.Scope.Private,
2922
3118
  parameters: [
2923
3119
  {
2924
3120
  name: "existingContext",
@@ -2971,6 +3167,111 @@ var ServiceIndexGenerator = class {
2971
3167
  }
2972
3168
  };
2973
3169
 
3170
+ // src/lib/core/config-validation.ts
3171
+ var RESPONSE_TYPES = [
3172
+ "json",
3173
+ "blob",
3174
+ "arraybuffer",
3175
+ "text"
3176
+ ];
3177
+ var ConfigValidationError = class extends Error {
3178
+ static {
3179
+ __name(this, "ConfigValidationError");
3180
+ }
3181
+ issues;
3182
+ constructor(issues) {
3183
+ super(`Invalid ng-openapi configuration:
3184
+ ${issues.map((issue) => ` - ${issue}`).join("\n")}`);
3185
+ this.name = "ConfigValidationError";
3186
+ this.issues = issues;
3187
+ }
3188
+ };
3189
+ function validateGeneratorConfig(config) {
3190
+ if (!config || typeof config !== "object") {
3191
+ throw new ConfigValidationError([
3192
+ "config must be an object \u2014 see https://ng-openapi.dev for the shape"
3193
+ ]);
3194
+ }
3195
+ const issues = [];
3196
+ const c = config;
3197
+ if (typeof c.input !== "string" || c.input.trim() === "") {
3198
+ issues.push("`input` must be a non-empty string (path or URL of the OpenAPI/Swagger spec)");
3199
+ }
3200
+ if (typeof c.output !== "string" || c.output.trim() === "") {
3201
+ issues.push("`output` must be a non-empty string (output directory)");
3202
+ }
3203
+ if (c.clientName !== void 0 && typeof c.clientName !== "string") {
3204
+ issues.push("`clientName` must be a string");
3205
+ }
3206
+ if (c.validateInput !== void 0 && typeof c.validateInput !== "function") {
3207
+ issues.push("`validateInput` must be a function (spec) => boolean");
3208
+ }
3209
+ if (!c.options || typeof c.options !== "object") {
3210
+ issues.push("`options` must be an object with at least `dateType` and `enumStyle`");
3211
+ } else {
3212
+ const options = c.options;
3213
+ if (options.dateType !== "string" && options.dateType !== "Date") {
3214
+ issues.push(`\`options.dateType\` must be "string" or "Date", got ${JSON.stringify(options.dateType)}`);
3215
+ }
3216
+ if (options.enumStyle !== "enum" && options.enumStyle !== "union") {
3217
+ issues.push(`\`options.enumStyle\` must be "enum" or "union", got ${JSON.stringify(options.enumStyle)}`);
3218
+ }
3219
+ const booleanKeys = [
3220
+ "generateServices",
3221
+ "generateEnumBasedOnDescription",
3222
+ "useSingleRequestParameter"
3223
+ ];
3224
+ for (const key of booleanKeys) {
3225
+ if (options[key] !== void 0 && typeof options[key] !== "boolean") {
3226
+ issues.push(`\`options.${key}\` must be a boolean`);
3227
+ }
3228
+ }
3229
+ if (options.customizeMethodName !== void 0 && typeof options.customizeMethodName !== "function") {
3230
+ issues.push("`options.customizeMethodName` must be a function (operationId) => string");
3231
+ }
3232
+ if (options.validation !== void 0 && (typeof options.validation !== "object" || options.validation === null)) {
3233
+ issues.push("`options.validation` must be an object like { response?: boolean }");
3234
+ }
3235
+ if (options.customHeaders !== void 0) {
3236
+ if (typeof options.customHeaders !== "object" || options.customHeaders === null) {
3237
+ issues.push("`options.customHeaders` must be an object of header name \u2192 value strings");
3238
+ } else {
3239
+ for (const [header, value] of Object.entries(options.customHeaders)) {
3240
+ if (typeof value !== "string") {
3241
+ issues.push(`\`options.customHeaders["${header}"]\` must be a string`);
3242
+ }
3243
+ }
3244
+ }
3245
+ }
3246
+ if (options.responseTypeMapping !== void 0) {
3247
+ if (typeof options.responseTypeMapping !== "object" || options.responseTypeMapping === null) {
3248
+ issues.push("`options.responseTypeMapping` must be an object of content type \u2192 response type");
3249
+ } else {
3250
+ for (const [contentType, value] of Object.entries(options.responseTypeMapping)) {
3251
+ if (!RESPONSE_TYPES.includes(value)) {
3252
+ issues.push(`\`options.responseTypeMapping["${contentType}"]\` must be one of ${RESPONSE_TYPES.join(", ")}, got ${JSON.stringify(value)}`);
3253
+ }
3254
+ }
3255
+ }
3256
+ }
3257
+ }
3258
+ if (c.plugins !== void 0) {
3259
+ if (!Array.isArray(c.plugins)) {
3260
+ issues.push("`plugins` must be an array of plugin classes (e.g. HttpResourcePlugin, ZodPlugin)");
3261
+ } else {
3262
+ c.plugins.forEach((plugin, index) => {
3263
+ if (typeof plugin !== "function") {
3264
+ issues.push(`\`plugins[${index}]\` must be a plugin class, got ${typeof plugin}`);
3265
+ }
3266
+ });
3267
+ }
3268
+ }
3269
+ if (issues.length > 0) {
3270
+ throw new ConfigValidationError(issues);
3271
+ }
3272
+ }
3273
+ __name(validateGeneratorConfig, "validateGeneratorConfig");
3274
+
2974
3275
  // src/lib/core/generator.ts
2975
3276
  var fs3 = __toESM(require("fs"));
2976
3277
  var path12 = __toESM(require("path"));
@@ -2979,7 +3280,7 @@ function validateInput(inputPath) {
2979
3280
  return;
2980
3281
  }
2981
3282
  if (!fs3.existsSync(inputPath)) {
2982
- throw new Error(`Input file not found: ${inputPath}`);
3283
+ throw new SpecLoadError(`Input file not found: ${inputPath}`, inputPath);
2983
3284
  }
2984
3285
  const extension = path12.extname(inputPath).toLowerCase();
2985
3286
  const supportedExtensions = [
@@ -2988,89 +3289,124 @@ function validateInput(inputPath) {
2988
3289
  ".yml"
2989
3290
  ];
2990
3291
  if (!supportedExtensions.includes(extension)) {
2991
- throw new Error(`Failed to parse ${extension || "specification"}. Supported formats are .json, .yaml, and .yml.`);
3292
+ throw new SpecLoadError(`Failed to parse ${extension || "specification"}. Supported formats are .json, .yaml, and .yml.`, inputPath);
2992
3293
  }
2993
3294
  }
2994
3295
  __name(validateInput, "validateInput");
2995
- async function generateFromConfig(config) {
3296
+ async function generateFromConfig(config, reporter = {}) {
3297
+ const startedAt = Date.now();
3298
+ validateGeneratorConfig(config);
2996
3299
  validateInput(config.input);
2997
3300
  const outputPath = config.output;
2998
3301
  const generateServices = config.options.generateServices ?? true;
2999
- const inputType = isUrl(config.input) ? "URL" : "file";
3302
+ const warnings = [];
3303
+ const onWarning = /* @__PURE__ */ __name((message) => {
3304
+ warnings.push(message);
3305
+ reporter.onWarning?.(message);
3306
+ }, "onWarning");
3000
3307
  if (!fs3.existsSync(outputPath)) {
3001
3308
  fs3.mkdirSync(outputPath, {
3002
3309
  recursive: true
3003
3310
  });
3004
3311
  }
3005
- try {
3006
- const project = new import_ts_morph7.Project({
3007
- compilerOptions: {
3008
- declaration: true,
3009
- target: import_ts_morph7.ScriptTarget.ES2022,
3010
- module: import_ts_morph7.ModuleKind.Preserve,
3011
- strict: true,
3012
- ...config.compilerOptions
3013
- }
3014
- });
3015
- console.log(`\u{1F4E1} Processing OpenAPI specification from ${inputType}: ${config.input}`);
3016
- const swaggerParser = await SwaggerParser.create(config.input, config);
3017
- const typeGenerator = new TypeGenerator(swaggerParser, project, config, outputPath);
3018
- await typeGenerator.generate();
3019
- console.log(`\u2705 TypeScript interfaces generated`);
3020
- if (generateServices) {
3021
- const tokenGenerator = new TokenGenerator(project, config.clientName);
3022
- tokenGenerator.generate(outputPath);
3023
- if (config.options.dateType === "Date") {
3024
- const dateTransformer = new DateTransformerGenerator(project);
3025
- dateTransformer.generate(outputPath);
3026
- }
3027
- const fileDownloadHelper = new FileDownloadGenerator(project);
3028
- fileDownloadHelper.generate(outputPath);
3029
- const httpParamsBuilderGenerator = new HttpParamsBuilderGenerator(project);
3030
- httpParamsBuilderGenerator.generate(outputPath);
3031
- const providerGenerator = new ProviderGenerator(project, config);
3032
- providerGenerator.generate(outputPath);
3033
- const baseInterceptorGenerator = new BaseInterceptorGenerator(project, config.clientName);
3034
- baseInterceptorGenerator.generate(outputPath);
3035
- const serviceGenerator = new ServiceGenerator(swaggerParser, project, config);
3036
- await serviceGenerator.generate(outputPath);
3037
- const indexGenerator = new ServiceIndexGenerator(project);
3038
- indexGenerator.generateIndex(outputPath);
3039
- console.log(`\u2705 Angular services generated`);
3040
- }
3041
- if (config.plugins?.length) {
3042
- for (const plugin of config.plugins) {
3043
- const generatorClass = plugin;
3044
- const pluginGenerator = new generatorClass(swaggerParser, project, config);
3045
- await pluginGenerator.generate(outputPath);
3046
- }
3047
- console.log(`\u2705 Plugins are generated`);
3048
- }
3049
- const mainIndexGenerator = new MainIndexGenerator(project, config);
3050
- mainIndexGenerator.generateMainIndex(outputPath);
3051
- const sourceInfo = `from ${inputType}: ${config.input}`;
3052
- if (config.clientName) {
3053
- console.log(`\u{1F389} ${config.clientName} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
3054
- } else {
3055
- console.log(`\u{1F389} Generation completed successfully ${sourceInfo} -> ${outputPath}`);
3056
- }
3057
- } catch (error) {
3058
- if (error instanceof Error) {
3059
- console.error("\u274C Error during generation:", error.message);
3060
- if (error.message.includes("fetch") || error.message.includes("Failed to fetch")) {
3061
- console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
3062
- console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");
3063
- }
3064
- } else {
3065
- console.error("\u274C Unknown error during generation:", error);
3312
+ const project = new import_ts_morph10.Project({
3313
+ compilerOptions: {
3314
+ declaration: true,
3315
+ target: import_ts_morph10.ScriptTarget.ES2022,
3316
+ module: import_ts_morph10.ModuleKind.Preserve,
3317
+ strict: true,
3318
+ ...config.compilerOptions
3066
3319
  }
3067
- throw error;
3068
- }
3320
+ });
3321
+ reporter.onPhase?.("processing-spec");
3322
+ const swaggerParser = await SwaggerParser.create(config.input, config);
3323
+ if (!swaggerParser.isValidSpec()) {
3324
+ const versionInfo = swaggerParser.getSpecVersion();
3325
+ throw new SpecParseError(`Invalid or unsupported specification format. Expected OpenAPI 3.x or Swagger 2.x. ${versionInfo ? `Found: ${versionInfo.type} ${versionInfo.version}` : "No version info found"}`, config.input);
3326
+ }
3327
+ const normalizedSpec = swaggerParser.getNormalizedSpec();
3328
+ const typeGenerator = new TypeGenerator(swaggerParser, project, config, outputPath, onWarning);
3329
+ await typeGenerator.generate();
3330
+ reporter.onPhase?.("types-generated");
3331
+ if (generateServices) {
3332
+ const tokenGenerator = new TokenGenerator(project, config.clientName);
3333
+ tokenGenerator.generate(outputPath);
3334
+ if (config.options.dateType === "Date") {
3335
+ const dateTransformer = new DateTransformerGenerator(project);
3336
+ dateTransformer.generate(outputPath);
3337
+ }
3338
+ const fileDownloadHelper = new FileDownloadGenerator(project);
3339
+ fileDownloadHelper.generate(outputPath);
3340
+ const httpParamsBuilderGenerator = new HttpParamsBuilderGenerator(project);
3341
+ httpParamsBuilderGenerator.generate(outputPath);
3342
+ const providerGenerator = new ProviderGenerator(project, config);
3343
+ providerGenerator.generate(outputPath);
3344
+ const baseInterceptorGenerator = new BaseInterceptorGenerator(project, config.clientName);
3345
+ baseInterceptorGenerator.generate(outputPath);
3346
+ const serviceGenerator = new ServiceGenerator(swaggerParser, project, config, onWarning);
3347
+ await serviceGenerator.generate(outputPath);
3348
+ const indexGenerator = new ServiceIndexGenerator(project);
3349
+ indexGenerator.generateIndex(outputPath);
3350
+ reporter.onPhase?.("services-generated");
3351
+ }
3352
+ if (config.plugins?.length) {
3353
+ for (const plugin of config.plugins) {
3354
+ const pluginGenerator = new plugin({
3355
+ spec: normalizedSpec,
3356
+ project,
3357
+ config,
3358
+ onWarning
3359
+ });
3360
+ await pluginGenerator.generate(outputPath);
3361
+ }
3362
+ reporter.onPhase?.("plugins-generated");
3363
+ }
3364
+ const mainIndexGenerator = new MainIndexGenerator(project, config);
3365
+ mainIndexGenerator.generateMainIndex(outputPath);
3366
+ return {
3367
+ client: config.clientName,
3368
+ filesWritten: project.getSourceFiles().map((sourceFile) => sourceFile.getFilePath()),
3369
+ warnings,
3370
+ durationMs: Date.now() - startedAt
3371
+ };
3069
3372
  }
3070
3373
  __name(generateFromConfig, "generateFromConfig");
3071
3374
 
3072
3375
  // src/lib/cli.ts
3073
3376
  var program = new import_commander.Command();
3377
+ function createConsoleReporter(config) {
3378
+ const inputType = isUrl(config.input) ? "URL" : "file";
3379
+ return {
3380
+ onPhase(phase) {
3381
+ switch (phase) {
3382
+ case "processing-spec":
3383
+ console.log(`\u{1F4E1} Processing OpenAPI specification from ${inputType}: ${config.input}`);
3384
+ break;
3385
+ case "types-generated":
3386
+ console.log("\u2705 TypeScript interfaces generated");
3387
+ break;
3388
+ case "services-generated":
3389
+ console.log("\u2705 Angular services generated");
3390
+ break;
3391
+ case "plugins-generated":
3392
+ console.log("\u2705 Plugins are generated");
3393
+ break;
3394
+ }
3395
+ },
3396
+ onWarning(message) {
3397
+ console.warn(`\u26A0\uFE0F ${message}`);
3398
+ }
3399
+ };
3400
+ }
3401
+ __name(createConsoleReporter, "createConsoleReporter");
3402
+ async function runGeneration(config) {
3403
+ const result = await generateFromConfig(config, createConsoleReporter(config));
3404
+ const inputType = isUrl(config.input) ? "URL" : "file";
3405
+ const sourceInfo = `from ${inputType}: ${config.input}`;
3406
+ const clientPrefix = result.client ? `${result.client} ` : "";
3407
+ console.log(`\u{1F389} ${clientPrefix}Generation completed successfully ${sourceInfo} -> ${config.output}`);
3408
+ }
3409
+ __name(runGeneration, "runGeneration");
3074
3410
  async function loadConfigFile(configPath) {
3075
3411
  const resolvedPath = path13.resolve(configPath);
3076
3412
  if (!fs4.existsSync(resolvedPath)) {
@@ -3104,32 +3440,38 @@ async function generateFromOptions(options) {
3104
3440
  try {
3105
3441
  if (options.config) {
3106
3442
  const config = await loadConfigFile(options.config);
3107
- await generateFromConfig(config);
3443
+ await runGeneration(config);
3108
3444
  } else if (options.input) {
3109
3445
  const config = {
3110
3446
  input: options.input,
3111
3447
  output: options.output || "./src/generated",
3112
3448
  options: {
3449
+ // Passed through unchecked on purpose: validateGeneratorConfig
3450
+ // rejects anything but "string" | "Date" with an actionable error
3113
3451
  dateType: options.dateType || "Date",
3114
3452
  enumStyle: "enum",
3115
3453
  generateEnumBasedOnDescription: true,
3116
3454
  generateServices: !options.typesOnly
3117
3455
  }
3118
3456
  };
3119
- await generateFromConfig(config);
3457
+ await runGeneration(config);
3120
3458
  } else {
3121
3459
  console.error("Error: Either --config or --input option is required");
3122
- program.help();
3123
- process.exit(1);
3460
+ program.help({
3461
+ error: true
3462
+ });
3124
3463
  }
3125
3464
  console.log("\u2728 Generation completed successfully!");
3126
3465
  } catch (error) {
3127
3466
  console.error("\u274C Generation failed:", error instanceof Error ? error.message : error);
3128
- process.exit(1);
3467
+ if (error instanceof SpecLoadError && isUrl(error.source)) {
3468
+ console.error("\u{1F4A1} Tip: Make sure the URL is accessible and returns a valid OpenAPI/Swagger specification");
3469
+ console.error("\u{1F4A1} Alternative: Download the specification file locally and use the file path instead");
3470
+ }
3471
+ process.exitCode = 1;
3129
3472
  } finally {
3130
3473
  const duration = ((/* @__PURE__ */ new Date()).getTime() - timestamp) / 1e3;
3131
3474
  console.log(`\u23F1\uFE0F Duration: ${duration.toFixed(2)} seconds`);
3132
- process.exit(0);
3133
3475
  }
3134
3476
  }
3135
3477
  __name(generateFromOptions, "generateFromOptions");