next-openapi-gen 1.1.1 → 1.2.1

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
@@ -162,6 +162,28 @@ For more adoption patterns, see
162
162
 
163
163
  When you target modern OpenAPI output, the Zod path can also split request and response component refs when a supported Zod 4 schema emits different input and output JSON Schema shapes, while the TypeScript path can use selective checker fallback for mapped, conditional, template-literal, and import-based named types.
164
164
 
165
+ ### Add OpenAPI metadata directly in Zod schemas
166
+
167
+ Use `.describe()` for a quick description, or Zod v4's `.meta()` to attach `description`, `examples`, `example`, `deprecated`, `title`, and custom `x-*` extensions without any JSDoc:
168
+
169
+ ```ts
170
+ // .describe() → description field
171
+ z.string().describe("ISO 639-1 language code");
172
+ // → { type: "string", description: "ISO 639-1 language code" }
173
+
174
+ // .meta() → description + examples (and any other OpenAPI key)
175
+ z.number()
176
+ .int()
177
+ .positive()
178
+ .meta({
179
+ description: "PIM ID of the slider",
180
+ examples: [42, 1337],
181
+ });
182
+ // → { type: "integer", exclusiveMinimum: 0, description: "PIM ID of the slider", examples: [42, 1337] }
183
+ ```
184
+
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
+
165
187
  ### Generate docs from Drizzle schemas
166
188
 
