@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/.turbo/turbo-build.log
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
> @alt-stack/zod-openapi@1.
|
|
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
|
[34mCLI[39m Building entry: src/index.ts
|
|
@@ -10,13 +10,13 @@
|
|
|
10
10
|
[34mCLI[39m Cleaning output folder
|
|
11
11
|
[34mESM[39m Build start
|
|
12
12
|
[34mCJS[39m Build start
|
|
13
|
-
[
|
|
14
|
-
[
|
|
15
|
-
[
|
|
16
|
-
[
|
|
17
|
-
[
|
|
18
|
-
[
|
|
13
|
+
[32mESM[39m [1mdist/index.js [22m[32m38.35 KB[39m
|
|
14
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m84.13 KB[39m
|
|
15
|
+
[32mESM[39m ⚡️ Build success in 32ms
|
|
16
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m39.98 KB[39m
|
|
17
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m85.09 KB[39m
|
|
18
|
+
[32mCJS[39m ⚡️ Build success in 33ms
|
|
19
19
|
[34mDTS[39m Build start
|
|
20
|
-
[32mDTS[39m ⚡️ Build success in
|
|
21
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
22
|
-
[32mDTS[39m [1mdist/index.d.cts [22m[
|
|
20
|
+
[32mDTS[39m ⚡️ Build success in 1497ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m5.68 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m5.68 KB[39m
|
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/
|
|
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
|
|
645
|
-
const
|
|
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
|
-
|
|
653
|
-
|
|
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(`${
|
|
659
|
-
if (param.required) {
|
|
660
|
-
required.push(param.name);
|
|
661
|
-
}
|
|
893
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
662
894
|
}
|
|
663
|
-
|
|
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
|
-
|
|
670
|
-
|
|
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(`${
|
|
929
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
678
930
|
}
|
|
679
|
-
|
|
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
|
-
|
|
686
|
-
|
|
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(`${
|
|
965
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
694
966
|
}
|
|
695
|
-
|
|
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
|
-
|
|
702
|
-
|
|
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
|
-
|
|
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
|
-
|
|
719
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
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(
|
|
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(
|
|
837
|
-
lines.push(`export
|
|
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
|
|
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 (
|
|
849
|
-
lines.push(
|
|
1204
|
+
if (declarations.length > 0) {
|
|
1205
|
+
lines.push("// Route Schemas");
|
|
1206
|
+
lines.push(...declarations);
|
|
850
1207
|
lines.push("");
|
|
851
|
-
const requestResponseObjs = generateRequestResponseObjects(
|
|
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
|