@alt-stack/zod-openapi 1.1.2 → 1.2.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.
@@ -1,5 +1,5 @@
1
1
 
2
- > @alt-stack/zod-openapi@1.1.2 build /home/runner/work/alt-stack/alt-stack/packages/zod-openapi
2
+ > @alt-stack/zod-openapi@1.2.0 build /home/runner/work/alt-stack/alt-stack/packages/zod-openapi
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/index.ts
@@ -10,13 +10,13 @@
10
10
  CLI Cleaning output folder
11
11
  ESM Build start
12
12
  CJS Build start
13
- CJS dist/index.cjs 26.95 KB
14
- CJS dist/index.cjs.map 55.60 KB
15
- CJS ⚡️ Build success in 31ms
16
- ESM dist/index.js 25.41 KB
17
- ESM dist/index.js.map 54.74 KB
18
- ESM ⚡️ Build success in 31ms
13
+ ESM dist/index.js 38.35 KB
14
+ ESM dist/index.js.map 84.13 KB
15
+ ESM ⚡️ Build success in 32ms
16
+ CJS dist/index.cjs 39.98 KB
17
+ CJS dist/index.cjs.map 85.09 KB
18
+ CJS ⚡️ Build success in 33ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 1417ms
21
- DTS dist/index.d.ts 4.83 KB
22
- DTS dist/index.d.cts 4.83 KB
20
+ DTS ⚡️ Build success in 1497ms
21
+ DTS dist/index.d.ts 5.68 KB
22
+ DTS dist/index.d.cts 5.68 KB
package/dist/index.cjs CHANGED
@@ -23,12 +23,14 @@ __export(index_exports, {
23
23
  SUPPORTED_STRING_FORMATS: () => SUPPORTED_STRING_FORMATS,
24
24
  clearZodSchemaToOpenApiSchemaRegistry: () => clearZodSchemaToOpenApiSchemaRegistry,
25
25
  convertSchemaToZodString: () => convertSchemaToZodString,
26
+ generateInterface: () => generateInterface,
26
27
  generateRouteSchemaNames: () => generateRouteSchemaNames,
27
28
  getSchemaExportedVariableNameForStringFormat: () => getSchemaExportedVariableNameForStringFormat,
28
29
  openApiToZodTsCode: () => openApiToZodTsCode,
29
30
  parseOpenApiPaths: () => parseOpenApiPaths,
30
31
  registerZodSchemaToOpenApiSchema: () => registerZodSchemaToOpenApiSchema,
31
- schemaRegistry: () => schemaRegistry
32
+ schemaRegistry: () => schemaRegistry,
33
+ schemaToTypeString: () => schemaToTypeString
32
34
  });
33
35
  module.exports = __toCommonJS(index_exports);
34
36
 
@@ -142,6 +144,87 @@ function topologicalSortSchemas(schemas) {
142
144
  return sorted;
143
145
  }
144
146
 
