@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.
- package/.turbo/turbo-build.log +10 -10
- package/dist/index.cjs +404 -42
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +25 -1
- package/dist/index.d.ts +25 -1
- package/dist/index.js +401 -41
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/interface-generator.spec.ts +595 -0
- package/src/interface-generator.ts +211 -0
- package/src/schema-dedup.ts +199 -0
- package/src/to-typescript.spec.ts +218 -0
- package/src/to-typescript.ts +253 -36
- package/src/to-zod.spec.ts +10 -6
package/dist/index.js
CHANGED
|
@@ -108,6 +108,87 @@ function topologicalSortSchemas(schemas) {
|
|
|
108
108
|
return sorted;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
+
// src/schema-dedup.ts
|
|
112
|
+
function sortObjectDeep(obj) {
|
|
113
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
114
|
+
if (Array.isArray(obj)) return obj.map(sortObjectDeep);
|
|
115
|
+
const sorted = {};
|
|
116
|
+
const keys = Object.keys(obj).sort();
|
|
117
|
+
for (const key of keys) {
|
|
118
|
+
sorted[key] = sortObjectDeep(obj[key]);
|
|
119
|
+
}
|
|
120
|
+
return sorted;
|
|
121
|
+
}
|
|
122
|
+
function getSchemaFingerprint(schema) {
|
|
123
|
+
return JSON.stringify(sortObjectDeep(schema));
|
|
124
|
+
}
|
|
125
|
+
function createSchemaRegistry() {
|
|
126
|
+
return {
|
|
127
|
+
fingerprintToName: /* @__PURE__ */ new Map(),
|
|
128
|
+
nameToFingerprint: /* @__PURE__ */ new Map()
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function registerSchema(registry, name, schema) {
|
|
132
|
+
const fingerprint = getSchemaFingerprint(schema);
|
|
133
|
+
const existing = registry.fingerprintToName.get(fingerprint);
|
|
134
|
+
if (existing) {
|
|
135
|
+
return { isNew: false, canonicalName: existing };
|
|
136
|
+
}
|
|
137
|
+
registry.fingerprintToName.set(fingerprint, name);
|
|
138
|
+
registry.nameToFingerprint.set(name, fingerprint);
|
|
139
|
+
return { isNew: true, canonicalName: name };
|
|
140
|
+
}
|
|
141
|
+
function preRegisterSchema(registry, name, fingerprint) {
|
|
142
|
+
registry.fingerprintToName.set(fingerprint, name);
|
|
143
|
+
registry.nameToFingerprint.set(name, fingerprint);
|
|
144
|
+
}
|
|
145
|
+
function extractErrorCode(schema) {
|
|
146
|
+
const properties = schema?.["properties"];
|
|
147
|
+
const errorObj = properties?.["error"];
|
|
148
|
+
const errorProps = errorObj?.["properties"];
|
|
149
|
+
const codeSchema = errorProps?.["code"];
|
|
150
|
+
const codeEnum = codeSchema?.["enum"];
|
|
151
|
+
if (Array.isArray(codeEnum) && codeEnum.length === 1) {
|
|
152
|
+
return codeEnum[0];
|
|
153
|
+
}
|
|
154
|
+
return null;
|
|
155
|
+
}
|
|
156
|
+
function errorCodeToPascalCase(code) {
|
|
157
|
+
return code.split("_").map((part) => part.charAt(0) + part.slice(1).toLowerCase()).join("");
|
|
158
|
+
}
|
|
159
|
+
function generateCommonErrorSchemaName(errorCode) {
|
|
160
|
+
return `${errorCodeToPascalCase(errorCode)}ErrorSchema`;
|
|
161
|
+
}
|
|
162
|
+
function findCommonSchemas(schemas, minCount = 2) {
|
|
163
|
+
const fingerprints = /* @__PURE__ */ new Map();
|
|
164
|
+
for (const { name, schema } of schemas) {
|
|
165
|
+
const fingerprint = getSchemaFingerprint(schema);
|
|
166
|
+
const existing = fingerprints.get(fingerprint);
|
|
167
|
+
if (existing) {
|
|
168
|
+
existing.names.push(name);
|
|
169
|
+
} else {
|
|
170
|
+
fingerprints.set(fingerprint, {
|
|
171
|
+
schema,
|
|
172
|
+
names: [name],
|
|
173
|
+
errorCode: extractErrorCode(schema)
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
const commonSchemas = [];
|
|
178
|
+
for (const [fingerprint, data] of fingerprints) {
|
|
179
|
+
if (data.names.length >= minCount) {
|
|
180
|
+
const name = data.errorCode ? generateCommonErrorSchemaName(data.errorCode) : data.names[0];
|
|
181
|
+
commonSchemas.push({
|
|
182
|
+
name,
|
|
183
|
+
schema: data.schema,
|
|
184
|
+
fingerprint,
|
|
185
|
+
count: data.names.length
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return commonSchemas.sort((a, b) => b.count - a.count);
|
|
190
|
+
}
|
|
191
|
+
|
|
111
192
|
// src/types/boolean.ts
|
|
112
193
|
function convertOpenAPIBooleanToZod(_) {
|
|
113
194
|
return "z.boolean()";
|
|
@@ -588,11 +669,150 @@ function generateRouteSchemaNames(route) {
|
|
|
588
669
|
return result;
|
|
589
670
|
}
|
|
590
671
|
|
|
591
|
-
// src/
|
|
672
|
+
// src/interface-generator.ts
|
|
592
673
|
var validIdentifierRegex2 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
593
674
|
function quotePropertyName2(name) {
|
|
594
675
|
return validIdentifierRegex2.test(name) ? name : `'${name}'`;
|
|
595
676
|
}
|
|
677
|
+
function schemaToTypeString(schema) {
|
|
678
|
+
if (!schema || typeof schema !== "object") return "unknown";
|
|
679
|
+
if (schema["$ref"] && typeof schema["$ref"] === "string") {
|
|
680
|
+
const match = schema["$ref"].match(
|
|
681
|
+
/#\/components\/schemas\/(.+)/
|
|
682
|
+
);
|
|
683
|
+
let result2 = "unknown";
|
|
684
|
+
if (match && match[1]) {
|
|
685
|
+
result2 = decodeURIComponent(match[1]);
|
|
686
|
+
}
|
|
687
|
+
if (schema["nullable"] === true) {
|
|
688
|
+
result2 = `(${result2} | null)`;
|
|
689
|
+
}
|
|
690
|
+
return result2;
|
|
691
|
+
}
|
|
692
|
+
let result = "unknown";
|
|
693
|
+
if ("oneOf" in schema && Array.isArray(schema["oneOf"])) {
|
|
694
|
+
const unionMembers = schema["oneOf"].map(
|
|
695
|
+
(s) => schemaToTypeString(s)
|
|
696
|
+
);
|
|
697
|
+
result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
|
|
698
|
+
} else if ("allOf" in schema && Array.isArray(schema["allOf"])) {
|
|
699
|
+
const intersectionMembers = schema["allOf"].map(
|
|
700
|
+
(s) => schemaToTypeString(s)
|
|
701
|
+
);
|
|
702
|
+
result = intersectionMembers.length > 1 ? `(${intersectionMembers.join(" & ")})` : intersectionMembers[0] ?? "unknown";
|
|
703
|
+
} else if ("anyOf" in schema && Array.isArray(schema["anyOf"])) {
|
|
704
|
+
const unionMembers = schema["anyOf"].map(
|
|
705
|
+
(s) => schemaToTypeString(s)
|
|
706
|
+
);
|
|
707
|
+
result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
|
|
708
|
+
} else {
|
|
709
|
+
switch (schema["type"]) {
|
|
710
|
+
case "string":
|
|
711
|
+
if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
712
|
+
result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
|
|
713
|
+
} else {
|
|
714
|
+
result = "string";
|
|
715
|
+
}
|
|
716
|
+
break;
|
|
717
|
+
case "number":
|
|
718
|
+
case "integer":
|
|
719
|
+
if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
720
|
+
result = schema["enum"].map((v) => String(v)).join(" | ");
|
|
721
|
+
} else {
|
|
722
|
+
result = "number";
|
|
723
|
+
}
|
|
724
|
+
break;
|
|
725
|
+
case "boolean":
|
|
726
|
+
result = "boolean";
|
|
727
|
+
break;
|
|
728
|
+
case "null":
|
|
729
|
+
result = "null";
|
|
730
|
+
break;
|
|
731
|
+
case "array":
|
|
732
|
+
if (schema["items"]) {
|
|
733
|
+
const itemType = schemaToTypeString(schema["items"]);
|
|
734
|
+
result = `Array<${itemType}>`;
|
|
735
|
+
} else {
|
|
736
|
+
result = "unknown[]";
|
|
737
|
+
}
|
|
738
|
+
break;
|
|
739
|
+
case "object":
|
|
740
|
+
result = objectSchemaToTypeString(schema);
|
|
741
|
+
break;
|
|
742
|
+
default:
|
|
743
|
+
if (schema["properties"]) {
|
|
744
|
+
result = objectSchemaToTypeString(schema);
|
|
745
|
+
} else if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
746
|
+
result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
|
|
747
|
+
} else {
|
|
748
|
+
result = "unknown";
|
|
749
|
+
}
|
|
750
|
+
break;
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (schema["nullable"] === true) {
|
|
754
|
+
result = `(${result} | null)`;
|
|
755
|
+
}
|
|
756
|
+
return result;
|
|
757
|
+
}
|
|
758
|
+
function objectSchemaToTypeString(schema) {
|
|
759
|
+
const properties = schema["properties"];
|
|
760
|
+
const required = new Set(schema["required"] ?? []);
|
|
761
|
+
const additionalProperties = schema["additionalProperties"];
|
|
762
|
+
if (!properties && !additionalProperties) {
|
|
763
|
+
return "Record<string, unknown>";
|
|
764
|
+
}
|
|
765
|
+
const propertyStrings = [];
|
|
766
|
+
if (properties) {
|
|
767
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
768
|
+
const isRequired = required.has(propName);
|
|
769
|
+
const propType = schemaToTypeString(propSchema);
|
|
770
|
+
const quotedName = quotePropertyName2(propName);
|
|
771
|
+
propertyStrings.push(
|
|
772
|
+
`${quotedName}${isRequired ? "" : "?"}: ${propType}`
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
if (additionalProperties === true) {
|
|
777
|
+
propertyStrings.push("[key: string]: unknown");
|
|
778
|
+
} else if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
779
|
+
const additionalType = schemaToTypeString(additionalProperties);
|
|
780
|
+
propertyStrings.push(`[key: string]: ${additionalType}`);
|
|
781
|
+
}
|
|
782
|
+
return `{ ${propertyStrings.join("; ")} }`;
|
|
783
|
+
}
|
|
784
|
+
function generateInterface(name, schema) {
|
|
785
|
+
const properties = schema["properties"];
|
|
786
|
+
const required = new Set(schema["required"] ?? []);
|
|
787
|
+
if (schema["type"] !== "object" && !properties) {
|
|
788
|
+
return `export type ${name} = ${schemaToTypeString(schema)};`;
|
|
789
|
+
}
|
|
790
|
+
const lines = [];
|
|
791
|
+
lines.push(`export interface ${name} {`);
|
|
792
|
+
if (properties) {
|
|
793
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
794
|
+
const isRequired = required.has(propName);
|
|
795
|
+
const propType = schemaToTypeString(propSchema);
|
|
796
|
+
const quotedName = quotePropertyName2(propName);
|
|
797
|
+
lines.push(` ${quotedName}${isRequired ? "" : "?"}: ${propType};`);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
const additionalProperties = schema["additionalProperties"];
|
|
801
|
+
if (additionalProperties === true) {
|
|
802
|
+
lines.push(" [key: string]: unknown;");
|
|
803
|
+
} else if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
804
|
+
const additionalType = schemaToTypeString(additionalProperties);
|
|
805
|
+
lines.push(` [key: string]: ${additionalType};`);
|
|
806
|
+
}
|
|
807
|
+
lines.push("}");
|
|
808
|
+
return lines.join("\n");
|
|
809
|
+
}
|
|
810
|
+
|
|
811
|
+
// src/to-typescript.ts
|
|
812
|
+
var validIdentifierRegex3 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
813
|
+
function quotePropertyName3(name) {
|
|
814
|
+
return validIdentifierRegex3.test(name) ? name : `'${name}'`;
|
|
815
|
+
}
|
|
596
816
|
function generateRouteSchemaName2(path, method, suffix) {
|
|
597
817
|
const pathParts = path.split("/").filter((p) => p).map((p) => {
|
|
598
818
|
if (p.startsWith("{") && p.endsWith("}")) {
|
|
@@ -606,68 +826,138 @@ function generateRouteSchemaName2(path, method, suffix) {
|
|
|
606
826
|
const parts = [methodPrefix, ...pathParts, suffix];
|
|
607
827
|
return parts.join("");
|
|
608
828
|
}
|
|
609
|
-
function generateRouteSchemas(routes, convertSchema) {
|
|
610
|
-
const
|
|
611
|
-
const
|
|
829
|
+
function generateRouteSchemas(routes, convertSchema, registry) {
|
|
830
|
+
const declarations = [];
|
|
831
|
+
const schemaNameToCanonical = /* @__PURE__ */ new Map();
|
|
832
|
+
const generatedNames = /* @__PURE__ */ new Set();
|
|
612
833
|
for (const route of routes) {
|
|
613
834
|
const names = generateRouteSchemaNames(route);
|
|
614
835
|
const pathParams = route.parameters.filter((p) => p.in === "path");
|
|
615
836
|
const queryParams = route.parameters.filter((p) => p.in === "query");
|
|
616
837
|
const headerParams = route.parameters.filter((p) => p.in === "header");
|
|
617
838
|
if (names.paramsSchemaName && pathParams.length > 0) {
|
|
618
|
-
|
|
619
|
-
|
|
839
|
+
const paramsSchema = {
|
|
840
|
+
type: "object",
|
|
841
|
+
properties: Object.fromEntries(
|
|
842
|
+
pathParams.map((p) => [p.name, p.schema])
|
|
843
|
+
),
|
|
844
|
+
required: pathParams.filter((p) => p.required).map((p) => p.name)
|
|
845
|
+
};
|
|
846
|
+
const { isNew, canonicalName } = registerSchema(
|
|
847
|
+
registry,
|
|
848
|
+
names.paramsSchemaName,
|
|
849
|
+
paramsSchema
|
|
850
|
+
);
|
|
851
|
+
schemaNameToCanonical.set(names.paramsSchemaName, canonicalName);
|
|
852
|
+
if (isNew && !generatedNames.has(names.paramsSchemaName)) {
|
|
853
|
+
generatedNames.add(names.paramsSchemaName);
|
|
620
854
|
const properties = [];
|
|
621
|
-
const required = [];
|
|
622
855
|
for (const param of pathParams) {
|
|
623
856
|
const zodExpr = convertSchema(param.schema);
|
|
624
|
-
properties.push(`${
|
|
625
|
-
if (param.required) {
|
|
626
|
-
required.push(param.name);
|
|
627
|
-
}
|
|
857
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
628
858
|
}
|
|
629
|
-
|
|
859
|
+
declarations.push(
|
|
630
860
|
`export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`
|
|
631
861
|
);
|
|
862
|
+
} else if (!isNew && names.paramsSchemaName !== canonicalName) {
|
|
863
|
+
if (!generatedNames.has(names.paramsSchemaName)) {
|
|
864
|
+
generatedNames.add(names.paramsSchemaName);
|
|
865
|
+
declarations.push(
|
|
866
|
+
`export const ${names.paramsSchemaName} = ${canonicalName};`
|
|
867
|
+
);
|
|
868
|
+
}
|
|
632
869
|
}
|
|
633
870
|
}
|
|
634
871
|
if (names.querySchemaName && queryParams.length > 0) {
|
|
635
|
-
|
|
636
|
-
|
|
872
|
+
const querySchema = {
|
|
873
|
+
type: "object",
|
|
874
|
+
properties: Object.fromEntries(
|
|
875
|
+
queryParams.map((p) => [p.name, p.schema])
|
|
876
|
+
),
|
|
877
|
+
required: queryParams.filter((p) => p.required).map((p) => p.name)
|
|
878
|
+
};
|
|
879
|
+
const { isNew, canonicalName } = registerSchema(
|
|
880
|
+
registry,
|
|
881
|
+
names.querySchemaName,
|
|
882
|
+
querySchema
|
|
883
|
+
);
|
|
884
|
+
schemaNameToCanonical.set(names.querySchemaName, canonicalName);
|
|
885
|
+
if (isNew && !generatedNames.has(names.querySchemaName)) {
|
|
886
|
+
generatedNames.add(names.querySchemaName);
|
|
637
887
|
const properties = [];
|
|
638
888
|
for (const param of queryParams) {
|
|
639
889
|
let zodExpr = convertSchema(param.schema);
|
|
640
890
|
if (!param.required) {
|
|
641
891
|
zodExpr += ".optional()";
|
|
642
892
|
}
|
|
643
|
-
properties.push(`${
|
|
893
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
644
894
|
}
|
|
645
|
-
|
|
895
|
+
declarations.push(
|
|
646
896
|
`export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`
|
|
647
897
|
);
|
|
898
|
+
} else if (!isNew && names.querySchemaName !== canonicalName) {
|
|
899
|
+
if (!generatedNames.has(names.querySchemaName)) {
|
|
900
|
+
generatedNames.add(names.querySchemaName);
|
|
901
|
+
declarations.push(
|
|
902
|
+
`export const ${names.querySchemaName} = ${canonicalName};`
|
|
903
|
+
);
|
|
904
|
+
}
|
|
648
905
|
}
|
|
649
906
|
}
|
|
650
907
|
if (names.headersSchemaName && headerParams.length > 0) {
|
|
651
|
-
|
|
652
|
-
|
|
908
|
+
const headersSchema = {
|
|
909
|
+
type: "object",
|
|
910
|
+
properties: Object.fromEntries(
|
|
911
|
+
headerParams.map((p) => [p.name, p.schema])
|
|
912
|
+
),
|
|
913
|
+
required: headerParams.filter((p) => p.required).map((p) => p.name)
|
|
914
|
+
};
|
|
915
|
+
const { isNew, canonicalName } = registerSchema(
|
|
916
|
+
registry,
|
|
917
|
+
names.headersSchemaName,
|
|
918
|
+
headersSchema
|
|
919
|
+
);
|
|
920
|
+
schemaNameToCanonical.set(names.headersSchemaName, canonicalName);
|
|
921
|
+
if (isNew && !generatedNames.has(names.headersSchemaName)) {
|
|
922
|
+
generatedNames.add(names.headersSchemaName);
|
|
653
923
|
const properties = [];
|
|
654
924
|
for (const param of headerParams) {
|
|
655
925
|
let zodExpr = convertSchema(param.schema);
|
|
656
926
|
if (!param.required) {
|
|
657
927
|
zodExpr += ".optional()";
|
|
658
928
|
}
|
|
659
|
-
properties.push(`${
|
|
929
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
660
930
|
}
|
|
661
|
-
|
|
931
|
+
declarations.push(
|
|
662
932
|
`export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`
|
|
663
933
|
);
|
|
934
|
+
} else if (!isNew && names.headersSchemaName !== canonicalName) {
|
|
935
|
+
if (!generatedNames.has(names.headersSchemaName)) {
|
|
936
|
+
generatedNames.add(names.headersSchemaName);
|
|
937
|
+
declarations.push(
|
|
938
|
+
`export const ${names.headersSchemaName} = ${canonicalName};`
|
|
939
|
+
);
|
|
940
|
+
}
|
|
664
941
|
}
|
|
665
942
|
}
|
|
666
943
|
if (names.bodySchemaName && route.requestBody) {
|
|
667
|
-
|
|
668
|
-
|
|
944
|
+
const { isNew, canonicalName } = registerSchema(
|
|
945
|
+
registry,
|
|
946
|
+
names.bodySchemaName,
|
|
947
|
+
route.requestBody
|
|
948
|
+
);
|
|
949
|
+
schemaNameToCanonical.set(names.bodySchemaName, canonicalName);
|
|
950
|
+
if (isNew && !generatedNames.has(names.bodySchemaName)) {
|
|
951
|
+
generatedNames.add(names.bodySchemaName);
|
|
669
952
|
const zodExpr = convertSchema(route.requestBody);
|
|
670
|
-
|
|
953
|
+
declarations.push(`export const ${names.bodySchemaName} = ${zodExpr};`);
|
|
954
|
+
} else if (!isNew && names.bodySchemaName !== canonicalName) {
|
|
955
|
+
if (!generatedNames.has(names.bodySchemaName)) {
|
|
956
|
+
generatedNames.add(names.bodySchemaName);
|
|
957
|
+
declarations.push(
|
|
958
|
+
`export const ${names.bodySchemaName} = ${canonicalName};`
|
|
959
|
+
);
|
|
960
|
+
}
|
|
671
961
|
}
|
|
672
962
|
}
|
|
673
963
|
for (const [statusCode, responseSchema] of Object.entries(
|
|
@@ -681,19 +971,35 @@ function generateRouteSchemas(routes, convertSchema) {
|
|
|
681
971
|
route.method,
|
|
682
972
|
suffix
|
|
683
973
|
);
|
|
684
|
-
|
|
685
|
-
|
|
974
|
+
const { isNew, canonicalName } = registerSchema(
|
|
975
|
+
registry,
|
|
976
|
+
responseSchemaName,
|
|
977
|
+
responseSchema
|
|
978
|
+
);
|
|
979
|
+
schemaNameToCanonical.set(responseSchemaName, canonicalName);
|
|
980
|
+
if (isNew && !generatedNames.has(responseSchemaName)) {
|
|
981
|
+
generatedNames.add(responseSchemaName);
|
|
686
982
|
const zodExpr = convertSchema(responseSchema);
|
|
687
|
-
|
|
983
|
+
declarations.push(`export const ${responseSchemaName} = ${zodExpr};`);
|
|
984
|
+
} else if (!isNew && responseSchemaName !== canonicalName) {
|
|
985
|
+
if (!generatedNames.has(responseSchemaName)) {
|
|
986
|
+
generatedNames.add(responseSchemaName);
|
|
987
|
+
declarations.push(
|
|
988
|
+
`export const ${responseSchemaName} = ${canonicalName};`
|
|
989
|
+
);
|
|
990
|
+
}
|
|
688
991
|
}
|
|
689
992
|
}
|
|
690
993
|
}
|
|
691
|
-
return
|
|
994
|
+
return { declarations, schemaNameToCanonical };
|
|
692
995
|
}
|
|
693
|
-
function generateRequestResponseObjects(routes) {
|
|
996
|
+
function generateRequestResponseObjects(routes, schemaNameToCanonical) {
|
|
694
997
|
const lines = [];
|
|
695
998
|
const requestPaths = {};
|
|
696
999
|
const responsePaths = {};
|
|
1000
|
+
const resolveSchemaName = (name) => {
|
|
1001
|
+
return schemaNameToCanonical.get(name) ?? name;
|
|
1002
|
+
};
|
|
697
1003
|
for (const route of routes) {
|
|
698
1004
|
const names = generateRouteSchemaNames(route);
|
|
699
1005
|
const pathParams = route.parameters.filter((p) => p.in === "path");
|
|
@@ -708,16 +1014,20 @@ function generateRequestResponseObjects(routes) {
|
|
|
708
1014
|
}
|
|
709
1015
|
const requestParts = [];
|
|
710
1016
|
if (names.paramsSchemaName && pathParams.length > 0) {
|
|
711
|
-
requestParts.push(
|
|
1017
|
+
requestParts.push(
|
|
1018
|
+
`params: ${resolveSchemaName(names.paramsSchemaName)}`
|
|
1019
|
+
);
|
|
712
1020
|
}
|
|
713
1021
|
if (names.querySchemaName && queryParams.length > 0) {
|
|
714
|
-
requestParts.push(`query: ${names.querySchemaName}`);
|
|
1022
|
+
requestParts.push(`query: ${resolveSchemaName(names.querySchemaName)}`);
|
|
715
1023
|
}
|
|
716
1024
|
if (names.headersSchemaName && headerParams.length > 0) {
|
|
717
|
-
requestParts.push(
|
|
1025
|
+
requestParts.push(
|
|
1026
|
+
`headers: ${resolveSchemaName(names.headersSchemaName)}`
|
|
1027
|
+
);
|
|
718
1028
|
}
|
|
719
1029
|
if (names.bodySchemaName && route.requestBody) {
|
|
720
|
-
requestParts.push(`body: ${names.bodySchemaName}`);
|
|
1030
|
+
requestParts.push(`body: ${resolveSchemaName(names.bodySchemaName)}`);
|
|
721
1031
|
}
|
|
722
1032
|
if (requestParts.length > 0) {
|
|
723
1033
|
requestMethodObj[route.method] = requestParts;
|
|
@@ -740,7 +1050,7 @@ function generateRequestResponseObjects(routes) {
|
|
|
740
1050
|
route.method,
|
|
741
1051
|
suffix
|
|
742
1052
|
);
|
|
743
|
-
responseMethodObj[route.method][statusCode] = responseSchemaName;
|
|
1053
|
+
responseMethodObj[route.method][statusCode] = resolveSchemaName(responseSchemaName);
|
|
744
1054
|
}
|
|
745
1055
|
}
|
|
746
1056
|
lines.push("export const Request = {");
|
|
@@ -780,6 +1090,25 @@ function generateRequestResponseObjects(routes) {
|
|
|
780
1090
|
lines.push("} as const;");
|
|
781
1091
|
return lines;
|
|
782
1092
|
}
|
|
1093
|
+
function collectRouteSchemas(routes) {
|
|
1094
|
+
const collected = [];
|
|
1095
|
+
for (const route of routes) {
|
|
1096
|
+
for (const [statusCode, responseSchema] of Object.entries(
|
|
1097
|
+
route.responses
|
|
1098
|
+
)) {
|
|
1099
|
+
if (!responseSchema) continue;
|
|
1100
|
+
const isSuccess = statusCode.startsWith("2");
|
|
1101
|
+
const suffix = isSuccess ? `${statusCode}Response` : `${statusCode}ErrorResponse`;
|
|
1102
|
+
const responseSchemaName = generateRouteSchemaName2(
|
|
1103
|
+
route.path,
|
|
1104
|
+
route.method,
|
|
1105
|
+
suffix
|
|
1106
|
+
);
|
|
1107
|
+
collected.push({ name: responseSchemaName, schema: responseSchema });
|
|
1108
|
+
}
|
|
1109
|
+
}
|
|
1110
|
+
return collected;
|
|
1111
|
+
}
|
|
783
1112
|
var openApiToZodTsCode = (openapi, customImportLines, options) => {
|
|
784
1113
|
const components = openapi["components"];
|
|
785
1114
|
const schemas = components?.["schemas"] ?? {};
|
|
@@ -792,29 +1121,58 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
|
|
|
792
1121
|
lines.push("import { z } from 'zod';");
|
|
793
1122
|
lines.push(...customImportLines ?? []);
|
|
794
1123
|
lines.push("");
|
|
1124
|
+
lines.push("// Type assertion helper - verifies interface matches schema at compile time");
|
|
1125
|
+
lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
|
|
1126
|
+
lines.push("");
|
|
1127
|
+
const registry = createSchemaRegistry();
|
|
795
1128
|
const sortedSchemaNames = topologicalSortSchemas(schemas);
|
|
1129
|
+
const typeAssertions = [];
|
|
796
1130
|
for (const name of sortedSchemaNames) {
|
|
797
1131
|
const schema = schemas[name];
|
|
798
1132
|
if (schema) {
|
|
799
1133
|
const zodExpr = convertSchemaToZodString(schema);
|
|
800
1134
|
const schemaName = `${name}Schema`;
|
|
801
1135
|
const typeName = name;
|
|
802
|
-
lines.push(
|
|
803
|
-
lines.push(`export
|
|
1136
|
+
lines.push(generateInterface(typeName, schema));
|
|
1137
|
+
lines.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
|
|
804
1138
|
lines.push("");
|
|
1139
|
+
typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
|
|
1140
|
+
const fingerprint = getSchemaFingerprint(schema);
|
|
1141
|
+
preRegisterSchema(registry, schemaName, fingerprint);
|
|
805
1142
|
}
|
|
806
1143
|
}
|
|
1144
|
+
if (typeAssertions.length > 0) {
|
|
1145
|
+
lines.push("// Compile-time type assertions - ensure interfaces match schemas");
|
|
1146
|
+
lines.push(typeAssertions.join("\n"));
|
|
1147
|
+
lines.push("");
|
|
1148
|
+
}
|
|
807
1149
|
if (options?.includeRoutes) {
|
|
808
1150
|
const routes = parseOpenApiPaths(openapi);
|
|
809
1151
|
if (routes.length > 0) {
|
|
810
|
-
const
|
|
1152
|
+
const routeSchemaList = collectRouteSchemas(routes);
|
|
1153
|
+
const commonSchemas = findCommonSchemas(routeSchemaList, 2);
|
|
1154
|
+
if (commonSchemas.length > 0) {
|
|
1155
|
+
lines.push("// Common Error Schemas (deduplicated)");
|
|
1156
|
+
for (const common of commonSchemas) {
|
|
1157
|
+
const zodExpr = convertSchemaToZodString(common.schema);
|
|
1158
|
+
lines.push(`export const ${common.name} = ${zodExpr};`);
|
|
1159
|
+
preRegisterSchema(registry, common.name, common.fingerprint);
|
|
1160
|
+
}
|
|
1161
|
+
lines.push("");
|
|
1162
|
+
}
|
|
1163
|
+
const { declarations, schemaNameToCanonical } = generateRouteSchemas(
|
|
811
1164
|
routes,
|
|
812
|
-
convertSchemaToZodString
|
|
1165
|
+
convertSchemaToZodString,
|
|
1166
|
+
registry
|
|
813
1167
|
);
|
|
814
|
-
if (
|
|
815
|
-
lines.push(
|
|
1168
|
+
if (declarations.length > 0) {
|
|
1169
|
+
lines.push("// Route Schemas");
|
|
1170
|
+
lines.push(...declarations);
|
|
816
1171
|
lines.push("");
|
|
817
|
-
const requestResponseObjs = generateRequestResponseObjects(
|
|
1172
|
+
const requestResponseObjs = generateRequestResponseObjects(
|
|
1173
|
+
routes,
|
|
1174
|
+
schemaNameToCanonical
|
|
1175
|
+
);
|
|
818
1176
|
lines.push(...requestResponseObjs);
|
|
819
1177
|
}
|
|
820
1178
|
}
|
|
@@ -825,11 +1183,13 @@ export {
|
|
|
825
1183
|
SUPPORTED_STRING_FORMATS,
|
|
826
1184
|
clearZodSchemaToOpenApiSchemaRegistry,
|
|
827
1185
|
convertSchemaToZodString,
|
|
1186
|
+
generateInterface,
|
|
828
1187
|
generateRouteSchemaNames,
|
|
829
1188
|
getSchemaExportedVariableNameForStringFormat,
|
|
830
1189
|
openApiToZodTsCode,
|
|
831
1190
|
parseOpenApiPaths,
|
|
832
1191
|
registerZodSchemaToOpenApiSchema,
|
|
833
|
-
schemaRegistry
|
|
1192
|
+
schemaRegistry,
|
|
1193
|
+
schemaToTypeString
|
|
834
1194
|
};
|
|
835
1195
|
//# sourceMappingURL=index.js.map
|