@cerios/openapi-to-zod 1.1.1 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs CHANGED
@@ -5241,14 +5241,17 @@ function escapeJSDoc(str) {
5241
5241
  function wrapNullable(validation, isNullable2) {
5242
5242
  return isNullable2 ? `${validation}.nullable()` : validation;
5243
5243
  }
5244
- function isNullable(schema) {
5244
+ function isNullable(schema, defaultNullable = false) {
5245
5245
  if (schema.nullable === true) {
5246
5246
  return true;
5247
5247
  }
5248
+ if (schema.nullable === false) {
5249
+ return false;
5250
+ }
5248
5251
  if (Array.isArray(schema.type)) {
5249
5252
  return schema.type.includes("null");
5250
5253
  }
5251
- return false;
5254
+ return defaultNullable;
5252
5255
  }
5253
5256
  function getPrimaryType(schema) {
5254
5257
  if (Array.isArray(schema.type)) {
@@ -5434,6 +5437,22 @@ function stripPrefix(input, pattern, ensureLeadingChar) {
5434
5437
  }
5435
5438
  return input;
5436
5439
  }
5440
+ function stripPathPrefix(path2, pattern) {
5441
+ if (!pattern) {
5442
+ return path2;
5443
+ }
5444
+ if (!isGlobPattern(pattern)) {
5445
+ let normalizedPattern = pattern.trim();
5446
+ if (!normalizedPattern.startsWith("/")) {
5447
+ normalizedPattern = `/${normalizedPattern}`;
5448
+ }
5449
+ if (normalizedPattern.endsWith("/") && normalizedPattern !== "/") {
5450
+ normalizedPattern = normalizedPattern.slice(0, -1);
5451
+ }
5452
+ return stripPrefix(path2, normalizedPattern, "/");
5453
+ }
5454
+ return stripPrefix(path2, pattern, "/");
5455
+ }
5437
5456
  var init_pattern_utils = __esm({
5438
5457
  "src/utils/pattern-utils.ts"() {
5439
5458
  "use strict";
@@ -5495,12 +5514,56 @@ var init_array_validator = __esm({
5495
5514
  });
5496
5515
 
5497
5516
  // src/validators/composition-validator.ts
5517
+ function isDiscriminatorRequired(schemas, discriminator, context) {
5518
+ const invalidSchemas = [];
5519
+ for (const schema of schemas) {
5520
+ const resolved = resolveSchema(schema, context);
5521
+ const required = resolved.required || [];
5522
+ if (!required.includes(discriminator)) {
5523
+ const schemaName = schema.$ref ? schema.$ref.split("/").pop() || "inline" : "inline";
5524
+ invalidSchemas.push(schemaName);
5525
+ }
5526
+ }
5527
+ return {
5528
+ valid: invalidSchemas.length === 0,
5529
+ invalidSchemas
5530
+ };
5531
+ }
5498
5532
  function generateUnion(schemas, discriminator, isNullable2, context, options, currentSchema) {
5533
+ if (schemas.length === 0) {
5534
+ console.warn(
5535
+ "[openapi-to-zod] Warning: Empty oneOf/anyOf array encountered. This is likely a malformed OpenAPI spec. Generating z.never() as fallback."
5536
+ );
5537
+ return wrapNullable(
5538
+ 'z.never().describe("Empty oneOf/anyOf in OpenAPI spec - no valid schema defined")',
5539
+ isNullable2
5540
+ );
5541
+ }
5542
+ if (schemas.length === 1) {
5543
+ let singleSchema = context.generatePropertySchema(schemas[0], currentSchema);
5544
+ if ((options == null ? void 0 : options.passthrough) && !singleSchema.includes(".catchall(")) {
5545
+ singleSchema = `${singleSchema}.catchall(z.unknown())`;
5546
+ }
5547
+ return wrapNullable(singleSchema, isNullable2);
5548
+ }
5499
5549
  if (discriminator) {
5500
5550
  let resolvedSchemas = schemas;
5501
5551
  if ((options == null ? void 0 : options.discriminatorMapping) && context.resolveDiscriminatorMapping) {
5502
5552
  resolvedSchemas = context.resolveDiscriminatorMapping(options.discriminatorMapping, schemas);
5503
5553
  }
5554
+ const discriminatorCheck = isDiscriminatorRequired(resolvedSchemas, discriminator, context);
5555
+ if (!discriminatorCheck.valid) {
5556
+ console.warn(
5557
+ `[openapi-to-zod] Warning: Discriminator "${discriminator}" is not required in schemas: ${discriminatorCheck.invalidSchemas.join(", ")}. Falling back to z.union() instead of z.discriminatedUnion().`
5558
+ );
5559
+ let schemaStrings3 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
5560
+ if (options == null ? void 0 : options.passthrough) {
5561
+ schemaStrings3 = schemaStrings3.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
5562
+ }
5563
+ const fallbackDescription = `Discriminator "${discriminator}" is optional in some schemas (${discriminatorCheck.invalidSchemas.join(", ")}), using z.union() instead of z.discriminatedUnion()`;
5564
+ const union3 = `z.union([${schemaStrings3.join(", ")}]).describe("${fallbackDescription}")`;
5565
+ return wrapNullable(union3, isNullable2);
5566
+ }
5504
5567
  let schemaStrings2 = resolvedSchemas.map((s) => context.generatePropertySchema(s, currentSchema));
5505
5568
  if (options == null ? void 0 : options.passthrough) {
5506
5569
  schemaStrings2 = schemaStrings2.map((s) => s.includes(".catchall(") ? s : `${s}.catchall(z.unknown())`);
@@ -5515,25 +5578,102 @@ function generateUnion(schemas, discriminator, isNullable2, context, options, cu
5515
5578
  const union = `z.union([${schemaStrings.join(", ")}])`;
5516
5579
  return wrapNullable(union, isNullable2);
5517
5580
  }
5581
+ function resolveSchema(schema, context) {
5582
+ if (schema.$ref && context.resolveSchemaRef) {
5583
+ const resolved = context.resolveSchemaRef(schema.$ref);
5584
+ if (resolved) {
5585
+ return resolved;
5586
+ }
5587
+ }
5588
+ return schema;
5589
+ }
5590
+ function collectProperties(schema, context) {
5591
+ const resolved = resolveSchema(schema, context);
5592
+ const props = /* @__PURE__ */ new Map();
5593
+ const sourceName = schema.$ref ? schema.$ref.split("/").pop() || "unknown" : "inline";
5594
+ if (resolved.properties) {
5595
+ for (const [key, value] of Object.entries(resolved.properties)) {
5596
+ props.set(key, { schema: value, source: sourceName });
5597
+ }
5598
+ }
5599
+ if (resolved.allOf) {
5600
+ for (const subSchema of resolved.allOf) {
5601
+ const subProps = collectProperties(subSchema, context);
5602
+ for (const [key, value] of subProps) {
5603
+ if (!props.has(key)) {
5604
+ props.set(key, value);
5605
+ }
5606
+ }
5607
+ }
5608
+ }
5609
+ return props;
5610
+ }
5611
+ function schemasMatch(a, b) {
5612
+ return JSON.stringify(a) === JSON.stringify(b);
5613
+ }
5614
+ function detectConflictingProperties(schemas, context) {
5615
+ const conflicts = [];
5616
+ const propertyMap = /* @__PURE__ */ new Map();
5617
+ for (const schema of schemas) {
5618
+ const schemaProps = collectProperties(schema, context);
5619
+ for (const [propName, propInfo] of schemaProps) {
5620
+ const existing = propertyMap.get(propName);
5621
+ if (existing) {
5622
+ if (!schemasMatch(existing.schema, propInfo.schema)) {
5623
+ conflicts.push(
5624
+ `Property "${propName}" has conflicting definitions in ${existing.source} and ${propInfo.source}`
5625
+ );
5626
+ }
5627
+ } else {
5628
+ propertyMap.set(propName, propInfo);
5629
+ }
5630
+ }
5631
+ }
5632
+ return conflicts;
5633
+ }
5518
5634
  function generateAllOf(schemas, isNullable2, context, currentSchema) {
5519
5635
  if (schemas.length === 1) {
5520
5636
  const singleSchema = context.generatePropertySchema(schemas[0], currentSchema, false);
5521
5637
  return wrapNullable(singleSchema, isNullable2);
5522
5638
  }
5639
+ const conflicts = detectConflictingProperties(schemas, context);
5640
+ let conflictDescription = "";
5641
+ if (conflicts.length > 0) {
5642
+ for (const conflict of conflicts) {
5643
+ console.warn(`[openapi-to-zod] Warning: allOf composition conflict - ${conflict}`);
5644
+ }
5645
+ conflictDescription = `allOf property conflicts detected: ${conflicts.join("; ")}`;
5646
+ }
5523
5647
  const allObjects = schemas.every((s) => s.type === "object" || s.properties || s.$ref || s.allOf);
5524
- const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
5648
+ let result;
5525
5649
  if (allObjects) {
5526
- let merged2 = schemaStrings[0];
5650
+ let merged = context.generatePropertySchema(schemas[0], currentSchema, false);
5651
+ for (let i = 1; i < schemas.length; i++) {
5652
+ const schema = schemas[i];
5653
+ if (schema.$ref) {
5654
+ const refSchema = context.generatePropertySchema(schema, currentSchema, false);
5655
+ merged = `${merged}.extend(${refSchema}.shape)`;
5656
+ } else if (context.generateInlineObjectShape && (schema.properties || schema.type === "object")) {
5657
+ const inlineShape = context.generateInlineObjectShape(schema, currentSchema);
5658
+ merged = `${merged}.extend(${inlineShape})`;
5659
+ } else {
5660
+ const schemaString = context.generatePropertySchema(schema, currentSchema, false);
5661
+ merged = `${merged}.extend(${schemaString}.shape)`;
5662
+ }
5663
+ }
5664
+ result = merged;
5665
+ } else {
5666
+ const schemaStrings = schemas.map((s) => context.generatePropertySchema(s, currentSchema, false));
5667
+ let merged = schemaStrings[0];
5527
5668
  for (let i = 1; i < schemaStrings.length; i++) {
5528
- merged2 = `${merged2}.merge(${schemaStrings[i]})`;
5669
+ merged = `${merged}.and(${schemaStrings[i]})`;
5529
5670
  }
5530
- return wrapNullable(merged2, isNullable2);
5671
+ result = merged;
5531
5672
  }
5532
- let merged = schemaStrings[0];
5533
- for (let i = 1; i < schemaStrings.length; i++) {
5534
- merged = `${merged}.and(${schemaStrings[i]})`;
5673
+ if (conflictDescription) {
5674
+ result = `${result}.describe("${conflictDescription}")`;
5535
5675
  }
5536
- return wrapNullable(merged, isNullable2);
5676
+ return wrapNullable(result, isNullable2);
5537
5677
  }
5538
5678
  var init_composition_validator = __esm({
5539
5679
  "src/validators/composition-validator.ts"() {
@@ -5999,6 +6139,26 @@ function configurePatternCache(size) {
5999
6139
  PATTERN_CACHE = new LRUCache(size);
6000
6140
  }
6001
6141
  }
6142
+ function configureDateTimeFormat(pattern) {
6143
+ if (!pattern) {
6144
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
6145
+ return;
6146
+ }
6147
+ const patternStr = pattern instanceof RegExp ? pattern.source : pattern;
6148
+ if (patternStr === "") {
6149
+ FORMAT_MAP["date-time"] = "z.iso.datetime()";
6150
+ return;
6151
+ }
6152
+ try {
6153
+ new RegExp(patternStr);
6154
+ } catch (error) {
6155
+ throw new Error(
6156
+ `Invalid regular expression pattern for customDateTimeFormatRegex: ${patternStr}. ${error instanceof Error ? error.message : "Pattern is malformed"}`
6157
+ );
6158
+ }
6159
+ const escapedPattern = escapePattern(patternStr);
6160
+ FORMAT_MAP["date-time"] = `z.string().regex(/${escapedPattern}/)`;
6161
+ }
6002
6162
  function generateStringValidation(schema, useDescribe) {
6003
6163
  let validation = FORMAT_MAP[schema.format || ""] || "z.string()";
6004
6164
  if (schema.minLength !== void 0) {
@@ -6064,7 +6224,7 @@ function generateStringValidation(schema, useDescribe) {
6064
6224
  }
6065
6225
  return addDescription(validation, schema.description, useDescribe);
6066
6226
  }
6067
- var PATTERN_CACHE, FORMAT_MAP;
6227
+ var PATTERN_CACHE, DEFAULT_FORMAT_MAP, FORMAT_MAP;
6068
6228
  var init_string_validator = __esm({
6069
6229
  "src/validators/string-validator.ts"() {
6070
6230
  "use strict";
@@ -6072,7 +6232,7 @@ var init_string_validator = __esm({
6072
6232
  init_lru_cache();
6073
6233
  init_string_utils();
6074
6234
  PATTERN_CACHE = new LRUCache(1e3);
6075
- FORMAT_MAP = {
6235
+ DEFAULT_FORMAT_MAP = {
6076
6236
  uuid: "z.uuid()",
6077
6237
  email: "z.email()",
6078
6238
  uri: "z.url()",
@@ -6082,7 +6242,6 @@ var init_string_validator = __esm({
6082
6242
  byte: "z.base64()",
6083
6243
  binary: "z.string()",
6084
6244
  date: "z.iso.date()",
6085
- "date-time": "z.iso.datetime()",
6086
6245
  time: "z.iso.time()",
6087
6246
  duration: 'z.string().refine((val) => /^P(?:(?:\\d+Y)?(?:\\d+M)?(?:\\d+D)?(?:T(?:\\d+H)?(?:\\d+M)?(?:\\d+(?:\\.\\d+)?S)?)?|\\d+W)$/.test(val) && !/^PT?$/.test(val), { message: "Must be a valid ISO 8601 duration" })',
6088
6247
  ipv4: "z.ipv4()",
@@ -6101,6 +6260,10 @@ var init_string_validator = __esm({
6101
6260
  "json-pointer": 'z.string().refine((val) => val === "" || /^(\\/([^~/]|~0|~1)+)+$/.test(val), { message: "Must be a valid JSON Pointer (RFC 6901)" })',
6102
6261
  "relative-json-pointer": 'z.string().refine((val) => /^(0|[1-9]\\d*)(#|(\\/([^~/]|~0|~1)+)*)$/.test(val), { message: "Must be a valid relative JSON Pointer" })'
6103
6262
  };
6263
+ FORMAT_MAP = {
6264
+ ...DEFAULT_FORMAT_MAP,
6265
+ "date-time": "z.iso.datetime()"
6266
+ };
6104
6267
  }
6105
6268
  });
6106
6269
 
@@ -6237,6 +6400,15 @@ var init_property_generator = __esm({
6237
6400
  }
6238
6401
  return mappedSchemas;
6239
6402
  }
6403
+ /**
6404
+ * Resolve a $ref string to the actual schema
6405
+ */
6406
+ resolveSchemaRef(ref) {
6407
+ var _a, _b;
6408
+ const schemaName = ref.split("/").pop();
6409
+ if (!schemaName) return void 0;
6410
+ return (_b = (_a = this.context.spec.components) == null ? void 0 : _a.schemas) == null ? void 0 : _b[schemaName];
6411
+ }
6240
6412
  /**
6241
6413
  * Resolve a schema name through any aliases to get the actual schema name
6242
6414
  * If the schema is an alias (allOf with single $ref), return the target name
@@ -6314,7 +6486,7 @@ var init_property_generator = __esm({
6314
6486
  let schemaWithCatchall = baseSchema;
6315
6487
  if (baseSchema.includes(".union([") || baseSchema.includes(".discriminatedUnion(")) {
6316
6488
  schemaWithCatchall = baseSchema;
6317
- } else if (baseSchema.includes(".merge(")) {
6489
+ } else if (baseSchema.includes(".extend(")) {
6318
6490
  schemaWithCatchall = `${baseSchema}.catchall(z.unknown())`;
6319
6491
  }
6320
6492
  if (schema.unevaluatedProperties === false) {
@@ -6341,7 +6513,12 @@ var init_property_generator = __esm({
6341
6513
  if ((this.context.schemaType === "request" || this.context.schemaType === "response") && schema.properties) {
6342
6514
  schema = this.filterNestedProperties(schema);
6343
6515
  }
6344
- const nullable = isNullable(schema);
6516
+ const isSchemaRef = !!schema.$ref;
6517
+ const isEnum = !!schema.enum;
6518
+ const isConst = schema.const !== void 0;
6519
+ const shouldApplyDefaultNullable = !isTopLevel && !isSchemaRef && !isEnum && !isConst;
6520
+ const effectiveDefaultNullable = shouldApplyDefaultNullable ? this.context.defaultNullable : false;
6521
+ const nullable = isNullable(schema, effectiveDefaultNullable);
6345
6522
  if (hasMultipleTypes(schema)) {
6346
6523
  const union = this.generateMultiTypeUnion(schema, currentSchema);
6347
6524
  return wrapNullable(union, nullable);
@@ -6393,7 +6570,11 @@ var init_property_generator = __esm({
6393
6570
  let composition = generateAllOf(
6394
6571
  schema.allOf,
6395
6572
  nullable,
6396
- { generatePropertySchema: this.generatePropertySchema.bind(this) },
6573
+ {
6574
+ generatePropertySchema: this.generatePropertySchema.bind(this),
6575
+ generateInlineObjectShape: this.generateInlineObjectShape.bind(this),
6576
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
6577
+ },
6397
6578
  currentSchema
6398
6579
  );
6399
6580
  if (schema.unevaluatedProperties !== void 0) {
@@ -6409,7 +6590,8 @@ var init_property_generator = __esm({
6409
6590
  nullable,
6410
6591
  {
6411
6592
  generatePropertySchema: this.generatePropertySchema.bind(this),
6412
- resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
6593
+ resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
6594
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
6413
6595
  },
6414
6596
  {
6415
6597
  passthrough: needsPassthrough,
@@ -6430,7 +6612,8 @@ var init_property_generator = __esm({
6430
6612
  nullable,
6431
6613
  {
6432
6614
  generatePropertySchema: this.generatePropertySchema.bind(this),
6433
- resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this)
6615
+ resolveDiscriminatorMapping: this.resolveDiscriminatorMapping.bind(this),
6616
+ resolveSchemaRef: this.resolveSchemaRef.bind(this)
6434
6617
  },
6435
6618
  {
6436
6619
  passthrough: needsPassthrough,
@@ -6493,7 +6676,17 @@ var init_property_generator = __esm({
6493
6676
  );
6494
6677
  validation = addDescription(validation, schema.description, this.context.useDescribe);
6495
6678
  } else {
6496
- validation = "z.record(z.string(), z.unknown())";
6679
+ switch (this.context.emptyObjectBehavior) {
6680
+ case "strict":
6681
+ validation = "z.strictObject({})";
6682
+ break;
6683
+ case "loose":
6684
+ validation = "z.looseObject({})";
6685
+ break;
6686
+ default:
6687
+ validation = "z.record(z.string(), z.unknown())";
6688
+ break;
6689
+ }
6497
6690
  validation = addDescription(validation, schema.description, this.context.useDescribe);
6498
6691
  }
6499
6692
  break;
@@ -6508,6 +6701,44 @@ var init_property_generator = __esm({
6508
6701
  }
6509
6702
  return result;
6510
6703
  }
6704
+ /**
6705
+ * Generate inline object shape for use with .extend()
6706
+ * Returns just the shape object literal: { prop1: z.string(), prop2: z.number() }
6707
+ *
6708
+ * This method is specifically for allOf compositions where we need to pass
6709
+ * the shape directly to .extend() instead of using z.object({...}).shape.
6710
+ * This avoids the .nullable().shape bug when inline objects have nullable: true.
6711
+ *
6712
+ * According to Zod docs (https://zod.dev/api?id=extend):
6713
+ * - .extend() accepts an object of shape definitions
6714
+ * - e.g., baseSchema.extend({ prop: z.string() })
6715
+ */
6716
+ generateInlineObjectShape(schema, currentSchema) {
6717
+ const required = new Set(schema.required || []);
6718
+ const properties = [];
6719
+ if (schema.properties) {
6720
+ for (const [propName, propSchema] of Object.entries(schema.properties)) {
6721
+ if (!this.shouldIncludeProperty(propSchema)) {
6722
+ continue;
6723
+ }
6724
+ const isRequired = required.has(propName);
6725
+ const zodSchema = this.generatePropertySchema(propSchema, currentSchema);
6726
+ const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
6727
+ const quotedPropName = validIdentifier.test(propName) ? propName : `"${propName}"`;
6728
+ let propertyDef = `${quotedPropName}: ${zodSchema}`;
6729
+ if (!isRequired) {
6730
+ propertyDef += ".optional()";
6731
+ }
6732
+ properties.push(propertyDef);
6733
+ }
6734
+ }
6735
+ if (properties.length === 0) {
6736
+ return "{}";
6737
+ }
6738
+ return `{
6739
+ ${properties.map((p) => ` ${p}`).join(",\n")}
6740
+ }`;
6741
+ }
6511
6742
  };
6512
6743
  // Performance optimization: Lookup table for faster inclusion checks
6513
6744
  _PropertyGenerator.INCLUSION_RULES = {
@@ -6659,6 +6890,65 @@ var init_operation_filters = __esm({
6659
6890
  }
6660
6891
  });
6661
6892
 
6893
+ // src/utils/ref-resolver.ts
6894
+ function resolveRef2(obj, spec, maxDepth = 10) {
6895
+ var _a, _b, _c, _d;
6896
+ if (!obj || typeof obj !== "object" || maxDepth <= 0) return obj;
6897
+ if (!obj.$ref) return obj;
6898
+ const ref = obj.$ref;
6899
+ let resolved = null;
6900
+ const paramMatch = ref.match(/^#\/components\/parameters\/(.+)$/);
6901
+ const requestBodyMatch = ref.match(/^#\/components\/requestBodies\/(.+)$/);
6902
+ const responseMatch = ref.match(/^#\/components\/responses\/(.+)$/);
6903
+ const schemaMatch = ref.match(/^#\/components\/schemas\/(.+)$/);
6904
+ if (paramMatch && ((_a = spec.components) == null ? void 0 : _a.parameters)) {
6905
+ const name = paramMatch[1];
6906
+ resolved = spec.components.parameters[name];
6907
+ } else if (requestBodyMatch && ((_b = spec.components) == null ? void 0 : _b.requestBodies)) {
6908
+ const name = requestBodyMatch[1];
6909
+ resolved = spec.components.requestBodies[name];
6910
+ } else if (responseMatch && ((_c = spec.components) == null ? void 0 : _c.responses)) {
6911
+ const name = responseMatch[1];
6912
+ resolved = spec.components.responses[name];
6913
+ } else if (schemaMatch && ((_d = spec.components) == null ? void 0 : _d.schemas)) {
6914
+ const name = schemaMatch[1];
6915
+ resolved = spec.components.schemas[name];
6916
+ }
6917
+ if (resolved) {
6918
+ if (resolved.$ref) {
6919
+ return resolveRef2(resolved, spec, maxDepth - 1);
6920
+ }
6921
+ return resolved;
6922
+ }
6923
+ return obj;
6924
+ }
6925
+ function resolveParameterRef(param, spec) {
6926
+ return resolveRef2(param, spec);
6927
+ }
6928
+ function mergeParameters(pathParams, operationParams, spec) {
6929
+ const resolvedPathParams = (pathParams || []).map((p) => resolveParameterRef(p, spec));
6930
+ const resolvedOperationParams = (operationParams || []).map((p) => resolveParameterRef(p, spec));
6931
+ const merged = [...resolvedPathParams];
6932
+ for (const opParam of resolvedOperationParams) {
6933
+ if (!opParam || typeof opParam !== "object") continue;
6934
+ const existingIndex = merged.findIndex(
6935
+ (p) => p && typeof p === "object" && p.name === opParam.name && p.in === opParam.in
6936
+ );
6937
+ if (existingIndex >= 0) {
6938
+ merged[existingIndex] = opParam;
6939
+ } else {
6940
+ merged.push(opParam);
6941
+ }
6942
+ }
6943
+ return merged;
6944
+ }
6945
+ var init_ref_resolver = __esm({
6946
+ "src/utils/ref-resolver.ts"() {
6947
+ "use strict";
6948
+ init_esm_shims();
6949
+ }
6950
+ });
6951
+
6662
6952
  // src/openapi-generator.ts
6663
6953
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
6664
6954
  import { dirname, normalize } from "path";
@@ -6676,6 +6966,7 @@ var init_openapi_generator = __esm({
6676
6966
  init_name_utils();
6677
6967
  init_operation_filters();
6678
6968
  init_pattern_utils();
6969
+ init_ref_resolver();
6679
6970
  init_string_validator();
6680
6971
  OpenApiGenerator = class {
6681
6972
  constructor(options) {
@@ -6685,7 +6976,7 @@ var init_openapi_generator = __esm({
6685
6976
  this.schemaUsageMap = /* @__PURE__ */ new Map();
6686
6977
  this.needsZodImport = true;
6687
6978
  this.filterStats = createFilterStatistics();
6688
- var _a, _b, _c, _d, _e;
6979
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i;
6689
6980
  if (!options.input) {
6690
6981
  throw new ConfigurationError("Input path is required", { providedOptions: options });
6691
6982
  }
@@ -6695,21 +6986,28 @@ var init_openapi_generator = __esm({
6695
6986
  output: options.output,
6696
6987
  includeDescriptions: (_a = options.includeDescriptions) != null ? _a : true,
6697
6988
  useDescribe: (_b = options.useDescribe) != null ? _b : false,
6989
+ defaultNullable: (_c = options.defaultNullable) != null ? _c : false,
6990
+ emptyObjectBehavior: (_d = options.emptyObjectBehavior) != null ? _d : "loose",
6698
6991
  schemaType: options.schemaType || "all",
6699
6992
  prefix: options.prefix,
6700
6993
  suffix: options.suffix,
6701
6994
  stripSchemaPrefix: options.stripSchemaPrefix,
6702
- showStats: (_c = options.showStats) != null ? _c : true,
6995
+ stripPathPrefix: options.stripPathPrefix,
6996
+ showStats: (_e = options.showStats) != null ? _e : true,
6703
6997
  request: options.request,
6704
6998
  response: options.response,
6705
6999
  operationFilters: options.operationFilters,
6706
7000
  ignoreHeaders: options.ignoreHeaders,
6707
- cacheSize: (_d = options.cacheSize) != null ? _d : 1e3,
6708
- batchSize: (_e = options.batchSize) != null ? _e : 10
7001
+ cacheSize: (_f = options.cacheSize) != null ? _f : 1e3,
7002
+ batchSize: (_g = options.batchSize) != null ? _g : 10,
7003
+ customDateTimeFormatRegex: options.customDateTimeFormatRegex
6709
7004
  };
6710
7005
  if (this.options.cacheSize) {
6711
7006
  configurePatternCache(this.options.cacheSize);
6712
7007
  }
7008
+ if (this.options.customDateTimeFormatRegex) {
7009
+ configureDateTimeFormat(this.options.customDateTimeFormatRegex);
7010
+ }
6713
7011
  try {
6714
7012
  const fs = __require("fs");
6715
7013
  if (!fs.existsSync(this.options.input)) {
@@ -6772,6 +7070,8 @@ var init_openapi_generator = __esm({
6772
7070
  mode: this.requestOptions.mode,
6773
7071
  includeDescriptions: this.requestOptions.includeDescriptions,
6774
7072
  useDescribe: this.requestOptions.useDescribe,
7073
+ defaultNullable: (_h = this.options.defaultNullable) != null ? _h : false,
7074
+ emptyObjectBehavior: (_i = this.options.emptyObjectBehavior) != null ? _i : "loose",
6775
7075
  namingOptions: {
6776
7076
  prefix: this.options.prefix,
6777
7077
  suffix: this.options.suffix
@@ -7125,7 +7425,7 @@ var init_openapi_generator = __esm({
7125
7425
  * Generate schema for a component
7126
7426
  */
7127
7427
  generateComponentSchema(name, schema) {
7128
- var _a, _b;
7428
+ var _a, _b, _c, _d;
7129
7429
  if (!this.schemaDependencies.has(name)) {
7130
7430
  this.schemaDependencies.set(name, /* @__PURE__ */ new Set());
7131
7431
  }
@@ -7157,14 +7457,15 @@ ${typeCode}`;
7157
7457
  mode: resolvedOptions.mode,
7158
7458
  includeDescriptions: resolvedOptions.includeDescriptions,
7159
7459
  useDescribe: resolvedOptions.useDescribe,
7460
+ defaultNullable: (_b = this.options.defaultNullable) != null ? _b : false,
7461
+ emptyObjectBehavior: (_c = this.options.emptyObjectBehavior) != null ? _c : "loose",
7160
7462
  namingOptions: {
7161
7463
  prefix: this.options.prefix,
7162
7464
  suffix: this.options.suffix
7163
7465
  },
7164
7466
  stripSchemaPrefix: this.options.stripSchemaPrefix
7165
7467
  });
7166
- const isAlias = !!(schema.$ref && !schema.properties && !schema.allOf && !schema.oneOf && !schema.anyOf);
7167
- const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, isAlias);
7468
+ const zodSchema = this.propertyGenerator.generatePropertySchema(schema, name, true);
7168
7469
  const zodSchemaCode = `${jsdoc}export const ${schemaName} = ${zodSchema};`;
7169
7470
  if (zodSchema.includes("z.discriminatedUnion(")) {
7170
7471
  const match = zodSchema.match(/z\.discriminatedUnion\([^,]+,\s*\[([^\]]+)\]/);
@@ -7174,7 +7475,7 @@ ${typeCode}`;
7174
7475
  const depMatch = ref.match(/^([a-z][a-zA-Z0-9]*?)Schema$/);
7175
7476
  if (depMatch) {
7176
7477
  const depName = depMatch[1].charAt(0).toUpperCase() + depMatch[1].slice(1);
7177
- (_b = this.schemaDependencies.get(name)) == null ? void 0 : _b.add(depName);
7478
+ (_d = this.schemaDependencies.get(name)) == null ? void 0 : _d.add(depName);
7178
7479
  }
7179
7480
  }
7180
7481
  }
@@ -7198,16 +7499,20 @@ ${typeCode}`;
7198
7499
  if (!shouldIncludeOperation(operation, path2, method, this.options.operationFilters)) {
7199
7500
  continue;
7200
7501
  }
7201
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
7202
- continue;
7203
- }
7204
- const queryParams = operation.parameters.filter(
7502
+ const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
7503
+ const queryParams = allParams.filter(
7205
7504
  (param) => param && typeof param === "object" && param.in === "query"
7206
7505
  );
7207
7506
  if (queryParams.length === 0) {
7208
7507
  continue;
7209
7508
  }
7210
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7509
+ let pascalOperationId;
7510
+ if (operation.operationId) {
7511
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7512
+ } else {
7513
+ const strippedPath = stripPathPrefix(path2, this.options.stripPathPrefix);
7514
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
7515
+ }
7211
7516
  const schemaName = `${pascalOperationId}QueryParams`;
7212
7517
  if (!this.schemaDependencies.has(schemaName)) {
7213
7518
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -7255,8 +7560,9 @@ ${propsCode}
7255
7560
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
7256
7561
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
7257
7562
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}QueryParamsSchema`;
7563
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path2}`;
7258
7564
  const jsdoc = `/**
7259
- * Query parameters for ${operation.operationId}
7565
+ * Query parameters for ${jsdocOperationName}
7260
7566
  */
7261
7567
  `;
7262
7568
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -7265,6 +7571,35 @@ ${propsCode}
7265
7571
  }
7266
7572
  }
7267
7573
  }
7574
+ /**
7575
+ * Generate a PascalCase method name from HTTP method and path
7576
+ * Used as fallback when operationId is not available
7577
+ * @internal
7578
+ */
7579
+ generateMethodNameFromPath(method, path2) {
7580
+ const segments = path2.split("/").filter(Boolean).map((segment) => {
7581
+ if (segment.startsWith("{") && segment.endsWith("}")) {
7582
+ const paramName = segment.slice(1, -1);
7583
+ return `By${this.capitalizeSegment(paramName)}`;
7584
+ }
7585
+ return this.capitalizeSegment(segment);
7586
+ }).join("");
7587
+ const capitalizedMethod = method.charAt(0).toUpperCase() + method.slice(1).toLowerCase();
7588
+ return `${capitalizedMethod}${segments}`;
7589
+ }
7590
+ /**
7591
+ * Capitalizes a path segment, handling special characters like dashes, underscores, and dots
7592
+ * @internal
7593
+ */
7594
+ capitalizeSegment(str) {
7595
+ if (str.includes("-") || str.includes("_") || str.includes(".")) {
7596
+ return str.split(/[-_.]/).map((part) => {
7597
+ if (!part) return "";
7598
+ return part.charAt(0).toUpperCase() + part.slice(1).toLowerCase();
7599
+ }).join("");
7600
+ }
7601
+ return str.charAt(0).toUpperCase() + str.slice(1);
7602
+ }
7268
7603
  /**
7269
7604
  * Check if a header should be ignored based on filter patterns
7270
7605
  * @internal
@@ -7301,16 +7636,20 @@ ${propsCode}
7301
7636
  if (!shouldIncludeOperation(operation, path2, method, this.options.operationFilters)) {
7302
7637
  continue;
7303
7638
  }
7304
- if (!operation.operationId || !operation.parameters || !Array.isArray(operation.parameters)) {
7305
- continue;
7306
- }
7307
- const headerParams = operation.parameters.filter(
7639
+ const allParams = mergeParameters(pathItem.parameters, operation.parameters, this.spec);
7640
+ const headerParams = allParams.filter(
7308
7641
  (param) => param && typeof param === "object" && param.in === "header" && !this.shouldIgnoreHeader(param.name)
7309
7642
  );
7310
7643
  if (headerParams.length === 0) {
7311
7644
  continue;
7312
7645
  }
7313
- const pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7646
+ let pascalOperationId;
7647
+ if (operation.operationId) {
7648
+ pascalOperationId = operation.operationId.includes("-") ? toPascalCase(operation.operationId) : operation.operationId.charAt(0).toUpperCase() + operation.operationId.slice(1);
7649
+ } else {
7650
+ const strippedPath = stripPathPrefix(path2, this.options.stripPathPrefix);
7651
+ pascalOperationId = this.generateMethodNameFromPath(method, strippedPath);
7652
+ }
7314
7653
  const schemaName = `${pascalOperationId}HeaderParams`;
7315
7654
  if (!this.schemaDependencies.has(schemaName)) {
7316
7655
  this.schemaDependencies.set(schemaName, /* @__PURE__ */ new Set());
@@ -7347,8 +7686,9 @@ ${propsCode}
7347
7686
  const prefixedName = this.options.prefix ? `${toPascalCase(this.options.prefix)}${operationName}` : operationName;
7348
7687
  const suffixedName = this.options.suffix ? `${prefixedName}${toPascalCase(this.options.suffix)}` : prefixedName;
7349
7688
  const camelCaseSchemaName = `${suffixedName.charAt(0).toLowerCase() + suffixedName.slice(1)}HeaderParamsSchema`;
7689
+ const jsdocOperationName = operation.operationId || `${method.toUpperCase()} ${path2}`;
7350
7690
  const jsdoc = `/**
7351
- * Header parameters for ${operation.operationId}
7691
+ * Header parameters for ${jsdocOperationName}
7352
7692
  */
7353
7693
  `;
7354
7694
  const fullSchemaCode = `${jsdoc}export const ${camelCaseSchemaName} = ${schemaCode};`;
@@ -7387,13 +7727,23 @@ ${propsCode}
7387
7727
  }
7388
7728
  const type = schema.type;
7389
7729
  if (type === "string") {
7730
+ const formatMap = {
7731
+ email: "z.email()",
7732
+ uri: "z.url()",
7733
+ url: "z.url()",
7734
+ uuid: "z.uuid()"
7735
+ };
7736
+ if (schema.format && formatMap[schema.format]) {
7737
+ let zodType2 = formatMap[schema.format];
7738
+ if (schema.minLength !== void 0) zodType2 = `${zodType2}.min(${schema.minLength})`;
7739
+ if (schema.maxLength !== void 0) zodType2 = `${zodType2}.max(${schema.maxLength})`;
7740
+ if (schema.pattern) zodType2 = `${zodType2}.regex(/${schema.pattern}/)`;
7741
+ return zodType2;
7742
+ }
7390
7743
  let zodType = "z.string()";
7391
7744
  if (schema.minLength !== void 0) zodType = `${zodType}.min(${schema.minLength})`;
7392
7745
  if (schema.maxLength !== void 0) zodType = `${zodType}.max(${schema.maxLength})`;
7393
7746
  if (schema.pattern) zodType = `${zodType}.regex(/${schema.pattern}/)`;
7394
- if (schema.format === "email") zodType = `${zodType}.email()`;
7395
- if (schema.format === "uri" || schema.format === "url") zodType = `${zodType}.url()`;
7396
- if (schema.format === "uuid") zodType = `${zodType}.uuid()`;
7397
7747
  return zodType;
7398
7748
  }
7399
7749
  if (type === "number" || type === "integer") {
@@ -7418,11 +7768,6 @@ ${propsCode}
7418
7768
  }
7419
7769
  return "z.unknown()";
7420
7770
  }
7421
- // REMOVED: generateNativeEnum method - no longer needed as we only generate Zod schemas
7422
- // REMOVED: toEnumKey method - was only used by generateNativeEnum
7423
- // REMOVED: addConstraintsToJSDoc method - was only used for native TypeScript types
7424
- // REMOVED: generateNativeTypeDefinition method - was only used for native TypeScript types
7425
- // REMOVED: generateObjectType method - was only used for native TypeScript types
7426
7771
  /**
7427
7772
  * Topological sort for schema dependencies
7428
7773
  * Returns schemas in the order they should be declared
@@ -7529,7 +7874,8 @@ var init_config_schemas = __esm({
7529
7874
  RequestResponseOptionsSchema = z.strictObject({
7530
7875
  mode: z.enum(["strict", "normal", "loose"]).optional(),
7531
7876
  useDescribe: z.boolean().optional(),
7532
- includeDescriptions: z.boolean().optional()
7877
+ includeDescriptions: z.boolean().optional(),
7878
+ defaultNullable: z.boolean().optional()
7533
7879
  });
7534
7880
  OperationFiltersSchema = z.strictObject({
7535
7881
  includeTags: z.array(z.string()).optional(),
@@ -7660,10 +8006,12 @@ function mergeConfigWithDefaults(config) {
7660
8006
  mode: defaults.mode,
7661
8007
  includeDescriptions: defaults.includeDescriptions,
7662
8008
  useDescribe: defaults.useDescribe,
8009
+ defaultNullable: defaults.defaultNullable,
7663
8010
  schemaType: defaults.schemaType,
7664
8011
  prefix: defaults.prefix,
7665
8012
  suffix: defaults.suffix,
7666
8013
  showStats: defaults.showStats,
8014
+ customDateTimeFormatRegex: defaults.customDateTimeFormatRegex,
7667
8015
  // Override with spec-specific values (including required input/output)
7668
8016
  ...spec
7669
8017
  };
@@ -7684,6 +8032,7 @@ var init_config_loader = __esm({
7684
8032
  output: z2.string(),
7685
8033
  includeDescriptions: z2.boolean().optional(),
7686
8034
  useDescribe: z2.boolean().optional(),
8035
+ defaultNullable: z2.boolean().optional(),
7687
8036
  schemaType: z2.enum(["all", "request", "response"]).optional(),
7688
8037
  prefix: z2.string().optional(),
7689
8038
  suffix: z2.string().optional(),
@@ -7694,13 +8043,28 @@ var init_config_loader = __esm({
7694
8043
  name: z2.string().optional(),
7695
8044
  operationFilters: OperationFiltersSchema.optional(),
7696
8045
  cacheSize: z2.number().positive().optional(),
7697
- batchSize: z2.number().positive().optional()
8046
+ batchSize: z2.number().positive().optional(),
8047
+ customDateTimeFormatRegex: z2.union([
8048
+ z2.string().refine(
8049
+ (pattern) => {
8050
+ try {
8051
+ new RegExp(pattern);
8052
+ return true;
8053
+ } catch {
8054
+ return false;
8055
+ }
8056
+ },
8057
+ { message: "Must be a valid regular expression pattern" }
8058
+ ),
8059
+ z2.instanceof(RegExp)
8060
+ ]).optional()
7698
8061
  });
7699
8062
  ConfigFileSchema = z2.strictObject({
7700
8063
  defaults: z2.strictObject({
7701
8064
  mode: z2.enum(["strict", "normal", "loose"]).optional(),
7702
8065
  includeDescriptions: z2.boolean().optional(),
7703
8066
  useDescribe: z2.boolean().optional(),
8067
+ defaultNullable: z2.boolean().optional(),
7704
8068
  schemaType: z2.enum(["all", "request", "response"]).optional(),
7705
8069
  prefix: z2.string().optional(),
7706
8070
  suffix: z2.string().optional(),
@@ -7710,7 +8074,21 @@ var init_config_loader = __esm({
7710
8074
  response: RequestResponseOptionsSchema.optional(),
7711
8075
  operationFilters: OperationFiltersSchema.optional(),
7712
8076
  cacheSize: z2.number().positive().optional(),
7713
- batchSize: z2.number().positive().optional()
8077
+ batchSize: z2.number().positive().optional(),
8078
+ customDateTimeFormatRegex: z2.union([
8079
+ z2.string().refine(
8080
+ (pattern) => {
8081
+ try {
8082
+ new RegExp(pattern);
8083
+ return true;
8084
+ } catch {
8085
+ return false;
8086
+ }
8087
+ },
8088
+ { message: "Must be a valid regular expression pattern" }
8089
+ ),
8090
+ z2.instanceof(RegExp)
8091
+ ]).optional()
7714
8092
  }).optional(),
7715
8093
  specs: z2.array(OpenApiGeneratorOptionsSchema).min(1, {
7716
8094
  message: "Configuration must include at least one specification. Each specification should have 'input' and 'output' paths."