nestjs-openapi 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -1,17 +1,17 @@
1
1
  #!/usr/bin/env node
2
2
  import 'tsx';
3
- import { g as generate, e as formatValidationResult } from './shared/nestjs-openapi.yWMsjl_8.mjs';
3
+ import { g as generate, e as formatValidationResult } from './shared/nestjs-openapi.CUKGdNSM.mjs';
4
4
  import minimist from 'minimist';
5
5
  import { relative } from 'node:path';
6
6
  import { createRequire } from 'node:module';
7
7
  import 'effect';
8
8
  import 'node:fs';
9
+ import 'node:crypto';
9
10
  import 'ts-morph';
10
11
  import 'glob';
11
12
  import 'js-yaml';
12
- import './shared/nestjs-openapi.CBj9xHZ4.mjs';
13
+ import './shared/nestjs-openapi.DRcy130f.mjs';
13
14
  import 'ts-json-schema-generator';
14
- import 'node:crypto';
15
15
  import 'node:url';
16
16
  import 'child_process';
17
17
 
@@ -117,7 +117,7 @@ const main = async () => {
117
117
  console.log(` ${formatValidationResult(result.validation)}`);
118
118
  }
119
119
  }
120
- process.exit(0);
120
+ process.exit(result.validation.valid ? 0 : 1);
121
121
  } catch (err) {
122
122
  const duration = performance.now() - startTime;
123
123
  const message = err instanceof Error ? err.message : String(err);
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Effect, Context, Layer, Option } from 'effect';
2
- import { C as ConfigNotFoundError, O as OpenApiGeneratorConfig, a as ConfigError, R as ResolvedConfig, P as ProjectError, b as ProjectInitError, E as EntryNotFoundError, M as MethodInfo, c as OpenApiPaths$1 } from './shared/nestjs-openapi.OhsaHu32.mjs';
3
- export { A as AnalysisError, i as ConfigLoadError, j as ConfigValidationError, G as GenerateOptions, k as GeneratorError, H as HttpMethod, I as InvalidMethodError, e as ParameterLocation, f as ResolvedParameter, h as ReturnTypeInfo, d as generateAsync, g as generateEffect } from './shared/nestjs-openapi.OhsaHu32.mjs';
2
+ import { C as ConfigNotFoundError, O as OpenApiGeneratorConfig, a as ConfigError, R as ResolvedConfig, P as ProjectError, b as ProjectInitError, E as EntryNotFoundError, M as MethodInfo, c as OpenApiPaths$1 } from './shared/nestjs-openapi.ClTIhsb-.mjs';
3
+ export { A as AnalysisError, i as ConfigLoadError, j as ConfigValidationError, G as GenerateOptions, k as GeneratorError, H as HttpMethod, I as InvalidMethodError, e as ParameterLocation, f as ResolvedParameter, h as ReturnTypeInfo, d as generateAsync, g as generateEffect } from './shared/nestjs-openapi.ClTIhsb-.mjs';
4
4
  import { DynamicModule } from '@nestjs/common';
5
5
  import { Project, SourceFile, ClassDeclaration, MethodDeclaration, Decorator, Symbol, ObjectLiteralExpression, Expression } from 'ts-morph';
6
6
 
@@ -275,6 +275,23 @@ interface OptionsConfig {
275
275
  * Query parameter handling options.
276
276
  */
277
277
  readonly query?: QueryOptions;
278
+ /**
279
+ * Schema generation and normalization options.
280
+ */
281
+ readonly schemas?: SchemaOptions;
282
+ }
283
+ /**
284
+ * Schema generation and normalization options.
285
+ */
286
+ interface SchemaOptions {
287
+ /**
288
+ * How to handle schema aliases that only redirect via `$ref`.
289
+ * - `"collapse"`: Rewrite refs to the final target schema and remove alias entries
290
+ * - `"preserve"`: Keep alias schemas as-is
291
+ *
292
+ * @default "collapse"
293
+ */
294
+ readonly aliasRefs?: 'collapse' | 'preserve';
278
295
  }
279
296
  /**
280
297
  * Query parameter handling options.
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Effect, Context, Layer, Option } from 'effect';
2
- import { C as ConfigNotFoundError, O as OpenApiGeneratorConfig, a as ConfigError, R as ResolvedConfig, P as ProjectError, b as ProjectInitError, E as EntryNotFoundError, M as MethodInfo, c as OpenApiPaths$1 } from './shared/nestjs-openapi.OhsaHu32.js';
3
- export { A as AnalysisError, i as ConfigLoadError, j as ConfigValidationError, G as GenerateOptions, k as GeneratorError, H as HttpMethod, I as InvalidMethodError, e as ParameterLocation, f as ResolvedParameter, h as ReturnTypeInfo, d as generateAsync, g as generateEffect } from './shared/nestjs-openapi.OhsaHu32.js';
2
+ import { C as ConfigNotFoundError, O as OpenApiGeneratorConfig, a as ConfigError, R as ResolvedConfig, P as ProjectError, b as ProjectInitError, E as EntryNotFoundError, M as MethodInfo, c as OpenApiPaths$1 } from './shared/nestjs-openapi.ClTIhsb-.js';
3
+ export { A as AnalysisError, i as ConfigLoadError, j as ConfigValidationError, G as GenerateOptions, k as GeneratorError, H as HttpMethod, I as InvalidMethodError, e as ParameterLocation, f as ResolvedParameter, h as ReturnTypeInfo, d as generateAsync, g as generateEffect } from './shared/nestjs-openapi.ClTIhsb-.js';
4
4
  import { DynamicModule } from '@nestjs/common';
5
5
  import { Project, SourceFile, ClassDeclaration, MethodDeclaration, Decorator, Symbol, ObjectLiteralExpression, Expression } from 'ts-morph';
6
6
 
@@ -275,6 +275,23 @@ interface OptionsConfig {
275
275
  * Query parameter handling options.
276
276
  */
277
277
  readonly query?: QueryOptions;
278
+ /**
279
+ * Schema generation and normalization options.
280
+ */
281
+ readonly schemas?: SchemaOptions;
282
+ }
283
+ /**
284
+ * Schema generation and normalization options.
285
+ */
286
+ interface SchemaOptions {
287
+ /**
288
+ * How to handle schema aliases that only redirect via `$ref`.
289
+ * - `"collapse"`: Rewrite refs to the final target schema and remove alias entries
290
+ * - `"preserve"`: Keep alias schemas as-is
291
+ *
292
+ * @default "collapse"
293
+ */
294
+ readonly aliasRefs?: 'collapse' | 'preserve';
278
295
  }
