@alt-stack/zod-openapi 1.1.3 → 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/.turbo/turbo-build.log +10 -10
- package/dist/index.cjs +229 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -3
- package/dist/index.d.ts +34 -3
- package/dist/index.js +226 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/index.ts +1 -0
- package/src/interface-generator.spec.ts +668 -0
- package/src/interface-generator.ts +290 -0
- package/src/registry.ts +32 -2
- package/src/to-typescript.spec.ts +60 -0
- package/src/to-typescript.ts +39 -3
- 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.3.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
|
+
[32mCJS[39m [1mdist/index.cjs [22m[32m42.75 KB[39m
|
|
14
|
+
[32mCJS[39m [1mdist/index.cjs.map [22m[32m90.45 KB[39m
|
|
15
|
+
[32mCJS[39m ⚡️ Build success in 33ms
|
|
16
|
+
[32mESM[39m [1mdist/index.js [22m[32m41.13 KB[39m
|
|
17
|
+
[32mESM[39m [1mdist/index.js.map [22m[32m89.49 KB[39m
|
|
18
|
+
[32mESM[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 1429ms
|
|
21
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m6.06 KB[39m
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.cts [22m[32m6.06 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
|
|
|
@@ -265,6 +267,9 @@ function isStringRegistration(reg) {
|
|
|
265
267
|
function isStringsRegistration(reg) {
|
|
266
268
|
return reg.type === "string" && "formats" in reg;
|
|
267
269
|
}
|
|
270
|
+
function isSupportedStringFormat(format) {
|
|
271
|
+
return Object.prototype.hasOwnProperty.call(SUPPORTED_STRING_FORMATS_MAP, format);
|
|
272
|
+
}
|
|
268
273
|
function getTypeFormatPairs(reg) {
|
|
269
274
|
if (isStringRegistration(reg)) {
|
|
270
275
|
return [{ type: "string", format: reg.format }];
|
|
@@ -315,6 +320,7 @@ var ZodSchemaRegistry = class {
|
|
|
315
320
|
* Reverse-lookup helper: given a string format, return the registered schema's exported variable name
|
|
316
321
|
*/
|
|
317
322
|
getSchemaExportedVariableNameForStringFormat(format) {
|
|
323
|
+
if (!isSupportedStringFormat(format)) return void 0;
|
|
318
324
|
for (const registration of this.map.values()) {
|
|
319
325
|
if (registration.type !== "string") continue;
|
|
320
326
|
if (isStringRegistration(registration) && registration.format === format) {
|
|
@@ -326,6 +332,17 @@ var ZodSchemaRegistry = class {
|
|
|
326
332
|
}
|
|
327
333
|
return void 0;
|
|
328
334
|
}
|
|
335
|
+
/**
|
|
336
|
+
* Reverse-lookup helper: given a primitive type, return the registered schema's exported variable name
|
|
337
|
+
*/
|
|
338
|
+
getSchemaExportedVariableNameForPrimitiveType(type) {
|
|
339
|
+
for (const registration of this.map.values()) {
|
|
340
|
+
if (registration.type === type) {
|
|
341
|
+
return registration.schemaExportedVariableName;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return void 0;
|
|
345
|
+
}
|
|
329
346
|
};
|
|
330
347
|
var schemaRegistry = new ZodSchemaRegistry();
|
|
331
348
|
function registerZodSchemaToOpenApiSchema(schema, openApiSchema) {
|
|
@@ -334,6 +351,9 @@ function registerZodSchemaToOpenApiSchema(schema, openApiSchema) {
|
|
|
334
351
|
function getSchemaExportedVariableNameForStringFormat(format) {
|
|
335
352
|
return schemaRegistry.getSchemaExportedVariableNameForStringFormat(format);
|
|
336
353
|
}
|
|
354
|
+
function getSchemaExportedVariableNameForPrimitiveType(type) {
|
|
355
|
+
return schemaRegistry.getSchemaExportedVariableNameForPrimitiveType(type);
|
|
356
|
+
}
|
|
337
357
|
function clearZodSchemaToOpenApiSchemaRegistry() {
|
|
338
358
|
schemaRegistry.clear();
|
|
339
359
|
}
|
|
@@ -703,11 +723,188 @@ function generateRouteSchemaNames(route) {
|
|
|
703
723
|
return result;
|
|
704
724
|
}
|
|
705
725
|
|
|
706
|
-
// src/
|
|
726
|
+
// src/interface-generator.ts
|
|
707
727
|
var validIdentifierRegex2 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
708
728
|
function quotePropertyName2(name) {
|
|
709
729
|
return validIdentifierRegex2.test(name) ? name : `'${name}'`;
|
|
710
730
|
}
|
|
731
|
+
function toPascalCase2(name) {
|
|
732
|
+
return name.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[^a-zA-Z0-9]/g, " ").split(" ").filter(Boolean).map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join("");
|
|
733
|
+
}
|
|
734
|
+
function schemaExportNameToOutputAlias(name) {
|
|
735
|
+
return `${toPascalCase2(name)}Output`;
|
|
736
|
+
}
|
|
737
|
+
function registerOutputSchemaName(schemaName, options) {
|
|
738
|
+
options?.outputSchemaNames?.add(schemaName);
|
|
739
|
+
return schemaExportNameToOutputAlias(schemaName);
|
|
740
|
+
}
|
|
741
|
+
function getRegisteredOutputAlias(schema, options) {
|
|
742
|
+
if (!schema || typeof schema !== "object") return void 0;
|
|
743
|
+
if (schema["type"] === "string" && typeof schema["format"] === "string") {
|
|
744
|
+
const customSchemaName = getSchemaExportedVariableNameForStringFormat(
|
|
745
|
+
schema["format"]
|
|
746
|
+
);
|
|
747
|
+
if (customSchemaName) {
|
|
748
|
+
return registerOutputSchemaName(customSchemaName, options);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
if (schema["type"] === "number" || schema["type"] === "integer" || schema["type"] === "boolean") {
|
|
752
|
+
const customSchemaName = getSchemaExportedVariableNameForPrimitiveType(
|
|
753
|
+
schema["type"]
|
|
754
|
+
);
|
|
755
|
+
if (customSchemaName) {
|
|
756
|
+
return registerOutputSchemaName(customSchemaName, options);
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
return void 0;
|
|
760
|
+
}
|
|
761
|
+
function schemaToTypeString(schema, options) {
|
|
762
|
+
if (!schema || typeof schema !== "object") return "unknown";
|
|
763
|
+
if (schema["$ref"] && typeof schema["$ref"] === "string") {
|
|
764
|
+
const match = schema["$ref"].match(
|
|
765
|
+
/#\/components\/schemas\/(.+)/
|
|
766
|
+
);
|
|
767
|
+
let result2 = "unknown";
|
|
768
|
+
if (match && match[1]) {
|
|
769
|
+
result2 = decodeURIComponent(match[1]);
|
|
770
|
+
}
|
|
771
|
+
if (schema["nullable"] === true) {
|
|
772
|
+
result2 = `(${result2} | null)`;
|
|
773
|
+
}
|
|
774
|
+
return result2;
|
|
775
|
+
}
|
|
776
|
+
let result = "unknown";
|
|
777
|
+
if ("oneOf" in schema && Array.isArray(schema["oneOf"])) {
|
|
778
|
+
const unionMembers = schema["oneOf"].map(
|
|
779
|
+
(s) => schemaToTypeString(s, options)
|
|
780
|
+
);
|
|
781
|
+
result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
|
|
782
|
+
} else if ("allOf" in schema && Array.isArray(schema["allOf"])) {
|
|
783
|
+
const intersectionMembers = schema["allOf"].map(
|
|
784
|
+
(s) => schemaToTypeString(s, options)
|
|
785
|
+
);
|
|
786
|
+
result = intersectionMembers.length > 1 ? `(${intersectionMembers.join(" & ")})` : intersectionMembers[0] ?? "unknown";
|
|
787
|
+
} else if ("anyOf" in schema && Array.isArray(schema["anyOf"])) {
|
|
788
|
+
const unionMembers = schema["anyOf"].map(
|
|
789
|
+
(s) => schemaToTypeString(s, options)
|
|
790
|
+
);
|
|
791
|
+
result = unionMembers.length > 1 ? `(${unionMembers.join(" | ")})` : unionMembers[0] ?? "unknown";
|
|
792
|
+
} else {
|
|
793
|
+
switch (schema["type"]) {
|
|
794
|
+
case "string": {
|
|
795
|
+
const registeredAlias = getRegisteredOutputAlias(schema, options);
|
|
796
|
+
if (registeredAlias) {
|
|
797
|
+
result = registeredAlias;
|
|
798
|
+
} else if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
799
|
+
result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
|
|
800
|
+
} else {
|
|
801
|
+
result = "string";
|
|
802
|
+
}
|
|
803
|
+
break;
|
|
804
|
+
}
|
|
805
|
+
case "number":
|
|
806
|
+
case "integer": {
|
|
807
|
+
const registeredAlias = getRegisteredOutputAlias(schema, options);
|
|
808
|
+
if (registeredAlias) {
|
|
809
|
+
result = registeredAlias;
|
|
810
|
+
} else if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
811
|
+
result = schema["enum"].map((v) => String(v)).join(" | ");
|
|
812
|
+
} else {
|
|
813
|
+
result = "number";
|
|
814
|
+
}
|
|
815
|
+
break;
|
|
816
|
+
}
|
|
817
|
+
case "boolean":
|
|
818
|
+
result = getRegisteredOutputAlias(schema, options) ?? "boolean";
|
|
819
|
+
break;
|
|
820
|
+
case "null":
|
|
821
|
+
result = "null";
|
|
822
|
+
break;
|
|
823
|
+
case "array":
|
|
824
|
+
if (schema["items"]) {
|
|
825
|
+
const itemType = schemaToTypeString(schema["items"], options);
|
|
826
|
+
result = `Array<${itemType}>`;
|
|
827
|
+
} else {
|
|
828
|
+
result = "unknown[]";
|
|
829
|
+
}
|
|
830
|
+
break;
|
|
831
|
+
case "object":
|
|
832
|
+
result = objectSchemaToTypeString(schema, options);
|
|
833
|
+
break;
|
|
834
|
+
default:
|
|
835
|
+
if (schema["properties"]) {
|
|
836
|
+
result = objectSchemaToTypeString(schema, options);
|
|
837
|
+
} else if (schema["enum"] && Array.isArray(schema["enum"])) {
|
|
838
|
+
result = schema["enum"].map((v) => JSON.stringify(v)).join(" | ");
|
|
839
|
+
} else {
|
|
840
|
+
result = "unknown";
|
|
841
|
+
}
|
|
842
|
+
break;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (schema["nullable"] === true) {
|
|
846
|
+
result = `(${result} | null)`;
|
|
847
|
+
}
|
|
848
|
+
return result;
|
|
849
|
+
}
|
|
850
|
+
function objectSchemaToTypeString(schema, options) {
|
|
851
|
+
const properties = schema["properties"];
|
|
852
|
+
const required = new Set(schema["required"] ?? []);
|
|
853
|
+
const additionalProperties = schema["additionalProperties"];
|
|
854
|
+
if (!properties && !additionalProperties) {
|
|
855
|
+
return "Record<string, unknown>";
|
|
856
|
+
}
|
|
857
|
+
const propertyStrings = [];
|
|
858
|
+
if (properties) {
|
|
859
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
860
|
+
const isRequired = required.has(propName);
|
|
861
|
+
const propType = schemaToTypeString(propSchema, options);
|
|
862
|
+
const quotedName = quotePropertyName2(propName);
|
|
863
|
+
propertyStrings.push(
|
|
864
|
+
`${quotedName}${isRequired ? "" : "?"}: ${propType}`
|
|
865
|
+
);
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (additionalProperties === true) {
|
|
869
|
+
propertyStrings.push("[key: string]: unknown");
|
|
870
|
+
} else if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
871
|
+
const additionalType = schemaToTypeString(additionalProperties, options);
|
|
872
|
+
propertyStrings.push(`[key: string]: ${additionalType}`);
|
|
873
|
+
}
|
|
874
|
+
return `{ ${propertyStrings.join("; ")} }`;
|
|
875
|
+
}
|
|
876
|
+
function generateInterface(name, schema, options) {
|
|
877
|
+
const properties = schema["properties"];
|
|
878
|
+
const required = new Set(schema["required"] ?? []);
|
|
879
|
+
if (schema["type"] !== "object" && !properties) {
|
|
880
|
+
return `export type ${name} = ${schemaToTypeString(schema, options)};`;
|
|
881
|
+
}
|
|
882
|
+
const lines = [];
|
|
883
|
+
lines.push(`export interface ${name} {`);
|
|
884
|
+
if (properties) {
|
|
885
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
886
|
+
const isRequired = required.has(propName);
|
|
887
|
+
const propType = schemaToTypeString(propSchema, options);
|
|
888
|
+
const quotedName = quotePropertyName2(propName);
|
|
889
|
+
lines.push(` ${quotedName}${isRequired ? "" : "?"}: ${propType};`);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
const additionalProperties = schema["additionalProperties"];
|
|
893
|
+
if (additionalProperties === true) {
|
|
894
|
+
lines.push(" [key: string]: unknown;");
|
|
895
|
+
} else if (typeof additionalProperties === "object" && additionalProperties !== null) {
|
|
896
|
+
const additionalType = schemaToTypeString(additionalProperties, options);
|
|
897
|
+
lines.push(` [key: string]: ${additionalType};`);
|
|
898
|
+
}
|
|
899
|
+
lines.push("}");
|
|
900
|
+
return lines.join("\n");
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
// src/to-typescript.ts
|
|
904
|
+
var validIdentifierRegex3 = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
|
|
905
|
+
function quotePropertyName3(name) {
|
|
906
|
+
return validIdentifierRegex3.test(name) ? name : `'${name}'`;
|
|
907
|
+
}
|
|
711
908
|
function generateRouteSchemaName2(path, method, suffix) {
|
|
712
909
|
const pathParts = path.split("/").filter((p) => p).map((p) => {
|
|
713
910
|
if (p.startsWith("{") && p.endsWith("}")) {
|
|
@@ -749,7 +946,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
|
|
|
749
946
|
const properties = [];
|
|
750
947
|
for (const param of pathParams) {
|
|
751
948
|
const zodExpr = convertSchema(param.schema);
|
|
752
|
-
properties.push(`${
|
|
949
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
753
950
|
}
|
|
754
951
|
declarations.push(
|
|
755
952
|
`export const ${names.paramsSchemaName} = z.object({ ${properties.join(", ")} });`
|
|
@@ -785,7 +982,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
|
|
|
785
982
|
if (!param.required) {
|
|
786
983
|
zodExpr += ".optional()";
|
|
787
984
|
}
|
|
788
|
-
properties.push(`${
|
|
985
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
789
986
|
}
|
|
790
987
|
declarations.push(
|
|
791
988
|
`export const ${names.querySchemaName} = z.object({ ${properties.join(", ")} });`
|
|
@@ -821,7 +1018,7 @@ function generateRouteSchemas(routes, convertSchema, registry) {
|
|
|
821
1018
|
if (!param.required) {
|
|
822
1019
|
zodExpr += ".optional()";
|
|
823
1020
|
}
|
|
824
|
-
properties.push(`${
|
|
1021
|
+
properties.push(`${quotePropertyName3(param.name)}: ${zodExpr}`);
|
|
825
1022
|
}
|
|
826
1023
|
declarations.push(
|
|
827
1024
|
`export const ${names.headersSchemaName} = z.object({ ${properties.join(", ")} });`
|
|
@@ -1016,21 +1213,42 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
|
|
|
1016
1213
|
lines.push("import { z } from 'zod';");
|
|
1017
1214
|
lines.push(...customImportLines ?? []);
|
|
1018
1215
|
lines.push("");
|
|
1216
|
+
lines.push("// Type assertion helper - verifies interface matches schema at compile time");
|
|
1217
|
+
lines.push("type _AssertEqual<T, U> = [T] extends [U] ? ([U] extends [T] ? true : never) : never;");
|
|
1218
|
+
lines.push("");
|
|
1019
1219
|
const registry = createSchemaRegistry();
|
|
1020
1220
|
const sortedSchemaNames = topologicalSortSchemas(schemas);
|
|
1221
|
+
const typeAssertions = [];
|
|
1222
|
+
const outputSchemaNames = /* @__PURE__ */ new Set();
|
|
1223
|
+
const schemaBlocks = [];
|
|
1021
1224
|
for (const name of sortedSchemaNames) {
|
|
1022
1225
|
const schema = schemas[name];
|
|
1023
1226
|
if (schema) {
|
|
1024
1227
|
const zodExpr = convertSchemaToZodString(schema);
|
|
1025
1228
|
const schemaName = `${name}Schema`;
|
|
1026
1229
|
const typeName = name;
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1230
|
+
schemaBlocks.push(generateInterface(typeName, schema, { outputSchemaNames }));
|
|
1231
|
+
schemaBlocks.push(`export const ${schemaName}: z.ZodType<${typeName}> = ${zodExpr};`);
|
|
1232
|
+
schemaBlocks.push("");
|
|
1233
|
+
typeAssertions.push(`type _Assert${typeName} = _AssertEqual<${typeName}, z.infer<typeof ${schemaName}>>;`);
|
|
1030
1234
|
const fingerprint = getSchemaFingerprint(schema);
|
|
1031
1235
|
preRegisterSchema(registry, schemaName, fingerprint);
|
|
1032
1236
|
}
|
|
1033
1237
|
}
|
|
1238
|
+
if (outputSchemaNames.size > 0) {
|
|
1239
|
+
lines.push("// Zod output aliases for registered schemas");
|
|
1240
|
+
for (const schemaName of outputSchemaNames) {
|
|
1241
|
+
const aliasName = schemaExportNameToOutputAlias(schemaName);
|
|
1242
|
+
lines.push(`type ${aliasName} = z.output<typeof ${schemaName}>;`);
|
|
1243
|
+
}
|
|
1244
|
+
lines.push("");
|
|
1245
|
+
}
|
|
1246
|
+
lines.push(...schemaBlocks);
|
|
1247
|
+
if (typeAssertions.length > 0) {
|
|
1248
|
+
lines.push("// Compile-time type assertions - ensure interfaces match schemas");
|
|
1249
|
+
lines.push(typeAssertions.join("\n"));
|
|
1250
|
+
lines.push("");
|
|
1251
|
+
}
|
|
1034
1252
|
if (options?.includeRoutes) {
|
|
1035
1253
|
const routes = parseOpenApiPaths(openapi);
|
|
1036
1254
|
if (routes.length > 0) {
|
|
@@ -1069,11 +1287,13 @@ var openApiToZodTsCode = (openapi, customImportLines, options) => {
|
|
|
1069
1287
|
SUPPORTED_STRING_FORMATS,
|
|
1070
1288
|
clearZodSchemaToOpenApiSchemaRegistry,
|
|
1071
1289
|
convertSchemaToZodString,
|
|
1290
|
+
generateInterface,
|
|
1072
1291
|
generateRouteSchemaNames,
|
|
1073
1292
|
getSchemaExportedVariableNameForStringFormat,
|
|
1074
1293
|
openApiToZodTsCode,
|
|
1075
1294
|
parseOpenApiPaths,
|
|
1076
1295
|
registerZodSchemaToOpenApiSchema,
|
|
1077
|
-
schemaRegistry
|
|
1296
|
+
schemaRegistry,
|
|
1297
|
+
schemaToTypeString
|
|
1078
1298
|
});
|
|
1079
1299
|
//# sourceMappingURL=index.cjs.map
|