147
+ // src/schema-dedup.ts
148
+ function sortObjectDeep(obj) {
149
+ if (obj === null || typeof obj !== "object") return obj;
150
+ if (Array.isArray(obj)) return obj.map(sortObjectDeep);
151
+ const sorted = {};
152
+ const keys = Object.keys(obj).sort();
153
+ for (const key of keys) {
154
+ sorted[key] = sortObjectDeep(obj[key]);
155
+ }
156
+ return sorted;
157
+ }
158
+ function getSchemaFingerprint(schema) {
159
+ return JSON.stringify(sortObjectDeep(schema));
160
+ }
161
+ function createSchemaRegistry() {
162
+ return {
163
+ fingerprintToName: /* @__PURE__ */ new Map(),
164
+ nameToFingerprint: /* @__PURE__ */ new Map()
165
+ };
166
+ }
167
+ function registerSchema(registry, name, schema) {
168
+ const fingerprint = getSchemaFingerprint(schema);
169
+ const existing = registry.fingerprintToName.get(fingerprint);
170
+ if (existing) {
171
+ return { isNew: false, canonicalName: existing };
172
+ }
173
+ registry.fingerprintToName.set(fingerprint, name);
174
+ registry.nameToFingerprint.set(name, fingerprint);
175
+ return { isNew: true, canonicalName: name };
176
+ }
177
+ function preRegisterSchema(registry, name, fingerprint) {
178
+ registry.fingerprintToName.set(fingerprint, name);
179
+ registry.nameToFingerprint.set(name, fingerprint);
180
+ }
181
+ function extractErrorCode(schema) {
182
+ const properties = schema?.["properties"];
183
+ const errorObj = properties?.["error"];
184
+ const errorProps = errorObj?.["properties"];
185
+ const codeSchema = errorProps?.["code"];
186
+ const codeEnum = codeSchema?.["enum"];
187
+ if (Array.isArray(codeEnum) && codeEnum.length === 1) {
188
+ return codeEnum[0];
189
+ }
190
+ return null;
191
+ }
192
+ function errorCodeToPascalCase(code) {
193
+ return code.split("_").map((part) => part.charAt(0) + part.slice(1).toLowerCase()).join("");
194
+ }
195
+ function generateCommonErrorSchemaName(errorCode) {
196
+ return `${errorCodeToPascalCase(errorCode)}ErrorSchema`;
197
+ }
198
+ function findCommonSchemas(schemas, minCount = 2) {
199
+ const fingerprints = /* @__PURE__ */ new Map();
200
+ for (const { name, schema } of schemas) {
201
+ const fingerprint = getSchemaFingerprint(schema);
202
+ const existing = fingerprints.get(fingerprint);
203
+ if (existing) {
204
+ existing.names.push(name);
205
+ } else {
206
+ fingerprints.set(fingerprint, {
207
+ schema,
208
+ names: [name],
209
+ errorCode: extractErrorCode(schema)
210
+ });
211
+ }
212
+ }
213
+ const commonSchemas = [];
214
+ for (const [fingerprint, data] of fingerprints) {
215
+ if (data.names.length >= minCount) {
216
+ const name = data.errorCode ? generateCommonErrorSchemaName(data.errorCode) : data.names[0];
217
+ commonSchemas.push({
218
+ name,
219
+ schema: data.schema,
220
+ fingerprint,
221
+ count: data.names.length
222
+ });
223
+ }
224
+ }
225
+ return commonSchemas.sort((a, b) => b.count - a.count);
226
+ }
227
+
145
228
  // src/types/boolean.ts