279
296
  /**
280
297
  * Query parameter handling options.
package/dist/index.mjs CHANGED
@@ -1,16 +1,16 @@
1
- export { c as categorizeBrokenRefs, d as defineConfig, f as findConfigFile, e as formatValidationResult, g as generate, b as loadAndResolveConfig, a as loadConfig, l as loadConfigFromFile, r as resolveConfig, v as validateSpec } from './shared/nestjs-openapi.yWMsjl_8.mjs';
1
+ export { c as categorizeBrokenRefs, d as defineConfig, f as findConfigFile, e as formatValidationResult, g as generate, b as loadAndResolveConfig, a as loadConfig, l as loadConfigFromFile, r as resolveConfig, v as validateSpec } from './shared/nestjs-openapi.CUKGdNSM.mjs';
2
2
  import { readFileSync } from 'node:fs';
3
3
  import { resolve } from 'node:path';
4
4
  import { Module } from '@nestjs/common';
5
5
  export { generateAsync, generate as generateEffect } from './internal.mjs';
6
- import { P as ProjectInitError, E as EntryNotFoundError } from './shared/nestjs-openapi.CBj9xHZ4.mjs';
7
- export { a as ConfigLoadError, C as ConfigNotFoundError, b as ConfigValidationError, I as InvalidMethodError, c as getAllControllers, q as getArrayInitializer, o as getControllerMethodInfos, e as getControllerName, d as getControllerPrefix, j as getControllerTags, h as getDecoratorName, k as getHttpDecorator, f as getHttpMethods, m as getMethodInfo, w as getModuleDecoratorArg, z as getModuleMetadata, g as getModules, s as getStringLiteralValue, u as getSymbolFromIdentifier, l as isHttpDecorator, i as isHttpMethod, v as isModuleClass, n as normalizePath, y as resolveArrayOfClasses, x as resolveClassFromExpression, r as resolveClassFromSymbol, t as transformMethod, p as transformMethods } from './shared/nestjs-openapi.CBj9xHZ4.mjs';
6
+ import { P as ProjectInitError, E as EntryNotFoundError } from './shared/nestjs-openapi.DRcy130f.mjs';
7
+ export { a as ConfigLoadError, C as ConfigNotFoundError, b as ConfigValidationError, I as InvalidMethodError, c as getAllControllers, q as getArrayInitializer, o as getControllerMethodInfos, e as getControllerName, d as getControllerPrefix, j as getControllerTags, h as getDecoratorName, k as getHttpDecorator, f as getHttpMethods, m as getMethodInfo, w as getModuleDecoratorArg, z as getModuleMetadata, g as getModules, s as getStringLiteralValue, u as getSymbolFromIdentifier, l as isHttpDecorator, i as isHttpMethod, v as isModuleClass, n as normalizePath, y as resolveArrayOfClasses, x as resolveClassFromExpression, r as resolveClassFromSymbol, t as transformMethod, p as transformMethods } from './shared/nestjs-openapi.DRcy130f.mjs';
8
8
  import { Context, Effect, Layer } from 'effect';
9
9
  import { Project } from 'ts-morph';
10
+ import 'node:crypto';
10
11
  import 'glob';
11
12
  import 'js-yaml';
12
13
  import 'ts-json-schema-generator';
13
- import 'node:crypto';
14
14
  import 'node:url';
15
15
  import 'child_process';
16
16
 
@@ -1,2 +1,2 @@
1
1
  import 'effect';
2
- export { G as GenerateOptions, g as generate, d as generateAsync } from './shared/nestjs-openapi.OhsaHu32.mjs';
2
+ export { G as GenerateOptions, g as generate, d as generateAsync } from './shared/nestjs-openapi.ClTIhsb-.mjs';
@@ -1,2 +1,2 @@
1
1
  import 'effect';
2
- export { G as GenerateOptions, g as generate, d as generateAsync } from './shared/nestjs-openapi.OhsaHu32.js';
2
+ export { G as GenerateOptions, g as generate, d as generateAsync } from './shared/nestjs-openapi.ClTIhsb-.js';
package/dist/internal.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Effect } from 'effect';
2
2
  import { Project } from 'ts-morph';
3
- import { E as EntryNotFoundError, g as getModules, o as getControllerMethodInfos, p as transformMethods } from './shared/nestjs-openapi.CBj9xHZ4.mjs';
3
+ import { E as EntryNotFoundError, g as getModules, o as getControllerMethodInfos, p as transformMethods } from './shared/nestjs-openapi.DRcy130f.mjs';
4
4
 
5
5
  const generate = (options) => Effect.gen(function* () {
6
6
  yield* Effect.logInfo("Starting OpenAPI generation").pipe(
@@ -1,12 +1,12 @@
1
1
  import { Schema, Effect, Logger, Layer, LogLevel } from 'effect';
2
2
  import { readFileSync, writeFileSync, existsSync, unlinkSync, mkdirSync } from 'node:fs';
3
- import { join, dirname, resolve } from 'node:path';
3
+ import { join, dirname, resolve, relative } from 'node:path';
4
+ import { randomUUID } from 'node:crypto';
4
5
  import { Project } from 'ts-morph';
5
6
  import { globSync, glob } from 'glob';
6
7
  import yaml from 'js-yaml';
7
- import { C as ConfigNotFoundError, a as ConfigLoadError, b as ConfigValidationError, p as transformMethods, A as extractClassConstraints, B as getRequiredProperties, D as mergeValidationConstraints, E as EntryNotFoundError, g as getModules, o as getControllerMethodInfos } from './nestjs-openapi.CBj9xHZ4.mjs';
8
+ import { C as ConfigNotFoundError, a as ConfigLoadError, b as ConfigValidationError, p as transformMethods, A as extractClassConstraints, B as getRequiredProperties, D as mergeValidationConstraints, E as EntryNotFoundError, g as getModules, o as getControllerMethodInfos } from './nestjs-openapi.DRcy130f.mjs';
8
9
  import { createGenerator } from 'ts-json-schema-generator';
9
- import { randomUUID } from 'node:crypto';
10
10
  import { pathToFileURL } from 'node:url';
11
11
  import { execSync } from 'child_process';
12
12
 
@@ -450,6 +450,165 @@ const resolveNewSchemaName = (originalName, structureMapping) => {
450
450
  return originalName;
451
451
  };
452
452
 
453
+ const COMPONENT_SCHEMA_REF_PREFIX = "#/components/schemas/";
454
+ const ALIAS_SCHEMA_KEYS = /* @__PURE__ */ new Set(["$ref", "description"]);
455
+ const extractSchemaRefName = (ref) => ref.startsWith(COMPONENT_SCHEMA_REF_PREFIX) ? ref.slice(COMPONENT_SCHEMA_REF_PREFIX.length) : null;
456
+ const toSchemaRef = (name) => `${COMPONENT_SCHEMA_REF_PREFIX}${name}`;
457
+ const isAliasSchema = (schema) => {
458
+ if (!schema.$ref) return false;
459
+ return Object.keys(schema).every(
460
+ (key) => ALIAS_SCHEMA_KEYS.has(key)
461
+ );
462
+ };
463
+ const rewriteSchemaRefs = (schema, rewriteRef) => {
464
+ const result = { ...schema };
465
+ if (typeof schema.$ref === "string") {
466
+ result["$ref"] = rewriteRef(schema.$ref);
467
+ }
468
+ if (schema.items) {
469
+ result["items"] = rewriteSchemaRefs(schema.items, rewriteRef);
470
+ }
471
+ if (schema.oneOf) {
472
+ result["oneOf"] = schema.oneOf.map(
473
+ (item) => rewriteSchemaRefs(item, rewriteRef)
474
+ );
475
+ }
476
+ if (schema.anyOf) {
477
+ result["anyOf"] = schema.anyOf.map(
478
+ (item) => rewriteSchemaRefs(item, rewriteRef)
479
+ );
480
+ }
481
+ if (schema.allOf) {
482
+ result["allOf"] = schema.allOf.map(
483
+ (item) => rewriteSchemaRefs(item, rewriteRef)
484
+ );
485
+ }
486
+ if (schema.properties) {
487
+ result["properties"] = Object.fromEntries(
488
+ Object.entries(schema.properties).map(([key, propertySchema]) => [
489
+ key,
490
+ rewriteSchemaRefs(propertySchema, rewriteRef)
491
+ ])
492
+ );
493
+ }
494
+ if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
495
+ result["additionalProperties"] = rewriteSchemaRefs(
496
+ schema.additionalProperties,
497
+ rewriteRef
498
+ );
499
+ }
500
+ return result;
501
+ };
502
+ const rewritePathsRefs = (paths, rewriteRef) => Object.fromEntries(
503
+ Object.entries(paths).map(([path, methods]) => [
504
+ path,
505
+ Object.fromEntries(
506
+ Object.entries(methods).map(([method, operation]) => [
507
+ method,
508
+ {
509
+ ...operation,
510
+ ...operation.parameters && {
511
+ parameters: operation.parameters.map((parameter) => ({
512
+ ...parameter,
513
+ schema: rewriteSchemaRefs(parameter.schema, rewriteRef)
514
+ }))
515
+ },
516
+ ...operation.requestBody && {
517
+ requestBody: {
518
+ ...operation.requestBody,
519
+ content: Object.fromEntries(
520
+ Object.entries(operation.requestBody.content).map(
521
+ ([contentType, content]) => [
522
+ contentType,
523
+ {
524
+ ...content,
525
+ schema: rewriteSchemaRefs(content.schema, rewriteRef)
526
+ }
527
+ ]
528
+ )
529
+ )
530
+ }
531
+ },
532
+ responses: Object.fromEntries(
533
+ Object.entries(operation.responses).map(([statusCode, response]) => [
534
+ statusCode,
535
+ response.content ? {
536
+ ...response,
537
+ content: Object.fromEntries(
538
+ Object.entries(response.content).map(
539
+ ([contentType, content]) => [
540
+ contentType,
541
+ {
542
+ ...content,
543
+ schema: rewriteSchemaRefs(content.schema, rewriteRef)
544
+ }
545
+ ]
546
+ )
547
+ )
548
+ } : response
549
+ ])
550
+ )
551
+ }
552
+ ])
553
+ )
554
+ ])
555
+ );
556
+ const rewriteSchemasRefs = (schemas, rewriteRef) => Object.fromEntries(
557
+ Object.entries(schemas).map(([name, schema]) => [
558
+ name,
559
+ rewriteSchemaRefs(schema, rewriteRef)
560
+ ])
561
+ );
562
+ const resolveAliasTargets = (schemas) => {
563
+ const directAliases = /* @__PURE__ */ new Map();
564
+ for (const [name, schema] of Object.entries(schemas)) {
565
+ if (!isAliasSchema(schema) || !schema.$ref) continue;
566
+ const target = extractSchemaRefName(schema.$ref);
567
+ if (!target) continue;
568
+ directAliases.set(name, target);
569
+ }
570
+ const resolvedAliases = /* @__PURE__ */ new Map();
571
+ const resolveFinalTarget = (start) => {
572
+ const visited = /* @__PURE__ */ new Set([start]);
573
+ let current = start;
574
+ while (true) {
575
+ const next = directAliases.get(current);
576
+ if (!next) return current === start ? null : current;
577
+ if (!(next in schemas)) return null;
578
+ if (visited.has(next)) return null;
579
+ visited.add(next);
580
+ current = next;
581
+ }
582
+ };
583
+ for (const aliasName of directAliases.keys()) {
584
+ const finalTarget = resolveFinalTarget(aliasName);
585
+ if (!finalTarget || finalTarget === aliasName) continue;
586
+ resolvedAliases.set(aliasName, finalTarget);
587
+ }
588
+ return resolvedAliases;
589
+ };
590
+ const collapseAliasRefs = (paths, schemas) => {
591
+ const aliasTargets = resolveAliasTargets(schemas);
592
+ if (aliasTargets.size === 0) return { paths, schemas };
593
+ const rewriteRef = (ref) => {
594
+ const refName = extractSchemaRefName(ref);
595
+ if (!refName) return ref;
596
+ const finalTarget = aliasTargets.get(refName);
597
+ return finalTarget ? toSchemaRef(finalTarget) : ref;
598
+ };
599
+ const rewrittenPaths = rewritePathsRefs(paths, rewriteRef);
600
+ const rewrittenSchemas = rewriteSchemasRefs(schemas, rewriteRef);
601
+ const collapsedSchemas = Object.fromEntries(
602
+ Object.entries(rewrittenSchemas).filter(
603
+ ([name]) => !aliasTargets.has(name)
604
+ )
605
+ );
606
+ return {
607
+ paths: rewrittenPaths,
608
+ schemas: collapsedSchemas
609
+ };
610
+ };
611
+
453
612
  const extractReferencedSchemas = (paths) => {
454
613
  const refs = /* @__PURE__ */ new Set();
455
614
  const extractFromSchema = (schema) => {
@@ -646,7 +805,11 @@ const createPathFilter = (pathFilter) => {
646
805
  if (typeof pathFilter === "function") {
647
806
  return (method) => pathFilter(method.path);
648
807
  }
649
- return (method) => pathFilter.test(method.path);
808
+ const pattern = new RegExp(pathFilter.source, pathFilter.flags);
809
+ return (method) => {
810
+ pattern.lastIndex = 0;
811
+ return pattern.test(method.path);
812
+ };
650
813
  };
651
814
  const combineFilters = (filters) => {
652
815
  if (filters.length === 0) {
@@ -678,6 +841,11 @@ const filterMethods = (methods, options) => {
678
841
  return methods.filter(filter);
679
842
  };
680
843
 
844
+ const NULL_SCHEMA = { type: "null" };
845
+ const schemaIncludesNull = (schema) => {
846
+ if (schema.type === "null") return true;
847
+ return Array.isArray(schema.type) && schema.type.includes("null");
848
+ };
681
849
  const transformSchemaToV31 = (schema) => {
682
850
  const transformedOneOf = schema.oneOf?.map(transformSchemaToV31);
683
851
  const transformedAnyOf = schema.anyOf?.map(transformSchemaToV31);
@@ -689,19 +857,36 @@ const transformSchemaToV31 = (schema) => {
689
857
  transformSchemaToV31(value)
690
858
  ])
691
859
  ) : void 0;
692
- const hasNullable = schema.nullable && schema.type && typeof schema.type === "string";
693
- const transformedType = hasNullable ? [schema.type, "null"] : schema.type;
694
- const { nullable: _nullable, ...restWithoutNullable } = schema;
695
- return {
860
+ const { nullable, ...restWithoutNullable } = schema;
861
+ const transformedType = nullable && typeof schema.type === "string" ? [schema.type, "null"] : schema.type;
862
+ const transformedSchema = {
696
863
  ...restWithoutNullable,
697
- type: transformedType,
864
+ ...transformedType !== void 0 && { type: transformedType },
698
865
  ...transformedOneOf && { oneOf: transformedOneOf },
699
866
  ...transformedAnyOf && { anyOf: transformedAnyOf },
700
867
  ...transformedAllOf && { allOf: transformedAllOf },
701
868
  ...transformedItems && { items: transformedItems },
702
869
  ...transformedProperties && { properties: transformedProperties }
703
870
  };
871
+ if (!nullable) return transformedSchema;
872
+ if (schema.type && typeof schema.type === "string") {
873
+ return transformedSchema;
874
+ }
875
+ if (transformedSchema.oneOf) {
876
+ return {
877
+ ...transformedSchema,
878
+ oneOf: schemaIncludesNullInVariants(transformedSchema.oneOf) ? transformedSchema.oneOf : [...transformedSchema.oneOf, NULL_SCHEMA]
879
+ };
880
+ }
881
+ if (transformedSchema.anyOf) {
882
+ return {
883
+ ...transformedSchema,
884
+ anyOf: schemaIncludesNullInVariants(transformedSchema.anyOf) ? transformedSchema.anyOf : [...transformedSchema.anyOf, NULL_SCHEMA]
885
+ };
886
+ }
887
+ return { anyOf: [transformedSchema, NULL_SCHEMA] };
704
888
  };
889
+ const schemaIncludesNullInVariants = (variants) => variants.some(schemaIncludesNull);
705
890
  const transformSchemasForVersion = (schemas, version) => {
706
891
  if (version === "3.0.3") {
707
892
  return schemas;
@@ -966,22 +1151,59 @@ const OpenApiTagConfig = Schema.Struct({
966
1151
  name: Schema.String,
967
1152
  description: Schema.optional(Schema.String)
968
1153
  });
969
- const SecuritySchemeType = Schema.Literal(
1154
+ Schema.Literal(
970
1155
  "apiKey",
971
1156
  "http",
972
1157
  "oauth2",
973
1158
  "openIdConnect"
974
1159
  );
975
1160
  const SecuritySchemeIn = Schema.Literal("query", "header", "cookie");
976
- const SecuritySchemeConfig = Schema.Struct({
1161
+ const OAuth2FlowConfig = Schema.Struct({
1162
+ authorizationUrl: Schema.optional(Schema.String),
1163
+ tokenUrl: Schema.optional(Schema.String),
1164
+ refreshUrl: Schema.optional(Schema.String),
1165
+ scopes: Schema.optional(
1166
+ Schema.Record({ key: Schema.String, value: Schema.String })
1167
+ )
1168
+ });
1169
+ const OAuth2FlowsConfig = Schema.Struct({
1170
+ implicit: Schema.optional(OAuth2FlowConfig),
1171
+ password: Schema.optional(OAuth2FlowConfig),
1172
+ clientCredentials: Schema.optional(OAuth2FlowConfig),
1173
+ authorizationCode: Schema.optional(OAuth2FlowConfig)
1174
+ });
1175
+ const HttpSecuritySchemeConfig = Schema.Struct({
977
1176
  name: Schema.String,
978
- type: SecuritySchemeType,
979
- scheme: Schema.optional(Schema.String),
1177
+ type: Schema.Literal("http"),
1178
+ scheme: Schema.String,
980
1179
  bearerFormat: Schema.optional(Schema.String),
981
- in: Schema.optional(SecuritySchemeIn),
982
- parameterName: Schema.optional(Schema.String),
983
1180
  description: Schema.optional(Schema.String)
984
1181
  });
1182
+ const ApiKeySecuritySchemeConfig = Schema.Struct({
1183
+ name: Schema.String,
1184
+ type: Schema.Literal("apiKey"),
1185
+ in: SecuritySchemeIn,
1186
+ parameterName: Schema.String,
1187
+ description: Schema.optional(Schema.String)
1188
+ });
1189
+ const OAuth2SecuritySchemeConfig = Schema.Struct({
1190
+ name: Schema.String,
1191
+ type: Schema.Literal("oauth2"),
1192
+ flows: OAuth2FlowsConfig,
1193
+ description: Schema.optional(Schema.String)
1194
+ });
1195
+ const OpenIdConnectSecuritySchemeConfig = Schema.Struct({
1196
+ name: Schema.String,
1197
+ type: Schema.Literal("openIdConnect"),
1198
+ openIdConnectUrl: Schema.String,
1199
+ description: Schema.optional(Schema.String)
1200
+ });
1201
+ const SecuritySchemeConfig = Schema.Union(
1202
+ HttpSecuritySchemeConfig,
1203
+ ApiKeySecuritySchemeConfig,
1204
+ OAuth2SecuritySchemeConfig,
1205
+ OpenIdConnectSecuritySchemeConfig
1206
+ );
985
1207
  const SecurityRequirement = Schema.Record({
986
1208
  key: Schema.String,
987
1209
  value: Schema.Array(Schema.String)
@@ -1013,6 +1235,9 @@ const OpenApiConfig = Schema.Struct({
1013
1235
  const QueryOptionsConfig = Schema.Struct({
1014
1236
  style: Schema.optional(Schema.Literal("inline", "ref"))
1015
1237
  });
1238
+ const SchemaOptionsConfig = Schema.Struct({
1239
+ aliasRefs: Schema.optional(Schema.Literal("collapse", "preserve"))
1240
+ });
1016
1241
  const PathFilterFunction = Schema.declare(
1017
1242
  (input) => typeof input === "function",
1018
1243
  {
@@ -1029,6 +1254,7 @@ const OptionsConfig = Schema.Struct({
1029
1254
  extractValidation: Schema.optional(Schema.Boolean),
1030
1255
  excludeDecorators: Schema.optional(Schema.Array(Schema.String)),
1031
1256
  query: Schema.optional(QueryOptionsConfig),
1257
+ schemas: Schema.optional(SchemaOptionsConfig),
1032
1258
  pathFilter: Schema.optional(PathFilter)
1033
1259
  });
1034
1260
  const OpenApiGeneratorConfig = Schema.Struct({
@@ -1047,6 +1273,7 @@ Schema.Struct({
1047
1273
  excludeDecorators: Schema.Array(Schema.String),
1048
1274
  dtoGlob: Schema.Array(Schema.String),
1049
1275
  extractValidation: Schema.Boolean,
1276
+ aliasRefs: Schema.Literal("collapse", "preserve"),
1050
1277
  basePath: Schema.optional(Schema.String),
1051
1278
  pathFilter: Schema.optional(PathFilter),
1052
1279
  version: Schema.optional(Schema.String),
@@ -1088,7 +1315,7 @@ const CONFIG_FILE_NAMES = [
1088
1315
  "openapi.config.cjs"
1089
1316
  ];
1090
1317
  const DEFAULT_ENTRY$1 = "src/app.module.ts";
1091
- const DEFAULT_DTO_GLOB = [
1318
+ const DEFAULT_DTO_GLOB$1 = [
1092
1319
  "**/*.dto.ts",
1093
1320
  "**/*.entity.ts",
1094
1321
  "**/*.model.ts",
@@ -1101,7 +1328,10 @@ const DEFAULT_CONFIG = {
1101
1328
  },
1102
1329
  options: {
1103
1330
  excludeDecorators: ["ApiExcludeEndpoint", "ApiExcludeController"],
1104
- extractValidation: true
1331
+ extractValidation: true,
1332
+ schemas: {
1333
+ aliasRefs: "collapse"
1334
+ }
1105
1335
  },
1106
1336
  format: "json",
1107
1337
  openapi: {
@@ -1115,8 +1345,7 @@ const DEFAULT_CONFIG = {
1115
1345
  };
1116
1346
  const findConfigFile = (startDir = process.cwd()) => Effect.gen(function* () {
1117
1347
  let currentDir = resolve(startDir);
1118
- const root = dirname(currentDir);
1119
- while (currentDir !== root) {
1348
+ while (true) {
1120
1349
  for (const fileName of CONFIG_FILE_NAMES) {
1121
1350
  const configPath = resolve(currentDir, fileName);
1122
1351
  if (existsSync(configPath)) {
@@ -1205,7 +1434,7 @@ const resolveConfig = (config) => {
1205
1434
  const rawEntry = files.entry ?? DEFAULT_ENTRY$1;
1206
1435
  const entry = Array.isArray(rawEntry) ? rawEntry : [rawEntry];
1207
1436
  const rawDtoGlob = files.dtoGlob;
1208
- const dtoGlob = rawDtoGlob ? Array.isArray(rawDtoGlob) ? rawDtoGlob : [rawDtoGlob] : [...DEFAULT_DTO_GLOB];
1437
+ const dtoGlob = rawDtoGlob ? Array.isArray(rawDtoGlob) ? rawDtoGlob : [rawDtoGlob] : [...DEFAULT_DTO_GLOB$1];
1209
1438
  const tsconfig = files.tsconfig;
1210
1439
  if (!tsconfig) {
1211
1440
  throw new Error("tsconfig is required in files configuration");
@@ -1218,6 +1447,7 @@ const resolveConfig = (config) => {
1218
1447
  excludeDecorators: options.excludeDecorators ?? DEFAULT_CONFIG.options.excludeDecorators,
1219
1448
  dtoGlob,
1220
1449
  extractValidation: options.extractValidation ?? DEFAULT_CONFIG.options.extractValidation,
1450
+ aliasRefs: options.schemas?.aliasRefs ?? DEFAULT_CONFIG.options.schemas.aliasRefs,
1221
1451
  basePath: options.basePath,
1222
1452
  version: openapi.version,
1223
1453
  info: openapi.info,
@@ -1484,6 +1714,22 @@ function resolveTypeLocationsFast(baseDir, missingTypes) {
1484
1714
  }
1485
1715
 
1486
1716
  const DEFAULT_ENTRY = "src/app.module.ts";
1717
+ const DEFAULT_DTO_GLOB = [
1718
+ "**/*.dto.ts",
1719
+ "**/*.entity.ts",
1720
+ "**/*.model.ts",
1721
+ "**/*.schema.ts"
1722
+ ];
1723
+ const mergeSingleSecurityRequirement = (left, right) => {
1724
+ const merged = {};
1725
+ for (const requirement of [left, right]) {
1726
+ for (const [scheme, scopes] of Object.entries(requirement)) {
1727
+ const existingScopes = merged[scheme] ?? [];
1728
+ merged[scheme] = [.../* @__PURE__ */ new Set([...existingScopes, ...scopes])];
1729
+ }
1730
+ }
1731
+ return merged;
1732
+ };
1487
1733
  const mergeSecurityWithGlobal = (paths, globalSecurity) => {
1488
1734
  if (!globalSecurity || globalSecurity.length === 0) {
1489
1735
  return paths;
@@ -1493,23 +1739,17 @@ const mergeSecurityWithGlobal = (paths, globalSecurity) => {
1493
1739
  const mergedMethods = {};
1494
1740
  for (const [method, operation] of Object.entries(methods)) {
1495
1741
  if (operation.security && operation.security.length > 0) {
1496
- const merged = {};
1742
+ const mergedSecurity = [];
1497
1743
  for (const globalReq of globalSecurity) {
1498
- for (const [scheme, scopes] of Object.entries(globalReq)) {
1499
- merged[scheme] = [...merged[scheme] ?? [], ...scopes];
1744
+ for (const operationReq of operation.security) {
1745
+ mergedSecurity.push(
1746
+ mergeSingleSecurityRequirement(globalReq, operationReq)
1747
+ );
1500
1748
  }
1501
1749
  }
1502
- for (const decoratorReq of operation.security) {
1503
- for (const [scheme, scopes] of Object.entries(decoratorReq)) {
1504
- merged[scheme] = [...merged[scheme] ?? [], ...scopes];
1505
- }
1506
- }
1507
- for (const scheme of Object.keys(merged)) {
1508
- merged[scheme] = [...new Set(merged[scheme])];
1509
- }
1510
1750
  mergedMethods[method] = {
1511
1751
  ...operation,
1512
- security: [merged]
1752
+ security: mergedSecurity
1513
1753
  };
1514
1754
  } else {
1515
1755
  mergedMethods[method] = operation;
@@ -1573,6 +1813,131 @@ const findMissingSchemaRefs = (paths, schemas) => {
1573
1813
  findRefs(paths);
1574
1814
  return missing;
1575
1815
  };
1816
+ const isGenericSchemaRef = (name) => name.includes("<") && name.endsWith(">");
1817
+ const NON_IMPORTABLE_TYPE_NAMES = /* @__PURE__ */ new Set([
1818
+ "string",
1819
+ "number",
1820
+ "boolean",
1821
+ "null",
1822
+ "undefined",
1823
+ "void",
1824
+ "unknown",
1825
+ "any",
1826
+ "never",
1827
+ "object",
1828
+ "true",
1829
+ "false",
1830
+ "Array",
1831
+ "ReadonlyArray",
1832
+ "Record",
1833
+ "Promise",
1834
+ "Partial",
1835
+ "Required",
1836
+ "Pick",
1837
+ "Omit",
1838
+ "Exclude",
1839
+ "Extract",
1840
+ "Readonly",
1841
+ "keyof",
1842
+ "infer",
1843
+ "extends"
1844
+ ]);
1845
+ const extractTypeIdentifiers = (typeRef) => {
1846
+ const withoutStringLiterals = typeRef.replace(
1847
+ /'[^']*'|"[^"]*"|`[^`]*`/g,
1848
+ ""
1849
+ );
1850
+ const matches = withoutStringLiterals.match(/\b[A-Za-z_$][A-Za-z0-9_$]*\b/g) ?? [];
1851
+ return new Set(
1852
+ matches.filter((name) => !NON_IMPORTABLE_TYPE_NAMES.has(name))
1853
+ );
1854
+ };
1855
+ const toModuleImportPath = (fromDir, filePath) => {
1856
+ const importPath = relative(fromDir, filePath).replace(/\\/g, "/");
1857
+ return importPath.startsWith(".") ? importPath : `./${importPath}`;
1858
+ };
1859
+ const resolveSymbolLocations = (tsconfig, symbolNames) => {
1860
+ if (symbolNames.size === 0) {
1861
+ return /* @__PURE__ */ new Map();
1862
+ }
1863
+ const tsconfigDir = dirname(tsconfig);
1864
+ const resolved = resolveTypeLocationsFast(tsconfigDir, symbolNames);
1865
+ const unresolved = new Set(
1866
+ [...symbolNames].filter((name) => !resolved.has(name))
1867
+ );
1868
+ if (unresolved.size > 0) {
1869
+ const project = createTypeResolverProject(tsconfig);
1870
+ const morphResolved = resolveTypeLocations(project, unresolved);
1871
+ for (const [name, filePath] of morphResolved) {
1872
+ resolved.set(name, filePath);
1873
+ }
1874
+ }
1875
+ return resolved;
1876
+ };
1877
+ const generateMissingGenericSchemas = async (genericRefs, tsconfig, symbolLocations, runEffect) => {
1878
+ if (genericRefs.length === 0) {
1879
+ return { definitions: {} };
1880
+ }
1881
+ const importGroups = /* @__PURE__ */ new Map();
1882
+ const aliases = [];
1883
+ const aliasLines = [];
1884
+ for (const [index, genericRef] of genericRefs.entries()) {
1885
+ const identifiers = [...extractTypeIdentifiers(genericRef)];
1886
+ if (identifiers.length === 0) {
1887
+ continue;
1888
+ }
1889
+ const unresolved = identifiers.filter((name) => !symbolLocations.has(name));
1890
+ if (unresolved.length > 0) {
1891
+ continue;
1892
+ }
1893
+ for (const identifier of identifiers) {
1894
+ const filePath = symbolLocations.get(identifier);
1895
+ if (!filePath) continue;
1896
+ const existing = importGroups.get(filePath) ?? /* @__PURE__ */ new Set();
1897
+ existing.add(identifier);
1898
+ importGroups.set(filePath, existing);
1899
+ }
1900
+ const aliasName = `__MissingGenericRef${index}`;
1901
+ aliases.push({ aliasName, schemaName: genericRef });
1902
+ aliasLines.push(`export type ${aliasName} = ${genericRef};`);
1903
+ }
1904
+ if (aliases.length === 0) {
1905
+ return { definitions: {} };
1906
+ }
1907
+ const tempDir = dirname(tsconfig);
1908
+ const tempFilePath = join(
1909
+ tempDir,
1910
+ `.openapi.missing-generic.${randomUUID()}.ts`
1911
+ );
1912
+ const importLines = [...importGroups.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([filePath, symbols]) => {
1913
+ const importPath = toModuleImportPath(tempDir, filePath);
1914
+ const names = [...symbols].sort().join(", ");
1915
+ return `import type { ${names} } from '${importPath}';`;
1916
+ });
1917
+ writeFileSync(
1918
+ tempFilePath,
1919
+ [...importLines, "", ...aliasLines, ""].join("\n"),
1920
+ "utf-8"
1921
+ );
1922
+ try {
1923
+ const generated = await runEffect(
1924
+ generateSchemasFromFiles([tempFilePath], tsconfig)
1925
+ );
1926
+ const definitions = { ...generated.definitions };
1927
+ for (const { aliasName, schemaName } of aliases) {
1928
+ const resolvedSchema = definitions[schemaName] ?? definitions[aliasName] ?? void 0;
1929
+ if (resolvedSchema) {
1930
+ definitions[schemaName] = resolvedSchema;
1931
+ }
1932
+ delete definitions[aliasName];
1933
+ }
1934
+ return { definitions };
1935
+ } finally {
1936
+ if (existsSync(tempFilePath)) {
1937
+ unlinkSync(tempFilePath);
1938
+ }
1939
+ }
1940
+ };
1576
1941
  const extractValidationConstraints = async (dtoGlobPatterns, basePath, tsconfig, schemas) => {
1577
1942
  const absolutePatterns = dtoGlobPatterns.map(
1578
1943
  (pattern) => pattern.startsWith("/") ? pattern : join(basePath, pattern)
@@ -1619,8 +1984,7 @@ const extractValidationConstraints = async (dtoGlobPatterns, basePath, tsconfig,
1619
1984
  };
1620
1985
  const findTsConfig = (startDir) => {
1621
1986
  let currentDir = resolve(startDir);
1622
- const root = dirname(currentDir);
1623
- while (currentDir !== root) {
1987
+ while (true) {
1624
1988
  const tsconfigPath = join(currentDir, "tsconfig.json");
1625
1989
  if (existsSync(tsconfigPath)) {
1626
1990
  return tsconfigPath;
@@ -1708,6 +2072,7 @@ const generate = async (configPath, overrides) => {
1708
2072
  );
1709
2073
  const files = config.files ?? {};
1710
2074
  const options = config.options ?? {};
2075
+ const aliasRefsMode = options.schemas?.aliasRefs ?? "collapse";
1711
2076
  const openapi = config.openapi;
1712
2077
  const security = openapi.security ?? {};
1713
2078
  const rawEntry = files.entry ?? DEFAULT_ENTRY;
@@ -1715,19 +2080,30 @@ const generate = async (configPath, overrides) => {
1715
2080
  (e) => resolve(configDir, e)
1716
2081
  );
1717
2082
  const output = resolve(configDir, config.output);
1718
- const tsconfig = files.tsconfig ? resolve(configDir, files.tsconfig) : findTsConfig(dirname(entries[0]));
1719
- if (!tsconfig) {
1720
- throw new Error(
1721
- `Could not find tsconfig.json. Please specify files.tsconfig in your config file.`
1722
- );
1723
- }
1724
- if (!existsSync(tsconfig)) {
1725
- throw new Error(`tsconfig.json not found at: ${tsconfig}`);
1726
- }
2083
+ const tsconfig = await runEffect(
2084
+ Effect.gen(function* () {
2085
+ const discoveredTsconfig = files.tsconfig ? resolve(configDir, files.tsconfig) : findTsConfig(dirname(entries[0]));
2086
+ if (!discoveredTsconfig) {
2087
+ return yield* Effect.fail(
2088
+ ConfigValidationError.fromIssues(absoluteConfigPath, [
2089
+ "Could not find tsconfig.json. Please specify files.tsconfig in your config file."
2090
+ ])
2091
+ );
2092
+ }
2093
+ if (!existsSync(discoveredTsconfig)) {
2094
+ return yield* Effect.fail(
2095
+ ConfigValidationError.fromIssues(absoluteConfigPath, [
2096
+ `tsconfig.json not found at: ${discoveredTsconfig}`
2097
+ ])
2098
+ );
2099
+ }
2100
+ return discoveredTsconfig;
2101
+ }).pipe(Effect.mapError((error) => new Error(error.message)))
2102
+ );
1727
2103
  const extractOptions = {
1728
2104
  query: options.query
1729
2105
  };
1730
- const dtoGlobArray = files.dtoGlob ? Array.isArray(files.dtoGlob) ? files.dtoGlob : [files.dtoGlob] : null;
2106
+ const dtoGlobArray = files.dtoGlob === void 0 ? [...DEFAULT_DTO_GLOB] : Array.isArray(files.dtoGlob) ? files.dtoGlob : [files.dtoGlob];
1731
2107
  const [extractedMethodInfos, initialSchemas] = await Promise.all([
1732
2108
  runEffect(
1733
2109
  extractMethodInfosEffect(tsconfig, entries, extractOptions).pipe(
@@ -1739,7 +2115,7 @@ const generate = async (configPath, overrides) => {
1739
2115
  Effect.mapError((error) => new Error(error.message))
1740
2116
  )
1741
2117
  ),
1742
- dtoGlobArray ? runEffect(
2118
+ runEffect(
1743
2119
  generateSchemas({
1744
2120
  dtoGlob: dtoGlobArray,
1745
2121
  tsconfig,
@@ -1755,7 +2131,7 @@ const generate = async (configPath, overrides) => {
1755
2131
  ),
1756
2132
  Effect.mapError((error) => new Error(error.message))
1757
2133
  )
1758
- ) : Promise.resolve(null)
2134
+ )
1759
2135
  ]);
1760
2136
  const filteredMethodInfos = filterMethods(extractedMethodInfos, {
1761
2137
  excludeDecorators: options.excludeDecorators,
@@ -1776,7 +2152,7 @@ const generate = async (configPath, overrides) => {
1776
2152
  security.global
1777
2153
  );
1778
2154
  let schemas = {};
1779
- if (initialSchemas && dtoGlobArray) {
2155
+ if (initialSchemas) {
1780
2156
  let generatedSchemas = initialSchemas;
1781
2157
  const shouldExtractValidation = options.extractValidation !== false;
1782
2158
  if (shouldExtractValidation) {
@@ -1828,6 +2204,50 @@ const generate = async (configPath, overrides) => {
1828
2204
  ...normalizedAdditional.definitions
1829
2205
  }
1830
2206
  };
2207
+ generatedSchemas = combinedSchemas;
2208
+ mergeResult = mergeSchemas(
2209
+ paths,
2210
+ combinedSchemas
2211
+ );
2212
+ schemas = mergeResult.schemas;
2213
+ }
2214
+ }
2215
+ const unresolvedAfterFileResolution = findMissingSchemaRefs(
2216
+ paths,
2217
+ schemas
2218
+ );
2219
+ const unresolvedGenericRefs = [...unresolvedAfterFileResolution].filter(
2220
+ isGenericSchemaRef
2221
+ );
2222
+ if (unresolvedGenericRefs.length > 0) {
2223
+ const genericSymbols = /* @__PURE__ */ new Set();
2224
+ for (const ref of unresolvedGenericRefs) {
2225
+ for (const symbol of extractTypeIdentifiers(ref)) {
2226
+ genericSymbols.add(symbol);
2227
+ }
2228
+ }
2229
+ const resolvedGenericSymbols = resolveSymbolLocations(
2230
+ tsconfig,
2231
+ genericSymbols
2232
+ );
2233
+ for (const [name, filePath] of resolvedLocations) {
2234
+ resolvedGenericSymbols.set(name, filePath);
2235
+ }
2236
+ const genericSchemas = await generateMissingGenericSchemas(
2237
+ unresolvedGenericRefs,
2238
+ tsconfig,
2239
+ resolvedGenericSymbols,
2240
+ runEffect
2241
+ );
2242
+ if (Object.keys(genericSchemas.definitions).length > 0) {
2243
+ const normalizedGeneric = normalizeStructureRefs(genericSchemas);
2244
+ const combinedSchemas = {
2245
+ definitions: {
2246
+ ...generatedSchemas.definitions,
2247
+ ...normalizedGeneric.definitions
2248
+ }
2249
+ };
2250
+ generatedSchemas = combinedSchemas;
1831
2251
  mergeResult = mergeSchemas(
1832
2252
  paths,
1833
2253
  combinedSchemas
@@ -1837,6 +2257,11 @@ const generate = async (configPath, overrides) => {
1837
2257
  }
1838
2258
  }
1839
2259
  }
2260
+ if (aliasRefsMode === "collapse" && Object.keys(schemas).length > 0) {
2261
+ const collapsed = collapseAliasRefs(paths, schemas);
2262
+ paths = collapsed.paths;
2263
+ schemas = collapsed.schemas;
2264
+ }
1840
2265
  const securitySchemes = security.schemes && security.schemes.length > 0 ? buildSecuritySchemes(security.schemes) : void 0;
1841
2266
  const hasSchemas = Object.keys(schemas).length > 0;
1842
2267
  const hasSecuritySchemes = securitySchemes && Object.keys(securitySchemes).length > 0;
@@ -252,15 +252,54 @@ declare const OpenApiGeneratorConfig: Schema.Struct<{
252
252
  description: Schema.optional<typeof Schema.String>;
253
253
  }>>>;
254
254
  security: Schema.optional<Schema.Struct<{
255
- schemes: Schema.optional<Schema.Array$<Schema.Struct<{
255
+ schemes: Schema.optional<Schema.Array$<Schema.Union<[Schema.Struct<{
256
256
  name: typeof Schema.String;
257
- type: Schema.Literal<["apiKey", "http", "oauth2", "openIdConnect"]>;
258
- scheme: Schema.optional<typeof Schema.String>;
257
+ type: Schema.Literal<["http"]>;
258
+ scheme: typeof Schema.String;
259
259
  bearerFormat: Schema.optional<typeof Schema.String>;
260
- in: Schema.optional<Schema.Literal<["query", "header", "cookie"]>>;
261
- parameterName: Schema.optional<typeof Schema.String>;
262
260
  description: Schema.optional<typeof Schema.String>;
263
- }>>>;
261
+ }>, Schema.Struct<{
262
+ name: typeof Schema.String;
263
+ type: Schema.Literal<["apiKey"]>;
264
+ in: Schema.Literal<["query", "header", "cookie"]>;
265
+ parameterName: typeof Schema.String;
266
+ description: Schema.optional<typeof Schema.String>;
267
+ }>, Schema.Struct<{
268
+ name: typeof Schema.String;
269
+ type: Schema.Literal<["oauth2"]>;
270
+ flows: Schema.Struct<{
271
+ implicit: Schema.optional<Schema.Struct<{
272
+ authorizationUrl: Schema.optional<typeof Schema.String>;
273
+ tokenUrl: Schema.optional<typeof Schema.String>;
274
+ refreshUrl: Schema.optional<typeof Schema.String>;
275
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
276
+ }>>;
277
+ password: Schema.optional<Schema.Struct<{
278
+ authorizationUrl: Schema.optional<typeof Schema.String>;
279
+ tokenUrl: Schema.optional<typeof Schema.String>;
280
+ refreshUrl: Schema.optional<typeof Schema.String>;
281
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
282
+ }>>;
283
+ clientCredentials: Schema.optional<Schema.Struct<{
284
+ authorizationUrl: Schema.optional<typeof Schema.String>;
285
+ tokenUrl: Schema.optional<typeof Schema.String>;
286
+ refreshUrl: Schema.optional<typeof Schema.String>;
287
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
288
+ }>>;
289
+ authorizationCode: Schema.optional<Schema.Struct<{
290
+ authorizationUrl: Schema.optional<typeof Schema.String>;
291
+ tokenUrl: Schema.optional<typeof Schema.String>;
292
+ refreshUrl: Schema.optional<typeof Schema.String>;
293
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
294
+ }>>;
295
+ }>;
296
+ description: Schema.optional<typeof Schema.String>;
297
+ }>, Schema.Struct<{
298
+ name: typeof Schema.String;
299
+ type: Schema.Literal<["openIdConnect"]>;
300
+ openIdConnectUrl: typeof Schema.String;
301
+ description: Schema.optional<typeof Schema.String>;
302
+ }>]>>>;
264
303
  global: Schema.optional<Schema.Array$<Schema.Record$<typeof Schema.String, Schema.Array$<typeof Schema.String>>>>;
265
304
  }>>;
266
305
  }>;
@@ -271,6 +310,9 @@ declare const OpenApiGeneratorConfig: Schema.Struct<{
271
310
  query: Schema.optional<Schema.Struct<{
272
311
  style: Schema.optional<Schema.Literal<["inline", "ref"]>>;
273
312
  }>>;
313
+ schemas: Schema.optional<Schema.Struct<{
314
+ aliasRefs: Schema.optional<Schema.Literal<["collapse", "preserve"]>>;
315
+ }>>;
274
316
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
275
317
  }>>;
276
318
  }>;
@@ -283,6 +325,7 @@ declare const ResolvedConfig: Schema.Struct<{
283
325
  excludeDecorators: Schema.Array$<typeof Schema.String>;
284
326
  dtoGlob: Schema.Array$<typeof Schema.String>;
285
327
  extractValidation: typeof Schema.Boolean;
328
+ aliasRefs: Schema.Literal<["collapse", "preserve"]>;
286
329
  basePath: Schema.optional<typeof Schema.String>;
287
330
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
288
331
  version: Schema.optional<typeof Schema.String>;
@@ -304,15 +347,54 @@ declare const ResolvedConfig: Schema.Struct<{
304
347
  url: typeof Schema.String;
305
348
  description: Schema.optional<typeof Schema.String>;
306
349
  }>>;
307
- securitySchemes: Schema.Array$<Schema.Struct<{
350
+ securitySchemes: Schema.Array$<Schema.Union<[Schema.Struct<{
308
351
  name: typeof Schema.String;
309
- type: Schema.Literal<["apiKey", "http", "oauth2", "openIdConnect"]>;
310
- scheme: Schema.optional<typeof Schema.String>;
352
+ type: Schema.Literal<["http"]>;
353
+ scheme: typeof Schema.String;
311
354
  bearerFormat: Schema.optional<typeof Schema.String>;
312
- in: Schema.optional<Schema.Literal<["query", "header", "cookie"]>>;
313
- parameterName: Schema.optional<typeof Schema.String>;
314
355
  description: Schema.optional<typeof Schema.String>;
315
- }>>;
356
+ }>, Schema.Struct<{
357
+ name: typeof Schema.String;
358
+ type: Schema.Literal<["apiKey"]>;
359
+ in: Schema.Literal<["query", "header", "cookie"]>;
360
+ parameterName: typeof Schema.String;
361
+ description: Schema.optional<typeof Schema.String>;
362
+ }>, Schema.Struct<{
363
+ name: typeof Schema.String;
364
+ type: Schema.Literal<["oauth2"]>;
365
+ flows: Schema.Struct<{
366
+ implicit: Schema.optional<Schema.Struct<{
367
+ authorizationUrl: Schema.optional<typeof Schema.String>;
368
+ tokenUrl: Schema.optional<typeof Schema.String>;
369
+ refreshUrl: Schema.optional<typeof Schema.String>;
370
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
371
+ }>>;
372
+ password: Schema.optional<Schema.Struct<{
373
+ authorizationUrl: Schema.optional<typeof Schema.String>;
374
+ tokenUrl: Schema.optional<typeof Schema.String>;
375
+ refreshUrl: Schema.optional<typeof Schema.String>;
376
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
377
+ }>>;
378
+ clientCredentials: Schema.optional<Schema.Struct<{
379
+ authorizationUrl: Schema.optional<typeof Schema.String>;
380
+ tokenUrl: Schema.optional<typeof Schema.String>;
381
+ refreshUrl: Schema.optional<typeof Schema.String>;
382
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
383
+ }>>;
384
+ authorizationCode: Schema.optional<Schema.Struct<{
385
+ authorizationUrl: Schema.optional<typeof Schema.String>;
386
+ tokenUrl: Schema.optional<typeof Schema.String>;
387
+ refreshUrl: Schema.optional<typeof Schema.String>;
388
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
389
+ }>>;
390
+ }>;
391
+ description: Schema.optional<typeof Schema.String>;
392
+ }>, Schema.Struct<{
393
+ name: typeof Schema.String;
394
+ type: Schema.Literal<["openIdConnect"]>;
395
+ openIdConnectUrl: typeof Schema.String;
396
+ description: Schema.optional<typeof Schema.String>;
397
+ }>]>>;
316
398
  securityRequirements: Schema.Array$<Schema.Record$<typeof Schema.String, Schema.Array$<typeof Schema.String>>>;
317
399
  tags: Schema.Array$<Schema.Struct<{
318
400
  name: typeof Schema.String;
@@ -252,15 +252,54 @@ declare const OpenApiGeneratorConfig: Schema.Struct<{
252
252
  description: Schema.optional<typeof Schema.String>;
253
253
  }>>>;
254
254
  security: Schema.optional<Schema.Struct<{
255
- schemes: Schema.optional<Schema.Array$<Schema.Struct<{
255
+ schemes: Schema.optional<Schema.Array$<Schema.Union<[Schema.Struct<{
256
256
  name: typeof Schema.String;
257
- type: Schema.Literal<["apiKey", "http", "oauth2", "openIdConnect"]>;
258
- scheme: Schema.optional<typeof Schema.String>;
257
+ type: Schema.Literal<["http"]>;
258
+ scheme: typeof Schema.String;
259
259
  bearerFormat: Schema.optional<typeof Schema.String>;
260
- in: Schema.optional<Schema.Literal<["query", "header", "cookie"]>>;
261
- parameterName: Schema.optional<typeof Schema.String>;
262
260
  description: Schema.optional<typeof Schema.String>;
263
- }>>>;
261
+ }>, Schema.Struct<{
262
+ name: typeof Schema.String;
263
+ type: Schema.Literal<["apiKey"]>;
264
+ in: Schema.Literal<["query", "header", "cookie"]>;
265
+ parameterName: typeof Schema.String;
266
+ description: Schema.optional<typeof Schema.String>;
267
+ }>, Schema.Struct<{
268
+ name: typeof Schema.String;
269
+ type: Schema.Literal<["oauth2"]>;
270
+ flows: Schema.Struct<{
271
+ implicit: Schema.optional<Schema.Struct<{
272
+ authorizationUrl: Schema.optional<typeof Schema.String>;
273
+ tokenUrl: Schema.optional<typeof Schema.String>;
274
+ refreshUrl: Schema.optional<typeof Schema.String>;
275
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
276
+ }>>;
277
+ password: Schema.optional<Schema.Struct<{
278
+ authorizationUrl: Schema.optional<typeof Schema.String>;
279
+ tokenUrl: Schema.optional<typeof Schema.String>;
280
+ refreshUrl: Schema.optional<typeof Schema.String>;
281
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
282
+ }>>;
283
+ clientCredentials: Schema.optional<Schema.Struct<{
284
+ authorizationUrl: Schema.optional<typeof Schema.String>;
285
+ tokenUrl: Schema.optional<typeof Schema.String>;
286
+ refreshUrl: Schema.optional<typeof Schema.String>;
287
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
288
+ }>>;
289
+ authorizationCode: Schema.optional<Schema.Struct<{
290
+ authorizationUrl: Schema.optional<typeof Schema.String>;
291
+ tokenUrl: Schema.optional<typeof Schema.String>;
292
+ refreshUrl: Schema.optional<typeof Schema.String>;
293
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
294
+ }>>;
295
+ }>;
296
+ description: Schema.optional<typeof Schema.String>;
297
+ }>, Schema.Struct<{
298
+ name: typeof Schema.String;
299
+ type: Schema.Literal<["openIdConnect"]>;
300
+ openIdConnectUrl: typeof Schema.String;
301
+ description: Schema.optional<typeof Schema.String>;
302
+ }>]>>>;
264
303
  global: Schema.optional<Schema.Array$<Schema.Record$<typeof Schema.String, Schema.Array$<typeof Schema.String>>>>;
265
304
  }>>;
266
305
  }>;
@@ -271,6 +310,9 @@ declare const OpenApiGeneratorConfig: Schema.Struct<{
271
310
  query: Schema.optional<Schema.Struct<{
272
311
  style: Schema.optional<Schema.Literal<["inline", "ref"]>>;
273
312
  }>>;
313
+ schemas: Schema.optional<Schema.Struct<{
314
+ aliasRefs: Schema.optional<Schema.Literal<["collapse", "preserve"]>>;
315
+ }>>;
274
316
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
275
317
  }>>;
276
318
  }>;
@@ -283,6 +325,7 @@ declare const ResolvedConfig: Schema.Struct<{
283
325
  excludeDecorators: Schema.Array$<typeof Schema.String>;
284
326
  dtoGlob: Schema.Array$<typeof Schema.String>;
285
327
  extractValidation: typeof Schema.Boolean;
328
+ aliasRefs: Schema.Literal<["collapse", "preserve"]>;
286
329
  basePath: Schema.optional<typeof Schema.String>;
287
330
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
288
331
  version: Schema.optional<typeof Schema.String>;
@@ -304,15 +347,54 @@ declare const ResolvedConfig: Schema.Struct<{
304
347
  url: typeof Schema.String;
305
348
  description: Schema.optional<typeof Schema.String>;
306
349
  }>>;
307
- securitySchemes: Schema.Array$<Schema.Struct<{
350
+ securitySchemes: Schema.Array$<Schema.Union<[Schema.Struct<{
308
351
  name: typeof Schema.String;
309
- type: Schema.Literal<["apiKey", "http", "oauth2", "openIdConnect"]>;
310
- scheme: Schema.optional<typeof Schema.String>;
352
+ type: Schema.Literal<["http"]>;
353
+ scheme: typeof Schema.String;
311
354
  bearerFormat: Schema.optional<typeof Schema.String>;
312
- in: Schema.optional<Schema.Literal<["query", "header", "cookie"]>>;
313
- parameterName: Schema.optional<typeof Schema.String>;
314
355
  description: Schema.optional<typeof Schema.String>;
315
- }>>;
356
+ }>, Schema.Struct<{
357
+ name: typeof Schema.String;
358
+ type: Schema.Literal<["apiKey"]>;
359
+ in: Schema.Literal<["query", "header", "cookie"]>;
360
+ parameterName: typeof Schema.String;
361
+ description: Schema.optional<typeof Schema.String>;
362
+ }>, Schema.Struct<{
363
+ name: typeof Schema.String;
364
+ type: Schema.Literal<["oauth2"]>;
365
+ flows: Schema.Struct<{
366
+ implicit: Schema.optional<Schema.Struct<{
367
+ authorizationUrl: Schema.optional<typeof Schema.String>;
368
+ tokenUrl: Schema.optional<typeof Schema.String>;
369
+ refreshUrl: Schema.optional<typeof Schema.String>;
370
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
371
+ }>>;
372
+ password: Schema.optional<Schema.Struct<{
373
+ authorizationUrl: Schema.optional<typeof Schema.String>;
374
+ tokenUrl: Schema.optional<typeof Schema.String>;
375
+ refreshUrl: Schema.optional<typeof Schema.String>;
376
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
377
+ }>>;
378
+ clientCredentials: Schema.optional<Schema.Struct<{
379
+ authorizationUrl: Schema.optional<typeof Schema.String>;
380
+ tokenUrl: Schema.optional<typeof Schema.String>;
381
+ refreshUrl: Schema.optional<typeof Schema.String>;
382
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
383
+ }>>;
384
+ authorizationCode: Schema.optional<Schema.Struct<{
385
+ authorizationUrl: Schema.optional<typeof Schema.String>;
386
+ tokenUrl: Schema.optional<typeof Schema.String>;
387
+ refreshUrl: Schema.optional<typeof Schema.String>;
388
+ scopes: Schema.optional<Schema.Record$<typeof Schema.String, typeof Schema.String>>;
389
+ }>>;
390
+ }>;
391
+ description: Schema.optional<typeof Schema.String>;
392
+ }>, Schema.Struct<{
393
+ name: typeof Schema.String;
394
+ type: Schema.Literal<["openIdConnect"]>;
395
+ openIdConnectUrl: typeof Schema.String;
396
+ description: Schema.optional<typeof Schema.String>;
397
+ }>]>>;
316
398
  securityRequirements: Schema.Array$<Schema.Record$<typeof Schema.String, Schema.Array$<typeof Schema.String>>>;
317
399
  tags: Schema.Array$<Schema.Struct<{
318
400
  name: typeof Schema.String;
@@ -703,6 +703,22 @@ const parseTypeText = (text) => {
703
703
  const trimmed = text.trim();
704
704
  return trimmed.startsWith("{") && trimmed.endsWith("}") ? { type: Option.none(), inline: Option.some(trimmed) } : { type: Option.some(trimmed), inline: Option.none() };
705
705
  };
706
+ const getGenericBaseType = (text) => {
707
+ const genericStart = text.indexOf("<");
708
+ return genericStart === -1 ? null : text.slice(0, genericStart).trim();
709
+ };
710
+ const hasAliasedImportCollision = (method, exportedName) => {
711
+ const localNames = /* @__PURE__ */ new Set();
712
+ for (const importDecl of method.getSourceFile().getImportDeclarations()) {
713
+ for (const namedImport of importDecl.getNamedImports()) {
714
+ const aliasNode = namedImport.getAliasNode();
715
+ if (!aliasNode) continue;
716
+ if (namedImport.getName() !== exportedName) continue;
717
+ localNames.add(aliasNode.getText());
718
+ }
719
+ }
720
+ return localNames.size > 1;
721
+ };
706
722
  const getReturnTypeInfo = (method) => {
707
723
  const returnType = method.getReturnType();
708
724
  const awaited = returnType.getAwaitedType?.() ?? returnType;
@@ -717,6 +733,25 @@ const getReturnTypeInfo = (method) => {
717
733
  return null;
718
734
  };
719
735
  let text = getOriginalTypeName() ?? awaited.getText(method);
736
+ const compilerType = awaited.compilerType;
737
+ const aliasName = compilerType.aliasSymbol?.escapedName?.toString();
738
+ if (aliasName && !aliasName.startsWith("__")) {
739
+ const genericBase = getGenericBaseType(text);
740
+ const shouldPreserveLocalAlias = genericBase !== null && genericBase !== aliasName && hasAliasedImportCollision(method, aliasName);
741
+ if (!shouldPreserveLocalAlias) {
742
+ text = genericBase === null ? aliasName : `${aliasName}${text.slice(text.indexOf("<"))}`;
743
+ }
744
+ }
745
+ const symbolName = symbol?.getName();
746
+ if (symbolName && !symbolName.startsWith("__")) {
747
+ const genericBase = getGenericBaseType(text);
748
+ if (genericBase !== null) {
749
+ const shouldPreserveLocalAlias = genericBase !== symbolName && hasAliasedImportCollision(method, symbolName);
750
+ if (!shouldPreserveLocalAlias) {
751
+ text = `${symbolName}${text.slice(text.indexOf("<"))}`;
752
+ }
753
+ }
754
+ }
720
755
  const promiseMatch = text.match(/^Promise<(.+)>$/);
721
756
  if (promiseMatch) text = promiseMatch[1].trim();
722
757
  text = text.replace(/\bimport\([^)]*\)\./g, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-openapi",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Static code analysis tool to generate OpenAPI specifications from NestJS applications",
5
5
  "main": "./dist/index.mjs",
6
6
  "module": "./dist/index.mjs",