@sdk-it/typescript 0.17.0 → 0.19.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 +649 -153
- package/dist/index.js.map +4 -4
- package/dist/lib/client.d.ts +2 -1
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/emitters/interface.d.ts +0 -4
- package/dist/lib/emitters/interface.d.ts.map +1 -1
- package/dist/lib/generate.d.ts +2 -1
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts +4 -1
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts +1 -0
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/style.d.ts +6 -0
- package/dist/lib/style.d.ts.map +1 -0
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,12 +1,138 @@
|
|
|
1
1
|
// packages/typescript/src/lib/generate.ts
|
|
2
|
-
import {
|
|
2
|
+
import { template as template2 } from "lodash-es";
|
|
3
|
+
import { join as join3 } from "node:path";
|
|
3
4
|
import { npmRunPathEnv } from "npm-run-path";
|
|
4
5
|
import { spinalcase as spinalcase3 } from "stringcase";
|
|
5
|
-
|
|
6
|
+
|
|
7
|
+
// packages/core/dist/index.js
|
|
8
|
+
import {
|
|
9
|
+
pascalcase as _pascalcase,
|
|
10
|
+
snakecase as _snakecase,
|
|
11
|
+
spinalcase as _spinalcase
|
|
12
|
+
} from "stringcase";
|
|
13
|
+
import ts, { TypeFlags, symbolName } from "typescript";
|
|
14
|
+
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
15
|
+
import { dirname, extname, isAbsolute, join } from "node:path";
|
|
16
|
+
import debug from "debug";
|
|
17
|
+
import ts2 from "typescript";
|
|
18
|
+
import { get } from "lodash-es";
|
|
19
|
+
var deriveSymbol = Symbol.for("serialize");
|
|
20
|
+
var $types = Symbol.for("types");
|
|
21
|
+
async function exist(file) {
|
|
22
|
+
return stat(file).then(() => true).catch(() => false);
|
|
23
|
+
}
|
|
24
|
+
async function writeFiles(dir, contents) {
|
|
25
|
+
await Promise.all(
|
|
26
|
+
Object.entries(contents).map(async ([file, content]) => {
|
|
27
|
+
if (content === null) {
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
const filePath = isAbsolute(file) ? file : join(dir, file);
|
|
31
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
32
|
+
if (typeof content === "string") {
|
|
33
|
+
await writeFile(filePath, content, "utf-8");
|
|
34
|
+
} else {
|
|
35
|
+
if (content.ignoreIfExists) {
|
|
36
|
+
if (!await exist(filePath)) {
|
|
37
|
+
await writeFile(filePath, content.content, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
await writeFile(filePath, content.content, "utf-8");
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
async function getFolderExports(folder, includeExtension = true, extensions = ["ts"], ignore = () => false) {
|
|
47
|
+
const files = await readdir(folder, { withFileTypes: true });
|
|
48
|
+
const exports = [];
|
|
49
|
+
for (const file of files) {
|
|
50
|
+
if (ignore(file)) {
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (file.isDirectory()) {
|
|
54
|
+
if (await exist(`${file.parentPath}/${file.name}/index.ts`)) {
|
|
55
|
+
exports.push(
|
|
56
|
+
`export * from './${file.name}/index${includeExtension ? ".ts" : ""}';`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
} else if (file.name !== "index.ts" && extensions.includes(getExt(file.name))) {
|
|
60
|
+
exports.push(
|
|
61
|
+
`export * from './${includeExtension ? file.name : file.name.replace(extname(file.name), "")}';`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return exports.join("\n");
|
|
66
|
+
}
|
|
67
|
+
var getExt = (fileName) => {
|
|
68
|
+
if (!fileName) {
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
const lastDot = fileName.lastIndexOf(".");
|
|
72
|
+
if (lastDot === -1) {
|
|
73
|
+
return "";
|
|
74
|
+
}
|
|
75
|
+
const ext = fileName.slice(lastDot + 1).split("/").filter(Boolean).join("");
|
|
76
|
+
if (ext === fileName) {
|
|
77
|
+
return "";
|
|
78
|
+
}
|
|
79
|
+
return ext || "txt";
|
|
80
|
+
};
|
|
81
|
+
var methods = [
|
|
82
|
+
"get",
|
|
83
|
+
"post",
|
|
84
|
+
"put",
|
|
85
|
+
"patch",
|
|
86
|
+
"delete",
|
|
87
|
+
"trace",
|
|
88
|
+
"head"
|
|
89
|
+
];
|
|
90
|
+
var logger = debug("january:client");
|
|
91
|
+
function isRef(obj) {
|
|
92
|
+
return obj && "$ref" in obj;
|
|
93
|
+
}
|
|
94
|
+
function cleanRef(ref) {
|
|
95
|
+
return ref.replace(/^#\//, "");
|
|
96
|
+
}
|
|
97
|
+
function parseRef(ref) {
|
|
98
|
+
const parts = ref.split("/");
|
|
99
|
+
const [model] = parts.splice(-1);
|
|
100
|
+
const [namespace] = parts.splice(-1);
|
|
101
|
+
return {
|
|
102
|
+
model,
|
|
103
|
+
namespace,
|
|
104
|
+
path: cleanRef(parts.join("/"))
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
function followRef(spec, ref) {
|
|
108
|
+
const pathParts = cleanRef(ref).split("/");
|
|
109
|
+
const entry = get(spec, pathParts);
|
|
110
|
+
if (entry && "$ref" in entry) {
|
|
111
|
+
return followRef(spec, entry.$ref);
|
|
112
|
+
}
|
|
113
|
+
return entry;
|
|
114
|
+
}
|
|
115
|
+
function removeDuplicates(data, accessor = (item) => item) {
|
|
116
|
+
return [...new Map(data.map((x) => [accessor(x), x])).values()];
|
|
117
|
+
}
|
|
118
|
+
function toLitObject(obj, accessor = (value) => value) {
|
|
119
|
+
return `{${Object.keys(obj).map((key) => `${key}: ${accessor(obj[key])}`).join(", ")}}`;
|
|
120
|
+
}
|
|
121
|
+
function isEmpty(value) {
|
|
122
|
+
if (value === null || value === void 0 || value === "") {
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
if (typeof value === "object" && Object.keys(value).length === 0) {
|
|
129
|
+
return true;
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
6
133
|
|
|
7
134
|
// packages/typescript/src/lib/client.ts
|
|
8
|
-
|
|
9
|
-
var client_default = (spec) => {
|
|
135
|
+
var client_default = (spec, style) => {
|
|
10
136
|
const optionsEntries = Object.entries(spec.options).map(
|
|
11
137
|
([key, value]) => [`'${key}'`, value]
|
|
12
138
|
);
|
|
@@ -28,8 +154,8 @@ var client_default = (spec) => {
|
|
|
28
154
|
}
|
|
29
155
|
};
|
|
30
156
|
return `
|
|
31
|
-
import type { RequestConfig } from './http/${spec.makeImport("request")}';
|
|
32
|
-
import { fetchType,
|
|
157
|
+
import type { HeadersInit, RequestConfig } from './http/${spec.makeImport("request")}';
|
|
158
|
+
import { fetchType, dispatch, parse } from './http/${spec.makeImport("send-request")}';
|
|
33
159
|
import z from 'zod';
|
|
34
160
|
import type { Endpoints } from './api/${spec.makeImport("endpoints")}';
|
|
35
161
|
import schemas from './api/${spec.makeImport("schemas")}';
|
|
@@ -56,9 +182,9 @@ export class ${spec.name} {
|
|
|
56
182
|
endpoint: E,
|
|
57
183
|
input: Endpoints[E]['input'],
|
|
58
184
|
options?: { signal?: AbortSignal, headers?: HeadersInit },
|
|
59
|
-
)
|
|
185
|
+
) ${style.errorAsValue ? `: Promise<readonly [Endpoints[E]['output'], Endpoints[E]['error'] | null]>` : `: Promise<Endpoints[E]['output']>`} {
|
|
60
186
|
const route = schemas[endpoint];
|
|
61
|
-
|
|
187
|
+
const result = await dispatch(Object.assign(this.#defaultInputs, input), route, {
|
|
62
188
|
fetch: this.options.fetch,
|
|
63
189
|
interceptors: [
|
|
64
190
|
createHeadersInterceptor(() => this.defaultHeaders, options?.headers ?? {}),
|
|
@@ -66,20 +192,23 @@ export class ${spec.name} {
|
|
|
66
192
|
],
|
|
67
193
|
signal: options?.signal,
|
|
68
194
|
});
|
|
195
|
+
return ${style.errorAsValue ? `result as [Endpoints[E]['output'], Endpoints[E]['error'] | null]` : `result as Endpoints[E]['output']`};
|
|
69
196
|
}
|
|
70
197
|
|
|
71
198
|
async prepare<E extends keyof Endpoints>(
|
|
72
199
|
endpoint: E,
|
|
73
200
|
input: Endpoints[E]['input'],
|
|
74
201
|
options?: { headers?: HeadersInit },
|
|
75
|
-
): Promise<
|
|
202
|
+
): ${style.errorAsValue ? `Promise<
|
|
76
203
|
readonly [
|
|
77
204
|
RequestConfig & {
|
|
78
205
|
parse: (response: Response) => ReturnType<typeof parse>;
|
|
79
206
|
},
|
|
80
207
|
ParseError<(typeof schemas)[E]['schema']> | null,
|
|
81
208
|
]
|
|
82
|
-
|
|
209
|
+
>` : `Promise<RequestConfig & {
|
|
210
|
+
parse: (response: Response) => ReturnType<typeof parse>;
|
|
211
|
+
}>`} {
|
|
83
212
|
const route = schemas[endpoint];
|
|
84
213
|
|
|
85
214
|
const interceptors = [
|
|
@@ -91,7 +220,7 @@ export class ${spec.name} {
|
|
|
91
220
|
];
|
|
92
221
|
const [parsedInput, parseError] = parseInput(route.schema, input);
|
|
93
222
|
if (parseError) {
|
|
94
|
-
return [null as never, parseError as never] as const;
|
|
223
|
+
${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
|
|
95
224
|
}
|
|
96
225
|
|
|
97
226
|
let config = route.toRequest(parsedInput as never);
|
|
@@ -100,10 +229,8 @@ export class ${spec.name} {
|
|
|
100
229
|
config = await interceptor.before(config);
|
|
101
230
|
}
|
|
102
231
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
null as never,
|
|
106
|
-
] as const;
|
|
232
|
+
const prepared = { ...config, parse: (response: Response) => parse(route, response) };
|
|
233
|
+
return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared"}
|
|
107
234
|
}
|
|
108
235
|
|
|
109
236
|
get defaultHeaders() {
|
|
@@ -127,10 +254,9 @@ export class ${spec.name} {
|
|
|
127
254
|
};
|
|
128
255
|
|
|
129
256
|
// packages/typescript/src/lib/generator.ts
|
|
130
|
-
import { merge } from "lodash-es";
|
|
131
|
-
import { join } from "node:path";
|
|
257
|
+
import { merge, template } from "lodash-es";
|
|
258
|
+
import { join as join2 } from "node:path";
|
|
132
259
|
import { camelcase as camelcase3, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
|
|
133
|
-
import { followRef as followRef4, isEmpty, isRef as isRef5 } from "@sdk-it/core";
|
|
134
260
|
|
|
135
261
|
// packages/spec/dist/lib/operation.js
|
|
136
262
|
import { camelcase } from "stringcase";
|
|
@@ -395,7 +521,6 @@ function determineGenericTag(pathString, operation) {
|
|
|
395
521
|
}
|
|
396
522
|
|
|
397
523
|
// packages/typescript/src/lib/emitters/zod.ts
|
|
398
|
-
import { cleanRef, followRef, isRef, parseRef } from "@sdk-it/core";
|
|
399
524
|
var ZodDeserialzer = class {
|
|
400
525
|
circularRefTracker = /* @__PURE__ */ new Set();
|
|
401
526
|
#spec;
|
|
@@ -657,12 +782,10 @@ function appendDefault(defaultValue) {
|
|
|
657
782
|
}
|
|
658
783
|
|
|
659
784
|
// packages/typescript/src/lib/sdk.ts
|
|
660
|
-
import { get } from "lodash-es";
|
|
785
|
+
import { get as get2 } from "lodash-es";
|
|
661
786
|
import { camelcase as camelcase2, pascalcase, spinalcase } from "stringcase";
|
|
662
|
-
import { followRef as followRef3, isRef as isRef4, toLitObject as toLitObject2 } from "@sdk-it/core";
|
|
663
787
|
|
|
664
788
|
// packages/typescript/src/lib/emitters/interface.ts
|
|
665
|
-
import { cleanRef as cleanRef2, followRef as followRef2, isRef as isRef2, parseRef as parseRef2 } from "@sdk-it/core";
|
|
666
789
|
var TypeScriptDeserialzer = class {
|
|
667
790
|
circularRefTracker = /* @__PURE__ */ new Set();
|
|
668
791
|
#spec;
|
|
@@ -671,70 +794,12 @@ var TypeScriptDeserialzer = class {
|
|
|
671
794
|
this.#spec = spec;
|
|
672
795
|
this.#onRef = onRef;
|
|
673
796
|
}
|
|
674
|
-
#stringifyKey = (
|
|
675
|
-
const reservedWords = [
|
|
676
|
-
"constructor",
|
|
677
|
-
"prototype",
|
|
678
|
-
"break",
|
|
679
|
-
"case",
|
|
680
|
-
"catch",
|
|
681
|
-
"class",
|
|
682
|
-
"const",
|
|
683
|
-
"continue",
|
|
684
|
-
"debugger",
|
|
685
|
-
"default",
|
|
686
|
-
"delete",
|
|
687
|
-
"do",
|
|
688
|
-
"else",
|
|
689
|
-
"export",
|
|
690
|
-
"extends",
|
|
691
|
-
"false",
|
|
692
|
-
"finally",
|
|
693
|
-
"for",
|
|
694
|
-
"function",
|
|
695
|
-
"if",
|
|
696
|
-
"import",
|
|
697
|
-
"in",
|
|
698
|
-
"instanceof",
|
|
699
|
-
"new",
|
|
700
|
-
"null",
|
|
701
|
-
"return",
|
|
702
|
-
"super",
|
|
703
|
-
"switch",
|
|
704
|
-
"this",
|
|
705
|
-
"throw",
|
|
706
|
-
"true",
|
|
707
|
-
"try",
|
|
708
|
-
"typeof",
|
|
709
|
-
"var",
|
|
710
|
-
"void",
|
|
711
|
-
"while",
|
|
712
|
-
"with",
|
|
713
|
-
"yield"
|
|
714
|
-
];
|
|
715
|
-
if (reservedWords.includes(key)) {
|
|
716
|
-
return `'${key}'`;
|
|
717
|
-
}
|
|
718
|
-
if (key.trim() === "") {
|
|
719
|
-
return `'${key}'`;
|
|
720
|
-
}
|
|
721
|
-
const firstChar = key.charAt(0);
|
|
722
|
-
const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
|
|
723
|
-
if (!validFirstChar) {
|
|
724
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
725
|
-
}
|
|
726
|
-
for (let i = 1; i < key.length; i++) {
|
|
727
|
-
const char = key.charAt(i);
|
|
728
|
-
const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
|
|
729
|
-
if (!validChar) {
|
|
730
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
return key;
|
|
734
|
-
};
|
|
735
|
-
#stringifyKeyV2 = (value) => {
|
|
797
|
+
#stringifyKey = (value) => {
|
|
736
798
|
return `'${value}'`;
|
|
737
799
|
};
|
|
800
|
+
#isInternal = (schema) => {
|
|
801
|
+
return isRef(schema) ? false : !!schema["x-internal"];
|
|
802
|
+
};
|
|
738
803
|
/**
|
|
739
804
|
* Handle objects (properties)
|
|
740
805
|
*/
|
|
@@ -743,7 +808,7 @@ var TypeScriptDeserialzer = class {
|
|
|
743
808
|
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
744
809
|
const isRequired = (schema.required ?? []).includes(key);
|
|
745
810
|
const tsType = this.handle(propSchema, isRequired);
|
|
746
|
-
return `${this.#
|
|
811
|
+
return `${this.#isInternal(propSchema) ? key : this.#stringifyKey(key)}: ${tsType}`;
|
|
747
812
|
});
|
|
748
813
|
if (schema.additionalProperties) {
|
|
749
814
|
if (typeof schema.additionalProperties === "object") {
|
|
@@ -794,14 +859,14 @@ var TypeScriptDeserialzer = class {
|
|
|
794
859
|
}
|
|
795
860
|
}
|
|
796
861
|
ref($ref, required) {
|
|
797
|
-
const schemaName =
|
|
862
|
+
const schemaName = cleanRef($ref).split("/").pop();
|
|
798
863
|
if (this.circularRefTracker.has(schemaName)) {
|
|
799
864
|
return schemaName;
|
|
800
865
|
}
|
|
801
866
|
this.circularRefTracker.add(schemaName);
|
|
802
867
|
this.#onRef?.(
|
|
803
868
|
schemaName,
|
|
804
|
-
this.handle(
|
|
869
|
+
this.handle(followRef(this.#spec, $ref), required)
|
|
805
870
|
);
|
|
806
871
|
this.circularRefTracker.delete(schemaName);
|
|
807
872
|
return appendOptional2(schemaName, required);
|
|
@@ -819,8 +884,8 @@ var TypeScriptDeserialzer = class {
|
|
|
819
884
|
}
|
|
820
885
|
oneOf(schemas, required) {
|
|
821
886
|
const oneOfTypes = schemas.map((sub) => {
|
|
822
|
-
if (
|
|
823
|
-
const { model } =
|
|
887
|
+
if (isRef(sub)) {
|
|
888
|
+
const { model } = parseRef(sub.$ref);
|
|
824
889
|
if (this.circularRefTracker.has(model)) {
|
|
825
890
|
return model;
|
|
826
891
|
}
|
|
@@ -867,7 +932,7 @@ var TypeScriptDeserialzer = class {
|
|
|
867
932
|
return appendOptional2(type, required);
|
|
868
933
|
}
|
|
869
934
|
handle(schema, required) {
|
|
870
|
-
if (
|
|
935
|
+
if (isRef(schema)) {
|
|
871
936
|
return this.ref(schema.$ref, required);
|
|
872
937
|
}
|
|
873
938
|
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
@@ -882,6 +947,12 @@ var TypeScriptDeserialzer = class {
|
|
|
882
947
|
if (schema.enum && Array.isArray(schema.enum)) {
|
|
883
948
|
return this.enum(schema.enum, required);
|
|
884
949
|
}
|
|
950
|
+
if (schema.const) {
|
|
951
|
+
if (schema["x-internal"]) {
|
|
952
|
+
return `${schema.const}`;
|
|
953
|
+
}
|
|
954
|
+
return this.enum([schema.const], required);
|
|
955
|
+
}
|
|
885
956
|
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
886
957
|
if (!types.length) {
|
|
887
958
|
if ("properties" in schema) {
|
|
@@ -900,20 +971,12 @@ var TypeScriptDeserialzer = class {
|
|
|
900
971
|
}
|
|
901
972
|
return this.normal(types[0], schema, required);
|
|
902
973
|
}
|
|
903
|
-
/**
|
|
904
|
-
* Generate an interface declaration
|
|
905
|
-
*/
|
|
906
|
-
generateInterface(name, schema) {
|
|
907
|
-
const content = this.handle(schema, true);
|
|
908
|
-
return `interface ${name} ${content}`;
|
|
909
|
-
}
|
|
910
974
|
};
|
|
911
975
|
function appendOptional2(type, isRequired) {
|
|
912
976
|
return isRequired ? type : `${type} | undefined`;
|
|
913
977
|
}
|
|
914
978
|
|
|
915
979
|
// packages/typescript/src/lib/utils.ts
|
|
916
|
-
import { isRef as isRef3, removeDuplicates } from "@sdk-it/core";
|
|
917
980
|
function securityToOptions(security2, securitySchemes, staticIn) {
|
|
918
981
|
securitySchemes ??= {};
|
|
919
982
|
const options = {};
|
|
@@ -923,7 +986,7 @@ function securityToOptions(security2, securitySchemes, staticIn) {
|
|
|
923
986
|
continue;
|
|
924
987
|
}
|
|
925
988
|
const schema = securitySchemes[name];
|
|
926
|
-
if (
|
|
989
|
+
if (isRef(schema)) {
|
|
927
990
|
throw new Error(`Ref security schemas are not supported`);
|
|
928
991
|
}
|
|
929
992
|
if (schema.type === "http") {
|
|
@@ -1012,7 +1075,7 @@ function generateInputs(operationsSet, commonZod, makeImport) {
|
|
|
1012
1075
|
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
1013
1076
|
for (const operation of operations) {
|
|
1014
1077
|
const schemaName = camelcase2(`${operation.name} schema`);
|
|
1015
|
-
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] :
|
|
1078
|
+
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject(operation.schemas)};`;
|
|
1016
1079
|
const inputContent = schema;
|
|
1017
1080
|
for (const schema2 of commonImports) {
|
|
1018
1081
|
if (inputContent.includes(schema2)) {
|
|
@@ -1082,7 +1145,7 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
|
|
|
1082
1145
|
return statusCode >= 200 && statusCode < 300;
|
|
1083
1146
|
}).length > 1;
|
|
1084
1147
|
for (const status in specOperation.responses) {
|
|
1085
|
-
const response =
|
|
1148
|
+
const response = isRef(specOperation.responses[status]) ? followRef(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
|
|
1086
1149
|
const handled = handleResponse(
|
|
1087
1150
|
spec,
|
|
1088
1151
|
operation.name,
|
|
@@ -1172,7 +1235,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1172
1235
|
);
|
|
1173
1236
|
const statusCode = +status;
|
|
1174
1237
|
const parser = (response.headers ?? {})["Transfer-Encoding"] ? "chunked" : "buffered";
|
|
1175
|
-
const statusName = statusCodeToResponseMap[status] || "APIResponse"
|
|
1238
|
+
const statusName = `http.${statusCodeToResponseMap[status] || "APIResponse"}`;
|
|
1176
1239
|
const interfaceName = pascalcase(
|
|
1177
1240
|
operationName + ` output${numbered ? status : ""}`
|
|
1178
1241
|
);
|
|
@@ -1180,22 +1243,34 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1180
1243
|
outputs.push(statusName);
|
|
1181
1244
|
} else {
|
|
1182
1245
|
if (status.endsWith("XX")) {
|
|
1183
|
-
outputs.push(`APIError<${interfaceName}>`);
|
|
1246
|
+
outputs.push(`http.APIError<${interfaceName}>`);
|
|
1184
1247
|
} else {
|
|
1185
1248
|
outputs.push(
|
|
1186
1249
|
parser !== "buffered" ? `{type: ${statusName}<${interfaceName}>, parser: ${parser}}` : `${statusName}<${interfaceName}>`
|
|
1187
1250
|
);
|
|
1188
1251
|
}
|
|
1189
1252
|
}
|
|
1190
|
-
const responseContent =
|
|
1253
|
+
const responseContent = get2(response, ["content"]);
|
|
1191
1254
|
const isJson = responseContent && responseContent["application/json"];
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1255
|
+
let responseSchema = parser === "chunked" ? "ReadableStream" : "void";
|
|
1256
|
+
if (isJson) {
|
|
1257
|
+
const schema = responseContent["application/json"].schema;
|
|
1258
|
+
const isObject = !isRef(schema) && schema.type === "object";
|
|
1259
|
+
if (isObject && schema.properties) {
|
|
1260
|
+
schema.properties["[http.KIND]"] = {
|
|
1261
|
+
"x-internal": true,
|
|
1262
|
+
const: `typeof ${statusName}.kind`,
|
|
1263
|
+
type: "string"
|
|
1264
|
+
};
|
|
1265
|
+
schema.required ??= [];
|
|
1266
|
+
schema.required.push("[KIND]");
|
|
1267
|
+
}
|
|
1268
|
+
responseSchema = typeScriptDeserialzer.handle(schema, true);
|
|
1269
|
+
}
|
|
1196
1270
|
responses.push({
|
|
1197
1271
|
name: interfaceName,
|
|
1198
|
-
schema: responseSchema
|
|
1272
|
+
schema: responseSchema,
|
|
1273
|
+
description: response.description
|
|
1199
1274
|
});
|
|
1200
1275
|
const statusGroup = +status.slice(0, 1);
|
|
1201
1276
|
if (statusCode >= 400 || statusGroup >= 4) {
|
|
@@ -1209,15 +1284,6 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1209
1284
|
namedImports: [{ isTypeOnly: true, name: interfaceName }]
|
|
1210
1285
|
};
|
|
1211
1286
|
} else if (statusCode >= 200 && statusCode < 300 || statusCode >= 2 || statusGroup <= 3) {
|
|
1212
|
-
endpointImports[statusName] = {
|
|
1213
|
-
moduleSpecifier: utils.makeImport("../http/response"),
|
|
1214
|
-
namedImports: [
|
|
1215
|
-
{
|
|
1216
|
-
isTypeOnly: false,
|
|
1217
|
-
name: statusName
|
|
1218
|
-
}
|
|
1219
|
-
]
|
|
1220
|
-
};
|
|
1221
1287
|
endpointImports[interfaceName] = {
|
|
1222
1288
|
defaultImport: void 0,
|
|
1223
1289
|
isTypeOnly: true,
|
|
@@ -1230,7 +1296,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1230
1296
|
}
|
|
1231
1297
|
|
|
1232
1298
|
// packages/typescript/src/lib/styles/github/endpoints.txt
|
|
1233
|
-
var endpoints_default = "
|
|
1299
|
+
var endpoints_default = "type Output<T extends OutputType> = T extends {\n parser: Parser;\n type: Type<unknown>;\n}\n ? InstanceType<T['type']>\n : T extends Type<unknown>\n ? InstanceType<T>\n : never;\n\ntype Unionize<T> = T extends [infer Single extends OutputType]\n ? Output<Single>\n : T extends readonly [...infer Tuple extends OutputType[]]\n ? { [I in keyof Tuple]: Output<Tuple[I]> }[number]\n : never;\n\ntype EndpointOutput<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n SuccessfulResponse\n>;\n\ntype EndpointError<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n ProblematicResponse\n>;\n\nexport type Endpoints = {\n [K in keyof typeof schemas]: {\n input: z.infer<(typeof schemas)[K]['schema']>;\n output: <% if (outputType === 'default') { %>EndpointOutput<K>['data']<% } else { %>EndpointOutput<K><% } %>;\n error: EndpointError<K> | ParseError<(typeof schemas)[K]['schema']>;\n };\n};";
|
|
1234
1300
|
|
|
1235
1301
|
// packages/typescript/src/lib/generator.ts
|
|
1236
1302
|
function generateCode(config) {
|
|
@@ -1256,7 +1322,7 @@ function generateCode(config) {
|
|
|
1256
1322
|
const inputs = {};
|
|
1257
1323
|
const additionalProperties = [];
|
|
1258
1324
|
for (const param of operation.parameters ?? []) {
|
|
1259
|
-
if (
|
|
1325
|
+
if (isRef(param)) {
|
|
1260
1326
|
throw new Error(`Found reference in parameter ${param.$ref}`);
|
|
1261
1327
|
}
|
|
1262
1328
|
if (!param.schema) {
|
|
@@ -1298,9 +1364,9 @@ function generateCode(config) {
|
|
|
1298
1364
|
};
|
|
1299
1365
|
let outgoingContentType;
|
|
1300
1366
|
if (!isEmpty(operation.requestBody)) {
|
|
1301
|
-
const requestBody =
|
|
1367
|
+
const requestBody = isRef(operation.requestBody) ? followRef(config.spec, operation.requestBody.$ref) : operation.requestBody;
|
|
1302
1368
|
for (const type in requestBody.content) {
|
|
1303
|
-
const ctSchema =
|
|
1369
|
+
const ctSchema = isRef(requestBody.content[type].schema) ? followRef(config.spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
|
|
1304
1370
|
if (!ctSchema) {
|
|
1305
1371
|
console.warn(
|
|
1306
1372
|
`Schema not found for ${type} in ${entry.method} ${entry.path}`
|
|
@@ -1370,14 +1436,23 @@ function generateCode(config) {
|
|
|
1370
1436
|
},
|
|
1371
1437
|
{ makeImport: config.makeImport }
|
|
1372
1438
|
);
|
|
1373
|
-
const output = [
|
|
1439
|
+
const output = [
|
|
1440
|
+
`import z from 'zod';`,
|
|
1441
|
+
`import type * as http from '../http';`
|
|
1442
|
+
];
|
|
1374
1443
|
const responses = endpoint.responses.flatMap((it) => it.responses);
|
|
1375
1444
|
const responsesImports = endpoint.responses.flatMap(
|
|
1376
1445
|
(it) => Object.values(it.imports)
|
|
1377
1446
|
);
|
|
1378
1447
|
if (responses.length) {
|
|
1379
1448
|
output.push(
|
|
1380
|
-
...responses.map(
|
|
1449
|
+
...responses.map(
|
|
1450
|
+
(it) => `${it.description ? `
|
|
1451
|
+
/**
|
|
1452
|
+
* ${it.description}
|
|
1453
|
+
*/
|
|
1454
|
+
` : ""} export type ${it.name} = ${it.schema};`
|
|
1455
|
+
)
|
|
1381
1456
|
);
|
|
1382
1457
|
} else {
|
|
1383
1458
|
output.push(
|
|
@@ -1428,7 +1503,7 @@ function generateCode(config) {
|
|
|
1428
1503
|
commonZod,
|
|
1429
1504
|
outputs,
|
|
1430
1505
|
endpoints: {
|
|
1431
|
-
[
|
|
1506
|
+
[join2("api", "endpoints.ts")]: `
|
|
1432
1507
|
|
|
1433
1508
|
|
|
1434
1509
|
import type z from 'zod';
|
|
@@ -1442,9 +1517,9 @@ import type { OutputType, Parser, Type } from '${config.makeImport(
|
|
|
1442
1517
|
|
|
1443
1518
|
import schemas from '${config.makeImport("./schemas")}';
|
|
1444
1519
|
|
|
1445
|
-
${endpoints_default}`,
|
|
1446
|
-
[`${
|
|
1447
|
-
|
|
1520
|
+
${template(endpoints_default)({ outputType: config.style?.outputType })}`,
|
|
1521
|
+
[`${join2("api", "schemas.ts")}`]: `${allSchemas.map((it) => it.import).join("\n")}
|
|
1522
|
+
import { KIND } from "${config.makeImport("../http/index")}";
|
|
1448
1523
|
export default {
|
|
1449
1524
|
${allSchemas.map((it) => it.use).join(",\n")}
|
|
1450
1525
|
};
|
|
@@ -1463,11 +1538,12 @@ ${allSchemas.map((it) => it.use).join(",\n")}
|
|
|
1463
1538
|
);
|
|
1464
1539
|
return [
|
|
1465
1540
|
[
|
|
1466
|
-
|
|
1541
|
+
join2("api", `${spinalcase2(name)}.ts`),
|
|
1467
1542
|
`${[
|
|
1468
1543
|
...imps,
|
|
1469
1544
|
// ...imports,
|
|
1470
1545
|
`import z from 'zod';`,
|
|
1546
|
+
`import * as http from '${config.makeImport("../http/response")}';`,
|
|
1471
1547
|
`import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
|
|
1472
1548
|
`import { chunked, buffered } from "${config.makeImport("../http/parse-response")}";`,
|
|
1473
1549
|
`import * as ${camelcase3(name)} from '../inputs/${config.makeImport(spinalcase2(name))}';`
|
|
@@ -1485,8 +1561,8 @@ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
|
|
|
1485
1561
|
};
|
|
1486
1562
|
}
|
|
1487
1563
|
function toProps(spec, schemaOrRef, aggregator = []) {
|
|
1488
|
-
if (
|
|
1489
|
-
const schema =
|
|
1564
|
+
if (isRef(schemaOrRef)) {
|
|
1565
|
+
const schema = followRef(spec, schemaOrRef.$ref);
|
|
1490
1566
|
return toProps(spec, schema, aggregator);
|
|
1491
1567
|
} else if (schemaOrRef.type === "object") {
|
|
1492
1568
|
for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
|
|
@@ -1531,7 +1607,7 @@ function bodyInputs(config, ctSchema) {
|
|
|
1531
1607
|
}
|
|
1532
1608
|
|
|
1533
1609
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1534
|
-
var interceptors_default = "export interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig> | RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n defaultHeaders: () => Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n const headers = defaultHeaders();\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\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 && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (\n getBaseUrl: () => string,\n): Interceptor => {\n return {\n before({ init, url }) {\n const baseUrl = getBaseUrl();\n if (url.protocol === 'local:') {\n return {\n init,\n url: new URL(url.href.replace('local://', baseUrl))\n };\n }\n return { init, url };\n },\n };\n};\n\nexport const logInterceptor: Interceptor = {\n before({ url, init }) {\n console.
|
|
1610
|
+
var interceptors_default = "export interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig> | RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n defaultHeaders: () => Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n const headers = defaultHeaders();\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\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 && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (\n getBaseUrl: () => string,\n): Interceptor => {\n return {\n before({ init, url }) {\n const baseUrl = getBaseUrl();\n if (url.protocol === 'local:') {\n return {\n init,\n url: new URL(url.href.replace('local://', baseUrl))\n };\n }\n return { init, url };\n },\n };\n};\n\nexport const logInterceptor: Interceptor = {\n before({ url, init }) {\n console.log('Request:', { url, init });\n return { url, init };\n },\n after(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";
|
|
1535
1611
|
|
|
1536
1612
|
// packages/typescript/src/lib/http/parse-response.txt
|
|
1537
1613
|
var parse_response_default = 'import { parse } from "fast-content-type-parse";\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';
|
|
@@ -1540,13 +1616,421 @@ var parse_response_default = 'import { parse } from "fast-content-type-parse";\n
|
|
|
1540
1616
|
var parser_default = "import { z } from 'zod';\n\nexport class ParseError<T extends z.ZodType<any, any, any>> {\n public data: z.typeToFlattenedError<T, z.ZodIssue>;\n constructor(data: z.typeToFlattenedError<T, z.ZodIssue>) {\n this.data = data;\n }\n}\n\nexport function parseInput<T extends z.ZodType<any, any, any>>(\n schema: T,\n input: unknown,\n) {\n const result = schema.safeParse(input);\n if (!result.success) {\n const error = result.error.flatten((issue) => issue);\n return [null, new ParseError(error)];\n }\n return [result.data as z.infer<T>, null];\n}\n";
|
|
1541
1617
|
|
|
1542
1618
|
// packages/typescript/src/lib/http/request.txt
|
|
1543
|
-
var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\n\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\n\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Record<string, string>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Record<string, string>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n if (\n this.props.inputBody.length === 1 &&\n this.props.inputBody[0] === '$body'\n ) {\n return JSON.stringify(this.input.$body);\n }\n\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n };\n }\n}\n\nclass NoBodySerializer extends Serializer {\n getBody(): BodyInit | null {\n return null;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass FormDataSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new FormData();\n for (const prop of this.props.inputBody) {\n body.append(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {\n Accept: 'application/json',\n };\n }\n}\n\nexport function json(input: Input, props: Props) {\n return new JsonSerializer(input, props).serialize();\n}\nexport function urlencoded(input: Input, props: Props) {\n return new UrlencodedSerializer(input, props).serialize();\n}\nexport function nobody(input: Input, props: Props) {\n return new NoBodySerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n): RequestConfig {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n return {\n url: createUrl(pathVariable, input.query),\n init: {\n method: method,\n headers: new Headers(input.headers),\n body: method === 'GET' ? undefined : input.body,\n },\n }\n}\n";
|
|
1619
|
+
var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type HeadersInit = [string, string][] | Record<string, string>;\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\n\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\n\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Record<string, string>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Record<string, string>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n if (\n this.props.inputBody.length === 1 &&\n this.props.inputBody[0] === '$body'\n ) {\n return JSON.stringify(this.input.$body);\n }\n\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n };\n }\n}\n\nclass NoBodySerializer extends Serializer {\n getBody(): BodyInit | null {\n return null;\n }\n getHeaders(): Record<string, string> {\n return {};\n }\n}\n\nclass FormDataSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new FormData();\n for (const prop of this.props.inputBody) {\n body.append(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {\n Accept: 'application/json',\n };\n }\n}\n\nexport function json(input: Input, props: Props) {\n return new JsonSerializer(input, props).serialize();\n}\nexport function urlencoded(input: Input, props: Props) {\n return new UrlencodedSerializer(input, props).serialize();\n}\nexport function nobody(input: Input, props: Props) {\n return new NoBodySerializer(input, props).serialize();\n}\nexport function formdata(input: Input, props: Props) {\n return new FormDataSerializer(input, props).serialize();\n}\n\nexport function toRequest<T extends Endpoint>(\n endpoint: T,\n input: Serialized,\n): RequestConfig {\n const [method, path] = endpoint.split(' ');\n const pathVariable = template(path, input.params);\n\n return {\n url: createUrl(pathVariable, input.query),\n init: {\n method: method,\n headers: new Headers(input.headers),\n body: method === 'GET' ? undefined : input.body,\n },\n }\n}\n";
|
|
1544
1620
|
|
|
1545
1621
|
// packages/typescript/src/lib/http/response.txt
|
|
1546
|
-
var response_default =
|
|
1622
|
+
var response_default = `export const KIND = Symbol('APIDATA');
|
|
1623
|
+
|
|
1624
|
+
export class APIResponse<Body = unknown, Status extends number = number> {
|
|
1625
|
+
static readonly status: number;
|
|
1626
|
+
static readonly kind: symbol = Symbol.for("APIResponse");
|
|
1627
|
+
status: Status;
|
|
1628
|
+
data: Body;
|
|
1629
|
+
|
|
1630
|
+
constructor(status: Status, data: Body) {
|
|
1631
|
+
this.status = status;
|
|
1632
|
+
this.data = data;
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
static create<Body = unknown>(status: number, data: Body) {
|
|
1636
|
+
return new this(status, data);
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
}
|
|
1640
|
+
|
|
1641
|
+
export class APIError<Body, Status extends number = number> extends APIResponse<
|
|
1642
|
+
Body,
|
|
1643
|
+
Status
|
|
1644
|
+
> {
|
|
1645
|
+
static override create<T>(status: number, data: T) {
|
|
1646
|
+
return new this(status, data);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
|
|
1651
|
+
// 2xx Success
|
|
1652
|
+
export class Ok<T> extends APIResponse<T, 200> {
|
|
1653
|
+
static override readonly kind = Symbol.for("Ok");
|
|
1654
|
+
static override readonly status = 200 as const;
|
|
1655
|
+
constructor(data: T) {
|
|
1656
|
+
super(Ok.status, data);
|
|
1657
|
+
}
|
|
1658
|
+
static override create<T>(status: number, data: T) {
|
|
1659
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1660
|
+
return new this(data);
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
static is<T extends {[KIND]:typeof Ok['kind']}>(value: unknown): value is T {
|
|
1664
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
|
|
1669
|
+
export class Created<T> extends APIResponse<T, 201> {
|
|
1670
|
+
static override readonly kind = Symbol.for("Created");
|
|
1671
|
+
static override status = 201 as const;
|
|
1672
|
+
constructor(data: T) {
|
|
1673
|
+
super(Created.status, data);
|
|
1674
|
+
}
|
|
1675
|
+
static override create<T>(status: number, data: T) {
|
|
1676
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1677
|
+
return new this(data);
|
|
1678
|
+
}
|
|
1679
|
+
|
|
1680
|
+
static is<T extends {[KIND]: typeof Created['kind']}>(value: unknown): value is T {
|
|
1681
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
export class Accepted<T> extends APIResponse<T, 202> {
|
|
1685
|
+
static override readonly kind = Symbol.for("Accepted");
|
|
1686
|
+
static override status = 202 as const;
|
|
1687
|
+
constructor(data: T) {
|
|
1688
|
+
super(Accepted.status, data);
|
|
1689
|
+
}
|
|
1690
|
+
static override create<T>(status: number, data: T) {
|
|
1691
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1692
|
+
return new this(data);
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
static is<T extends {[KIND]: typeof Accepted['kind']}>(value: unknown): value is T {
|
|
1696
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
export class NoContent extends APIResponse<never, 204> {
|
|
1700
|
+
static override readonly kind = Symbol.for("NoContent");
|
|
1701
|
+
static override status = 204 as const;
|
|
1702
|
+
constructor() {
|
|
1703
|
+
super(NoContent.status, null as never);
|
|
1704
|
+
}
|
|
1705
|
+
static override create(status: number, data: never): NoContent {
|
|
1706
|
+
return new this();
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
static is<T extends {[KIND]: typeof NoContent['kind']}>(value: unknown): value is T {
|
|
1710
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
// 4xx Client Errors
|
|
1715
|
+
export class BadRequest<T> extends APIError<T, 400> {
|
|
1716
|
+
static override readonly kind = Symbol.for("BadRequest");
|
|
1717
|
+
static override status = 400 as const;
|
|
1718
|
+
constructor(data: T) {
|
|
1719
|
+
super(BadRequest.status, data);
|
|
1720
|
+
}
|
|
1721
|
+
static override create<T>(status: number, data: T) {
|
|
1722
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1723
|
+
return new this(data);
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
static is<T extends {[KIND]: typeof BadRequest['kind']}>(value: unknown): value is T {
|
|
1727
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1728
|
+
}
|
|
1729
|
+
}
|
|
1730
|
+
export class Unauthorized<T = { message: string }> extends APIError<T, 401> {
|
|
1731
|
+
static override readonly kind = Symbol.for("Unauthorized");
|
|
1732
|
+
static override status = 401 as const;
|
|
1733
|
+
constructor(data: T) {
|
|
1734
|
+
super(Unauthorized.status, data);
|
|
1735
|
+
}
|
|
1736
|
+
static override create<T>(status: number, data: T) {
|
|
1737
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1738
|
+
return new this(data);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
static is<T extends {[KIND]: typeof Unauthorized['kind']}>(value: unknown): value is T {
|
|
1742
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
export class PaymentRequired<T = { message: string }> extends APIError<T, 402> {
|
|
1746
|
+
static override readonly kind = Symbol.for("PaymentRequired");
|
|
1747
|
+
static override status = 402 as const;
|
|
1748
|
+
constructor(data: T) {
|
|
1749
|
+
super(PaymentRequired.status, data);
|
|
1750
|
+
}
|
|
1751
|
+
static override create<T>(status: number, data: T) {
|
|
1752
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1753
|
+
return new this(data);
|
|
1754
|
+
}
|
|
1755
|
+
|
|
1756
|
+
static is<T extends {[KIND]: typeof PaymentRequired['kind']}>(value: unknown): value is T {
|
|
1757
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
export class Forbidden<T = { message: string }> extends APIError<T, 403> {
|
|
1761
|
+
static override readonly kind = Symbol.for("Forbidden");
|
|
1762
|
+
static override status = 403 as const;
|
|
1763
|
+
constructor(data: T) {
|
|
1764
|
+
super(Forbidden.status, data);
|
|
1765
|
+
}
|
|
1766
|
+
static override create<T>(status: number, data: T) {
|
|
1767
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1768
|
+
return new this(data);
|
|
1769
|
+
}
|
|
1770
|
+
|
|
1771
|
+
static is<T extends {[KIND]: typeof Forbidden['kind']}>(value: unknown): value is T {
|
|
1772
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
export class NotFound<T = { message: string }> extends APIError<T, 404> {
|
|
1776
|
+
static override readonly kind = Symbol.for("NotFound");
|
|
1777
|
+
static override status = 404 as const;
|
|
1778
|
+
constructor(data: T) {
|
|
1779
|
+
super(NotFound.status, data);
|
|
1780
|
+
}
|
|
1781
|
+
static override create<T>(status: number, data: T) {
|
|
1782
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1783
|
+
return new this(data);
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
static is<T extends {[KIND]: typeof NotFound['kind']}>(value: unknown): value is T {
|
|
1787
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
export class MethodNotAllowed<T = { message: string }> extends APIError<
|
|
1791
|
+
T,
|
|
1792
|
+
405
|
|
1793
|
+
> {
|
|
1794
|
+
static override readonly kind = Symbol.for("MethodNotAllowed");
|
|
1795
|
+
static override status = 405 as const;
|
|
1796
|
+
constructor(data: T) {
|
|
1797
|
+
super(MethodNotAllowed.status, data);
|
|
1798
|
+
}
|
|
1799
|
+
static override create<T>(status: number, data: T) {
|
|
1800
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1801
|
+
return new this(data);
|
|
1802
|
+
}
|
|
1803
|
+
|
|
1804
|
+
static is<T extends {[KIND]: typeof MethodNotAllowed['kind']}>(value: unknown): value is T {
|
|
1805
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
export class NotAcceptable<T = { message: string }> extends APIError<T, 406> {
|
|
1809
|
+
static override readonly kind = Symbol.for("NotAcceptable");
|
|
1810
|
+
static override status = 406 as const;
|
|
1811
|
+
constructor(data: T) {
|
|
1812
|
+
super(NotAcceptable.status, data);
|
|
1813
|
+
}
|
|
1814
|
+
static override create<T>(status: number, data: T) {
|
|
1815
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1816
|
+
return new this(data);
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
static is<T extends {[KIND]: typeof NotAcceptable['kind']}>(value: unknown): value is T {
|
|
1820
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
export class Conflict<T = { message: string }> extends APIError<T, 409> {
|
|
1824
|
+
static override readonly kind = Symbol.for("Conflict");
|
|
1825
|
+
static override status = 409 as const;
|
|
1826
|
+
constructor(data: T) {
|
|
1827
|
+
super(Conflict.status, data);
|
|
1828
|
+
}
|
|
1829
|
+
static override create<T>(status: number, data: T) {
|
|
1830
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1831
|
+
return new this(data);
|
|
1832
|
+
}
|
|
1833
|
+
|
|
1834
|
+
static is<T extends {[KIND]: typeof Conflict['kind']}>(value: unknown): value is T {
|
|
1835
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
export class Gone<T = { message: string }> extends APIError<T, 410> {
|
|
1839
|
+
static override readonly kind = Symbol.for("Gone");
|
|
1840
|
+
static override status = 410 as const;
|
|
1841
|
+
constructor(data: T) {
|
|
1842
|
+
super(Gone.status, data);
|
|
1843
|
+
}
|
|
1844
|
+
static override create<T>(status: number, data: T) {
|
|
1845
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1846
|
+
return new this(data);
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
static is<T extends {[KIND]: typeof Gone['kind']}>(value: unknown): value is T {
|
|
1850
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
export class UnprocessableEntity<
|
|
1854
|
+
T = { message: string; errors?: Record<string, string[]> },
|
|
1855
|
+
> extends APIError<T, 422> {
|
|
1856
|
+
static override readonly kind = Symbol.for("UnprocessableEntity");
|
|
1857
|
+
static override status = 422 as const;
|
|
1858
|
+
constructor(data: T) {
|
|
1859
|
+
super(UnprocessableEntity.status, data);
|
|
1860
|
+
}
|
|
1861
|
+
static override create<T>(status: number, data: T) {
|
|
1862
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1863
|
+
return new this(data);
|
|
1864
|
+
}
|
|
1865
|
+
|
|
1866
|
+
static is<T extends {[KIND]: typeof UnprocessableEntity['kind']}>(value: unknown): value is T {
|
|
1867
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
export class TooManyRequests<
|
|
1871
|
+
T = { message: string; retryAfter?: string },
|
|
1872
|
+
> extends APIError<T, 429> {
|
|
1873
|
+
static override readonly kind = Symbol.for("TooManyRequests");
|
|
1874
|
+
static override status = 429 as const;
|
|
1875
|
+
constructor(data: T) {
|
|
1876
|
+
super(TooManyRequests.status, data);
|
|
1877
|
+
}
|
|
1878
|
+
static override create<T>(status: number, data: T) {
|
|
1879
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1880
|
+
return new this(data);
|
|
1881
|
+
}
|
|
1882
|
+
|
|
1883
|
+
static is<T extends {[KIND]: typeof TooManyRequests['kind']}>(value: unknown): value is T {
|
|
1884
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
export class PayloadTooLarge<T = { message: string }> extends APIError<T, 413> {
|
|
1888
|
+
static override readonly kind = Symbol.for("PayloadTooLarge");
|
|
1889
|
+
static override status = 413 as const;
|
|
1890
|
+
constructor(data: T) {
|
|
1891
|
+
super(PayloadTooLarge.status, data);
|
|
1892
|
+
}
|
|
1893
|
+
static override create<T>(status: number, data: T) {
|
|
1894
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1895
|
+
return new this(data);
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
static is<T extends {[KIND]: typeof PayloadTooLarge['kind']}>(value: unknown): value is T {
|
|
1899
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
export class UnsupportedMediaType<T = { message: string }> extends APIError<
|
|
1903
|
+
T,
|
|
1904
|
+
415
|
|
1905
|
+
> {
|
|
1906
|
+
static override readonly kind = Symbol.for("UnsupportedMediaType");
|
|
1907
|
+
static override status = 415 as const;
|
|
1908
|
+
constructor(data: T) {
|
|
1909
|
+
super(UnsupportedMediaType.status, data);
|
|
1910
|
+
}
|
|
1911
|
+
static override create<T>(status: number, data: T) {
|
|
1912
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1913
|
+
return new this(data);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
static is<T extends {[KIND]: typeof UnsupportedMediaType['kind']}>(value: unknown): value is T {
|
|
1917
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1921
|
+
// 5xx Server Errors
|
|
1922
|
+
export class InternalServerError<T = { message: string }> extends APIError<
|
|
1923
|
+
T,
|
|
1924
|
+
500
|
|
1925
|
+
> {
|
|
1926
|
+
static override readonly kind = Symbol.for("InternalServerError");
|
|
1927
|
+
static override status = 500 as const;
|
|
1928
|
+
constructor(data: T) {
|
|
1929
|
+
super(InternalServerError.status, data);
|
|
1930
|
+
}
|
|
1931
|
+
static override create<T>(status: number, data: T) {
|
|
1932
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1933
|
+
return new this(data);
|
|
1934
|
+
}
|
|
1935
|
+
|
|
1936
|
+
static is<T extends {[KIND]: typeof InternalServerError['kind']}>(value: unknown): value is T {
|
|
1937
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
export class NotImplemented<T = { message: string }> extends APIError<T, 501> {
|
|
1941
|
+
static override readonly kind = Symbol.for("NotImplemented");
|
|
1942
|
+
static override status = 501 as const;
|
|
1943
|
+
constructor(data: T) {
|
|
1944
|
+
super(NotImplemented.status, data);
|
|
1945
|
+
}
|
|
1946
|
+
static override create<T>(status: number, data: T) {
|
|
1947
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1948
|
+
return new this(data);
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1951
|
+
static is<T extends {[KIND]: typeof NotImplemented['kind']}>(value: unknown): value is T {
|
|
1952
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
export class BadGateway<T = { message: string }> extends APIError<T, 502> {
|
|
1956
|
+
static override readonly kind = Symbol.for("BadGateway");
|
|
1957
|
+
static override status = 502 as const;
|
|
1958
|
+
constructor(data: T) {
|
|
1959
|
+
super(BadGateway.status, data);
|
|
1960
|
+
}
|
|
1961
|
+
static override create<T>(status: number, data: T) {
|
|
1962
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1963
|
+
return new this(data);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1966
|
+
static is<T extends {[KIND]: typeof BadGateway['kind']}>(value: unknown): value is T {
|
|
1967
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
export class ServiceUnavailable<
|
|
1971
|
+
T = { message: string; retryAfter?: string },
|
|
1972
|
+
> extends APIError<T, 503> {
|
|
1973
|
+
static override readonly kind = Symbol.for("ServiceUnavailable");
|
|
1974
|
+
static override status = 503 as const;
|
|
1975
|
+
constructor(data: T) {
|
|
1976
|
+
super(ServiceUnavailable.status, data);
|
|
1977
|
+
}
|
|
1978
|
+
static override create<T>(status: number, data: T) {
|
|
1979
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1980
|
+
return new this(data);
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
static is<T extends {[KIND]: typeof ServiceUnavailable['kind']}>(value: unknown): value is T {
|
|
1984
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
export class GatewayTimeout<T = { message: string }> extends APIError<T, 504> {
|
|
1988
|
+
static override readonly kind = Symbol.for("GatewayTimeout");
|
|
1989
|
+
static override status = 504 as const;
|
|
1990
|
+
constructor(data: T) {
|
|
1991
|
+
super(GatewayTimeout.status, data);
|
|
1992
|
+
}
|
|
1993
|
+
static override create<T>(status: number, data: T) {
|
|
1994
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1995
|
+
return new this(data);
|
|
1996
|
+
}
|
|
1997
|
+
|
|
1998
|
+
static is<T extends {[KIND]: typeof GatewayTimeout['kind']}>(value: unknown): value is T {
|
|
1999
|
+
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
2000
|
+
}
|
|
2001
|
+
}
|
|
2002
|
+
|
|
2003
|
+
export type ClientError =
|
|
2004
|
+
| BadRequest<{ message: string }>
|
|
2005
|
+
| Unauthorized<unknown>
|
|
2006
|
+
| PaymentRequired<unknown>
|
|
2007
|
+
| Forbidden<unknown>
|
|
2008
|
+
| NotFound<unknown>
|
|
2009
|
+
| MethodNotAllowed<unknown>
|
|
2010
|
+
| NotAcceptable<unknown>
|
|
2011
|
+
| Conflict<unknown>
|
|
2012
|
+
| Gone<unknown>
|
|
2013
|
+
| UnprocessableEntity<unknown>
|
|
2014
|
+
| TooManyRequests<unknown>;
|
|
2015
|
+
|
|
2016
|
+
export type ServerError =
|
|
2017
|
+
| InternalServerError<unknown>
|
|
2018
|
+
| NotImplemented<unknown>
|
|
2019
|
+
| BadGateway<unknown>
|
|
2020
|
+
| ServiceUnavailable<unknown>
|
|
2021
|
+
| GatewayTimeout<unknown>;
|
|
2022
|
+
|
|
2023
|
+
export type ProblematicResponse = ClientError | ServerError;
|
|
2024
|
+
|
|
2025
|
+
export type SuccessfulResponse =
|
|
2026
|
+
| Ok<unknown>
|
|
2027
|
+
| Created<unknown>
|
|
2028
|
+
| Accepted<unknown>
|
|
2029
|
+
| NoContent;
|
|
2030
|
+
`;
|
|
1547
2031
|
|
|
1548
2032
|
// packages/typescript/src/lib/http/send-request.txt
|
|
1549
|
-
var send_request_default = "export interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n output: OutputType[];\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
|
|
2033
|
+
var send_request_default = "export interface Type<T> {\n new (...args: any[]): T;\n}\nexport type Parser = (\n response: Response,\n) => Promise<unknown> | ReadableStream<any>;\nexport type OutputType =\n | Type<APIResponse>\n | { parser: Parser; type: Type<APIResponse> };\n\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n output: OutputType[];\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 dispatch(\n input: unknown,\n route: RequestSchema,\n options: {\n fetch?: z.infer<typeof fetchType>;\n interceptors?: Interceptor[];\n signal?: AbortSignal;\n },\n) {\n const { interceptors = [] } = options;\n const [parsedInput, parseError] = parseInput(route.schema, input);\n if (parseError) {\n <% if(throwError) { %>\n throw parseError;\n <% } else { %>\n return [null as never, parseError as never] as const;\n <% } %>\n }\n\n let config = route.toRequest(parsedInput as never);\n for (const interceptor of interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (options.fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: options.signal,\n },\n );\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 return await parse(route, response);\n}\n\nexport async function parse(route: RequestSchema, response: Response) {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of route.output) {\n if ('parser' in outputType) {\n parser = outputType.parser;\n if (isTypeOf(outputType.type, APIResponse)) {\n if (response.status === outputType.type.status) {\n output = outputType.type;\n break;\n }\n }\n } else if (isTypeOf(outputType, APIResponse)) {\n if (response.status === outputType.status) {\n output = outputType;\n break;\n }\n }\n }\n\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n <% if(throwError) { %>\n return <% if (outputType === 'default') { %>apiresponse.data<% } else { %>apiresponse<% } %>;\n <% } else { %>\n return [<% if (outputType === 'default') { %>apiresponse.data<% } else { %>apiresponse<% } %> , null] as const;\n <% } %>\n }\n<% if(throwError) { %>\n throw (output || APIError).create(\n response.status,\n await parser(response),\n );\n<% } else { %>\n const data = (output || APIError).create(\n response.status,\n await parser(response),\n );\n return [null as never, data as never] as const;\n<% } %>\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n";
|
|
1550
2034
|
|
|
1551
2035
|
// packages/typescript/src/lib/generate.ts
|
|
1552
2036
|
function security(spec) {
|
|
@@ -1570,6 +2054,15 @@ function security(spec) {
|
|
|
1570
2054
|
return options;
|
|
1571
2055
|
}
|
|
1572
2056
|
async function generate(spec, settings) {
|
|
2057
|
+
const style = Object.assign(
|
|
2058
|
+
{},
|
|
2059
|
+
{
|
|
2060
|
+
errorAsValue: true,
|
|
2061
|
+
name: "github",
|
|
2062
|
+
outputType: "default"
|
|
2063
|
+
},
|
|
2064
|
+
settings.style ?? {}
|
|
2065
|
+
);
|
|
1573
2066
|
settings.useTsExtension ??= true;
|
|
1574
2067
|
const makeImport = (moduleSpecifier) => {
|
|
1575
2068
|
return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
|
|
@@ -1577,11 +2070,11 @@ async function generate(spec, settings) {
|
|
|
1577
2070
|
const { commonSchemas, endpoints, groups, outputs, commonZod } = generateCode(
|
|
1578
2071
|
{
|
|
1579
2072
|
spec,
|
|
1580
|
-
style
|
|
2073
|
+
style,
|
|
1581
2074
|
makeImport
|
|
1582
2075
|
}
|
|
1583
2076
|
);
|
|
1584
|
-
const output = settings.mode === "full" ?
|
|
2077
|
+
const output = settings.mode === "full" ? join3(settings.output, "src") : settings.output;
|
|
1585
2078
|
const options = security(spec);
|
|
1586
2079
|
const clientName = settings.name || "Client";
|
|
1587
2080
|
const inputFiles = generateInputs(groups, commonZod, makeImport);
|
|
@@ -1591,9 +2084,9 @@ async function generate(spec, settings) {
|
|
|
1591
2084
|
"models/.getkeep": ""
|
|
1592
2085
|
// 'README.md': readme,
|
|
1593
2086
|
});
|
|
1594
|
-
await writeFiles(
|
|
2087
|
+
await writeFiles(join3(output, "http"), {
|
|
1595
2088
|
"interceptors.ts": `
|
|
1596
|
-
import {
|
|
2089
|
+
import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
|
|
1597
2090
|
${interceptors_default}`,
|
|
1598
2091
|
"parse-response.ts": parse_response_default,
|
|
1599
2092
|
"send-request.ts": `import z from 'zod';
|
|
@@ -1603,20 +2096,23 @@ import { parseInput } from './${makeImport("parser")}';
|
|
|
1603
2096
|
import type { RequestConfig } from './${makeImport("request")}';
|
|
1604
2097
|
import { APIError, APIResponse } from './${makeImport("response")}';
|
|
1605
2098
|
|
|
1606
|
-
${send_request_default}`,
|
|
2099
|
+
${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputType: style.outputType })}`,
|
|
1607
2100
|
"response.ts": response_default,
|
|
1608
2101
|
"parser.ts": parser_default,
|
|
1609
2102
|
"request.ts": request_default
|
|
1610
2103
|
});
|
|
1611
|
-
await writeFiles(
|
|
2104
|
+
await writeFiles(join3(output, "outputs"), outputs);
|
|
1612
2105
|
const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
|
|
1613
2106
|
await writeFiles(output, {
|
|
1614
|
-
"client.ts": client_default(
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
2107
|
+
"client.ts": client_default(
|
|
2108
|
+
{
|
|
2109
|
+
name: clientName,
|
|
2110
|
+
servers: (spec.servers ?? []).map((server) => server.url) || [],
|
|
2111
|
+
options,
|
|
2112
|
+
makeImport
|
|
2113
|
+
},
|
|
2114
|
+
style
|
|
2115
|
+
),
|
|
1620
2116
|
...inputFiles,
|
|
1621
2117
|
...endpoints,
|
|
1622
2118
|
...Object.fromEntries(
|
|
@@ -1633,24 +2129,24 @@ ${send_request_default}`,
|
|
|
1633
2129
|
)
|
|
1634
2130
|
});
|
|
1635
2131
|
const folders = [
|
|
1636
|
-
getFolderExports(
|
|
2132
|
+
getFolderExports(join3(output, "outputs"), settings.useTsExtension),
|
|
1637
2133
|
getFolderExports(
|
|
1638
|
-
|
|
2134
|
+
join3(output, "inputs"),
|
|
1639
2135
|
settings.useTsExtension,
|
|
1640
2136
|
["ts"],
|
|
1641
2137
|
(dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
|
|
1642
2138
|
),
|
|
1643
|
-
getFolderExports(
|
|
2139
|
+
getFolderExports(join3(output, "api"), settings.useTsExtension),
|
|
1644
2140
|
getFolderExports(
|
|
1645
|
-
|
|
2141
|
+
join3(output, "http"),
|
|
1646
2142
|
settings.useTsExtension,
|
|
1647
2143
|
["ts"],
|
|
1648
|
-
(dirent) =>
|
|
2144
|
+
(dirent) => !["response.ts", "parser.ts"].includes(dirent.name)
|
|
1649
2145
|
)
|
|
1650
2146
|
];
|
|
1651
2147
|
if (modelsImports.length) {
|
|
1652
2148
|
folders.push(
|
|
1653
|
-
getFolderExports(
|
|
2149
|
+
getFolderExports(join3(output, "models"), settings.useTsExtension)
|
|
1654
2150
|
);
|
|
1655
2151
|
}
|
|
1656
2152
|
const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|