146
229
  function convertOpenAPIBooleanToZod(_) {
147
230
  return "z.boolean()";
@@ -622,11 +705,150 @@ function generateRouteSchemaNames(route) {
622
705
  return result;
623
706
  }
624
707
 
625
- // src/to-typescript.ts
708
+ // src/interface-generator.ts
626
709
  var validIdentifierRegex2 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
627
710
  function quotePropertyName2(name) {
628
711
  return validIdentifierRegex2.test(name) ? name : `'${name}'`;
629
712
  }
713
+ function schemaToTypeString(schema) {
714
+ if (!schema || typeof schema !== "object") return "unknown";
715
+ if (schema["$ref"] && typeof schema["$ref"] === "string") {
716
+ const match = schema["$ref"].match(
717
+ /#\/components\/schemas\/(.+)/
718
+ );
719
+ let result2 = "unknown";
720
+ if (match && match[1]) {
721
+ result2 = decodeURIComponent(match[1]);
722
+ }
723
+ if (schema["nullable"] === true) {
724
+ result2 = `(${result2} | null)`;
725
+ }
726
+ return result2;
727
+ }
728
+ let result = "unknown";
729
+ if ("oneOf" in schema && Array.isArray(schema["oneOf"])) {
730
+ const unionMembers = schema["oneOf"].map(
731
+ (s) => schemaToTypeString(s)
732
+ );
733
+ result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
734
+ } else if ("allOf" in schema && Array.isArray(schema["allOf"])) {
735
+ const intersectionMembers = schema["allOf"].map(
736
+ (s) => schemaToTypeString(s)
737
+ );
738
+ result = intersectionMembers.length > 1 ? `(${intersectionMembers.join(" & ")})` : intersectionMembers[0] ?? "unknown";
739
+ } else if ("anyOf" in schema && Array.isArray(schema["anyOf"])) {
740
+ const unionMembers = schema["anyOf"].map(
741
+ (s) => schemaToTypeString(s)
742
+ );
743
+ result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
744
+ } else {
745
+ switch (schema["type"]) {
746
+ case "string":
747
+ if (schema["enum"] && Array.isArray(schema["enum"])) {
748
+ result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
749
+ } else {
750
+ result = "string";
751
+ }
752
+ break;
753
+ case "number":
754
+ case "integer":
755
+ if (schema["enum"] && Array.isArray(schema["enum"])) {
756
+ result = schema["enum"].map((v) => String(v)).join(" | ");
757
+ } else {
758
+ result = "number";
759
+ }
760
+ break;
761
+ case "boolean":
762
+ result = "boolean";
763
+ break;
764
+ case "null":
765
+ result = "null";
766
+ break;
767
+ case "array":
768
+ if (schema["items"]) {
769
+ const itemType = schemaToTypeString(schema["items"]);
770
+ result = `Array<${itemType}>`;
771
+ } else {
772
+ result = "unknown[]";
773
+ }
774
+ break;
775
+ case "object":
776
+ result = objectSchemaToTypeString(schema);
777
+ break;
778
+ default:
779
+ if (schema["properties"]) {
780
+ result = objectSchemaToTypeString(schema);
781
+ } else if (schema["enum"] && Array.isArray(schema["enum"])) {
782
+ result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
783
+ } else {
784
+ result = "unknown";
785
+ }
786
+ break;
787
+ }
788
+ }
789
+ if (schema["nullable"] === true) {
790
+ result = `(${result} | null)`;
791
+ }
792
+ return result;
793
+ }
794
+ function objectSchemaToTypeString(schema) {
795
+ const properties = schema["properties"];
796
+ const required = new Set(schema["required"] ?? []);
797
+ const additionalProperties = schema["additionalProperties"];
798
+ if (!properties && !additionalProperties) {
799
+ return "Record<string, unknown>";
800
+ }
801
+ const propertyStrings = [];
802
+ if (properties) {
803
+ for (const [propName, propSchema] of Object.entries(properties)) {
804
+ const isRequired = required.has(propName);
805
+ const propType = schemaToTypeString(propSchema);
806
+ const quotedName = quotePropertyName2(propName);
807
+ propertyStrings.push(
808
+ `${quotedName}${isRequired ? "" : "?"}: ${propType}`
809
+ );
810
+ }
811
+ }
812
+ if (additionalProperties === true) {
813
+ propertyStrings.push("[key: string]: unknown");
814
+ } else if (typeof additionalProperties === "object" && additionalProperties !== null) {
815
+ const additionalType = schemaToTypeString(additionalProperties);
816
+ propertyStrings.push(`[key: string]: ${additionalType}`);
817
+ }
818
+ return `{ ${propertyStrings.join("; ")} }`;
819
+ }
820
+ function generateInterface(name, schema) {
821
+ const properties = schema["properties"];
822
+ const required = new Set(schema["required"] ?? []);
823
+ if (schema["type"] !== "object" && !properties) {
824
+ return `export type ${name} = ${schemaToTypeString(schema)};`;
825
+ }
826
+ const lines = [];
827
+ lines.push(`export interface ${name} {`);
828
+ if (properties) {
829
+ for (const [propName, propSchema] of Object.entries(properties)) {
830
+ const isRequired = required.has(propName);
831
+ const propType = schemaToTypeString(propSchema);
832
+ const quotedName = quotePropertyName2(propName);
833
+ lines.push(` ${quotedName}${isRequired ? "" : "?"}: ${propType};`);
834
+ }
835
+ }
836
+ const additionalProperties = schema["additionalProperties"];
837
+ if (additionalProperties === true) {
838
+ lines.push(" [key: string]: unknown;");
839
+ } else if (typeof additionalProperties === "object" && additionalProperties !== null) {
840
+ const additionalType = schemaToTypeString(additionalProperties);
841
+ lines.push(` [key: string]: ${additionalType};`);
842
+ }
843
+ lines.push("}");
844
+ return lines.join("\n");
845
+ }
846
+
847
+ // src/to-typescript.ts
848
+ var validIdentifierRegex3 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
849
+ function quotePropertyName3(name) {
850
+ return validIdentifierRegex3.test(name) ? name : `'${name}'`;
851
+ }
630
852
  function generateRouteSchemaName2(path, method, suffix) {
631
853
  const pathParts = path.split("/").filter((p) => p).map((p) => {
632
854
  if (p.startsWith("{") && p.endsWith("}")) {
@@ -640,68 +862,138 @@ function generateRouteSchemaName2(path, method, suffix) {
640
862
  const parts = [methodPrefix, ...pathParts, suffix];
641
863
  return parts.join("");
642
864
  }
643
- function generateRouteSchemas(routes, convertSchema) {
644
- const lines = [];
645
- const schemaNames = /* @__PURE__ */ new Set();
865
+ function generateRouteSchemas(routes, convertSchema, registry) {
866
+ const declarations = [];
867
+ const schemaNameToCanonical = /* @__PURE__ */ new Map();
868
+ const generatedNames = /* @__PURE__ */ new Set();
646
869
  for (const route of routes) {
647
870
  const names = generateRouteSchemaNames(route);
648
871
  const pathParams = route.parameters.filter((p) => p.in === "path");
649
872
  const queryParams = route.parameters.filter((p) => p.in === "query");
650
873
  const headerParams = route.parameters.filter((p) => p.in === "header");
651
874
  if (names.paramsSchemaName && pathParams.length > 0) {
652
- if (!schemaNames.has(names.paramsSchemaName)) {
653
- schemaNames.add(names.paramsSchemaName);
875
+ const paramsSchema = {
876
+ type: "object",
877
+ properties: Object.fromEntries(
878
+ pathParams.map((p) => [p.name, p.schema])
879
+ ),
880
+ required: pathParams.filter((p) => p.required).map((p) => p.name)
881
+ };
882
+ const { isNew, canonicalName } = registerSchema(
883
+ registry,
884
+ names.paramsSchemaName,
885
+ paramsSchema
886
+ );
887
+ schemaNameToCanonical.set(names.paramsSchemaName, canonicalName);
888
+ if (isNew && !generatedNames.has(names.paramsSchemaName)) {
889
+ generatedNames.add(names.paramsSchemaName);
654
890
  const properties = [];
655
- const required = [];
656
891
  for (const param of pathParams) {
657
892
  const zodExpr = convertSchema(param.schema);
658
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
659
- if (param.required) {
660
- required.push(param.name);
661
- }
893
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
662
894
  }
663
- lines.push(
895
+ declarations.push(
664
896
  `export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`
665
897
  );
898
+ } else if (!isNew && names.paramsSchemaName !== canonicalName) {
899
+ if (!generatedNames.has(names.paramsSchemaName)) {
900
+ generatedNames.add(names.paramsSchemaName);
901
+ declarations.push(
902
+ `export const ${names.paramsSchemaName} = ${canonicalName};`
903
+ );
904
+ }
666
905
  }
667
906
  }
668
907
  if (names.querySchemaName && queryParams.length > 0) {
669
- if (!schemaNames.has(names.querySchemaName)) {
670
- schemaNames.add(names.querySchemaName);
908
+ const querySchema = {
909
+ type: "object",
910
+ properties: Object.fromEntries(
911
+ queryParams.map((p) => [p.name, p.schema])
912
+ ),
913
+ required: queryParams.filter((p) => p.required).map((p) => p.name)
914
+ };
915
+ const { isNew, canonicalName } = registerSchema(
916
+ registry,
917
+ names.querySchemaName,
918
+ querySchema
919
+ );
920
+ schemaNameToCanonical.set(names.querySchemaName, canonicalName);
921
+ if (isNew && !generatedNames.has(names.querySchemaName)) {
922
+ generatedNames.add(names.querySchemaName);
671
923
  const properties = [];
672
924
  for (const param of queryParams) {
673
925
  let zodExpr = convertSchema(param.schema);
674
926
  if (!param.required) {
675
927
  zodExpr += ".optional()";
676
928
  }
677
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
929
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
678
930
  }
679
- lines.push(
931
+ declarations.push(
680
932
  `export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`
681
933
  );
934
+ } else if (!isNew && names.querySchemaName !== canonicalName) {
935
+ if (!generatedNames.has(names.querySchemaName)) {
936
+ generatedNames.add(names.querySchemaName);
937
+ declarations.push(
938
+ `export const ${names.querySchemaName} = ${canonicalName};`
939
+ );
940
+ }
682
941
  }
683
942
  }
684
943
  if (names.headersSchemaName && headerParams.length > 0) {
685
- if (!schemaNames.has(names.headersSchemaName)) {
686
- schemaNames.add(names.headersSchemaName);
944
+ const headersSchema = {
945
+ type: "object",
946
+ properties: Object.fromEntries(
947
+ headerParams.map((p) => [p.name, p.schema])
948
+ ),
949
+ required: headerParams.filter((p) => p.required).map((p) => p.name)
950
+ };
951
+ const { isNew, canonicalName } = registerSchema(
952
+ registry,
953
+ names.headersSchemaName,
954
+ headersSchema
955
+ );
956
+ schemaNameToCanonical.set(names.headersSchemaName, canonicalName);
957
+ if (isNew && !generatedNames.has(names.headersSchemaName)) {
958
+ generatedNames.add(names.headersSchemaName);
687
959
  const properties = [];
688
960
  for (const param of headerParams) {
689
961
  let zodExpr = convertSchema(param.schema);
690
962
  if (!param.required) {
691
963
  zodExpr += ".optional()";
692
964
  }
693
- properties.push(`${quotePropertyName2(param.name)}: ${zodExpr}`);
965
+ properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
694
966
  }
695
- lines.push(
967
+ declarations.push(
696
968
  `export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`
697
969
  );
970
+ } else if (!isNew && names.headersSchemaName !== canonicalName) {
971
+ if (!generatedNames.has(names.headersSchemaName)) {
972
+ generatedNames.add(names.headersSchemaName);
973
+ declarations.push(
974
+ `export const ${names.headersSchemaName} = ${canonicalName};`
975
+ );
976
+ }
698
977
  }
699
978
  }
700
979
  if (names.bodySchemaName && route.requestBody) {
701
- if (!schemaNames.has(names.bodySchemaName)) {
702
- schemaNames.add(names.bodySchemaName);
980
+ const { isNew, canonicalName } = registerSchema(
981
+ registry,
982
+ names.bodySchemaName,
983
+ route.requestBody
984
+ );
985
+ schemaNameToCanonical.set(names.bodySchemaName, canonicalName);
986
+ if (isNew && !generatedNames.has(names.bodySchemaName)) {
987
+ generatedNames.add(names.bodySchemaName);
703
988
  const zodExpr = convertSchema(route.requestBody);
704
- lines.push(`export const ${names.bodySchemaName} = ${zodExpr};`);
989
+ declarations.push(`export const ${names.bodySchemaName} = ${zodExpr};`);
990
+ } else if (!isNew && names.bodySchemaName !== canonicalName) {
991
+ if (!generatedNames.has(names.bodySchemaName)) {
992
+ generatedNames.add(names.bodySchemaName);
993
+ declarations.push(
994
+ `export const ${names.bodySchemaName} = ${canonicalName};`
995
+ );
996
+ }
705
997
  }
706
998
  }
707
999
  for (const [statusCode, responseSchema] of Object.entries(
@@ -715,19 +1007,35 @@ function generateRouteSchemas(routes, convertSchema) {
715
1007
  route.method,
716
1008
  suffix
717
1009
  );
718
- if (!schemaNames.has(responseSchemaName)) {
719
- schemaNames.add(responseSchemaName);
1010
+ const { isNew, canonicalName } = registerSchema(
1011
+ registry,
1012
+ responseSchemaName,
1013
+ responseSchema
1014
+ );
1015
+ schemaNameToCanonical.set(responseSchemaName, canonicalName);
1016
+ if (isNew && !generatedNames.has(responseSchemaName)) {
1017
+ generatedNames.add(responseSchemaName);
720
1018
  const zodExpr = convertSchema(responseSchema);
721
- lines.push(`export const ${responseSchemaName} = ${zodExpr};`);
1019
+ declarations.push(`export const ${responseSchemaName} = ${zodExpr};`);
1020
+ } else if (!isNew && responseSchemaName !== canonicalName) {
1021
+ if (!generatedNames.has(responseSchemaName)) {
1022
+ generatedNames.add(responseSchemaName);
1023
+ declarations.push(
1024
+ `export const ${responseSchemaName} = ${canonicalName};`
1025
+ );
1026
+ }
722
1027
  }
723
1028
  }
724
1029
  }
725
- return lines;
1030
+ return { declarations, schemaNameToCanonical };
726
1031
  }
727
- function generateRequestResponseObjects(routes) {
1032
+ function generateRequestResponseObjects(routes, schemaNameToCanonical) {
728
1033
  const lines = [];
729
1034
  const requestPaths = {};
730
1035
  const responsePaths = {};
1036
+ const resolveSchemaName = (name) => {
1037
+ return schemaNameToCanonical.get(name) ?? name;
1038
+ };
731
1039
  for (const route of routes) {
732
1040
  const names = generateRouteSchemaNames(route);
733
1041
  const pathParams = route.parameters.filter((p) => p.in === "path");
@@ -742,16 +1050,20 @@ function generateRequestResponseObjects(routes) {
742
1050
  }
743
1051
  const requestParts = [];
744
1052
  if (names.paramsSchemaName && pathParams.length > 0) {
745
- requestParts.push(`params: ${names.paramsSchemaName}`);
1053
+ requestParts.push(
1054
+ `params: ${resolveSchemaName(names.paramsSchemaName)}`
1055
+ );
746
1056
  }
747
1057
  if (names.querySchemaName && queryParams.length > 0) {
748
- requestParts.push(`query: ${names.querySchemaName}`);
1058
+ requestParts.push(`query: ${resolveSchemaName(names.querySchemaName)}`);
749
1059
  }
750
1060
  if (names.headersSchemaName && headerParams.length > 0) {
751
- requestParts.push(`headers: ${names.headersSchemaName}`);
1061
+ requestParts.push(
1062
+ `headers: ${resolveSchemaName(names.headersSchemaName)}`
1063
+ );
752
1064
  }
753
1065
  if (names.bodySchemaName && route.requestBody) {
754
- requestParts.push(`body: ${names.bodySchemaName}`);
1066
+ requestParts.push(`body: ${resolveSchemaName(names.bodySchemaName)}`);
755
1067
  }
756
1068
  if (requestParts.length > 0) {
757
1069
  requestMethodObj[route.method] = requestParts;
@@ -774,7 +1086,7 @@ function generateRequestResponseObjects(routes) {
774
1086
  route.method,
775
1087
  suffix
776
1088
  );
777
- responseMethodObj[route.method][statusCode] = responseSchemaName;
1089
+ responseMethodObj[route.method][statusCode] = resolveSchemaName(responseSchemaName);
778
1090
  }
779
1091
  }
780
1092
  lines.push("export const Request = {");
@@ -814,6 +1126,25 @@ function generateRequestResponseObjects(routes) {
814
1126
  lines.push("} as const;");
815
1127
  return lines;
816
1128
  }
1129
+ function collectRouteSchemas(routes) {
1130
+ const collected = [];
1131
+ for (const route of routes) {
1132
+ for (const [statusCode, responseSchema] of Object.entries(
1133
+ route.responses
1134
+ )) {
1135
+ if (!responseSchema) continue;
1136
+ const isSuccess = statusCode.startsWith("2");
1137
+ const suffix = isSuccess ? `${statusCode}Response` : `${statusCode}ErrorResponse`;
1138
+ const responseSchemaName = generateRouteSchemaName2(
1139
+ route.path,
1140
+ route.method,
1141
+ suffix
1142
+ );
1143
+ collected.push({ name: responseSchemaName, schema: responseSchema });
1144
+ }
1145
+ }
1146
+ return collected;
1147
+ }
817
1148
  var openApiToZodTsCode = (openapi, customImportLines, options) => {
818
1149
  const components = openapi["components"];
819
1150
  const schemas = components?.["schemas"] ?? {};
@@ -826,29 +1157,58 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
826
1157
  lines.push("import { z } from 'zod';");
827
1158
  lines.push(...customImportLines ?? []);
828
1159
  lines.push("");
1160
+ lines.push("// Type assertion helper - verifies interface matches schema at compile time");
1161
+ lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
1162
+ lines.push("");
1163
+ const registry = createSchemaRegistry();
829
1164
  const sortedSchemaNames = topologicalSortSchemas(schemas);
1165
+ const typeAssertions = [];
830
1166
  for (const name of sortedSchemaNames) {
831
1167
  const schema = schemas[name];
832
1168
  if (schema) {
833
1169
  const zodExpr = convertSchemaToZodString(schema);
834
1170
  const schemaName = `${name}Schema`;
835
1171
  const typeName = name;
836
- lines.push(`export const ${schemaName} = ${zodExpr};`);
837
- lines.push(`export type ${typeName} = z.infer<typeof ${schemaName}>;`);
1172
+ lines.push(generateInterface(typeName, schema));
1173
+ lines.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
838
1174
  lines.push("");
1175
+ typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
1176
+ const fingerprint = getSchemaFingerprint(schema);
1177
+ preRegisterSchema(registry, schemaName, fingerprint);
839
1178
  }
840
1179
  }
1180
+ if (typeAssertions.length > 0) {
1181
+ lines.push("// Compile-time type assertions - ensure interfaces match schemas");
1182
+ lines.push(typeAssertions.join("\n"));
1183
+ lines.push("");
1184
+ }
841
1185
  if (options?.includeRoutes) {
842
1186
  const routes = parseOpenApiPaths(openapi);
843
1187
  if (routes.length > 0) {
844
- const routeSchemas = generateRouteSchemas(
1188
+ const routeSchemaList = collectRouteSchemas(routes);
1189
+ const commonSchemas = findCommonSchemas(routeSchemaList, 2);
1190
+ if (commonSchemas.length > 0) {
1191
+ lines.push("// Common Error Schemas (deduplicated)");
1192
+ for (const common of commonSchemas) {
1193
+ const zodExpr = convertSchemaToZodString(common.schema);
1194
+ lines.push(`export const ${common.name} = ${zodExpr};`);
1195
+ preRegisterSchema(registry, common.name, common.fingerprint);
1196
+ }
1197
+ lines.push("");
1198
+ }
1199
+ const { declarations, schemaNameToCanonical } = generateRouteSchemas(
845
1200
  routes,
846
- convertSchemaToZodString
1201
+ convertSchemaToZodString,
1202
+ registry
847
1203
  );
848
- if (routeSchemas.length > 0) {
849
- lines.push(...routeSchemas);
1204
+ if (declarations.length > 0) {
1205
+ lines.push("// Route Schemas");
1206
+ lines.push(...declarations);
850
1207
  lines.push("");
851
- const requestResponseObjs = generateRequestResponseObjects(routes);
1208
+ const requestResponseObjs = generateRequestResponseObjects(
1209
+ routes,
1210
+ schemaNameToCanonical
1211
+ );
852
1212
  lines.push(...requestResponseObjs);
853
1213
  }
854
1214
  }
@@ -860,11 +1220,13 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
860
1220
  SUPPORTED_STRING_FORMATS,
861
1221
  clearZodSchemaToOpenApiSchemaRegistry,
862
1222
  convertSchemaToZodString,
1223
+ generateInterface,
863
1224
  generateRouteSchemaNames,
864
1225
  getSchemaExportedVariableNameForStringFormat,
865
1226
  openApiToZodTsCode,
866
1227
  parseOpenApiPaths,
867
1228
  registerZodSchemaToOpenApiSchema,
868
- schemaRegistry
1229
+ schemaRegistry,
1230
+ schemaToTypeString
869
1231
  });
870
1232
  //# sourceMappingURL=index.cjs.map