nestjs-openapi 0.1.3 → 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,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import 'tsx';
3
- import { g as generate, e as formatValidationResult } from './shared/nestjs-openapi.D2c4bMP5.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';
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.t3iwzrrT.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.t3iwzrrT.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.t3iwzrrT.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.t3iwzrrT.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,4 +1,4 @@
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.D2c4bMP5.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';
@@ -1,2 +1,2 @@
1
1
  import 'effect';
2
- export { G as GenerateOptions, g as generate, d as generateAsync } from './shared/nestjs-openapi.t3iwzrrT.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.t3iwzrrT.js';
2
+ export { G as GenerateOptions, g as generate, d as generateAsync } from './shared/nestjs-openapi.ClTIhsb-.js';
@@ -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) => {
@@ -1076,6 +1235,9 @@ const OpenApiConfig = Schema.Struct({
1076
1235
  const QueryOptionsConfig = Schema.Struct({
1077
1236
  style: Schema.optional(Schema.Literal("inline", "ref"))
1078
1237
  });
1238
+ const SchemaOptionsConfig = Schema.Struct({
1239
+ aliasRefs: Schema.optional(Schema.Literal("collapse", "preserve"))
1240
+ });
1079
1241
  const PathFilterFunction = Schema.declare(
1080
1242
  (input) => typeof input === "function",
1081
1243
  {
@@ -1092,6 +1254,7 @@ const OptionsConfig = Schema.Struct({
1092
1254
  extractValidation: Schema.optional(Schema.Boolean),
1093
1255
  excludeDecorators: Schema.optional(Schema.Array(Schema.String)),
1094
1256
  query: Schema.optional(QueryOptionsConfig),
1257
+ schemas: Schema.optional(SchemaOptionsConfig),
1095
1258
  pathFilter: Schema.optional(PathFilter)
1096
1259
  });
1097
1260
  const OpenApiGeneratorConfig = Schema.Struct({
@@ -1110,6 +1273,7 @@ Schema.Struct({
1110
1273
  excludeDecorators: Schema.Array(Schema.String),
1111
1274
  dtoGlob: Schema.Array(Schema.String),
1112
1275
  extractValidation: Schema.Boolean,
1276
+ aliasRefs: Schema.Literal("collapse", "preserve"),
1113
1277
  basePath: Schema.optional(Schema.String),
1114
1278
  pathFilter: Schema.optional(PathFilter),
1115
1279
  version: Schema.optional(Schema.String),
@@ -1164,7 +1328,10 @@ const DEFAULT_CONFIG = {
1164
1328
  },
1165
1329
  options: {
1166
1330
  excludeDecorators: ["ApiExcludeEndpoint", "ApiExcludeController"],
1167
- extractValidation: true
1331
+ extractValidation: true,
1332
+ schemas: {
1333
+ aliasRefs: "collapse"
1334
+ }
1168
1335
  },
1169
1336
  format: "json",
1170
1337
  openapi: {
@@ -1280,6 +1447,7 @@ const resolveConfig = (config) => {
1280
1447
  excludeDecorators: options.excludeDecorators ?? DEFAULT_CONFIG.options.excludeDecorators,
1281
1448
  dtoGlob,
1282
1449
  extractValidation: options.extractValidation ?? DEFAULT_CONFIG.options.extractValidation,
1450
+ aliasRefs: options.schemas?.aliasRefs ?? DEFAULT_CONFIG.options.schemas.aliasRefs,
1283
1451
  basePath: options.basePath,
1284
1452
  version: openapi.version,
1285
1453
  info: openapi.info,
@@ -1904,6 +2072,7 @@ const generate = async (configPath, overrides) => {
1904
2072
  );
1905
2073
  const files = config.files ?? {};
1906
2074
  const options = config.options ?? {};
2075
+ const aliasRefsMode = options.schemas?.aliasRefs ?? "collapse";
1907
2076
  const openapi = config.openapi;
1908
2077
  const security = openapi.security ?? {};
1909
2078
  const rawEntry = files.entry ?? DEFAULT_ENTRY;
@@ -2088,6 +2257,11 @@ const generate = async (configPath, overrides) => {
2088
2257
  }
2089
2258
  }
2090
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
+ }
2091
2265
  const securitySchemes = security.schemes && security.schemes.length > 0 ? buildSecuritySchemes(security.schemes) : void 0;
2092
2266
  const hasSchemas = Object.keys(schemas).length > 0;
2093
2267
  const hasSecuritySchemes = securitySchemes && Object.keys(securitySchemes).length > 0;
@@ -310,6 +310,9 @@ declare const OpenApiGeneratorConfig: Schema.Struct<{
310
310
  query: Schema.optional<Schema.Struct<{
311
311
  style: Schema.optional<Schema.Literal<["inline", "ref"]>>;
312
312
  }>>;
313
+ schemas: Schema.optional<Schema.Struct<{
314
+ aliasRefs: Schema.optional<Schema.Literal<["collapse", "preserve"]>>;
315
+ }>>;
313
316
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
314
317
  }>>;
315
318
  }>;
@@ -322,6 +325,7 @@ declare const ResolvedConfig: Schema.Struct<{
322
325
  excludeDecorators: Schema.Array$<typeof Schema.String>;
323
326
  dtoGlob: Schema.Array$<typeof Schema.String>;
324
327
  extractValidation: typeof Schema.Boolean;
328
+ aliasRefs: Schema.Literal<["collapse", "preserve"]>;
325
329
  basePath: Schema.optional<typeof Schema.String>;
326
330
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
327
331
  version: Schema.optional<typeof Schema.String>;
@@ -310,6 +310,9 @@ declare const OpenApiGeneratorConfig: Schema.Struct<{
310
310
  query: Schema.optional<Schema.Struct<{
311
311
  style: Schema.optional<Schema.Literal<["inline", "ref"]>>;
312
312
  }>>;
313
+ schemas: Schema.optional<Schema.Struct<{
314
+ aliasRefs: Schema.optional<Schema.Literal<["collapse", "preserve"]>>;
315
+ }>>;
313
316
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
314
317
  }>>;
315
318
  }>;
@@ -322,6 +325,7 @@ declare const ResolvedConfig: Schema.Struct<{
322
325
  excludeDecorators: Schema.Array$<typeof Schema.String>;
323
326
  dtoGlob: Schema.Array$<typeof Schema.String>;
324
327
  extractValidation: typeof Schema.Boolean;
328
+ aliasRefs: Schema.Literal<["collapse", "preserve"]>;
325
329
  basePath: Schema.optional<typeof Schema.String>;
326
330
  pathFilter: Schema.optional<Schema.Union<[Schema.instanceOf<RegExp>, Schema.declare<(path: string) => boolean, (path: string) => boolean, readonly [], never>]>>;
327
331
  version: Schema.optional<typeof Schema.String>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nestjs-openapi",
3
- "version": "0.1.3",
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",