167
189
  `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
@@ -3154,7 +3154,7 @@ var SymbolResolver = class {
3154
3154
 
3155
3155
  // ../openapi-core/dist/schema/zod/drizzle-zod-processor.js
3156
3156
  import * as t4 from "@babel/types";
3157
- var DrizzleZodProcessor = class {
3157
+ var DrizzleZodProcessor = class _DrizzleZodProcessor {
3158
3158
  /**
3159
3159
  * Known drizzle-zod helper function names
3160
3160
  */
@@ -3580,6 +3580,16 @@ var DrizzleZodProcessor = class {
3580
3580
  result.description = args[0].value;
3581
3581
  }
3582
3582
  break;
3583
+ case "meta": {
3584
+ const firstArg = args[0];
3585
+ if (firstArg && !t4.isSpreadElement(firstArg) && !t4.isArgumentPlaceholder(firstArg)) {
3586
+ const metadata = _DrizzleZodProcessor.extractStaticObject(firstArg);
3587
+ if (metadata) {
3588
+ Object.assign(result, metadata);
3589
+ }
3590
+ }
3591
+ break;
3592
+ }
3583
3593
  case "default":
3584
3594
  if (args.length > 0) {
3585
3595
  if (t4.isStringLiteral(args[0])) {
@@ -3600,6 +3610,49 @@ var DrizzleZodProcessor = class {
3600
3610
  static isDrizzleZodHelper(name) {
3601
3611
  return this.DRIZZLE_ZOD_HELPERS.includes(name);
3602
3612
  }
3613
+ static extractStaticObject(node) {
3614
+ if (!t4.isObjectExpression(node))
3615
+ return null;
3616
+ const out = {};
3617
+ for (const prop of node.properties) {
3618
+ if (!t4.isObjectProperty(prop))
3619
+ return null;
3620
+ const key = t4.isIdentifier(prop.key) ? prop.key.name : t4.isStringLiteral(prop.key) ? prop.key.value : null;
3621
+ if (!key)
3622
+ return null;
3623
+ const val = _DrizzleZodProcessor.extractStaticValue(prop.value);
3624
+ if (typeof val === "undefined")
3625
+ return null;
3626
+ out[key] = val;
3627
+ }
3628
+ return out;
3629
+ }
3630
+ static extractStaticValue(node) {
3631
+ if (t4.isStringLiteral(node))
3632
+ return node.value;
3633
+ if (t4.isNumericLiteral(node))
3634
+ return node.value;
3635
+ if (t4.isBooleanLiteral(node))
3636
+ return node.value;
3637
+ if (t4.isNullLiteral(node))
3638
+ return null;
3639
+ if (t4.isArrayExpression(node)) {
3640
+ const values = [];
3641
+ for (const el of node.elements) {
3642
+ if (!el || t4.isSpreadElement(el) || t4.isArgumentPlaceholder(el))
3643
+ return void 0;
3644
+ const v = _DrizzleZodProcessor.extractStaticValue(el);
3645
+ if (typeof v === "undefined")
3646
+ return void 0;
3647
+ values.push(v);
3648
+ }
3649
+ return values;
3650
+ }
3651
+ if (t4.isObjectExpression(node)) {
3652
+ return _DrizzleZodProcessor.extractStaticObject(node);
3653
+ }
3654
+ return void 0;
3655
+ }
3603
3656
  };
3604
3657
 
3605
3658
  // ../openapi-core/dist/schema/zod/converter-runtime.js
@@ -4570,8 +4623,12 @@ function processZodPrimitiveNode(node, context) {
4570
4623
  };
4571
4624
  break;
4572
4625
  }
4626
+ case "strictObject":
4573
4627
  case "object":
4574
4628
  schema = node.arguments.length > 0 ? context.processObject(node) : { type: "object" };
4629
+ if (zodType === "strictObject") {
4630
+ schema.additionalProperties = false;
4631
+ }
4575
4632
  break;
4576
4633
  case "templateLiteral":
4577
4634
  schema = { type: "string" };
@@ -11638,6 +11695,10 @@ var ZodRuntimeExporter = class {
11638
11695
  return this.buildEnum(node);
11639
11696
  case "array":
11640
11697
  return node.arguments[0] && isProcessableNode(node.arguments[0]) ? array(this.buildSchema(node.arguments[0]) ?? unknown()) : array(unknown());
11698
+ case "strictObject": {
11699
+ const base = this.buildObject(node);
11700
+ return base && typeof base.strict === "function" ? base.strict() : base;
11701
+ }
11641
11702
  case "object":
11642
11703
  return this.buildObject(node);
11643
11704
  case "record":
@@ -12045,7 +12106,9 @@ var ZodSchemaConverter = class {
12045
12106
  apiDir;
12046
12107
  zodSchemas = {};
12047
12108
  processingSchemas = /* @__PURE__ */ new Set();
12048
- processedModules = /* @__PURE__ */ new Set();
12109
+ /** Memoization guard for processFileForZodSchema. Keys: `${filePath}|${schemaName}`.
12110
+ * Prevents infinite recursion when re-export files reference schemas via z.infer<typeof X>. */
12111
+ processedFileSchemaPairs = /* @__PURE__ */ new Set();
12049
12112
  typeToSchemaMapping = {};
12050
12113
  drizzleZodImports = /* @__PURE__ */ new Set();
12051
12114
  factoryCache = /* @__PURE__ */ new Map();
@@ -12259,6 +12322,11 @@ var ZodSchemaConverter = class {
12259
12322
  * Process a file to find Zod schema definitions
12260
12323
  */
12261
12324
  processFileForZodSchema(filePath, schemaName) {
12325
+ const visitKey = `${filePath}|${schemaName}|${this.currentContentType}`;
12326
+ if (this.processedFileSchemaPairs.has(visitKey)) {
12327
+ return;
12328
+ }
12329
+ this.processedFileSchemaPairs.add(visitKey);
12262
12330
  try {
12263
12331
  const content = this.fileAccess.readFileSync(filePath, "utf-8");
12264
12332
  if (!content.includes(schemaName)) {
@@ -12572,7 +12640,9 @@ var ZodSchemaConverter = class {
12572
12640
  const param = path25.node.typeAnnotation.typeParameters.params[0];
12573
12641
  if (t10.isTSTypeQuery(param) && t10.isIdentifier(param.exprName)) {
12574
12642
  const referencedSchemaName = param.exprName.name;
12575
- this.processFileForZodSchema(filePath, referencedSchemaName);
12643
+ if (!this.getStoredSchema(referencedSchemaName)) {
12644
+ this.processFileForZodSchema(filePath, referencedSchemaName);
12645
+ }
12576
12646
  }
12577
12647
  }
12578
12648
  }
@@ -12657,8 +12727,12 @@ var ZodSchemaConverter = class {
12657
12727
  }
12658
12728
  if (t10.isCallExpression(node) && t10.isMemberExpression(node.callee) && t10.isIdentifier(node.callee.object) && this.isZodLocalName(node.callee.object.name) && t10.isIdentifier(node.callee.property)) {
12659
12729
  const methodName = node.callee.property.name;
12660
- if (methodName === "object" && node.arguments.length > 0) {
12661
- return this.processZodObject(node);
12730
+ if ((methodName === "object" || methodName === "strictObject") && node.arguments.length > 0) {
12731
+ const schema = this.processZodObject(node);
12732
+ if (methodName === "strictObject") {
12733
+ schema.additionalProperties = false;
12734
+ }
12735
+ return schema;
12662
12736
  } else if (methodName === "union" && node.arguments.length > 0) {
12663
12737
  return this.processZodUnion(node);
12664
12738
  } else if (methodName === "intersection" && node.arguments.length > 0) {
@@ -13943,15 +14017,39 @@ function extractKeysFromLiteralType(node) {
13943
14017
  }
13944
14018
  return [];
13945
14019
  }
14020
+ function parsePropertyComment(commentValue) {
14021
+ const text = commentValue.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0).join(" ").trim();
14022
+ const result = {};
14023
+ let remaining = text;
14024
+ const formatMatch = remaining.match(/@format\s+(\S+)/);
14025
+ if (formatMatch?.[1]) {
14026
+ result.format = formatMatch[1];
14027
+ remaining = remaining.replace(formatMatch[0], "").trim();
14028
+ }
14029
+ const exampleMatch = remaining.match(/@example\s+(.+?)(?=\s*@\w|$)/);
14030
+ if (exampleMatch?.[1]) {
14031
+ const raw = exampleMatch[1].trim();
14032
+ try {
14033
+ result.example = JSON.parse(raw);
14034
+ } catch {
14035
+ result.example = raw;
14036
+ }
14037
+ remaining = remaining.replace(exampleMatch[0], "").trim();
14038
+ }
14039
+ remaining = remaining.replace(/@\w+(?:\s+\S+)*/g, "").trim();
14040
+ if (remaining) {
14041
+ result.description = remaining;
14042
+ }
14043
+ return result;
14044
+ }
13946
14045
  function getPropertyOptions(node, contentType) {
13947
14046
  const isOptional = !!node.optional;
13948
- let description = null;
13949
- if (node.trailingComments && node.trailingComments.length) {
13950
- description = node.trailingComments[0].value.trim();
13951
- }
13952
14047
  const options = {};
13953
- if (description) {
13954
- options.description = description;
14048
+ const leadingComment = node.leadingComments?.[node.leadingComments.length - 1];
14049
+ const trailingComment = node.trailingComments?.[0];
14050
+ const sourceComment = leadingComment ?? trailingComment;
14051
+ if (sourceComment) {
14052
+ Object.assign(options, parsePropertyComment(sourceComment.value));
13955
14053
  }
13956
14054
  if (contentType === "body") {
13957
14055
  options.nullable = isOptional;
@@ -14816,6 +14914,8 @@ var SchemaProcessor = class {
14816
14914
  // Track imports per file for resolving ReturnType<typeof func>
14817
14915
  importMap = {};
14818
14916
  // { filePath: { importName: importPath } }
14917
+ // Inverted index: typeName → first filePath that imports it (O(1) lookup for findFileImportingType)
14918
+ typeToFileIndex = /* @__PURE__ */ new Map();
14819
14919
  currentFilePath = "";
14820
14920
  // Track the file being processed
14821
14921
  constructor(schemaDir, schemaType = "typescript", schemaFiles, apiDir, fileAccess = defaultFileAccess2, runtime) {
@@ -14969,6 +15069,13 @@ var SchemaProcessor = class {
14969
15069
  }
14970
15070
  collectImports(ast, filePath) {
14971
15071
  collectImports(ast, filePath, this.importMap);
15072
+ const normalizedPath = path14.normalize(filePath);
15073
+ const entries = this.importMap[normalizedPath] ?? {};
15074
+ for (const typeName of Object.keys(entries)) {
15075
+ if (!this.typeToFileIndex.has(typeName)) {
15076
+ this.typeToFileIndex.set(typeName, normalizedPath);
15077
+ }
15078
+ }
14972
15079
  }
14973
15080
  /**
14974
15081
  * Resolve an import path relative to the current file
@@ -15007,6 +15114,15 @@ var SchemaProcessor = class {
15007
15114
  }
15008
15115
  const typeDefEntry = this.typeDefinitions[typeName.toString()];
15009
15116
  if (!typeDefEntry) {
15117
+ const contextFile = this.findFileImportingType(typeName);
15118
+ if (contextFile) {
15119
+ logger.debug(`resolveType: "${typeName}" not in schema dirs; attempting TypeScript checker fallback via ${contextFile}`);
15120
+ const checkerSchema = this.resolveTypeWithTypeScriptChecker(typeName, contextFile);
15121
+ if (checkerSchema && Object.keys(checkerSchema).length > 0) {
15122
+ this.openapiDefinitions[typeName] = checkerSchema;
15123
+ return checkerSchema;
15124
+ }
15125
+ }
15010
15126
  logger.debug(`resolveType: no TypeScript definition found for "${typeName}" in ${this.currentFilePath}; returning empty schema`);
15011
15127
  return {};
15012
15128
  }
@@ -15218,6 +15334,14 @@ var SchemaProcessor = class {
15218
15334
  }
15219
15335
  return null;
15220
15336
  }
15337
+ /**
15338
+ * Return the path of the first scanned file that imports `typeName`, or `null` when none is
15339
+ * found. Used as a fallback context for {@link resolveTypeWithTypeScriptChecker} when the type
15340
+ * is not defined in any schema-dir file (e.g. comes from node_modules or a shared package).
15341
+ */
15342
+ findFileImportingType(typeName) {
15343
+ return this.typeToFileIndex.get(typeName) ?? null;
15344
+ }
15221
15345
  shouldUseTypeScriptChecker(node) {
15222
15346
  return t15.isTSConditionalType(node) || t15.isTSMappedType(node) || t15.isTSTemplateLiteralType(node) || t15.isTSImportType(node) || t15.isTSTypeOperator(node) && node.operator === "keyof";
15223
15347
  }
@@ -15281,7 +15405,9 @@ var SchemaProcessor = class {
15281
15405
  if (seen.has(seenKey)) {
15282
15406
  return { type: "object" };
15283
15407
  }
15284
- seen.add(seenKey);
15408
+ if (!(type.flags & (primitiveLikeFlags | ts2.TypeFlags.Any | ts2.TypeFlags.Never | ts2.TypeFlags.Unknown | ts2.TypeFlags.Void))) {
15409
+ seen.add(seenKey);
15410
+ }
15285
15411
  if (type.isStringLiteral()) {
15286
15412
  return { type: "string", enum: [type.value] };
15287
15413
  }
package/dist/index.js CHANGED
@@ -2703,7 +2703,7 @@ var SymbolResolver = class {
2703
2703
 
2704
2704
  // ../openapi-core/dist/schema/zod/drizzle-zod-processor.js
2705
2705
  import * as t4 from "@babel/types";
2706
- var DrizzleZodProcessor = class {
2706
+ var DrizzleZodProcessor = class _DrizzleZodProcessor {
2707
2707
  /**
2708
2708
  * Known drizzle-zod helper function names
2709
2709
  */
@@ -3129,6 +3129,16 @@ var DrizzleZodProcessor = class {
3129
3129
  result.description = args[0].value;
3130
3130
  }
3131
3131
  break;
3132
+ case "meta": {
3133
+ const firstArg = args[0];
3134
+ if (firstArg && !t4.isSpreadElement(firstArg) && !t4.isArgumentPlaceholder(firstArg)) {
3135
+ const metadata = _DrizzleZodProcessor.extractStaticObject(firstArg);
3136
+ if (metadata) {
3137
+ Object.assign(result, metadata);
3138
+ }
3139
+ }
3140
+ break;
3141
+ }
3132
3142
  case "default":
3133
3143
  if (args.length > 0) {
3134
3144
  if (t4.isStringLiteral(args[0])) {
@@ -3149,6 +3159,49 @@ var DrizzleZodProcessor = class {
3149
3159
  static isDrizzleZodHelper(name) {
3150
3160
  return this.DRIZZLE_ZOD_HELPERS.includes(name);
3151
3161
  }
3162
+ static extractStaticObject(node) {
3163
+ if (!t4.isObjectExpression(node))
3164
+ return null;
3165
+ const out = {};
3166
+ for (const prop of node.properties) {
3167
+ if (!t4.isObjectProperty(prop))
3168
+ return null;
3169
+ const key = t4.isIdentifier(prop.key) ? prop.key.name : t4.isStringLiteral(prop.key) ? prop.key.value : null;
3170
+ if (!key)
3171
+ return null;
3172
+ const val = _DrizzleZodProcessor.extractStaticValue(prop.value);
3173
+ if (typeof val === "undefined")
3174
+ return null;
3175
+ out[key] = val;
3176
+ }
3177
+ return out;
3178
+ }
3179
+ static extractStaticValue(node) {
3180
+ if (t4.isStringLiteral(node))
3181
+ return node.value;
3182
+ if (t4.isNumericLiteral(node))
3183
+ return node.value;
3184
+ if (t4.isBooleanLiteral(node))
3185
+ return node.value;
3186
+ if (t4.isNullLiteral(node))
3187
+ return null;
3188
+ if (t4.isArrayExpression(node)) {
3189
+ const values = [];
3190
+ for (const el of node.elements) {
3191
+ if (!el || t4.isSpreadElement(el) || t4.isArgumentPlaceholder(el))
3192
+ return void 0;
3193
+ const v = _DrizzleZodProcessor.extractStaticValue(el);
3194
+ if (typeof v === "undefined")
3195
+ return void 0;
3196
+ values.push(v);
3197
+ }
3198
+ return values;
3199
+ }
3200
+ if (t4.isObjectExpression(node)) {
3201
+ return _DrizzleZodProcessor.extractStaticObject(node);
3202
+ }
3203
+ return void 0;
3204
+ }
3152
3205
  };
3153
3206
 
3154
3207
  // ../openapi-core/dist/schema/zod/converter-runtime.js
@@ -4119,8 +4172,12 @@ function processZodPrimitiveNode(node, context) {
4119
4172
  };
4120
4173
  break;
4121
4174
  }
4175
+ case "strictObject":
4122
4176
  case "object":
4123
4177
  schema = node.arguments.length > 0 ? context.processObject(node) : { type: "object" };
4178
+ if (zodType === "strictObject") {
4179
+ schema.additionalProperties = false;
4180
+ }
4124
4181
  break;
4125
4182
  case "templateLiteral":
4126
4183
  schema = { type: "string" };
@@ -11187,6 +11244,10 @@ var ZodRuntimeExporter = class {
11187
11244
  return this.buildEnum(node);
11188
11245
  case "array":
11189
11246
  return node.arguments[0] && isProcessableNode(node.arguments[0]) ? array(this.buildSchema(node.arguments[0]) ?? unknown()) : array(unknown());
11247
+ case "strictObject": {
11248
+ const base = this.buildObject(node);
11249
+ return base && typeof base.strict === "function" ? base.strict() : base;
11250
+ }
11190
11251
  case "object":
11191
11252
  return this.buildObject(node);
11192
11253
  case "record":
@@ -11594,7 +11655,9 @@ var ZodSchemaConverter = class {
11594
11655
  apiDir;
11595
11656
  zodSchemas = {};
11596
11657
  processingSchemas = /* @__PURE__ */ new Set();
11597
- processedModules = /* @__PURE__ */ new Set();
11658
+ /** Memoization guard for processFileForZodSchema. Keys: `${filePath}|${schemaName}`.
11659
+ * Prevents infinite recursion when re-export files reference schemas via z.infer<typeof X>. */
11660
+ processedFileSchemaPairs = /* @__PURE__ */ new Set();
11598
11661
  typeToSchemaMapping = {};
11599
11662
  drizzleZodImports = /* @__PURE__ */ new Set();
11600
11663
  factoryCache = /* @__PURE__ */ new Map();
@@ -11808,6 +11871,11 @@ var ZodSchemaConverter = class {
11808
11871
  * Process a file to find Zod schema definitions
11809
11872
  */
11810
11873
  processFileForZodSchema(filePath, schemaName) {
11874
+ const visitKey = `${filePath}|${schemaName}|${this.currentContentType}`;
11875
+ if (this.processedFileSchemaPairs.has(visitKey)) {
11876
+ return;
11877
+ }
11878
+ this.processedFileSchemaPairs.add(visitKey);
11811
11879
  try {
11812
11880
  const content = this.fileAccess.readFileSync(filePath, "utf-8");
11813
11881
  if (!content.includes(schemaName)) {
@@ -12121,7 +12189,9 @@ var ZodSchemaConverter = class {
12121
12189
  const param = path25.node.typeAnnotation.typeParameters.params[0];
12122
12190
  if (t10.isTSTypeQuery(param) && t10.isIdentifier(param.exprName)) {
12123
12191
  const referencedSchemaName = param.exprName.name;
12124
- this.processFileForZodSchema(filePath, referencedSchemaName);
12192
+ if (!this.getStoredSchema(referencedSchemaName)) {
12193
+ this.processFileForZodSchema(filePath, referencedSchemaName);
12194
+ }
12125
12195
  }
12126
12196
  }
12127
12197
  }
@@ -12206,8 +12276,12 @@ var ZodSchemaConverter = class {
12206
12276
  }
12207
12277
  if (t10.isCallExpression(node) && t10.isMemberExpression(node.callee) && t10.isIdentifier(node.callee.object) && this.isZodLocalName(node.callee.object.name) && t10.isIdentifier(node.callee.property)) {
12208
12278
  const methodName = node.callee.property.name;
12209
- if (methodName === "object" && node.arguments.length > 0) {
12210
- return this.processZodObject(node);
12279
+ if ((methodName === "object" || methodName === "strictObject") && node.arguments.length > 0) {
12280
+ const schema = this.processZodObject(node);
12281
+ if (methodName === "strictObject") {
12282
+ schema.additionalProperties = false;
12283
+ }
12284
+ return schema;
12211
12285
  } else if (methodName === "union" && node.arguments.length > 0) {
12212
12286
  return this.processZodUnion(node);
12213
12287
  } else if (methodName === "intersection" && node.arguments.length > 0) {
@@ -13492,15 +13566,39 @@ function extractKeysFromLiteralType(node) {
13492
13566
  }
13493
13567
  return [];
13494
13568
  }
13569
+ function parsePropertyComment(commentValue) {
13570
+ const text = commentValue.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0).join(" ").trim();
13571
+ const result = {};
13572
+ let remaining = text;
13573
+ const formatMatch = remaining.match(/@format\s+(\S+)/);
13574
+ if (formatMatch?.[1]) {
13575
+ result.format = formatMatch[1];
13576
+ remaining = remaining.replace(formatMatch[0], "").trim();
13577
+ }
13578
+ const exampleMatch = remaining.match(/@example\s+(.+?)(?=\s*@\w|$)/);
13579
+ if (exampleMatch?.[1]) {
13580
+ const raw = exampleMatch[1].trim();
13581
+ try {
13582
+ result.example = JSON.parse(raw);
13583
+ } catch {
13584
+ result.example = raw;
13585
+ }
13586
+ remaining = remaining.replace(exampleMatch[0], "").trim();
13587
+ }
13588
+ remaining = remaining.replace(/@\w+(?:\s+\S+)*/g, "").trim();
13589
+ if (remaining) {
13590
+ result.description = remaining;
13591
+ }
13592
+ return result;
13593
+ }
13495
13594
  function getPropertyOptions(node, contentType) {
13496
13595
  const isOptional = !!node.optional;
13497
- let description = null;
13498
- if (node.trailingComments && node.trailingComments.length) {
13499
- description = node.trailingComments[0].value.trim();
13500
- }
13501
13596
  const options = {};
13502
- if (description) {
13503
- options.description = description;
13597
+ const leadingComment = node.leadingComments?.[node.leadingComments.length - 1];
13598
+ const trailingComment = node.trailingComments?.[0];
13599
+ const sourceComment = leadingComment ?? trailingComment;
13600
+ if (sourceComment) {
13601
+ Object.assign(options, parsePropertyComment(sourceComment.value));
13504
13602
  }
13505
13603
  if (contentType === "body") {
13506
13604
  options.nullable = isOptional;
@@ -14365,6 +14463,8 @@ var SchemaProcessor = class {
14365
14463
  // Track imports per file for resolving ReturnType<typeof func>
14366
14464
  importMap = {};
14367
14465
  // { filePath: { importName: importPath } }
14466
+ // Inverted index: typeName → first filePath that imports it (O(1) lookup for findFileImportingType)
14467
+ typeToFileIndex = /* @__PURE__ */ new Map();
14368
14468
  currentFilePath = "";
14369
14469
  // Track the file being processed
14370
14470
  constructor(schemaDir, schemaType = "typescript", schemaFiles, apiDir, fileAccess = defaultFileAccess2, runtime) {
@@ -14518,6 +14618,13 @@ var SchemaProcessor = class {
14518
14618
  }
14519
14619
  collectImports(ast, filePath) {
14520
14620
  collectImports(ast, filePath, this.importMap);
14621
+ const normalizedPath = path9.normalize(filePath);
14622
+ const entries = this.importMap[normalizedPath] ?? {};
14623
+ for (const typeName of Object.keys(entries)) {
14624
+ if (!this.typeToFileIndex.has(typeName)) {
14625
+ this.typeToFileIndex.set(typeName, normalizedPath);
14626
+ }
14627
+ }
14521
14628
  }
14522
14629
  /**
14523
14630
  * Resolve an import path relative to the current file
@@ -14556,6 +14663,15 @@ var SchemaProcessor = class {
14556
14663
  }
14557
14664
  const typeDefEntry = this.typeDefinitions[typeName.toString()];
14558
14665
  if (!typeDefEntry) {
14666
+ const contextFile = this.findFileImportingType(typeName);
14667
+ if (contextFile) {
14668
+ logger.debug(`resolveType: "${typeName}" not in schema dirs; attempting TypeScript checker fallback via ${contextFile}`);
14669
+ const checkerSchema = this.resolveTypeWithTypeScriptChecker(typeName, contextFile);
14670
+ if (checkerSchema && Object.keys(checkerSchema).length > 0) {
14671
+ this.openapiDefinitions[typeName] = checkerSchema;
14672
+ return checkerSchema;
14673
+ }
14674
+ }
14559
14675
  logger.debug(`resolveType: no TypeScript definition found for "${typeName}" in ${this.currentFilePath}; returning empty schema`);
14560
14676
  return {};
14561
14677
  }
@@ -14767,6 +14883,14 @@ var SchemaProcessor = class {
14767
14883
  }
14768
14884
  return null;
14769
14885
  }
14886
+ /**
14887
+ * Return the path of the first scanned file that imports `typeName`, or `null` when none is
14888
+ * found. Used as a fallback context for {@link resolveTypeWithTypeScriptChecker} when the type
14889
+ * is not defined in any schema-dir file (e.g. comes from node_modules or a shared package).
14890
+ */
14891
+ findFileImportingType(typeName) {
14892
+ return this.typeToFileIndex.get(typeName) ?? null;
14893
+ }
14770
14894
  shouldUseTypeScriptChecker(node) {
14771
14895
  return t15.isTSConditionalType(node) || t15.isTSMappedType(node) || t15.isTSTemplateLiteralType(node) || t15.isTSImportType(node) || t15.isTSTypeOperator(node) && node.operator === "keyof";
14772
14896
  }
@@ -14830,7 +14954,9 @@ var SchemaProcessor = class {
14830
14954
  if (seen.has(seenKey)) {
14831
14955
  return { type: "object" };
14832
14956
  }
14833
- seen.add(seenKey);
14957
+ if (!(type.flags & (primitiveLikeFlags | ts2.TypeFlags.Any | ts2.TypeFlags.Never | ts2.TypeFlags.Unknown | ts2.TypeFlags.Void))) {
14958
+ seen.add(seenKey);
14959
+ }
14834
14960
  if (type.isStringLiteral()) {
14835
14961
  return { type: "string", enum: [type.value] };
14836
14962
  }
@@ -3737,7 +3737,7 @@ var SymbolResolver = class {
3737
3737
 
3738
3738
  // ../openapi-core/dist/schema/zod/drizzle-zod-processor.js
3739
3739
  import * as t5 from "@babel/types";
3740
- var DrizzleZodProcessor = class {
3740
+ var DrizzleZodProcessor = class _DrizzleZodProcessor {
3741
3741
  /**
3742
3742
  * Known drizzle-zod helper function names
3743
3743
  */
@@ -4163,6 +4163,16 @@ var DrizzleZodProcessor = class {
4163
4163
  result.description = args[0].value;
4164
4164
  }
4165
4165
  break;
4166
+ case "meta": {
4167
+ const firstArg = args[0];
4168
+ if (firstArg && !t5.isSpreadElement(firstArg) && !t5.isArgumentPlaceholder(firstArg)) {
4169
+ const metadata = _DrizzleZodProcessor.extractStaticObject(firstArg);
4170
+ if (metadata) {
4171
+ Object.assign(result, metadata);
4172
+ }
4173
+ }
4174
+ break;
4175
+ }
4166
4176
  case "default":
4167
4177
  if (args.length > 0) {
4168
4178
  if (t5.isStringLiteral(args[0])) {
@@ -4183,6 +4193,49 @@ var DrizzleZodProcessor = class {
4183
4193
  static isDrizzleZodHelper(name) {
4184
4194
  return this.DRIZZLE_ZOD_HELPERS.includes(name);
4185
4195
  }
4196
+ static extractStaticObject(node) {
4197
+ if (!t5.isObjectExpression(node))
4198
+ return null;
4199
+ const out = {};
4200
+ for (const prop of node.properties) {
4201
+ if (!t5.isObjectProperty(prop))
4202
+ return null;
4203
+ const key = t5.isIdentifier(prop.key) ? prop.key.name : t5.isStringLiteral(prop.key) ? prop.key.value : null;
4204
+ if (!key)
4205
+ return null;
4206
+ const val = _DrizzleZodProcessor.extractStaticValue(prop.value);
4207
+ if (typeof val === "undefined")
4208
+ return null;
4209
+ out[key] = val;
4210
+ }
4211
+ return out;
4212
+ }
4213
+ static extractStaticValue(node) {
4214
+ if (t5.isStringLiteral(node))
4215
+ return node.value;
4216
+ if (t5.isNumericLiteral(node))
4217
+ return node.value;
4218
+ if (t5.isBooleanLiteral(node))
4219
+ return node.value;
4220
+ if (t5.isNullLiteral(node))
4221
+ return null;
4222
+ if (t5.isArrayExpression(node)) {
4223
+ const values = [];
4224
+ for (const el of node.elements) {
4225
+ if (!el || t5.isSpreadElement(el) || t5.isArgumentPlaceholder(el))
4226
+ return void 0;
4227
+ const v = _DrizzleZodProcessor.extractStaticValue(el);
4228
+ if (typeof v === "undefined")
4229
+ return void 0;
4230
+ values.push(v);
4231
+ }
4232
+ return values;
4233
+ }
4234
+ if (t5.isObjectExpression(node)) {
4235
+ return _DrizzleZodProcessor.extractStaticObject(node);
4236
+ }
4237
+ return void 0;
4238
+ }
4186
4239
  };
4187
4240
 
4188
4241
  // ../openapi-core/dist/schema/zod/converter-runtime.js
@@ -5153,8 +5206,12 @@ function processZodPrimitiveNode(node, context) {
5153
5206
  };
5154
5207
  break;
5155
5208
  }
5209
+ case "strictObject":
5156
5210
  case "object":
5157
5211
  schema = node.arguments.length > 0 ? context.processObject(node) : { type: "object" };
5212
+ if (zodType === "strictObject") {
5213
+ schema.additionalProperties = false;
5214
+ }
5158
5215
  break;
5159
5216
  case "templateLiteral":
5160
5217
  schema = { type: "string" };
@@ -12221,6 +12278,10 @@ var ZodRuntimeExporter = class {
12221
12278
  return this.buildEnum(node);
12222
12279
  case "array":
12223
12280
  return node.arguments[0] && isProcessableNode(node.arguments[0]) ? array(this.buildSchema(node.arguments[0]) ?? unknown()) : array(unknown());
12281
+ case "strictObject": {
12282
+ const base = this.buildObject(node);
12283
+ return base && typeof base.strict === "function" ? base.strict() : base;
12284
+ }
12224
12285
  case "object":
12225
12286
  return this.buildObject(node);
12226
12287
  case "record":
@@ -12628,7 +12689,9 @@ var ZodSchemaConverter = class {
12628
12689
  apiDir;
12629
12690
  zodSchemas = {};
12630
12691
  processingSchemas = /* @__PURE__ */ new Set();
12631
- processedModules = /* @__PURE__ */ new Set();
12692
+ /** Memoization guard for processFileForZodSchema. Keys: `${filePath}|${schemaName}`.
12693
+ * Prevents infinite recursion when re-export files reference schemas via z.infer<typeof X>. */
12694
+ processedFileSchemaPairs = /* @__PURE__ */ new Set();
12632
12695
  typeToSchemaMapping = {};
12633
12696
  drizzleZodImports = /* @__PURE__ */ new Set();
12634
12697
  factoryCache = /* @__PURE__ */ new Map();
@@ -12842,6 +12905,11 @@ var ZodSchemaConverter = class {
12842
12905
  * Process a file to find Zod schema definitions
12843
12906
  */
12844
12907
  processFileForZodSchema(filePath, schemaName) {
12908
+ const visitKey = `${filePath}|${schemaName}|${this.currentContentType}`;
12909
+ if (this.processedFileSchemaPairs.has(visitKey)) {
12910
+ return;
12911
+ }
12912
+ this.processedFileSchemaPairs.add(visitKey);
12845
12913
  try {
12846
12914
  const content = this.fileAccess.readFileSync(filePath, "utf-8");
12847
12915
  if (!content.includes(schemaName)) {
@@ -13155,7 +13223,9 @@ var ZodSchemaConverter = class {
13155
13223
  const param = path19.node.typeAnnotation.typeParameters.params[0];
13156
13224
  if (t11.isTSTypeQuery(param) && t11.isIdentifier(param.exprName)) {
13157
13225
  const referencedSchemaName = param.exprName.name;
13158
- this.processFileForZodSchema(filePath, referencedSchemaName);
13226
+ if (!this.getStoredSchema(referencedSchemaName)) {
13227
+ this.processFileForZodSchema(filePath, referencedSchemaName);
13228
+ }
13159
13229
  }
13160
13230
  }
13161
13231
  }
@@ -13240,8 +13310,12 @@ var ZodSchemaConverter = class {
13240
13310
  }
13241
13311
  if (t11.isCallExpression(node) && t11.isMemberExpression(node.callee) && t11.isIdentifier(node.callee.object) && this.isZodLocalName(node.callee.object.name) && t11.isIdentifier(node.callee.property)) {
13242
13312
  const methodName = node.callee.property.name;
13243
- if (methodName === "object" && node.arguments.length > 0) {
13244
- return this.processZodObject(node);
13313
+ if ((methodName === "object" || methodName === "strictObject") && node.arguments.length > 0) {
13314
+ const schema = this.processZodObject(node);
13315
+ if (methodName === "strictObject") {
13316
+ schema.additionalProperties = false;
13317
+ }
13318
+ return schema;
13245
13319
  } else if (methodName === "union" && node.arguments.length > 0) {
13246
13320
  return this.processZodUnion(node);
13247
13321
  } else if (methodName === "intersection" && node.arguments.length > 0) {
@@ -14526,15 +14600,39 @@ function extractKeysFromLiteralType(node) {
14526
14600
  }
14527
14601
  return [];
14528
14602
  }
14603
+ function parsePropertyComment(commentValue) {
14604
+ const text = commentValue.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0).join(" ").trim();
14605
+ const result = {};
14606
+ let remaining = text;
14607
+ const formatMatch = remaining.match(/@format\s+(\S+)/);
14608
+ if (formatMatch?.[1]) {
14609
+ result.format = formatMatch[1];
14610
+ remaining = remaining.replace(formatMatch[0], "").trim();
14611
+ }
14612
+ const exampleMatch = remaining.match(/@example\s+(.+?)(?=\s*@\w|$)/);
14613
+ if (exampleMatch?.[1]) {
14614
+ const raw = exampleMatch[1].trim();
14615
+ try {
14616
+ result.example = JSON.parse(raw);
14617
+ } catch {
14618
+ result.example = raw;
14619
+ }
14620
+ remaining = remaining.replace(exampleMatch[0], "").trim();
14621
+ }
14622
+ remaining = remaining.replace(/@\w+(?:\s+\S+)*/g, "").trim();
14623
+ if (remaining) {
14624
+ result.description = remaining;
14625
+ }
14626
+ return result;
14627
+ }
14529
14628
  function getPropertyOptions(node, contentType) {
14530
14629
  const isOptional = !!node.optional;
14531
- let description = null;
14532
- if (node.trailingComments && node.trailingComments.length) {
14533
- description = node.trailingComments[0].value.trim();
14534
- }
14535
14630
  const options = {};
14536
- if (description) {
14537
- options.description = description;
14631
+ const leadingComment = node.leadingComments?.[node.leadingComments.length - 1];
14632
+ const trailingComment = node.trailingComments?.[0];
14633
+ const sourceComment = leadingComment ?? trailingComment;
14634
+ if (sourceComment) {
14635
+ Object.assign(options, parsePropertyComment(sourceComment.value));
14538
14636
  }
14539
14637
  if (contentType === "body") {
14540
14638
  options.nullable = isOptional;
@@ -15399,6 +15497,8 @@ var SchemaProcessor = class {
15399
15497
  // Track imports per file for resolving ReturnType<typeof func>
15400
15498
  importMap = {};
15401
15499
  // { filePath: { importName: importPath } }
15500
+ // Inverted index: typeName → first filePath that imports it (O(1) lookup for findFileImportingType)
15501
+ typeToFileIndex = /* @__PURE__ */ new Map();
15402
15502
  currentFilePath = "";
15403
15503
  // Track the file being processed
15404
15504
  constructor(schemaDir, schemaType = "typescript", schemaFiles, apiDir, fileAccess = defaultFileAccess2, runtime) {
@@ -15552,6 +15652,13 @@ var SchemaProcessor = class {
15552
15652
  }
15553
15653
  collectImports(ast, filePath) {
15554
15654
  collectImports(ast, filePath, this.importMap);
15655
+ const normalizedPath = path12.normalize(filePath);
15656
+ const entries = this.importMap[normalizedPath] ?? {};
15657
+ for (const typeName of Object.keys(entries)) {
15658
+ if (!this.typeToFileIndex.has(typeName)) {
15659
+ this.typeToFileIndex.set(typeName, normalizedPath);
15660
+ }
15661
+ }
15555
15662
  }
15556
15663
  /**
15557
15664
  * Resolve an import path relative to the current file
@@ -15590,6 +15697,15 @@ var SchemaProcessor = class {
15590
15697
  }
15591
15698
  const typeDefEntry = this.typeDefinitions[typeName.toString()];
15592
15699
  if (!typeDefEntry) {
15700
+ const contextFile = this.findFileImportingType(typeName);
15701
+ if (contextFile) {
15702
+ logger.debug(`resolveType: "${typeName}" not in schema dirs; attempting TypeScript checker fallback via ${contextFile}`);
15703
+ const checkerSchema = this.resolveTypeWithTypeScriptChecker(typeName, contextFile);
15704
+ if (checkerSchema && Object.keys(checkerSchema).length > 0) {
15705
+ this.openapiDefinitions[typeName] = checkerSchema;
15706
+ return checkerSchema;
15707
+ }
15708
+ }
15593
15709
  logger.debug(`resolveType: no TypeScript definition found for "${typeName}" in ${this.currentFilePath}; returning empty schema`);
15594
15710
  return {};
15595
15711
  }
@@ -15801,6 +15917,14 @@ var SchemaProcessor = class {
15801
15917
  }
15802
15918
  return null;
15803
15919
  }
15920
+ /**
15921
+ * Return the path of the first scanned file that imports `typeName`, or `null` when none is
15922
+ * found. Used as a fallback context for {@link resolveTypeWithTypeScriptChecker} when the type
15923
+ * is not defined in any schema-dir file (e.g. comes from node_modules or a shared package).
15924
+ */
15925
+ findFileImportingType(typeName) {
15926
+ return this.typeToFileIndex.get(typeName) ?? null;
15927
+ }
15804
15928
  shouldUseTypeScriptChecker(node) {
15805
15929
  return t16.isTSConditionalType(node) || t16.isTSMappedType(node) || t16.isTSTemplateLiteralType(node) || t16.isTSImportType(node) || t16.isTSTypeOperator(node) && node.operator === "keyof";
15806
15930
  }
@@ -15864,7 +15988,9 @@ var SchemaProcessor = class {
15864
15988
  if (seen.has(seenKey)) {
15865
15989
  return { type: "object" };
15866
15990
  }
15867
- seen.add(seenKey);
15991
+ if (!(type.flags & (primitiveLikeFlags | ts3.TypeFlags.Any | ts3.TypeFlags.Never | ts3.TypeFlags.Unknown | ts3.TypeFlags.Void))) {
15992
+ seen.add(seenKey);
15993
+ }
15868
15994
  if (type.isStringLiteral()) {
15869
15995
  return { type: "string", enum: [type.value] };
15870
15996
  }
@@ -2741,7 +2741,7 @@ var SymbolResolver = class {
2741
2741
 
2742
2742
  // ../openapi-core/dist/schema/zod/drizzle-zod-processor.js
2743
2743
  import * as t4 from "@babel/types";
2744
- var DrizzleZodProcessor = class {
2744
+ var DrizzleZodProcessor = class _DrizzleZodProcessor {
2745
2745
  /**
2746
2746
  * Known drizzle-zod helper function names
2747
2747
  */
@@ -3167,6 +3167,16 @@ var DrizzleZodProcessor = class {
3167
3167
  result.description = args[0].value;
3168
3168
  }
3169
3169
  break;
3170
+ case "meta": {
3171
+ const firstArg = args[0];
3172
+ if (firstArg && !t4.isSpreadElement(firstArg) && !t4.isArgumentPlaceholder(firstArg)) {
3173
+ const metadata = _DrizzleZodProcessor.extractStaticObject(firstArg);
3174
+ if (metadata) {
3175
+ Object.assign(result, metadata);
3176
+ }
3177
+ }
3178
+ break;
3179
+ }
3170
3180
  case "default":
3171
3181
  if (args.length > 0) {
3172
3182
  if (t4.isStringLiteral(args[0])) {
@@ -3187,6 +3197,49 @@ var DrizzleZodProcessor = class {
3187
3197
  static isDrizzleZodHelper(name) {
3188
3198
  return this.DRIZZLE_ZOD_HELPERS.includes(name);
3189
3199
  }
3200
+ static extractStaticObject(node) {
3201
+ if (!t4.isObjectExpression(node))
3202
+ return null;
3203
+ const out = {};
3204
+ for (const prop of node.properties) {
3205
+ if (!t4.isObjectProperty(prop))
3206
+ return null;
3207
+ const key = t4.isIdentifier(prop.key) ? prop.key.name : t4.isStringLiteral(prop.key) ? prop.key.value : null;
3208
+ if (!key)
3209
+ return null;
3210
+ const val = _DrizzleZodProcessor.extractStaticValue(prop.value);
3211
+ if (typeof val === "undefined")
3212
+ return null;
3213
+ out[key] = val;
3214
+ }
3215
+ return out;
3216
+ }
3217
+ static extractStaticValue(node) {
3218
+ if (t4.isStringLiteral(node))
3219
+ return node.value;
3220
+ if (t4.isNumericLiteral(node))
3221
+ return node.value;
3222
+ if (t4.isBooleanLiteral(node))
3223
+ return node.value;
3224
+ if (t4.isNullLiteral(node))
3225
+ return null;
3226
+ if (t4.isArrayExpression(node)) {
3227
+ const values = [];
3228
+ for (const el of node.elements) {
3229
+ if (!el || t4.isSpreadElement(el) || t4.isArgumentPlaceholder(el))
3230
+ return void 0;
3231
+ const v = _DrizzleZodProcessor.extractStaticValue(el);
3232
+ if (typeof v === "undefined")
3233
+ return void 0;
3234
+ values.push(v);
3235
+ }
3236
+ return values;
3237
+ }
3238
+ if (t4.isObjectExpression(node)) {
3239
+ return _DrizzleZodProcessor.extractStaticObject(node);
3240
+ }
3241
+ return void 0;
3242
+ }
3190
3243
  };
3191
3244
 
3192
3245
  // ../openapi-core/dist/schema/zod/converter-runtime.js
@@ -4157,8 +4210,12 @@ function processZodPrimitiveNode(node, context) {
4157
4210
  };
4158
4211
  break;
4159
4212
  }
4213
+ case "strictObject":
4160
4214
  case "object":
4161
4215
  schema = node.arguments.length > 0 ? context.processObject(node) : { type: "object" };
4216
+ if (zodType === "strictObject") {
4217
+ schema.additionalProperties = false;
4218
+ }
4162
4219
  break;
4163
4220
  case "templateLiteral":
4164
4221
  schema = { type: "string" };
@@ -11225,6 +11282,10 @@ var ZodRuntimeExporter = class {
11225
11282
  return this.buildEnum(node);
11226
11283
  case "array":
11227
11284
  return node.arguments[0] && isProcessableNode(node.arguments[0]) ? array(this.buildSchema(node.arguments[0]) ?? unknown()) : array(unknown());
11285
+ case "strictObject": {
11286
+ const base = this.buildObject(node);
11287
+ return base && typeof base.strict === "function" ? base.strict() : base;
11288
+ }
11228
11289
  case "object":
11229
11290
  return this.buildObject(node);
11230
11291
  case "record":
@@ -11632,7 +11693,9 @@ var ZodSchemaConverter = class {
11632
11693
  apiDir;
11633
11694
  zodSchemas = {};
11634
11695
  processingSchemas = /* @__PURE__ */ new Set();
11635
- processedModules = /* @__PURE__ */ new Set();
11696
+ /** Memoization guard for processFileForZodSchema. Keys: `${filePath}|${schemaName}`.
11697
+ * Prevents infinite recursion when re-export files reference schemas via z.infer<typeof X>. */
11698
+ processedFileSchemaPairs = /* @__PURE__ */ new Set();
11636
11699
  typeToSchemaMapping = {};
11637
11700
  drizzleZodImports = /* @__PURE__ */ new Set();
11638
11701
  factoryCache = /* @__PURE__ */ new Map();
@@ -11846,6 +11909,11 @@ var ZodSchemaConverter = class {
11846
11909
  * Process a file to find Zod schema definitions
11847
11910
  */
11848
11911
  processFileForZodSchema(filePath, schemaName) {
11912
+ const visitKey = `${filePath}|${schemaName}|${this.currentContentType}`;
11913
+ if (this.processedFileSchemaPairs.has(visitKey)) {
11914
+ return;
11915
+ }
11916
+ this.processedFileSchemaPairs.add(visitKey);
11849
11917
  try {
11850
11918
  const content = this.fileAccess.readFileSync(filePath, "utf-8");
11851
11919
  if (!content.includes(schemaName)) {
@@ -12159,7 +12227,9 @@ var ZodSchemaConverter = class {
12159
12227
  const param = path17.node.typeAnnotation.typeParameters.params[0];
12160
12228
  if (t10.isTSTypeQuery(param) && t10.isIdentifier(param.exprName)) {
12161
12229
  const referencedSchemaName = param.exprName.name;
12162
- this.processFileForZodSchema(filePath, referencedSchemaName);
12230
+ if (!this.getStoredSchema(referencedSchemaName)) {
12231
+ this.processFileForZodSchema(filePath, referencedSchemaName);
12232
+ }
12163
12233
  }
12164
12234
  }
12165
12235
  }
@@ -12244,8 +12314,12 @@ var ZodSchemaConverter = class {
12244
12314
  }
12245
12315
  if (t10.isCallExpression(node) && t10.isMemberExpression(node.callee) && t10.isIdentifier(node.callee.object) && this.isZodLocalName(node.callee.object.name) && t10.isIdentifier(node.callee.property)) {
12246
12316
  const methodName = node.callee.property.name;
12247
- if (methodName === "object" && node.arguments.length > 0) {
12248
- return this.processZodObject(node);
12317
+ if ((methodName === "object" || methodName === "strictObject") && node.arguments.length > 0) {
12318
+ const schema = this.processZodObject(node);
12319
+ if (methodName === "strictObject") {
12320
+ schema.additionalProperties = false;
12321
+ }
12322
+ return schema;
12249
12323
  } else if (methodName === "union" && node.arguments.length > 0) {
12250
12324
  return this.processZodUnion(node);
12251
12325
  } else if (methodName === "intersection" && node.arguments.length > 0) {
@@ -13530,15 +13604,39 @@ function extractKeysFromLiteralType(node) {
13530
13604
  }
13531
13605
  return [];
13532
13606
  }
13607
+ function parsePropertyComment(commentValue) {
13608
+ const text = commentValue.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0).join(" ").trim();
13609
+ const result = {};
13610
+ let remaining = text;
13611
+ const formatMatch = remaining.match(/@format\s+(\S+)/);
13612
+ if (formatMatch?.[1]) {
13613
+ result.format = formatMatch[1];
13614
+ remaining = remaining.replace(formatMatch[0], "").trim();
13615
+ }
13616
+ const exampleMatch = remaining.match(/@example\s+(.+?)(?=\s*@\w|$)/);
13617
+ if (exampleMatch?.[1]) {
13618
+ const raw = exampleMatch[1].trim();
13619
+ try {
13620
+ result.example = JSON.parse(raw);
13621
+ } catch {
13622
+ result.example = raw;
13623
+ }
13624
+ remaining = remaining.replace(exampleMatch[0], "").trim();
13625
+ }
13626
+ remaining = remaining.replace(/@\w+(?:\s+\S+)*/g, "").trim();
13627
+ if (remaining) {
13628
+ result.description = remaining;
13629
+ }
13630
+ return result;
13631
+ }
13533
13632
  function getPropertyOptions(node, contentType) {
13534
13633
  const isOptional = !!node.optional;
13535
- let description = null;
13536
- if (node.trailingComments && node.trailingComments.length) {
13537
- description = node.trailingComments[0].value.trim();
13538
- }
13539
13634
  const options = {};
13540
- if (description) {
13541
- options.description = description;
13635
+ const leadingComment = node.leadingComments?.[node.leadingComments.length - 1];
13636
+ const trailingComment = node.trailingComments?.[0];
13637
+ const sourceComment = leadingComment ?? trailingComment;
13638
+ if (sourceComment) {
13639
+ Object.assign(options, parsePropertyComment(sourceComment.value));
13542
13640
  }
13543
13641
  if (contentType === "body") {
13544
13642
  options.nullable = isOptional;
@@ -14403,6 +14501,8 @@ var SchemaProcessor = class {
14403
14501
  // Track imports per file for resolving ReturnType<typeof func>
14404
14502
  importMap = {};
14405
14503
  // { filePath: { importName: importPath } }
14504
+ // Inverted index: typeName → first filePath that imports it (O(1) lookup for findFileImportingType)
14505
+ typeToFileIndex = /* @__PURE__ */ new Map();
14406
14506
  currentFilePath = "";
14407
14507
  // Track the file being processed
14408
14508
  constructor(schemaDir, schemaType = "typescript", schemaFiles, apiDir, fileAccess = defaultFileAccess2, runtime) {
@@ -14556,6 +14656,13 @@ var SchemaProcessor = class {
14556
14656
  }
14557
14657
  collectImports(ast, filePath) {
14558
14658
  collectImports(ast, filePath, this.importMap);
14659
+ const normalizedPath = path9.normalize(filePath);
14660
+ const entries = this.importMap[normalizedPath] ?? {};
14661
+ for (const typeName of Object.keys(entries)) {
14662
+ if (!this.typeToFileIndex.has(typeName)) {
14663
+ this.typeToFileIndex.set(typeName, normalizedPath);
14664
+ }
14665
+ }
14559
14666
  }
14560
14667
  /**
14561
14668
  * Resolve an import path relative to the current file
@@ -14594,6 +14701,15 @@ var SchemaProcessor = class {
14594
14701
  }
14595
14702
  const typeDefEntry = this.typeDefinitions[typeName.toString()];
14596
14703
  if (!typeDefEntry) {
14704
+ const contextFile = this.findFileImportingType(typeName);
14705
+ if (contextFile) {
14706
+ logger.debug(`resolveType: "${typeName}" not in schema dirs; attempting TypeScript checker fallback via ${contextFile}`);
14707
+ const checkerSchema = this.resolveTypeWithTypeScriptChecker(typeName, contextFile);
14708
+ if (checkerSchema && Object.keys(checkerSchema).length > 0) {
14709
+ this.openapiDefinitions[typeName] = checkerSchema;
14710
+ return checkerSchema;
14711
+ }
14712
+ }
14597
14713
  logger.debug(`resolveType: no TypeScript definition found for "${typeName}" in ${this.currentFilePath}; returning empty schema`);
14598
14714
  return {};
14599
14715
  }
@@ -14805,6 +14921,14 @@ var SchemaProcessor = class {
14805
14921
  }
14806
14922
  return null;
14807
14923
  }
14924
+ /**
14925
+ * Return the path of the first scanned file that imports `typeName`, or `null` when none is
14926
+ * found. Used as a fallback context for {@link resolveTypeWithTypeScriptChecker} when the type
14927
+ * is not defined in any schema-dir file (e.g. comes from node_modules or a shared package).
14928
+ */
14929
+ findFileImportingType(typeName) {
14930
+ return this.typeToFileIndex.get(typeName) ?? null;
14931
+ }
14808
14932
  shouldUseTypeScriptChecker(node) {
14809
14933
  return t15.isTSConditionalType(node) || t15.isTSMappedType(node) || t15.isTSTemplateLiteralType(node) || t15.isTSImportType(node) || t15.isTSTypeOperator(node) && node.operator === "keyof";
14810
14934
  }
@@ -14868,7 +14992,9 @@ var SchemaProcessor = class {
14868
14992
  if (seen.has(seenKey)) {
14869
14993
  return { type: "object" };
14870
14994
  }
14871
- seen.add(seenKey);
14995
+ if (!(type.flags & (primitiveLikeFlags | ts2.TypeFlags.Any | ts2.TypeFlags.Never | ts2.TypeFlags.Unknown | ts2.TypeFlags.Void))) {
14996
+ seen.add(seenKey);
14997
+ }
14872
14998
  if (type.isStringLiteral()) {
14873
14999
  return { type: "string", enum: [type.value] };
14874
15000
  }
@@ -2741,7 +2741,7 @@ var SymbolResolver = class {
2741
2741
 
2742
2742
  // ../openapi-core/dist/schema/zod/drizzle-zod-processor.js
2743
2743
  import * as t4 from "@babel/types";
2744
- var DrizzleZodProcessor = class {
2744
+ var DrizzleZodProcessor = class _DrizzleZodProcessor {
2745
2745
  /**
2746
2746
  * Known drizzle-zod helper function names
2747
2747
  */
@@ -3167,6 +3167,16 @@ var DrizzleZodProcessor = class {
3167
3167
  result.description = args[0].value;
3168
3168
  }
3169
3169
  break;
3170
+ case "meta": {
3171
+ const firstArg = args[0];
3172
+ if (firstArg && !t4.isSpreadElement(firstArg) && !t4.isArgumentPlaceholder(firstArg)) {
3173
+ const metadata = _DrizzleZodProcessor.extractStaticObject(firstArg);
3174
+ if (metadata) {
3175
+ Object.assign(result, metadata);
3176
+ }
3177
+ }
3178
+ break;
3179
+ }
3170
3180
  case "default":
3171
3181
  if (args.length > 0) {
3172
3182
  if (t4.isStringLiteral(args[0])) {
@@ -3187,6 +3197,49 @@ var DrizzleZodProcessor = class {
3187
3197
  static isDrizzleZodHelper(name) {
3188
3198
  return this.DRIZZLE_ZOD_HELPERS.includes(name);
3189
3199
  }
3200
+ static extractStaticObject(node) {
3201
+ if (!t4.isObjectExpression(node))
3202
+ return null;
3203
+ const out = {};
3204
+ for (const prop of node.properties) {
3205
+ if (!t4.isObjectProperty(prop))
3206
+ return null;
3207
+ const key = t4.isIdentifier(prop.key) ? prop.key.name : t4.isStringLiteral(prop.key) ? prop.key.value : null;
3208
+ if (!key)
3209
+ return null;
3210
+ const val = _DrizzleZodProcessor.extractStaticValue(prop.value);
3211
+ if (typeof val === "undefined")
3212
+ return null;
3213
+ out[key] = val;
3214
+ }
3215
+ return out;
3216
+ }
3217
+ static extractStaticValue(node) {
3218
+ if (t4.isStringLiteral(node))
3219
+ return node.value;
3220
+ if (t4.isNumericLiteral(node))
3221
+ return node.value;
3222
+ if (t4.isBooleanLiteral(node))
3223
+ return node.value;
3224
+ if (t4.isNullLiteral(node))
3225
+ return null;
3226
+ if (t4.isArrayExpression(node)) {
3227
+ const values = [];
3228
+ for (const el of node.elements) {
3229
+ if (!el || t4.isSpreadElement(el) || t4.isArgumentPlaceholder(el))
3230
+ return void 0;
3231
+ const v = _DrizzleZodProcessor.extractStaticValue(el);
3232
+ if (typeof v === "undefined")
3233
+ return void 0;
3234
+ values.push(v);
3235
+ }
3236
+ return values;
3237
+ }
3238
+ if (t4.isObjectExpression(node)) {
3239
+ return _DrizzleZodProcessor.extractStaticObject(node);
3240
+ }
3241
+ return void 0;
3242
+ }
3190
3243
  };
3191
3244
 
3192
3245
  // ../openapi-core/dist/schema/zod/converter-runtime.js
@@ -4157,8 +4210,12 @@ function processZodPrimitiveNode(node, context) {
4157
4210
  };
4158
4211
  break;
4159
4212
  }
4213
+ case "strictObject":
4160
4214
  case "object":
4161
4215
  schema = node.arguments.length > 0 ? context.processObject(node) : { type: "object" };
4216
+ if (zodType === "strictObject") {
4217
+ schema.additionalProperties = false;
4218
+ }
4162
4219
  break;
4163
4220
  case "templateLiteral":
4164
4221
  schema = { type: "string" };
@@ -11225,6 +11282,10 @@ var ZodRuntimeExporter = class {
11225
11282
  return this.buildEnum(node);
11226
11283
  case "array":
11227
11284
  return node.arguments[0] && isProcessableNode(node.arguments[0]) ? array(this.buildSchema(node.arguments[0]) ?? unknown()) : array(unknown());
11285
+ case "strictObject": {
11286
+ const base = this.buildObject(node);
11287
+ return base && typeof base.strict === "function" ? base.strict() : base;
11288
+ }
11228
11289
  case "object":
11229
11290
  return this.buildObject(node);
11230
11291
  case "record":
@@ -11632,7 +11693,9 @@ var ZodSchemaConverter = class {
11632
11693
  apiDir;
11633
11694
  zodSchemas = {};
11634
11695
  processingSchemas = /* @__PURE__ */ new Set();
11635
- processedModules = /* @__PURE__ */ new Set();
11696
+ /** Memoization guard for processFileForZodSchema. Keys: `${filePath}|${schemaName}`.
11697
+ * Prevents infinite recursion when re-export files reference schemas via z.infer<typeof X>. */
11698
+ processedFileSchemaPairs = /* @__PURE__ */ new Set();
11636
11699
  typeToSchemaMapping = {};
11637
11700
  drizzleZodImports = /* @__PURE__ */ new Set();
11638
11701
  factoryCache = /* @__PURE__ */ new Map();
@@ -11846,6 +11909,11 @@ var ZodSchemaConverter = class {
11846
11909
  * Process a file to find Zod schema definitions
11847
11910
  */
11848
11911
  processFileForZodSchema(filePath, schemaName) {
11912
+ const visitKey = `${filePath}|${schemaName}|${this.currentContentType}`;
11913
+ if (this.processedFileSchemaPairs.has(visitKey)) {
11914
+ return;
11915
+ }
11916
+ this.processedFileSchemaPairs.add(visitKey);
11849
11917
  try {
11850
11918
  const content = this.fileAccess.readFileSync(filePath, "utf-8");
11851
11919
  if (!content.includes(schemaName)) {
@@ -12159,7 +12227,9 @@ var ZodSchemaConverter = class {
12159
12227
  const param = path17.node.typeAnnotation.typeParameters.params[0];
12160
12228
  if (t10.isTSTypeQuery(param) && t10.isIdentifier(param.exprName)) {
12161
12229
  const referencedSchemaName = param.exprName.name;
12162
- this.processFileForZodSchema(filePath, referencedSchemaName);
12230
+ if (!this.getStoredSchema(referencedSchemaName)) {
12231
+ this.processFileForZodSchema(filePath, referencedSchemaName);
12232
+ }
12163
12233
  }
12164
12234
  }
12165
12235
  }
@@ -12244,8 +12314,12 @@ var ZodSchemaConverter = class {
12244
12314
  }
12245
12315
  if (t10.isCallExpression(node) && t10.isMemberExpression(node.callee) && t10.isIdentifier(node.callee.object) && this.isZodLocalName(node.callee.object.name) && t10.isIdentifier(node.callee.property)) {
12246
12316
  const methodName = node.callee.property.name;
12247
- if (methodName === "object" && node.arguments.length > 0) {
12248
- return this.processZodObject(node);
12317
+ if ((methodName === "object" || methodName === "strictObject") && node.arguments.length > 0) {
12318
+ const schema = this.processZodObject(node);
12319
+ if (methodName === "strictObject") {
12320
+ schema.additionalProperties = false;
12321
+ }
12322
+ return schema;
12249
12323
  } else if (methodName === "union" && node.arguments.length > 0) {
12250
12324
  return this.processZodUnion(node);
12251
12325
  } else if (methodName === "intersection" && node.arguments.length > 0) {
@@ -13530,15 +13604,39 @@ function extractKeysFromLiteralType(node) {
13530
13604
  }
13531
13605
  return [];
13532
13606
  }
13607
+ function parsePropertyComment(commentValue) {
13608
+ const text = commentValue.split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter((line) => line.length > 0).join(" ").trim();
13609
+ const result = {};
13610
+ let remaining = text;
13611
+ const formatMatch = remaining.match(/@format\s+(\S+)/);
13612
+ if (formatMatch?.[1]) {
13613
+ result.format = formatMatch[1];
13614
+ remaining = remaining.replace(formatMatch[0], "").trim();
13615
+ }
13616
+ const exampleMatch = remaining.match(/@example\s+(.+?)(?=\s*@\w|$)/);
13617
+ if (exampleMatch?.[1]) {
13618
+ const raw = exampleMatch[1].trim();
13619
+ try {
13620
+ result.example = JSON.parse(raw);
13621
+ } catch {
13622
+ result.example = raw;
13623
+ }
13624
+ remaining = remaining.replace(exampleMatch[0], "").trim();
13625
+ }
13626
+ remaining = remaining.replace(/@\w+(?:\s+\S+)*/g, "").trim();
13627
+ if (remaining) {
13628
+ result.description = remaining;
13629
+ }
13630
+ return result;
13631
+ }
13533
13632
  function getPropertyOptions(node, contentType) {
13534
13633
  const isOptional = !!node.optional;
13535
- let description = null;
13536
- if (node.trailingComments && node.trailingComments.length) {
13537
- description = node.trailingComments[0].value.trim();
13538
- }
13539
13634
  const options = {};
13540
- if (description) {
13541
- options.description = description;
13635
+ const leadingComment = node.leadingComments?.[node.leadingComments.length - 1];
13636
+ const trailingComment = node.trailingComments?.[0];
13637
+ const sourceComment = leadingComment ?? trailingComment;
13638
+ if (sourceComment) {
13639
+ Object.assign(options, parsePropertyComment(sourceComment.value));
13542
13640
  }
13543
13641
  if (contentType === "body") {
13544
13642
  options.nullable = isOptional;
@@ -14403,6 +14501,8 @@ var SchemaProcessor = class {
14403
14501
  // Track imports per file for resolving ReturnType<typeof func>
14404
14502
  importMap = {};
14405
14503
  // { filePath: { importName: importPath } }
14504
+ // Inverted index: typeName → first filePath that imports it (O(1) lookup for findFileImportingType)
14505
+ typeToFileIndex = /* @__PURE__ */ new Map();
14406
14506
  currentFilePath = "";
14407
14507
  // Track the file being processed
14408
14508
  constructor(schemaDir, schemaType = "typescript", schemaFiles, apiDir, fileAccess = defaultFileAccess2, runtime) {
@@ -14556,6 +14656,13 @@ var SchemaProcessor = class {
14556
14656
  }
14557
14657
  collectImports(ast, filePath) {
14558
14658
  collectImports(ast, filePath, this.importMap);
14659
+ const normalizedPath = path9.normalize(filePath);
14660
+ const entries = this.importMap[normalizedPath] ?? {};
14661
+ for (const typeName of Object.keys(entries)) {
14662
+ if (!this.typeToFileIndex.has(typeName)) {
14663
+ this.typeToFileIndex.set(typeName, normalizedPath);
14664
+ }
14665
+ }
14559
14666
  }
14560
14667
  /**
14561
14668
  * Resolve an import path relative to the current file
@@ -14594,6 +14701,15 @@ var SchemaProcessor = class {
14594
14701
  }
14595
14702
  const typeDefEntry = this.typeDefinitions[typeName.toString()];
14596
14703
  if (!typeDefEntry) {
14704
+ const contextFile = this.findFileImportingType(typeName);
14705
+ if (contextFile) {
14706
+ logger.debug(`resolveType: "${typeName}" not in schema dirs; attempting TypeScript checker fallback via ${contextFile}`);
14707
+ const checkerSchema = this.resolveTypeWithTypeScriptChecker(typeName, contextFile);
14708
+ if (checkerSchema && Object.keys(checkerSchema).length > 0) {
14709
+ this.openapiDefinitions[typeName] = checkerSchema;
14710
+ return checkerSchema;
14711
+ }
14712
+ }
14597
14713
  logger.debug(`resolveType: no TypeScript definition found for "${typeName}" in ${this.currentFilePath}; returning empty schema`);
14598
14714
  return {};
14599
14715
  }
@@ -14805,6 +14921,14 @@ var SchemaProcessor = class {
14805
14921
  }
14806
14922
  return null;
14807
14923
  }
14924
+ /**
14925
+ * Return the path of the first scanned file that imports `typeName`, or `null` when none is
14926
+ * found. Used as a fallback context for {@link resolveTypeWithTypeScriptChecker} when the type
14927
+ * is not defined in any schema-dir file (e.g. comes from node_modules or a shared package).
14928
+ */
14929
+ findFileImportingType(typeName) {
14930
+ return this.typeToFileIndex.get(typeName) ?? null;
14931
+ }
14808
14932
  shouldUseTypeScriptChecker(node) {
14809
14933
  return t15.isTSConditionalType(node) || t15.isTSMappedType(node) || t15.isTSTemplateLiteralType(node) || t15.isTSImportType(node) || t15.isTSTypeOperator(node) && node.operator === "keyof";
14810
14934
  }
@@ -14868,7 +14992,9 @@ var SchemaProcessor = class {
14868
14992
  if (seen.has(seenKey)) {
14869
14993
  return { type: "object" };
14870
14994
  }
14871
- seen.add(seenKey);
14995
+ if (!(type.flags & (primitiveLikeFlags | ts2.TypeFlags.Any | ts2.TypeFlags.Never | ts2.TypeFlags.Unknown | ts2.TypeFlags.Void))) {
14996
+ seen.add(seenKey);
14997
+ }
14872
14998
  if (type.isStringLiteral()) {
14873
14999
  return { type: "string", enum: [type.value] };
14874
15000
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "next-openapi-gen",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "description": "Automatically generate OpenAPI 3.0, 3.1, and 3.2 documentation from Next.js projects, with support for Zod schemas, TypeScript types, and reusable OpenAPI fragments.",
5
5
  "keywords": [
6
6
  "api",