@sdk-it/typescript 0.12.10 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +153 -71
- package/dist/index.js.map +3 -3
- package/dist/lib/emitters/interface.d.ts.map +1 -1
- package/dist/lib/emitters/zod.d.ts +6 -3
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts +1 -0
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts +1 -0
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts +6 -3
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +1 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/package.json +2 -2
- package/dist/index 2.js +0 -977
- package/dist/index 3.js +0 -977
- package/dist/index 4.js +0 -1228
- package/dist/index.d 2.ts +0 -3
- package/dist/index.d 3.ts +0 -3
- package/dist/index.d 4.ts +0 -3
- package/dist/index.d.ts 2.map +0 -0
- package/dist/index.d.ts 3.map +0 -1
- package/dist/index.d.ts 4.map +0 -1
- package/dist/index.js 2.map +0 -7
- package/dist/index.js 3.map +0 -7
- package/dist/index.js 4.map +0 -7
- package/dist/lib/backend.d.ts +0 -4
- package/dist/lib/backend.d.ts.map +0 -1
- package/dist/lib/emitters/json-zod.d.ts +0 -10
- package/dist/lib/emitters/json-zod.d.ts.map +0 -1
- package/dist/lib/interface.d.ts +0 -2
- package/dist/lib/interface.d.ts.map +0 -1
- package/dist/lib/play.d.ts +0 -2
- package/dist/lib/play.d.ts.map +0 -1
- package/dist/lib/typescript.d.ts +0 -2
- package/dist/lib/typescript.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -39,14 +39,14 @@ var client_default = (spec) => {
|
|
|
39
39
|
}
|
|
40
40
|
};
|
|
41
41
|
return `
|
|
42
|
-
import { fetchType, sendRequest } from './http
|
|
42
|
+
import { fetchType, sendRequest } from './http/${spec.makeImport("send-request")}';
|
|
43
43
|
import z from 'zod';
|
|
44
|
-
import type { Endpoints } from '
|
|
45
|
-
import schemas from '
|
|
44
|
+
import type { Endpoints } from './${spec.makeImport("endpoints")}';
|
|
45
|
+
import schemas from './${spec.makeImport("schemas")}';
|
|
46
46
|
import {
|
|
47
47
|
createBaseUrlInterceptor,
|
|
48
48
|
createDefaultHeadersInterceptor,
|
|
49
|
-
} from './http
|
|
49
|
+
} from './http/${spec.makeImport("interceptors")}';
|
|
50
50
|
|
|
51
51
|
${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, null, 2)} as const` : ""}
|
|
52
52
|
const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
|
|
@@ -57,7 +57,7 @@ type ${spec.name}Options = z.infer<typeof optionsSchema>;
|
|
|
57
57
|
export class ${spec.name} {
|
|
58
58
|
public options: ${spec.name}Options
|
|
59
59
|
constructor(options: ${spec.name}Options) {
|
|
60
|
-
this.options = options;
|
|
60
|
+
this.options = optionsSchema.parse(options);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
63
|
async request<E extends keyof Endpoints>(
|
|
@@ -96,12 +96,18 @@ export class ${spec.name} {
|
|
|
96
96
|
|
|
97
97
|
// packages/typescript/src/lib/sdk.ts
|
|
98
98
|
var SchemaEndpoint = class {
|
|
99
|
-
#
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
99
|
+
#makeImport;
|
|
100
|
+
#imports = [];
|
|
101
|
+
constructor(makeImport) {
|
|
102
|
+
this.#makeImport = makeImport;
|
|
103
|
+
this.#imports = [
|
|
104
|
+
`import z from 'zod';`,
|
|
105
|
+
`import type { Endpoints } from '${this.#makeImport("./endpoints")}';`,
|
|
106
|
+
`import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${this.#makeImport("./http/request")}';`,
|
|
107
|
+
`import type { ParseError } from '${this.#makeImport("./http/parser")}';`,
|
|
108
|
+
`import { chunked, buffered } from "${this.#makeImport("./http/parse-response")}";`
|
|
109
|
+
];
|
|
110
|
+
}
|
|
105
111
|
#endpoints = [];
|
|
106
112
|
addEndpoint(endpoint, operation) {
|
|
107
113
|
this.#endpoints.push(` "${endpoint}": ${operation},`);
|
|
@@ -117,10 +123,15 @@ ${this.#endpoints.join("\n")}
|
|
|
117
123
|
}
|
|
118
124
|
};
|
|
119
125
|
var Emitter = class {
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
126
|
+
#makeImport;
|
|
127
|
+
imports = [];
|
|
128
|
+
constructor(makeImport) {
|
|
129
|
+
this.#makeImport = makeImport;
|
|
130
|
+
this.imports = [
|
|
131
|
+
`import type z from 'zod';`,
|
|
132
|
+
`import type { ParseError } from '${this.#makeImport("./http/parser")}';`
|
|
133
|
+
];
|
|
134
|
+
}
|
|
124
135
|
endpoints = [];
|
|
125
136
|
addEndpoint(endpoint, operation) {
|
|
126
137
|
this.endpoints.push(` "${endpoint}": ${operation};`);
|
|
@@ -135,7 +146,7 @@ ${this.endpoints.join("\n")}
|
|
|
135
146
|
}`;
|
|
136
147
|
}
|
|
137
148
|
};
|
|
138
|
-
function generateInputs(operationsSet, commonZod) {
|
|
149
|
+
function generateInputs(operationsSet, commonZod, makeImport) {
|
|
139
150
|
const commonImports = commonZod.keys().toArray();
|
|
140
151
|
const inputs = {};
|
|
141
152
|
for (const [name, operations] of Object.entries(operationsSet)) {
|
|
@@ -148,7 +159,7 @@ function generateInputs(operationsSet, commonZod) {
|
|
|
148
159
|
for (const schema2 of commonImports) {
|
|
149
160
|
if (inputContent.includes(schema2)) {
|
|
150
161
|
imports.add(
|
|
151
|
-
`import { ${schema2} } from './schemas/${spinalcase(schema2)}
|
|
162
|
+
`import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
|
|
152
163
|
);
|
|
153
164
|
}
|
|
154
165
|
}
|
|
@@ -163,7 +174,7 @@ function generateInputs(operationsSet, commonZod) {
|
|
|
163
174
|
const preciseMatch = new RegExp(`\\b${schema2}\\b`);
|
|
164
175
|
if (preciseMatch.test(content) && schema2 !== name) {
|
|
165
176
|
output.push(
|
|
166
|
-
`import { ${schema2} } from './${spinalcase(schema2)}
|
|
177
|
+
`import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
|
|
167
178
|
);
|
|
168
179
|
}
|
|
169
180
|
}
|
|
@@ -179,15 +190,15 @@ function generateInputs(operationsSet, commonZod) {
|
|
|
179
190
|
};
|
|
180
191
|
}
|
|
181
192
|
function generateSDK(spec) {
|
|
182
|
-
const emitter = new Emitter();
|
|
183
|
-
const schemaEndpoint = new SchemaEndpoint();
|
|
193
|
+
const emitter = new Emitter(spec.makeImport);
|
|
194
|
+
const schemaEndpoint = new SchemaEndpoint(spec.makeImport);
|
|
184
195
|
const errors = [];
|
|
185
196
|
for (const [name, operations] of Object.entries(spec.operations)) {
|
|
186
197
|
emitter.addImport(
|
|
187
|
-
`import * as ${camelcase(name)} from './inputs/${spinalcase(name)}
|
|
198
|
+
`import type * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
|
|
188
199
|
);
|
|
189
200
|
schemaEndpoint.addImport(
|
|
190
|
-
`import * as ${camelcase(name)} from './inputs/${spinalcase(name)}
|
|
201
|
+
`import * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
|
|
191
202
|
);
|
|
192
203
|
for (const operation of operations) {
|
|
193
204
|
const schemaName = camelcase(`${operation.name} schema`);
|
|
@@ -217,7 +228,7 @@ function generateSDK(spec) {
|
|
|
217
228
|
}
|
|
218
229
|
}
|
|
219
230
|
emitter.addImport(
|
|
220
|
-
`import type {${output.import}} from './outputs/${spinalcase(operation.name)}
|
|
231
|
+
`import type {${output.import}} from './outputs/${spec.makeImport(spinalcase(operation.name))}';`
|
|
221
232
|
);
|
|
222
233
|
errors.push(...operation.errors ?? []);
|
|
223
234
|
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
@@ -236,9 +247,10 @@ function generateSDK(spec) {
|
|
|
236
247
|
endpoint,
|
|
237
248
|
`{
|
|
238
249
|
schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
|
|
250
|
+
deserializer: ${operation.parser === "chunked" ? "chunked" : "buffered"},
|
|
239
251
|
toRequest(input: Endpoints['${endpoint}']['input']) {
|
|
240
252
|
const endpoint = '${endpoint}';
|
|
241
|
-
return toRequest(endpoint, ${operation.
|
|
253
|
+
return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
|
|
242
254
|
inputHeaders: [${inputHeaders}],
|
|
243
255
|
inputQuery: [${inputQuery}],
|
|
244
256
|
inputBody: [${inputBody}],
|
|
@@ -251,7 +263,7 @@ function generateSDK(spec) {
|
|
|
251
263
|
}
|
|
252
264
|
}
|
|
253
265
|
emitter.addImport(
|
|
254
|
-
`import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from './http/response
|
|
266
|
+
`import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from '${spec.makeImport("./http/response")}';`
|
|
255
267
|
);
|
|
256
268
|
return {
|
|
257
269
|
"client.ts": client_default(spec),
|
|
@@ -342,8 +354,8 @@ function importsToString(...imports) {
|
|
|
342
354
|
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
343
355
|
});
|
|
344
356
|
}
|
|
345
|
-
function
|
|
346
|
-
return list.filter((it) => !
|
|
357
|
+
function exclude(list, exclude2) {
|
|
358
|
+
return list.filter((it) => !exclude2.includes(it));
|
|
347
359
|
}
|
|
348
360
|
function useImports(content, imports) {
|
|
349
361
|
const output = [];
|
|
@@ -580,6 +592,9 @@ var TypeScriptDeserialzer = class {
|
|
|
580
592
|
}
|
|
581
593
|
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
582
594
|
if (!types.length) {
|
|
595
|
+
if ("properties" in schema) {
|
|
596
|
+
return this.object(schema, required);
|
|
597
|
+
}
|
|
583
598
|
return appendOptional("any", required);
|
|
584
599
|
}
|
|
585
600
|
if (types.length > 1) {
|
|
@@ -655,19 +670,24 @@ var ZodDeserialzer = class {
|
|
|
655
670
|
}
|
|
656
671
|
// oneOf() {}
|
|
657
672
|
// enum() {}
|
|
673
|
+
#suffixes = (defaultValue, required, nullable) => {
|
|
674
|
+
return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional2(required)}`;
|
|
675
|
+
};
|
|
658
676
|
/**
|
|
659
677
|
* Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
|
|
660
678
|
* We'll also handle .optional() if needed.
|
|
661
679
|
*/
|
|
662
|
-
normal(type, schema, required = false) {
|
|
680
|
+
normal(type, schema, required = false, nullable = false) {
|
|
663
681
|
switch (type) {
|
|
664
682
|
case "string":
|
|
665
|
-
return this.string(schema, required)
|
|
683
|
+
return `${this.string(schema)}${this.#suffixes(schema.default, required, nullable)}`;
|
|
666
684
|
case "number":
|
|
667
|
-
case "integer":
|
|
668
|
-
|
|
685
|
+
case "integer": {
|
|
686
|
+
const { base, defaultValue } = this.number(schema);
|
|
687
|
+
return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
|
|
688
|
+
}
|
|
669
689
|
case "boolean":
|
|
670
|
-
return `z.boolean()${
|
|
690
|
+
return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
|
|
671
691
|
case "object":
|
|
672
692
|
return this.object(schema, required);
|
|
673
693
|
case "array":
|
|
@@ -693,10 +713,20 @@ var ZodDeserialzer = class {
|
|
|
693
713
|
}
|
|
694
714
|
allOf(schemas) {
|
|
695
715
|
const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
716
|
+
if (allOfSchemas.length === 0) {
|
|
717
|
+
return `z.unknown()`;
|
|
718
|
+
}
|
|
696
719
|
if (allOfSchemas.length === 1) {
|
|
697
720
|
return allOfSchemas[0];
|
|
698
721
|
}
|
|
699
|
-
return
|
|
722
|
+
return this.#toIntersection(allOfSchemas);
|
|
723
|
+
}
|
|
724
|
+
#toIntersection(schemas) {
|
|
725
|
+
const [left, ...right] = schemas;
|
|
726
|
+
if (!right.length) {
|
|
727
|
+
return left;
|
|
728
|
+
}
|
|
729
|
+
return `z.intersection(${left}, ${this.#toIntersection(right)})`;
|
|
700
730
|
}
|
|
701
731
|
anyOf(schemas, required) {
|
|
702
732
|
const anyOfSchemas = schemas.map((sub) => this.handle(sub, false));
|
|
@@ -733,7 +763,7 @@ var ZodDeserialzer = class {
|
|
|
733
763
|
/**
|
|
734
764
|
* Handle a `string` schema with possible format keywords (JSON Schema).
|
|
735
765
|
*/
|
|
736
|
-
string(schema
|
|
766
|
+
string(schema) {
|
|
737
767
|
let base = "z.string()";
|
|
738
768
|
switch (schema.format) {
|
|
739
769
|
case "date-time":
|
|
@@ -775,20 +805,20 @@ var ZodDeserialzer = class {
|
|
|
775
805
|
default:
|
|
776
806
|
break;
|
|
777
807
|
}
|
|
778
|
-
return
|
|
808
|
+
return base;
|
|
779
809
|
}
|
|
780
810
|
/**
|
|
781
811
|
* Handle number/integer constraints from OpenAPI/JSON Schema.
|
|
782
812
|
* In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
|
|
783
813
|
* rather than a boolean toggling `minimum`/`maximum`.
|
|
784
814
|
*/
|
|
785
|
-
number(schema
|
|
786
|
-
let defaultValue = schema.default
|
|
815
|
+
number(schema) {
|
|
816
|
+
let defaultValue = schema.default;
|
|
787
817
|
let base = "z.number()";
|
|
788
818
|
if (schema.format === "int64") {
|
|
789
819
|
base = "z.bigint()";
|
|
790
820
|
if (schema.default !== void 0) {
|
|
791
|
-
defaultValue =
|
|
821
|
+
defaultValue = `BigInt(${schema.default})`;
|
|
792
822
|
}
|
|
793
823
|
}
|
|
794
824
|
if (schema.format === "int32") {
|
|
@@ -809,7 +839,7 @@ var ZodDeserialzer = class {
|
|
|
809
839
|
if (typeof schema.multipleOf === "number") {
|
|
810
840
|
base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
|
|
811
841
|
}
|
|
812
|
-
return
|
|
842
|
+
return { base, defaultValue };
|
|
813
843
|
}
|
|
814
844
|
handle(schema, required) {
|
|
815
845
|
if (isRef(schema)) {
|
|
@@ -831,16 +861,18 @@ var ZodDeserialzer = class {
|
|
|
831
861
|
if (!types.length) {
|
|
832
862
|
return `z.unknown()${appendOptional2(required)}`;
|
|
833
863
|
}
|
|
864
|
+
if ("nullable" in schema && schema.nullable) {
|
|
865
|
+
types.push("null");
|
|
866
|
+
}
|
|
834
867
|
if (types.length > 1) {
|
|
835
868
|
const realTypes = types.filter((t) => t !== "null");
|
|
836
869
|
if (realTypes.length === 1 && types.includes("null")) {
|
|
837
|
-
|
|
838
|
-
return `${typeZod}.nullable()${appendOptional2(required)}`;
|
|
870
|
+
return this.normal(realTypes[0], schema, false, true);
|
|
839
871
|
}
|
|
840
872
|
const subSchemas = types.map((t) => this.normal(t, schema, false));
|
|
841
873
|
return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
|
|
842
874
|
}
|
|
843
|
-
return this.normal(types[0], schema, required);
|
|
875
|
+
return this.normal(types[0], schema, required, false);
|
|
844
876
|
}
|
|
845
877
|
};
|
|
846
878
|
function appendOptional2(isRequired) {
|
|
@@ -889,7 +921,7 @@ function generateCode(config) {
|
|
|
889
921
|
commonZodImports.push({
|
|
890
922
|
defaultImport: void 0,
|
|
891
923
|
isTypeOnly: true,
|
|
892
|
-
moduleSpecifier: `./${model}
|
|
924
|
+
moduleSpecifier: `./${config.makeImport(model)}`,
|
|
893
925
|
namedImports: [{ isTypeOnly: true, name: model }],
|
|
894
926
|
namespaceImport: void 0
|
|
895
927
|
});
|
|
@@ -901,7 +933,7 @@ function generateCode(config) {
|
|
|
901
933
|
const formatOperationId = config.operationId ?? defaults.operationId;
|
|
902
934
|
const operationName = formatOperationId(operation, path, method);
|
|
903
935
|
console.log(`Processing ${method} ${path}`);
|
|
904
|
-
const groupName = (operation.tags
|
|
936
|
+
const [groupName] = Array.isArray(operation.tags) ? operation.tags : ["unknown"];
|
|
905
937
|
groups[groupName] ??= [];
|
|
906
938
|
const inputs = {};
|
|
907
939
|
const additionalProperties = [];
|
|
@@ -942,7 +974,7 @@ function generateCode(config) {
|
|
|
942
974
|
"application/xml": "xml",
|
|
943
975
|
"text/plain": "text"
|
|
944
976
|
};
|
|
945
|
-
let
|
|
977
|
+
let outgoingContentType;
|
|
946
978
|
if (operation.requestBody && Object.keys(operation.requestBody).length) {
|
|
947
979
|
const content = isRef(operation.requestBody) ? get2(followRef(config.spec, operation.requestBody.$ref), ["content"]) : operation.requestBody.content;
|
|
948
980
|
for (const type in content) {
|
|
@@ -961,22 +993,17 @@ function generateCode(config) {
|
|
|
961
993
|
{}
|
|
962
994
|
)
|
|
963
995
|
});
|
|
964
|
-
|
|
965
|
-
inputs[name] = {
|
|
966
|
-
in: "body",
|
|
967
|
-
schema: ""
|
|
968
|
-
};
|
|
969
|
-
}
|
|
996
|
+
Object.assign(inputs, bodyInputs(config, ctSchema));
|
|
970
997
|
types[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
|
|
971
998
|
}
|
|
972
999
|
if (content["application/json"]) {
|
|
973
|
-
|
|
1000
|
+
outgoingContentType = "json";
|
|
974
1001
|
} else if (content["application/x-www-form-urlencoded"]) {
|
|
975
|
-
|
|
1002
|
+
outgoingContentType = "urlencoded";
|
|
976
1003
|
} else if (content["multipart/form-data"]) {
|
|
977
|
-
|
|
1004
|
+
outgoingContentType = "formdata";
|
|
978
1005
|
} else {
|
|
979
|
-
|
|
1006
|
+
outgoingContentType = "json";
|
|
980
1007
|
}
|
|
981
1008
|
} else {
|
|
982
1009
|
const properties = additionalProperties.reduce(
|
|
@@ -999,8 +1026,14 @@ function generateCode(config) {
|
|
|
999
1026
|
operation.responses ??= {};
|
|
1000
1027
|
let foundResponse = false;
|
|
1001
1028
|
const output = [`import z from 'zod';`];
|
|
1029
|
+
let parser = "buffered";
|
|
1002
1030
|
for (const status in operation.responses) {
|
|
1003
|
-
const response =
|
|
1031
|
+
const response = isRef(
|
|
1032
|
+
operation.responses[status]
|
|
1033
|
+
) ? followRef(
|
|
1034
|
+
config.spec,
|
|
1035
|
+
operation.responses[status].$ref
|
|
1036
|
+
) : operation.responses[status];
|
|
1004
1037
|
const statusCode = +status;
|
|
1005
1038
|
if (statusCode >= 400) {
|
|
1006
1039
|
errors.push(responses[status] ?? "ProblematicResponse");
|
|
@@ -1009,6 +1042,9 @@ function generateCode(config) {
|
|
|
1009
1042
|
foundResponse = true;
|
|
1010
1043
|
const responseContent = get2(response, ["content"]);
|
|
1011
1044
|
const isJson = responseContent && responseContent["application/json"];
|
|
1045
|
+
if ((response.headers ?? {})["Transfer-Encoding"]) {
|
|
1046
|
+
parser = "chunked";
|
|
1047
|
+
}
|
|
1012
1048
|
const imports = [];
|
|
1013
1049
|
const typeScriptDeserialzer = new TypeScriptDeserialzer(
|
|
1014
1050
|
config.spec,
|
|
@@ -1017,7 +1053,7 @@ function generateCode(config) {
|
|
|
1017
1053
|
imports.push({
|
|
1018
1054
|
defaultImport: void 0,
|
|
1019
1055
|
isTypeOnly: true,
|
|
1020
|
-
moduleSpecifier: `../models/${schemaName}
|
|
1056
|
+
moduleSpecifier: `../models/${config.makeImport(schemaName)}`,
|
|
1021
1057
|
namedImports: [{ isTypeOnly: true, name: schemaName }],
|
|
1022
1058
|
namespaceImport: void 0
|
|
1023
1059
|
});
|
|
@@ -1044,8 +1080,9 @@ function generateCode(config) {
|
|
|
1044
1080
|
type: "http",
|
|
1045
1081
|
inputs,
|
|
1046
1082
|
errors: errors.length ? errors : ["ServerError"],
|
|
1047
|
-
|
|
1083
|
+
outgoingContentType,
|
|
1048
1084
|
schemas: types,
|
|
1085
|
+
parser,
|
|
1049
1086
|
formatOutput: () => ({
|
|
1050
1087
|
import: pascalcase(operationName + " output"),
|
|
1051
1088
|
use: pascalcase(operationName + " output")
|
|
@@ -1059,12 +1096,42 @@ function generateCode(config) {
|
|
|
1059
1096
|
}
|
|
1060
1097
|
return { groups, commonSchemas, commonZod, outputs };
|
|
1061
1098
|
}
|
|
1099
|
+
function toProps(spec, schemaOrRef, aggregator = []) {
|
|
1100
|
+
if (isRef(schemaOrRef)) {
|
|
1101
|
+
const schema = followRef(spec, schemaOrRef.$ref);
|
|
1102
|
+
return toProps(spec, schema, aggregator);
|
|
1103
|
+
} else if (schemaOrRef.type === "object") {
|
|
1104
|
+
for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
|
|
1105
|
+
aggregator.push(name);
|
|
1106
|
+
}
|
|
1107
|
+
return void 0;
|
|
1108
|
+
} else if (schemaOrRef.allOf) {
|
|
1109
|
+
for (const it of schemaOrRef.allOf) {
|
|
1110
|
+
toProps(spec, it, aggregator);
|
|
1111
|
+
}
|
|
1112
|
+
return void 0;
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
function bodyInputs(config, ctSchema) {
|
|
1116
|
+
const props = [];
|
|
1117
|
+
toProps(config.spec, ctSchema, props);
|
|
1118
|
+
return props.reduce(
|
|
1119
|
+
(acc, prop) => ({
|
|
1120
|
+
...acc,
|
|
1121
|
+
[prop]: {
|
|
1122
|
+
in: "body",
|
|
1123
|
+
schema: ""
|
|
1124
|
+
}
|
|
1125
|
+
}),
|
|
1126
|
+
{}
|
|
1127
|
+
);
|
|
1128
|
+
}
|
|
1062
1129
|
|
|
1063
1130
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1064
|
-
var interceptors_default = "export interface Interceptor {\n before?: (request: Request) => Promise<Request> | Request;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createDefaultHeadersInterceptor = (\n getHeaders: () => Record<string, string | undefined>,\n) => {\n return {\n before(request: Request) {\n const headers = getHeaders();\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !request.headers.has(key)) {\n request.headers.set(key, value);\n }\n }\n\n return request;\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (getBaseUrl: () => string) => {\n return {\n before(request: Request) {\n const baseUrl = getBaseUrl();\n if (request.url.startsWith('local://')) {\n return new Request(request.url.replace('local://', baseUrl), request);\n }\n return request;\n },\n };\n};\n\nexport const logInterceptor = {\n before(request: Request) {\n console.log('Request', request);\n return request;\n },\n after(response: Response) {\n console.log('Response', response);\n return response;\n },\n};\n";
|
|
1131
|
+
var interceptors_default = "export interface Interceptor {\n before?: (request: Request) => Promise<Request> | Request;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createDefaultHeadersInterceptor = (\n getHeaders: () => Record<string, string | undefined>,\n) => {\n return {\n before(request: Request) {\n const headers = getHeaders();\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !request.headers.has(key)) {\n request.headers.set(key, value);\n }\n }\n\n return request;\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (getBaseUrl: () => string) => {\n return {\n before(request: Request) {\n const baseUrl = getBaseUrl();\n if (request.url.startsWith('local://')) {\n return new Request(request.url.replace('local://', baseUrl), request);\n }\n return request;\n },\n };\n};\n\nexport const logInterceptor = {\n before(request: Request) {\n console.log('Request', request);\n return request;\n },\n after(response: Response) {\n console.log('Response', response);\n return response;\n },\n};\n\n/**\n * Creates an interceptor that logs detailed information about requests and responses.\n * @param options Configuration options for the logger\n * @returns An interceptor object with before and after handlers\n */\nexport const createDetailedLogInterceptor = (options?: {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n includeRequestBody?: boolean;\n includeResponseBody?: boolean;\n}) => {\n const logLevel = options?.logLevel || 'info';\n const includeRequestBody = options?.includeRequestBody || false;\n const includeResponseBody = options?.includeResponseBody || false;\n\n return {\n async before(request: Request) {\n const logData = {\n url: request.url,\n method: request.method,\n contentType: request.headers.get('Content-Type'),\n headers: Object.fromEntries([...request.headers.entries()]),\n };\n\n console[logLevel]('\u{1F680} Outgoing Request:', logData);\n\n if (includeRequestBody) {\n try {\n // Clone the request to avoid consuming the body stream\n const clonedRequest = request.clone();\n if (clonedRequest.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedRequest.json().catch(() => null);\n console[logLevel]('Request Body:', body);\n } else {\n const body = await clonedRequest.text().catch(() => null);\n console[logLevel]('Request Body:', body);\n }\n } catch (error) {\n console.error('Could not log request body:', error);\n }\n }\n\n return request;\n },\n\n async after(response: Response) {\n const logData = {\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n headers: Object.fromEntries([...response.headers.entries()]),\n };\n\n console[logLevel]('\u{1F4E5} Incoming Response:', logData);\n\n if (includeResponseBody && response.body) {\n try {\n // Clone the response to avoid consuming the body stream\n const clonedResponse = response.clone();\n if (clonedResponse.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedResponse.json().catch(() => null);\n console[logLevel]('Response Body:', body);\n } else {\n const body = await clonedResponse.text().catch(() => null);\n if (body) {\n console[logLevel]('Response Body:', body.substring(0, 500) + (body.length > 500 ? '...' : ''));\n } else {\n console[logLevel]('No response body');\n }\n }\n } catch (error) {\n console.error('Could not log response body:', error);\n }\n }\n\n return response;\n },\n };\n};\n";
|
|
1065
1132
|
|
|
1066
1133
|
// packages/typescript/src/lib/http/parse-response.txt
|
|
1067
|
-
var parse_response_default =
|
|
1134
|
+
var parse_response_default = 'import { parse } from "fast-content-type-parse";\n\nexport async function handleError(response: Response) {\n try {\n if (response.status >= 400 && response.status < 500) {\n const body = (await response.json()) as Record<string, any>;\n return {\n status: response.status,\n body: body,\n };\n }\n return new Error(\n `An error occurred while fetching the data. Status: ${response.status}`,\n );\n } catch (error) {\n return error as any;\n }\n}\n\nasync function handleChunkedResponse(response: Response, contentType: string) {\n const { type } = parse(contentType);\n\n switch (type) {\n case "application/json": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return JSON.parse(buffer);\n }\n case "text/html":\n case "text/plain": {\n let buffer = "";\n const reader = response.body!.getReader();\n const decoder = new TextDecoder();\n while (true) {\n const { value, done } = await reader.read();\n if (done) break;\n buffer += decoder.decode(value);\n }\n return buffer;\n }\n default:\n return response.body;\n }\n}\n\nexport function chunked(response: Response) {\n return response.body;\n}\n\nexport async function buffered(response: Response) {\n const contentType = response.headers.get("Content-Type");\n if (!contentType) {\n throw new Error("Content-Type header is missing");\n }\n\n if (response.status === 204) {\n return null;\n }\n\n const { type } = parse(contentType);\n switch (type) {\n case "application/json":\n return response.json();\n case "text/plain":\n return response.text();\n case "text/html":\n return response.text();\n case "text/xml":\n case "application/xml":\n return response.text();\n case "application/x-www-form-urlencoded": {\n const text = await response.text();\n return Object.fromEntries(new URLSearchParams(text));\n }\n case "multipart/form-data":\n return response.formData();\n default:\n throw new Error(`Unsupported content type: ${contentType}`);\n }\n}\n';
|
|
1068
1135
|
|
|
1069
1136
|
// packages/typescript/src/lib/http/parser.txt
|
|
1070
1137
|
var parser_default = "import { z } from 'zod';\n\nexport type ParseError<T extends z.ZodType<any, any, any>> = {\n kind: 'parse';\n} & z.inferFlattenedErrors<T>;\n\nexport function parse<T extends z.ZodType>(\n schema: T,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const errors = result.error.flatten((issue) => issue);\n return [null, errors];\n }\n return [result.data as z.infer<T>, null];\n}\n";
|
|
@@ -1076,7 +1143,7 @@ var request_default = "export type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | '
|
|
|
1076
1143
|
var response_default = "export interface ApiResponse<Status extends number, Body extends unknown> {\n kind: 'response';\n status: Status;\n body: Body;\n}\n\n// 4xx Client Errors\nexport type BadRequest = ApiResponse<400, { message: string }>;\nexport type Unauthorized = ApiResponse<401, { message: string }>;\nexport type PaymentRequired = ApiResponse<402, { message: string }>;\nexport type Forbidden = ApiResponse<403, { message: string }>;\nexport type NotFound = ApiResponse<404, { message: string }>;\nexport type MethodNotAllowed = ApiResponse<405, { message: string }>;\nexport type NotAcceptable = ApiResponse<406, { message: string }>;\nexport type Conflict = ApiResponse<409, { message: string }>;\nexport type Gone = ApiResponse<410, { message: string }>;\nexport type UnprocessableEntity = ApiResponse<422, { message: string; errors?: Record<string, string[]> }>;\nexport type TooManyRequests = ApiResponse<429, { message: string; retryAfter?: string }>;\nexport type PayloadTooLarge = ApiResponse<413, { message: string; }>;\nexport type UnsupportedMediaType = ApiResponse<415, { message: string; }>;\n\n// 5xx Server Errors\nexport type InternalServerError = ApiResponse<500, { message: string }>;\nexport type NotImplemented = ApiResponse<501, { message: string }>;\nexport type BadGateway = ApiResponse<502, { message: string }>;\nexport type ServiceUnavailable = ApiResponse<503, { message: string; retryAfter?: string }>;\nexport type GatewayTimeout = ApiResponse<504, { message: string }>;\n\nexport type ClientError =\n | BadRequest\n | Unauthorized\n | PaymentRequired\n | Forbidden\n | NotFound\n | MethodNotAllowed\n | NotAcceptable\n | Conflict\n | Gone\n | UnprocessableEntity\n | TooManyRequests;\n\nexport type ServerError =\n | InternalServerError\n | NotImplemented\n | BadGateway\n | ServiceUnavailable\n | GatewayTimeout;\n\nexport type ProblematicResponse = ClientError | ServerError;\n";
|
|
1077
1144
|
|
|
1078
1145
|
// packages/typescript/src/lib/http/send-request.txt
|
|
1079
|
-
var send_request_default = "
|
|
1146
|
+
var send_request_default = "\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => Request;\n deserializer: (response: Response) => Promise<unknown> | unknown;\n}\n\nexport const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function sendRequest(\n input: unknown,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\n },\n) {\n const { interceptors = [] } = options;\n const [parsedInput, parseError] = parse(route.schema, input);\n if (parseError) {\n return [null as never, { ...parseError, kind: 'parse' } as never] as const;\n }\n\n let request = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n request = await interceptor.before(request);\n }\n }\n\n let response = await (options.fetch ?? fetch)(request);\n\n for (let i = interceptors.length - 1; i >= 0; i--) {\n const interceptor = interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n if (response.ok) {\n const data = await route.deserializer(response);\n return [data as never, null] as const;\n }\n const error = await handleError(response);\n return [null as never, { ...error, kind: 'response' }] as const;\n}\n";
|
|
1080
1147
|
|
|
1081
1148
|
// packages/typescript/src/lib/generate.ts
|
|
1082
1149
|
function security(spec) {
|
|
@@ -1100,10 +1167,15 @@ function security(spec) {
|
|
|
1100
1167
|
return options;
|
|
1101
1168
|
}
|
|
1102
1169
|
async function generate(spec, settings) {
|
|
1170
|
+
settings.useTsExtension ??= true;
|
|
1171
|
+
const makeImport = (moduleSpecifier) => {
|
|
1172
|
+
return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
|
|
1173
|
+
};
|
|
1103
1174
|
const { commonSchemas, groups, outputs, commonZod } = generateCode({
|
|
1104
1175
|
spec,
|
|
1105
1176
|
style: "github",
|
|
1106
|
-
target: "javascript"
|
|
1177
|
+
target: "javascript",
|
|
1178
|
+
makeImport
|
|
1107
1179
|
});
|
|
1108
1180
|
const output = settings.mode === "full" ? join(settings.output, "src") : settings.output;
|
|
1109
1181
|
const options = security(spec);
|
|
@@ -1111,9 +1183,10 @@ async function generate(spec, settings) {
|
|
|
1111
1183
|
name: settings.name || "Client",
|
|
1112
1184
|
operations: groups,
|
|
1113
1185
|
servers: spec.servers?.map((server) => server.url) || [],
|
|
1114
|
-
options
|
|
1186
|
+
options,
|
|
1187
|
+
makeImport
|
|
1115
1188
|
});
|
|
1116
|
-
const inputFiles = generateInputs(groups, commonZod);
|
|
1189
|
+
const inputFiles = generateInputs(groups, commonZod, makeImport);
|
|
1117
1190
|
await writeFiles(output, {
|
|
1118
1191
|
"outputs/.gitkeep": "",
|
|
1119
1192
|
"inputs/.gitkeep": "",
|
|
@@ -1123,7 +1196,11 @@ async function generate(spec, settings) {
|
|
|
1123
1196
|
await writeFiles(join(output, "http"), {
|
|
1124
1197
|
"interceptors.ts": interceptors_default,
|
|
1125
1198
|
"parse-response.ts": parse_response_default,
|
|
1126
|
-
"send-request.ts":
|
|
1199
|
+
"send-request.ts": `import z from 'zod';
|
|
1200
|
+
import type { Interceptor } from './${makeImport("interceptors")}';
|
|
1201
|
+
import { handleError } from './${makeImport("parse-response")}';
|
|
1202
|
+
import { parse } from './${makeImport("parser")}';
|
|
1203
|
+
${send_request_default}`,
|
|
1127
1204
|
"response.ts": response_default,
|
|
1128
1205
|
"parser.ts": parser_default,
|
|
1129
1206
|
"request.ts": request_default
|
|
@@ -1138,7 +1215,7 @@ async function generate(spec, settings) {
|
|
|
1138
1215
|
`models/${name}.ts`,
|
|
1139
1216
|
[
|
|
1140
1217
|
`import { z } from 'zod';`,
|
|
1141
|
-
...
|
|
1218
|
+
...exclude(modelsImports, [name]).map(
|
|
1142
1219
|
(it) => `import type { ${it} } from './${it}.ts';`
|
|
1143
1220
|
),
|
|
1144
1221
|
`export type ${name} = ${schema};`
|
|
@@ -1147,17 +1224,20 @@ async function generate(spec, settings) {
|
|
|
1147
1224
|
)
|
|
1148
1225
|
});
|
|
1149
1226
|
const folders = [
|
|
1150
|
-
getFolderExports(output),
|
|
1151
|
-
getFolderExports(join(output, "outputs")),
|
|
1227
|
+
getFolderExports(output, settings.useTsExtension),
|
|
1228
|
+
getFolderExports(join(output, "outputs"), settings.useTsExtension),
|
|
1152
1229
|
getFolderExports(
|
|
1153
1230
|
join(output, "inputs"),
|
|
1231
|
+
settings.useTsExtension,
|
|
1154
1232
|
["ts"],
|
|
1155
1233
|
(dirent) => dirent.isDirectory() && dirent.name === "schemas"
|
|
1156
1234
|
),
|
|
1157
|
-
getFolderExports(join(output, "http"))
|
|
1235
|
+
getFolderExports(join(output, "http"), settings.useTsExtension)
|
|
1158
1236
|
];
|
|
1159
1237
|
if (modelsImports.length) {
|
|
1160
|
-
folders.push(
|
|
1238
|
+
folders.push(
|
|
1239
|
+
getFolderExports(join(output, "models"), settings.useTsExtension)
|
|
1240
|
+
);
|
|
1161
1241
|
}
|
|
1162
1242
|
const [index, outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|
|
1163
1243
|
await writeFiles(output, {
|
|
@@ -1173,6 +1253,7 @@ async function generate(spec, settings) {
|
|
|
1173
1253
|
ignoreIfExists: true,
|
|
1174
1254
|
content: JSON.stringify(
|
|
1175
1255
|
{
|
|
1256
|
+
name: "sdk",
|
|
1176
1257
|
type: "module",
|
|
1177
1258
|
main: "./src/index.ts",
|
|
1178
1259
|
dependencies: {
|
|
@@ -1185,7 +1266,7 @@ async function generate(spec, settings) {
|
|
|
1185
1266
|
)
|
|
1186
1267
|
},
|
|
1187
1268
|
"tsconfig.json": {
|
|
1188
|
-
ignoreIfExists:
|
|
1269
|
+
ignoreIfExists: true,
|
|
1189
1270
|
content: JSON.stringify(
|
|
1190
1271
|
{
|
|
1191
1272
|
compilerOptions: {
|
|
@@ -1194,6 +1275,7 @@ async function generate(spec, settings) {
|
|
|
1194
1275
|
target: "ESNext",
|
|
1195
1276
|
module: "ESNext",
|
|
1196
1277
|
noEmit: true,
|
|
1278
|
+
strict: true,
|
|
1197
1279
|
allowImportingTsExtensions: true,
|
|
1198
1280
|
verbatimModuleSyntax: true,
|
|
1199
1281
|
baseUrl: ".",
|