next-openapi-gen 1.2.1 → 1.2.2

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/README.md CHANGED
@@ -184,6 +184,23 @@ z.number()
184
184
 
185
185
  Both work inside `z.object({...})` properties, in drizzle-zod override callbacks, and at the top level of named schemas. See [docs/zod4-support-matrix.md](./docs/zod4-support-matrix.md) for the full supported metadata surface.
186
186
 
187
+ ### Decouple component names from source identifiers
188
+
189
+ Use `.meta({ id })` for Zod schemas or `/** @id */` for TypeScript types to set the OpenAPI component name independently of the export name — useful when enforcing PascalCase naming or migrating from another generator without renaming existing exports:
190
+
191
+ ```ts
192
+ // Zod: use .meta({ id }) to decouple the component name from the variable name
193
+ export const audioSchema = z.object({ ... }).meta({ id: "Audio" });
194
+ // → components.schemas.Audio (not: audioSchema)
195
+
196
+ // TypeScript: use /** @id */ at the declaration level
197
+ /** @id Audio */
198
+ export interface AudioInterface { ... }
199
+ // → components.schemas.Audio (not: AudioInterface)
200
+ ```
201
+
202
+ Existing `@body audioSchema` or `@response audioSchema` references in route handlers continue to work — the generator resolves them transparently to the override name. See [docs/jsdoc-reference.md#component-naming](./docs/jsdoc-reference.md#component-naming) for details.
203
+
187
204
  ### Generate docs from Drizzle schemas
188
205
 
189
206
  `next-openapi-gen` works well with `drizzle-zod`, so your database schema, validation, and API docs can share the same source of truth.
package/dist/cli.js CHANGED
@@ -1406,6 +1406,17 @@ function mergeJSDocData(target, source) {
1406
1406
  function cleanComment(commentValue) {
1407
1407
  return commentValue.replace(/\*\s*/g, "").trim();
1408
1408
  }
1409
+ function extractSchemaIdFromComments(comments) {
1410
+ if (!comments)
1411
+ return null;
1412
+ for (const comment of comments) {
1413
+ const cleaned = cleanComment(comment.value);
1414
+ const id = extractTokenValue(cleaned, "@id");
1415
+ if (id)
1416
+ return id;
1417
+ }
1418
+ return null;
1419
+ }
1409
1420
  function extractLineValue(commentValue, tag) {
1410
1421
  return commentValue.match(new RegExp(`${escapeRegExp(tag)}\\s*(.*)`, "m"))?.[1]?.trim() || "";
1411
1422
  }
@@ -3585,7 +3596,8 @@ var DrizzleZodProcessor = class _DrizzleZodProcessor {
3585
3596
  if (firstArg && !t4.isSpreadElement(firstArg) && !t4.isArgumentPlaceholder(firstArg)) {
3586
3597
  const metadata = _DrizzleZodProcessor.extractStaticObject(firstArg);
3587
3598
  if (metadata) {
3588
- Object.assign(result, metadata);
3599
+ const { id: _id, ...rest } = metadata;
3600
+ Object.assign(result, rest);
3589
3601
  }
3590
3602
  }
3591
3603
  break;
@@ -11869,8 +11881,11 @@ var ZodRuntimeExporter = class {
11869
11881
  case "deprecated":
11870
11882
  return schema.meta({ deprecated: true });
11871
11883
  case "meta": {
11872
- const metadata = node.arguments[0] ? this.buildMetadataObject(node.arguments[0]) : null;
11873
- return metadata ? schema.meta(metadata) : schema;
11884
+ const rawMetadata = node.arguments[0] ? this.buildMetadataObject(node.arguments[0]) : null;
11885
+ if (!rawMetadata)
11886
+ return schema;
11887
+ const { id: _id, ...metadata } = rawMetadata;
11888
+ return Object.keys(metadata).length > 0 ? schema.meta(metadata) : schema;
11874
11889
  }
11875
11890
  case "default":
11876
11891
  case "prefault":
@@ -12660,6 +12675,8 @@ var ZodSchemaConverter = class {
12660
12675
  try {
12661
12676
  const content = this.fileAccess.readFileSync(filePath, "utf-8");
12662
12677
  const ast = parseTypeScriptFile(content);
12678
+ this.currentFilePath = filePath;
12679
+ this.currentAST = ast;
12663
12680
  resolvedTraverse(ast, {
12664
12681
  ExportNamedDeclaration: (path25) => {
12665
12682
  if (t10.isVariableDeclaration(path25.node.declaration)) {
@@ -12674,6 +12691,17 @@ var ZodSchemaConverter = class {
12674
12691
  }
12675
12692
  this.processingSchemas.delete(schemaName);
12676
12693
  }
12694
+ } else if (t10.isIdentifier(declaration.id) && declaration.init) {
12695
+ const schemaName = declaration.id.name;
12696
+ const overrideId = this.extractMetaIdFromNode(declaration.init);
12697
+ if (overrideId && !this.getStoredSchema(schemaName) && !this.processingSchemas.has(schemaName)) {
12698
+ this.processingSchemas.add(schemaName);
12699
+ const schema = this.processZodNode(declaration.init);
12700
+ this.processingSchemas.delete(schemaName);
12701
+ if (schema) {
12702
+ this.applyMetaIdOverride(schemaName, schema, overrideId, filePath);
12703
+ }
12704
+ }
12677
12705
  }
12678
12706
  });
12679
12707
  }
@@ -13227,6 +13255,24 @@ var ZodSchemaConverter = class {
13227
13255
  }
13228
13256
  return void 0;
13229
13257
  }
13258
+ extractMetaIdFromNode(node) {
13259
+ if (!t10.isCallExpression(node))
13260
+ return null;
13261
+ if (t10.isMemberExpression(node.callee) && t10.isIdentifier(node.callee.property)) {
13262
+ if (node.callee.property.name === "meta" && node.arguments.length > 0) {
13263
+ const metadata = this.extractStaticJsonValue(node.arguments[0]);
13264
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
13265
+ const id = metadata.id;
13266
+ if (typeof id === "string" && id.length > 0)
13267
+ return id;
13268
+ }
13269
+ }
13270
+ if (t10.isCallExpression(node.callee.object)) {
13271
+ return this.extractMetaIdFromNode(node.callee.object);
13272
+ }
13273
+ }
13274
+ return null;
13275
+ }
13230
13276
  shouldUseRuntimeExport(node) {
13231
13277
  if (!t10.isCallExpression(node)) {
13232
13278
  return false;
@@ -13467,7 +13513,8 @@ var ZodSchemaConverter = class {
13467
13513
  if (node.arguments.length > 0) {
13468
13514
  const metadata = this.extractStaticJsonValue(node.arguments[0]);
13469
13515
  if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
13470
- Object.assign(schema, metadata);
13516
+ const { id: _id, ...rest } = metadata;
13517
+ Object.assign(schema, rest);
13471
13518
  }
13472
13519
  }
13473
13520
  break;
@@ -13753,6 +13800,7 @@ var ZodSchemaConverter = class {
13753
13800
  this.currentFilePath = filePath;
13754
13801
  this.currentAST = ast;
13755
13802
  this.currentImports = importedModules;
13803
+ this.preprocessedFiles.add(filePath);
13756
13804
  resolvedTraverse(ast, {
13757
13805
  ExportNamedDeclaration: (path25) => {
13758
13806
  if (t10.isVariableDeclaration(path25.node.declaration)) {
@@ -13760,15 +13808,19 @@ var ZodSchemaConverter = class {
13760
13808
  if (t10.isIdentifier(declaration.id) && declaration.init) {
13761
13809
  const schemaName = declaration.id.name;
13762
13810
  if (this.isZodSchema(declaration.init)) {
13763
- this.indexSchemaName(schemaName, filePath);
13764
13811
  if (!this.getStoredSchema(schemaName)) {
13765
13812
  logger.debug(`Pre-processing Zod schema: ${schemaName}`);
13766
13813
  this.processingSchemas.add(schemaName);
13767
13814
  const schema = this.processZodNode(declaration.init);
13815
+ this.processingSchemas.delete(schemaName);
13768
13816
  if (schema) {
13769
- this.storeResolvedSchema(schemaName, schema);
13817
+ const overrideId = this.extractMetaIdFromNode(declaration.init);
13818
+ this.applyMetaIdOverride(schemaName, schema, overrideId, filePath);
13819
+ } else {
13820
+ this.indexSchemaName(schemaName, filePath);
13770
13821
  }
13771
- this.processingSchemas.delete(schemaName);
13822
+ } else {
13823
+ this.indexSchemaName(schemaName, filePath);
13772
13824
  }
13773
13825
  }
13774
13826
  }
@@ -13781,22 +13833,25 @@ var ZodSchemaConverter = class {
13781
13833
  if (t10.isIdentifier(declaration.id) && declaration.init) {
13782
13834
  const schemaName = declaration.id.name;
13783
13835
  if (this.isZodSchema(declaration.init)) {
13784
- this.indexSchemaName(schemaName, filePath);
13785
13836
  if (!this.getStoredSchema(schemaName) && !this.processingSchemas.has(schemaName)) {
13786
13837
  logger.debug(`Pre-processing Zod schema: ${schemaName}`);
13787
13838
  this.processingSchemas.add(schemaName);
13788
13839
  const schema = this.processZodNode(declaration.init);
13840
+ this.processingSchemas.delete(schemaName);
13789
13841
  if (schema) {
13790
- this.storeResolvedSchema(schemaName, schema);
13842
+ const overrideId = this.extractMetaIdFromNode(declaration.init);
13843
+ this.applyMetaIdOverride(schemaName, schema, overrideId, filePath);
13844
+ } else {
13845
+ this.indexSchemaName(schemaName, filePath);
13791
13846
  }
13792
- this.processingSchemas.delete(schemaName);
13847
+ } else {
13848
+ this.indexSchemaName(schemaName, filePath);
13793
13849
  }
13794
13850
  }
13795
13851
  }
13796
13852
  });
13797
13853
  }
13798
13854
  });
13799
- this.preprocessedFiles.add(filePath);
13800
13855
  } catch (error2) {
13801
13856
  logger.error(`Error pre-processing file ${filePath}: ${error2}`);
13802
13857
  }
@@ -13814,6 +13869,21 @@ var ZodSchemaConverter = class {
13814
13869
  }
13815
13870
  bucket.add(filePath);
13816
13871
  }
13872
+ applyMetaIdOverride(schemaName, schema, overrideId, filePath) {
13873
+ const finalName = overrideId && overrideId !== schemaName ? overrideId : schemaName;
13874
+ this.indexSchemaName(schemaName, filePath);
13875
+ if (finalName !== schemaName) {
13876
+ this.indexSchemaName(finalName, filePath);
13877
+ }
13878
+ if (!this.getStoredSchema(finalName)) {
13879
+ if (overrideId && overrideId !== schemaName) {
13880
+ this.typeToSchemaMapping[schemaName] = overrideId;
13881
+ }
13882
+ this.storeResolvedSchema(finalName, schema);
13883
+ } else {
13884
+ logger.warn(`Schema component name '${overrideId ?? finalName}' conflicts with an existing schema, ignoring .meta({ id }) on '${schemaName}'`);
13885
+ }
13886
+ }
13817
13887
  /**
13818
13888
  * Check if node is Zod schema
13819
13889
  */
@@ -14045,7 +14115,7 @@ function parsePropertyComment(commentValue) {
14045
14115
  function getPropertyOptions(node, contentType) {
14046
14116
  const isOptional = !!node.optional;
14047
14117
  const options = {};
14048
- const leadingComment = node.leadingComments?.[node.leadingComments.length - 1];
14118
+ const leadingComment = node.leadingComments?.findLast((c) => c.type === "CommentBlock" || !node.trailingComments?.length);
14049
14119
  const trailingComment = node.trailingComments?.[0];
14050
14120
  const sourceComment = leadingComment ?? trailingComment;
14051
14121
  if (sourceComment) {
@@ -14448,31 +14518,60 @@ function resolveImportPath(importPath, fromFilePath, fileAccess) {
14448
14518
  }
14449
14519
  return null;
14450
14520
  }
14451
- function collectAllExportedDefinitions(ast, typeDefinitions, currentFile) {
14521
+ function collectFirstMemberLeadingComments(interfaceDecl) {
14522
+ const body = interfaceDecl?.body;
14523
+ if (!body)
14524
+ return [];
14525
+ const firstMember = body.body?.[0];
14526
+ return firstMember?.leadingComments ?? [];
14527
+ }
14528
+ function collectAllExportedDefinitions(ast, typeDefinitions, currentFile, schemaIdAliases) {
14529
+ function registerDefinition(name, entry, allComments) {
14530
+ if (!typeDefinitions[name]) {
14531
+ typeDefinitions[name] = entry;
14532
+ }
14533
+ const overrideId = extractSchemaIdFromComments(allComments);
14534
+ if (overrideId && schemaIdAliases) {
14535
+ schemaIdAliases[name] = overrideId;
14536
+ if (!typeDefinitions[overrideId]) {
14537
+ typeDefinitions[overrideId] = entry;
14538
+ }
14539
+ }
14540
+ }
14452
14541
  resolvedTraverse(ast, {
14453
14542
  TSTypeAliasDeclaration: (path25) => {
14454
14543
  if (path25.node.id && t12.isIdentifier(path25.node.id)) {
14455
14544
  const name = path25.node.id.name;
14456
- if (!typeDefinitions[name]) {
14457
- const node = path25.node.typeParameters && path25.node.typeParameters.params.length > 0 ? path25.node : path25.node.typeAnnotation;
14458
- typeDefinitions[name] = { node, filePath: currentFile };
14459
- }
14545
+ const node = path25.node.typeParameters && path25.node.typeParameters.params.length > 0 ? path25.node : path25.node.typeAnnotation;
14546
+ const allComments = [
14547
+ ...path25.parentPath?.node?.leadingComments ?? [],
14548
+ ...path25.node.leadingComments ?? [],
14549
+ ...path25.node.trailingComments ?? []
14550
+ ];
14551
+ registerDefinition(name, { node, filePath: currentFile }, allComments);
14460
14552
  }
14461
14553
  },
14462
14554
  TSInterfaceDeclaration: (path25) => {
14463
14555
  if (path25.node.id && t12.isIdentifier(path25.node.id)) {
14464
14556
  const name = path25.node.id.name;
14465
- if (!typeDefinitions[name]) {
14466
- typeDefinitions[name] = { node: path25.node, filePath: currentFile };
14467
- }
14557
+ const allComments = [
14558
+ ...path25.parentPath?.node?.leadingComments ?? [],
14559
+ ...path25.node.leadingComments ?? [],
14560
+ ...path25.node.trailingComments ?? [],
14561
+ ...collectFirstMemberLeadingComments(path25.node)
14562
+ ];
14563
+ registerDefinition(name, { node: path25.node, filePath: currentFile }, allComments);
14468
14564
  }
14469
14565
  },
14470
14566
  TSEnumDeclaration: (path25) => {
14471
14567
  if (path25.node.id && t12.isIdentifier(path25.node.id)) {
14472
14568
  const name = path25.node.id.name;
14473
- if (!typeDefinitions[name]) {
14474
- typeDefinitions[name] = { node: path25.node, filePath: currentFile };
14475
- }
14569
+ const allComments = [
14570
+ ...path25.parentPath?.node?.leadingComments ?? [],
14571
+ ...path25.node.leadingComments ?? [],
14572
+ ...path25.node.trailingComments ?? []
14573
+ ];
14574
+ registerDefinition(name, { node: path25.node, filePath: currentFile }, allComments);
14476
14575
  }
14477
14576
  },
14478
14577
  ExportNamedDeclaration: (path25) => {
@@ -14480,19 +14579,26 @@ function collectAllExportedDefinitions(ast, typeDefinitions, currentFile) {
14480
14579
  const interfaceDecl = path25.node.declaration;
14481
14580
  if (interfaceDecl.id && t12.isIdentifier(interfaceDecl.id)) {
14482
14581
  const name = interfaceDecl.id.name;
14483
- if (!typeDefinitions[name]) {
14484
- typeDefinitions[name] = { node: interfaceDecl, filePath: currentFile };
14485
- }
14582
+ const allComments = [
14583
+ ...path25.node.leadingComments ?? [],
14584
+ ...interfaceDecl.leadingComments ?? [],
14585
+ ...interfaceDecl.trailingComments ?? [],
14586
+ ...collectFirstMemberLeadingComments(interfaceDecl)
14587
+ ];
14588
+ registerDefinition(name, { node: interfaceDecl, filePath: currentFile }, allComments);
14486
14589
  }
14487
14590
  }
14488
14591
  if (t12.isTSTypeAliasDeclaration(path25.node.declaration)) {
14489
14592
  const typeDecl = path25.node.declaration;
14490
14593
  if (typeDecl.id && t12.isIdentifier(typeDecl.id)) {
14491
14594
  const name = typeDecl.id.name;
14492
- if (!typeDefinitions[name]) {
14493
- const node = typeDecl.typeParameters && typeDecl.typeParameters.params.length > 0 ? typeDecl : typeDecl.typeAnnotation;
14494
- typeDefinitions[name] = { node, filePath: currentFile };
14495
- }
14595
+ const node = typeDecl.typeParameters && typeDecl.typeParameters.params.length > 0 ? typeDecl : typeDecl.typeAnnotation;
14596
+ const allComments = [
14597
+ ...path25.node.leadingComments ?? [],
14598
+ ...typeDecl.leadingComments ?? [],
14599
+ ...typeDecl.trailingComments ?? []
14600
+ ];
14601
+ registerDefinition(name, { node, filePath: currentFile }, allComments);
14496
14602
  }
14497
14603
  }
14498
14604
  }
@@ -14909,6 +15015,7 @@ var SchemaProcessor = class {
14909
15015
  zodSchemaProcessor = null;
14910
15016
  schemaTypes;
14911
15017
  isResolvingPickOmitBase = false;
15018
+ schemaIdAliases = {};
14912
15019
  fileAccess;
14913
15020
  symbolResolver;
14914
15021
  // Track imports per file for resolving ReturnType<typeof func>
@@ -14949,7 +15056,7 @@ var SchemaProcessor = class {
14949
15056
  getDefinedSchemas() {
14950
15057
  const filteredSchemas = {};
14951
15058
  Object.entries(this.openapiDefinitions).forEach(([key, value]) => {
14952
- if (!this.isGenericTypeParameter(key) && !this.isInvalidSchemaName(key) && !this.isBuiltInUtilityType(key) && !this.isFunctionSchema(key)) {
15059
+ if (!this.schemaIdAliases[key] && !this.isGenericTypeParameter(key) && !this.isInvalidSchemaName(key) && !this.isBuiltInUtilityType(key) && !this.isFunctionSchema(key)) {
14953
15060
  filteredSchemas[key] = value;
14954
15061
  }
14955
15062
  });
@@ -14964,6 +15071,10 @@ var SchemaProcessor = class {
14964
15071
  if (schemaName.includes("<") && schemaName.includes(">")) {
14965
15072
  return this.resolveGenericTypeFromString(schemaName);
14966
15073
  }
15074
+ const overrideId = this.schemaIdAliases[schemaName];
15075
+ if (overrideId) {
15076
+ return this.findSchemaDefinition(overrideId, contentType);
15077
+ }
14967
15078
  if (this.openapiDefinitions[schemaName]) {
14968
15079
  return this.openapiDefinitions[schemaName];
14969
15080
  }
@@ -15045,6 +15156,7 @@ var SchemaProcessor = class {
15045
15156
  return;
15046
15157
  }
15047
15158
  this.collectImports(ast, filePath);
15159
+ const aliasesBeforeFile = new Set(Object.keys(this.schemaIdAliases));
15048
15160
  this.collectAllExportedDefinitions(ast, filePath);
15049
15161
  collectTopLevelDefinitionNames(ast).forEach((name) => {
15050
15162
  const indexedFiles = this.schemaDefinitionIndex[name];
@@ -15056,6 +15168,16 @@ var SchemaProcessor = class {
15056
15168
  }
15057
15169
  this.schemaDefinitionIndex[name] = [filePath];
15058
15170
  });
15171
+ Object.entries(this.schemaIdAliases).forEach(([originalName, aliasName]) => {
15172
+ if (aliasesBeforeFile.has(originalName))
15173
+ return;
15174
+ if (!this.schemaDefinitionIndex[aliasName]) {
15175
+ this.schemaDefinitionIndex[aliasName] = [];
15176
+ }
15177
+ if (!this.schemaDefinitionIndex[aliasName].includes(filePath)) {
15178
+ this.schemaDefinitionIndex[aliasName].push(filePath);
15179
+ }
15180
+ });
15059
15181
  }
15060
15182
  getParsedSchemaFile(filePath) {
15061
15183
  const cachedAst = this.fileASTCache.get(filePath);
@@ -15094,7 +15216,7 @@ var SchemaProcessor = class {
15094
15216
  * Used when processing imported files to ensure all referenced types are available
15095
15217
  */
15096
15218
  collectAllExportedDefinitions(ast, filePath) {
15097
- collectAllExportedDefinitions(ast, this.typeDefinitions, filePath || this.currentFilePath);
15219
+ collectAllExportedDefinitions(ast, this.typeDefinitions, filePath || this.currentFilePath, this.schemaIdAliases);
15098
15220
  }
15099
15221
  collectTypeDefinitions(ast, schemaName, filePath) {
15100
15222
  collectTypeDefinitions(ast, schemaName, this.typeDefinitions, filePath || this.currentFilePath);
@@ -15691,6 +15813,9 @@ var SchemaProcessor = class {
15691
15813
  logger.debug(`Record<...> used with ${node.typeParameters?.params.length ?? 0} type parameters; expected 2`);
15692
15814
  return { type: "object", additionalProperties: true };
15693
15815
  }
15816
+ if ((!node.typeParameters || node.typeParameters.params.length === 0) && this.schemaIdAliases[typeName] && this.openapiDefinitions[this.schemaIdAliases[typeName]]) {
15817
+ return { $ref: `#/components/schemas/${this.schemaIdAliases[typeName]}` };
15818
+ }
15694
15819
  const utilityType = resolveUtilityTypeReference(node, {
15695
15820
  currentFilePath: this.currentFilePath,
15696
15821
  contentType: this.contentType,
@@ -15866,7 +15991,9 @@ var SchemaProcessor = class {
15866
15991
  };
15867
15992
  }
15868
15993
  if (t15.isTSTypeReference(node) && t15.isIdentifier(node.typeName)) {
15869
- return { $ref: `#/components/schemas/${node.typeName.name}` };
15994
+ const refName = node.typeName.name;
15995
+ const aliasedName = this.schemaIdAliases[refName] ?? refName;
15996
+ return { $ref: `#/components/schemas/${aliasedName}` };
15870
15997
  }
15871
15998
  logger.debug("Unrecognized TypeScript type node:", node);
15872
15999
  return { type: "object" };
@@ -16007,7 +16134,8 @@ var SchemaProcessor = class {
16007
16134
  if (this.schemaTypes.includes("zod") && this.zodSchemaConverter) {
16008
16135
  return this.zodSchemaConverter.getSchemaReferenceName(baseTypeName, contentType);
16009
16136
  }
16010
- return baseTypeName;
16137
+ const aliasedName = this.schemaIdAliases[baseTypeName] ?? baseTypeName;
16138
+ return aliasedName;
16011
16139
  }
16012
16140
  /**
16013
16141
  * Parse and resolve a generic type from a string like "MyApiSuccessResponseBody<LLMSResponse>"