@sdk-it/typescript 0.18.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 +641 -151
- 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 -2
- 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,13 +1,138 @@
|
|
|
1
1
|
// packages/typescript/src/lib/generate.ts
|
|
2
|
-
import { template } from "lodash-es";
|
|
3
|
-
import { join as
|
|
2
|
+
import { template as template2 } from "lodash-es";
|
|
3
|
+
import { join as join3 } from "node:path";
|
|
4
4
|
import { npmRunPathEnv } from "npm-run-path";
|
|
5
5
|
import { spinalcase as spinalcase3 } from "stringcase";
|
|
6
|
-
|
|
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
|
+
}
|
|
7
133
|
|
|
8
134
|
// packages/typescript/src/lib/client.ts
|
|
9
|
-
|
|
10
|
-
var client_default = (spec, throwError) => {
|
|
135
|
+
var client_default = (spec, style) => {
|
|
11
136
|
const optionsEntries = Object.entries(spec.options).map(
|
|
12
137
|
([key, value]) => [`'${key}'`, value]
|
|
13
138
|
);
|
|
@@ -29,8 +154,8 @@ var client_default = (spec, throwError) => {
|
|
|
29
154
|
}
|
|
30
155
|
};
|
|
31
156
|
return `
|
|
32
|
-
import type { RequestConfig } from './http/${spec.makeImport("request")}';
|
|
33
|
-
import { fetchType,
|
|
157
|
+
import type { HeadersInit, RequestConfig } from './http/${spec.makeImport("request")}';
|
|
158
|
+
import { fetchType, dispatch, parse } from './http/${spec.makeImport("send-request")}';
|
|
34
159
|
import z from 'zod';
|
|
35
160
|
import type { Endpoints } from './api/${spec.makeImport("endpoints")}';
|
|
36
161
|
import schemas from './api/${spec.makeImport("schemas")}';
|
|
@@ -57,11 +182,9 @@ export class ${spec.name} {
|
|
|
57
182
|
endpoint: E,
|
|
58
183
|
input: Endpoints[E]['input'],
|
|
59
184
|
options?: { signal?: AbortSignal, headers?: HeadersInit },
|
|
60
|
-
)
|
|
61
|
-
${throwError ? `: Endpoints[E]['output']` : `: Promise<readonly [Endpoints[E]['output'], Endpoints[E]['error'] | null]>`}
|
|
62
|
-
{
|
|
185
|
+
) ${style.errorAsValue ? `: Promise<readonly [Endpoints[E]['output'], Endpoints[E]['error'] | null]>` : `: Promise<Endpoints[E]['output']>`} {
|
|
63
186
|
const route = schemas[endpoint];
|
|
64
|
-
|
|
187
|
+
const result = await dispatch(Object.assign(this.#defaultInputs, input), route, {
|
|
65
188
|
fetch: this.options.fetch,
|
|
66
189
|
interceptors: [
|
|
67
190
|
createHeadersInterceptor(() => this.defaultHeaders, options?.headers ?? {}),
|
|
@@ -69,20 +192,23 @@ export class ${spec.name} {
|
|
|
69
192
|
],
|
|
70
193
|
signal: options?.signal,
|
|
71
194
|
});
|
|
195
|
+
return ${style.errorAsValue ? `result as [Endpoints[E]['output'], Endpoints[E]['error'] | null]` : `result as Endpoints[E]['output']`};
|
|
72
196
|
}
|
|
73
197
|
|
|
74
198
|
async prepare<E extends keyof Endpoints>(
|
|
75
199
|
endpoint: E,
|
|
76
200
|
input: Endpoints[E]['input'],
|
|
77
201
|
options?: { headers?: HeadersInit },
|
|
78
|
-
): Promise<
|
|
202
|
+
): ${style.errorAsValue ? `Promise<
|
|
79
203
|
readonly [
|
|
80
204
|
RequestConfig & {
|
|
81
205
|
parse: (response: Response) => ReturnType<typeof parse>;
|
|
82
206
|
},
|
|
83
207
|
ParseError<(typeof schemas)[E]['schema']> | null,
|
|
84
208
|
]
|
|
85
|
-
|
|
209
|
+
>` : `Promise<RequestConfig & {
|
|
210
|
+
parse: (response: Response) => ReturnType<typeof parse>;
|
|
211
|
+
}>`} {
|
|
86
212
|
const route = schemas[endpoint];
|
|
87
213
|
|
|
88
214
|
const interceptors = [
|
|
@@ -94,7 +220,7 @@ export class ${spec.name} {
|
|
|
94
220
|
];
|
|
95
221
|
const [parsedInput, parseError] = parseInput(route.schema, input);
|
|
96
222
|
if (parseError) {
|
|
97
|
-
return [null as never, parseError as never] as const;
|
|
223
|
+
${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
|
|
98
224
|
}
|
|
99
225
|
|
|
100
226
|
let config = route.toRequest(parsedInput as never);
|
|
@@ -103,10 +229,8 @@ export class ${spec.name} {
|
|
|
103
229
|
config = await interceptor.before(config);
|
|
104
230
|
}
|
|
105
231
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
null as never,
|
|
109
|
-
] as const;
|
|
232
|
+
const prepared = { ...config, parse: (response: Response) => parse(route, response) };
|
|
233
|
+
return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared"}
|
|
110
234
|
}
|
|
111
235
|
|
|
112
236
|
get defaultHeaders() {
|
|
@@ -130,10 +254,9 @@ export class ${spec.name} {
|
|
|
130
254
|
};
|
|
131
255
|
|
|
132
256
|
// packages/typescript/src/lib/generator.ts
|
|
133
|
-
import { merge } from "lodash-es";
|
|
134
|
-
import { join } from "node:path";
|
|
257
|
+
import { merge, template } from "lodash-es";
|
|
258
|
+
import { join as join2 } from "node:path";
|
|
135
259
|
import { camelcase as camelcase3, pascalcase as pascalcase2, spinalcase as spinalcase2 } from "stringcase";
|
|
136
|
-
import { followRef as followRef4, isEmpty, isRef as isRef5 } from "@sdk-it/core";
|
|
137
260
|
|
|
138
261
|
// packages/spec/dist/lib/operation.js
|
|
139
262
|
import { camelcase } from "stringcase";
|
|
@@ -398,7 +521,6 @@ function determineGenericTag(pathString, operation) {
|
|
|
398
521
|
}
|
|
399
522
|
|
|
400
523
|
// packages/typescript/src/lib/emitters/zod.ts
|
|
401
|
-
import { cleanRef, followRef, isRef, parseRef } from "@sdk-it/core";
|
|
402
524
|
var ZodDeserialzer = class {
|
|
403
525
|
circularRefTracker = /* @__PURE__ */ new Set();
|
|
404
526
|
#spec;
|
|
@@ -660,12 +782,10 @@ function appendDefault(defaultValue) {
|
|
|
660
782
|
}
|
|
661
783
|
|
|
662
784
|
// packages/typescript/src/lib/sdk.ts
|
|
663
|
-
import { get } from "lodash-es";
|
|
785
|
+
import { get as get2 } from "lodash-es";
|
|
664
786
|
import { camelcase as camelcase2, pascalcase, spinalcase } from "stringcase";
|
|
665
|
-
import { followRef as followRef3, isRef as isRef4, toLitObject as toLitObject2 } from "@sdk-it/core";
|
|
666
787
|
|
|
667
788
|
// packages/typescript/src/lib/emitters/interface.ts
|
|
668
|
-
import { cleanRef as cleanRef2, followRef as followRef2, isRef as isRef2, parseRef as parseRef2 } from "@sdk-it/core";
|
|
669
789
|
var TypeScriptDeserialzer = class {
|
|
670
790
|
circularRefTracker = /* @__PURE__ */ new Set();
|
|
671
791
|
#spec;
|
|
@@ -674,70 +794,12 @@ var TypeScriptDeserialzer = class {
|
|
|
674
794
|
this.#spec = spec;
|
|
675
795
|
this.#onRef = onRef;
|
|
676
796
|
}
|
|
677
|
-
#stringifyKey = (
|
|
678
|
-
const reservedWords = [
|
|
679
|
-
"constructor",
|
|
680
|
-
"prototype",
|
|
681
|
-
"break",
|
|
682
|
-
"case",
|
|
683
|
-
"catch",
|
|
684
|
-
"class",
|
|
685
|
-
"const",
|
|
686
|
-
"continue",
|
|
687
|
-
"debugger",
|
|
688
|
-
"default",
|
|
689
|
-
"delete",
|
|
690
|
-
"do",
|
|
691
|
-
"else",
|
|
692
|
-
"export",
|
|
693
|
-
"extends",
|
|
694
|
-
"false",
|
|
695
|
-
"finally",
|
|
696
|
-
"for",
|
|
697
|
-
"function",
|
|
698
|
-
"if",
|
|
699
|
-
"import",
|
|
700
|
-
"in",
|
|
701
|
-
"instanceof",
|
|
702
|
-
"new",
|
|
703
|
-
"null",
|
|
704
|
-
"return",
|
|
705
|
-
"super",
|
|
706
|
-
"switch",
|
|
707
|
-
"this",
|
|
708
|
-
"throw",
|
|
709
|
-
"true",
|
|
710
|
-
"try",
|
|
711
|
-
"typeof",
|
|
712
|
-
"var",
|
|
713
|
-
"void",
|
|
714
|
-
"while",
|
|
715
|
-
"with",
|
|
716
|
-
"yield"
|
|
717
|
-
];
|
|
718
|
-
if (reservedWords.includes(key)) {
|
|
719
|
-
return `'${key}'`;
|
|
720
|
-
}
|
|
721
|
-
if (key.trim() === "") {
|
|
722
|
-
return `'${key}'`;
|
|
723
|
-
}
|
|
724
|
-
const firstChar = key.charAt(0);
|
|
725
|
-
const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
|
|
726
|
-
if (!validFirstChar) {
|
|
727
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
728
|
-
}
|
|
729
|
-
for (let i = 1; i < key.length; i++) {
|
|
730
|
-
const char = key.charAt(i);
|
|
731
|
-
const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
|
|
732
|
-
if (!validChar) {
|
|
733
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
734
|
-
}
|
|
735
|
-
}
|
|
736
|
-
return key;
|
|
737
|
-
};
|
|
738
|
-
#stringifyKeyV2 = (value) => {
|
|
797
|
+
#stringifyKey = (value) => {
|
|
739
798
|
return `'${value}'`;
|
|
740
799
|
};
|
|
800
|
+
#isInternal = (schema) => {
|
|
801
|
+
return isRef(schema) ? false : !!schema["x-internal"];
|
|
802
|
+
};
|
|
741
803
|
/**
|
|
742
804
|
* Handle objects (properties)
|
|
743
805
|
*/
|
|
@@ -746,7 +808,7 @@ var TypeScriptDeserialzer = class {
|
|
|
746
808
|
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
747
809
|
const isRequired = (schema.required ?? []).includes(key);
|
|
748
810
|
const tsType = this.handle(propSchema, isRequired);
|
|
749
|
-
return `${this.#
|
|
811
|
+
return `${this.#isInternal(propSchema) ? key : this.#stringifyKey(key)}: ${tsType}`;
|
|
750
812
|
});
|
|
751
813
|
if (schema.additionalProperties) {
|
|
752
814
|
if (typeof schema.additionalProperties === "object") {
|
|
@@ -797,14 +859,14 @@ var TypeScriptDeserialzer = class {
|
|
|
797
859
|
}
|
|
798
860
|
}
|
|
799
861
|
ref($ref, required) {
|
|
800
|
-
const schemaName =
|
|
862
|
+
const schemaName = cleanRef($ref).split("/").pop();
|
|
801
863
|
if (this.circularRefTracker.has(schemaName)) {
|
|
802
864
|
return schemaName;
|
|
803
865
|
}
|
|
804
866
|
this.circularRefTracker.add(schemaName);
|
|
805
867
|
this.#onRef?.(
|
|
806
868
|
schemaName,
|
|
807
|
-
this.handle(
|
|
869
|
+
this.handle(followRef(this.#spec, $ref), required)
|
|
808
870
|
);
|
|
809
871
|
this.circularRefTracker.delete(schemaName);
|
|
810
872
|
return appendOptional2(schemaName, required);
|
|
@@ -822,8 +884,8 @@ var TypeScriptDeserialzer = class {
|
|
|
822
884
|
}
|
|
823
885
|
oneOf(schemas, required) {
|
|
824
886
|
const oneOfTypes = schemas.map((sub) => {
|
|
825
|
-
if (
|
|
826
|
-
const { model } =
|
|
887
|
+
if (isRef(sub)) {
|
|
888
|
+
const { model } = parseRef(sub.$ref);
|
|
827
889
|
if (this.circularRefTracker.has(model)) {
|
|
828
890
|
return model;
|
|
829
891
|
}
|
|
@@ -870,7 +932,7 @@ var TypeScriptDeserialzer = class {
|
|
|
870
932
|
return appendOptional2(type, required);
|
|
871
933
|
}
|
|
872
934
|
handle(schema, required) {
|
|
873
|
-
if (
|
|
935
|
+
if (isRef(schema)) {
|
|
874
936
|
return this.ref(schema.$ref, required);
|
|
875
937
|
}
|
|
876
938
|
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
@@ -885,6 +947,12 @@ var TypeScriptDeserialzer = class {
|
|
|
885
947
|
if (schema.enum && Array.isArray(schema.enum)) {
|
|
886
948
|
return this.enum(schema.enum, required);
|
|
887
949
|
}
|
|
950
|
+
if (schema.const) {
|
|
951
|
+
if (schema["x-internal"]) {
|
|
952
|
+
return `${schema.const}`;
|
|
953
|
+
}
|
|
954
|
+
return this.enum([schema.const], required);
|
|
955
|
+
}
|
|
888
956
|
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
889
957
|
if (!types.length) {
|
|
890
958
|
if ("properties" in schema) {
|
|
@@ -903,20 +971,12 @@ var TypeScriptDeserialzer = class {
|
|
|
903
971
|
}
|
|
904
972
|
return this.normal(types[0], schema, required);
|
|
905
973
|
}
|
|
906
|
-
/**
|
|
907
|
-
* Generate an interface declaration
|
|
908
|
-
*/
|
|
909
|
-
generateInterface(name, schema) {
|
|
910
|
-
const content = this.handle(schema, true);
|
|
911
|
-
return `interface ${name} ${content}`;
|
|
912
|
-
}
|
|
913
974
|
};
|
|
914
975
|
function appendOptional2(type, isRequired) {
|
|
915
976
|
return isRequired ? type : `${type} | undefined`;
|
|
916
977
|
}
|
|
917
978
|
|
|
918
979
|
// packages/typescript/src/lib/utils.ts
|
|
919
|
-
import { isRef as isRef3, removeDuplicates } from "@sdk-it/core";
|
|
920
980
|
function securityToOptions(security2, securitySchemes, staticIn) {
|
|
921
981
|
securitySchemes ??= {};
|
|
922
982
|
const options = {};
|
|
@@ -926,7 +986,7 @@ function securityToOptions(security2, securitySchemes, staticIn) {
|
|
|
926
986
|
continue;
|
|
927
987
|
}
|
|
928
988
|
const schema = securitySchemes[name];
|
|
929
|
-
if (
|
|
989
|
+
if (isRef(schema)) {
|
|
930
990
|
throw new Error(`Ref security schemas are not supported`);
|
|
931
991
|
}
|
|
932
992
|
if (schema.type === "http") {
|
|
@@ -1015,7 +1075,7 @@ function generateInputs(operationsSet, commonZod, makeImport) {
|
|
|
1015
1075
|
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
1016
1076
|
for (const operation of operations) {
|
|
1017
1077
|
const schemaName = camelcase2(`${operation.name} schema`);
|
|
1018
|
-
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)};`;
|
|
1019
1079
|
const inputContent = schema;
|
|
1020
1080
|
for (const schema2 of commonImports) {
|
|
1021
1081
|
if (inputContent.includes(schema2)) {
|
|
@@ -1085,7 +1145,7 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
|
|
|
1085
1145
|
return statusCode >= 200 && statusCode < 300;
|
|
1086
1146
|
}).length > 1;
|
|
1087
1147
|
for (const status in specOperation.responses) {
|
|
1088
|
-
const response =
|
|
1148
|
+
const response = isRef(specOperation.responses[status]) ? followRef(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
|
|
1089
1149
|
const handled = handleResponse(
|
|
1090
1150
|
spec,
|
|
1091
1151
|
operation.name,
|
|
@@ -1175,7 +1235,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1175
1235
|
);
|
|
1176
1236
|
const statusCode = +status;
|
|
1177
1237
|
const parser = (response.headers ?? {})["Transfer-Encoding"] ? "chunked" : "buffered";
|
|
1178
|
-
const statusName = statusCodeToResponseMap[status] || "APIResponse"
|
|
1238
|
+
const statusName = `http.${statusCodeToResponseMap[status] || "APIResponse"}`;
|
|
1179
1239
|
const interfaceName = pascalcase(
|
|
1180
1240
|
operationName + ` output${numbered ? status : ""}`
|
|
1181
1241
|
);
|
|
@@ -1183,22 +1243,34 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1183
1243
|
outputs.push(statusName);
|
|
1184
1244
|
} else {
|
|
1185
1245
|
if (status.endsWith("XX")) {
|
|
1186
|
-
outputs.push(`APIError<${interfaceName}>`);
|
|
1246
|
+
outputs.push(`http.APIError<${interfaceName}>`);
|
|
1187
1247
|
} else {
|
|
1188
1248
|
outputs.push(
|
|
1189
1249
|
parser !== "buffered" ? `{type: ${statusName}<${interfaceName}>, parser: ${parser}}` : `${statusName}<${interfaceName}>`
|
|
1190
1250
|
);
|
|
1191
1251
|
}
|
|
1192
1252
|
}
|
|
1193
|
-
const responseContent =
|
|
1253
|
+
const responseContent = get2(response, ["content"]);
|
|
1194
1254
|
const isJson = responseContent && responseContent["application/json"];
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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
|
+
}
|
|
1199
1270
|
responses.push({
|
|
1200
1271
|
name: interfaceName,
|
|
1201
|
-
schema: responseSchema
|
|
1272
|
+
schema: responseSchema,
|
|
1273
|
+
description: response.description
|
|
1202
1274
|
});
|
|
1203
1275
|
const statusGroup = +status.slice(0, 1);
|
|
1204
1276
|
if (statusCode >= 400 || statusGroup >= 4) {
|
|
@@ -1212,15 +1284,6 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1212
1284
|
namedImports: [{ isTypeOnly: true, name: interfaceName }]
|
|
1213
1285
|
};
|
|
1214
1286
|
} else if (statusCode >= 200 && statusCode < 300 || statusCode >= 2 || statusGroup <= 3) {
|
|
1215
|
-
endpointImports[statusName] = {
|
|
1216
|
-
moduleSpecifier: utils.makeImport("../http/response"),
|
|
1217
|
-
namedImports: [
|
|
1218
|
-
{
|
|
1219
|
-
isTypeOnly: false,
|
|
1220
|
-
name: statusName
|
|
1221
|
-
}
|
|
1222
|
-
]
|
|
1223
|
-
};
|
|
1224
1287
|
endpointImports[interfaceName] = {
|
|
1225
1288
|
defaultImport: void 0,
|
|
1226
1289
|
isTypeOnly: true,
|
|
@@ -1233,7 +1296,7 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1233
1296
|
}
|
|
1234
1297
|
|
|
1235
1298
|
// packages/typescript/src/lib/styles/github/endpoints.txt
|
|
1236
|
-
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};";
|
|
1237
1300
|
|
|
1238
1301
|
// packages/typescript/src/lib/generator.ts
|
|
1239
1302
|
function generateCode(config) {
|
|
@@ -1259,7 +1322,7 @@ function generateCode(config) {
|
|
|
1259
1322
|
const inputs = {};
|
|
1260
1323
|
const additionalProperties = [];
|
|
1261
1324
|
for (const param of operation.parameters ?? []) {
|
|
1262
|
-
if (
|
|
1325
|
+
if (isRef(param)) {
|
|
1263
1326
|
throw new Error(`Found reference in parameter ${param.$ref}`);
|
|
1264
1327
|
}
|
|
1265
1328
|
if (!param.schema) {
|
|
@@ -1301,9 +1364,9 @@ function generateCode(config) {
|
|
|
1301
1364
|
};
|
|
1302
1365
|
let outgoingContentType;
|
|
1303
1366
|
if (!isEmpty(operation.requestBody)) {
|
|
1304
|
-
const requestBody =
|
|
1367
|
+
const requestBody = isRef(operation.requestBody) ? followRef(config.spec, operation.requestBody.$ref) : operation.requestBody;
|
|
1305
1368
|
for (const type in requestBody.content) {
|
|
1306
|
-
const ctSchema =
|
|
1369
|
+
const ctSchema = isRef(requestBody.content[type].schema) ? followRef(config.spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
|
|
1307
1370
|
if (!ctSchema) {
|
|
1308
1371
|
console.warn(
|
|
1309
1372
|
`Schema not found for ${type} in ${entry.method} ${entry.path}`
|
|
@@ -1373,14 +1436,23 @@ function generateCode(config) {
|
|
|
1373
1436
|
},
|
|
1374
1437
|
{ makeImport: config.makeImport }
|
|
1375
1438
|
);
|
|
1376
|
-
const output = [
|
|
1439
|
+
const output = [
|
|
1440
|
+
`import z from 'zod';`,
|
|
1441
|
+
`import type * as http from '../http';`
|
|
1442
|
+
];
|
|
1377
1443
|
const responses = endpoint.responses.flatMap((it) => it.responses);
|
|
1378
1444
|
const responsesImports = endpoint.responses.flatMap(
|
|
1379
1445
|
(it) => Object.values(it.imports)
|
|
1380
1446
|
);
|
|
1381
1447
|
if (responses.length) {
|
|
1382
1448
|
output.push(
|
|
1383
|
-
...responses.map(
|
|
1449
|
+
...responses.map(
|
|
1450
|
+
(it) => `${it.description ? `
|
|
1451
|
+
/**
|
|
1452
|
+
* ${it.description}
|
|
1453
|
+
*/
|
|
1454
|
+
` : ""} export type ${it.name} = ${it.schema};`
|
|
1455
|
+
)
|
|
1384
1456
|
);
|
|
1385
1457
|
} else {
|
|
1386
1458
|
output.push(
|
|
@@ -1431,7 +1503,7 @@ function generateCode(config) {
|
|
|
1431
1503
|
commonZod,
|
|
1432
1504
|
outputs,
|
|
1433
1505
|
endpoints: {
|
|
1434
|
-
[
|
|
1506
|
+
[join2("api", "endpoints.ts")]: `
|
|
1435
1507
|
|
|
1436
1508
|
|
|
1437
1509
|
import type z from 'zod';
|
|
@@ -1445,9 +1517,9 @@ import type { OutputType, Parser, Type } from '${config.makeImport(
|
|
|
1445
1517
|
|
|
1446
1518
|
import schemas from '${config.makeImport("./schemas")}';
|
|
1447
1519
|
|
|
1448
|
-
${endpoints_default}`,
|
|
1449
|
-
[`${
|
|
1450
|
-
|
|
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")}";
|
|
1451
1523
|
export default {
|
|
1452
1524
|
${allSchemas.map((it) => it.use).join(",\n")}
|
|
1453
1525
|
};
|
|
@@ -1466,11 +1538,12 @@ ${allSchemas.map((it) => it.use).join(",\n")}
|
|
|
1466
1538
|
);
|
|
1467
1539
|
return [
|
|
1468
1540
|
[
|
|
1469
|
-
|
|
1541
|
+
join2("api", `${spinalcase2(name)}.ts`),
|
|
1470
1542
|
`${[
|
|
1471
1543
|
...imps,
|
|
1472
1544
|
// ...imports,
|
|
1473
1545
|
`import z from 'zod';`,
|
|
1546
|
+
`import * as http from '${config.makeImport("../http/response")}';`,
|
|
1474
1547
|
`import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
|
|
1475
1548
|
`import { chunked, buffered } from "${config.makeImport("../http/parse-response")}";`,
|
|
1476
1549
|
`import * as ${camelcase3(name)} from '../inputs/${config.makeImport(spinalcase2(name))}';`
|
|
@@ -1488,8 +1561,8 @@ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
|
|
|
1488
1561
|
};
|
|
1489
1562
|
}
|
|
1490
1563
|
function toProps(spec, schemaOrRef, aggregator = []) {
|
|
1491
|
-
if (
|
|
1492
|
-
const schema =
|
|
1564
|
+
if (isRef(schemaOrRef)) {
|
|
1565
|
+
const schema = followRef(spec, schemaOrRef.$ref);
|
|
1493
1566
|
return toProps(spec, schema, aggregator);
|
|
1494
1567
|
} else if (schemaOrRef.type === "object") {
|
|
1495
1568
|
for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
|
|
@@ -1534,7 +1607,7 @@ function bodyInputs(config, ctSchema) {
|
|
|
1534
1607
|
}
|
|
1535
1608
|
|
|
1536
1609
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1537
|
-
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";
|
|
1538
1611
|
|
|
1539
1612
|
// packages/typescript/src/lib/http/parse-response.txt
|
|
1540
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';
|
|
@@ -1543,13 +1616,421 @@ var parse_response_default = 'import { parse } from "fast-content-type-parse";\n
|
|
|
1543
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";
|
|
1544
1617
|
|
|
1545
1618
|
// packages/typescript/src/lib/http/request.txt
|
|
1546
|
-
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";
|
|
1547
1620
|
|
|
1548
1621
|
// packages/typescript/src/lib/http/response.txt
|
|
1549
|
-
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
|
+
`;
|
|
1550
2031
|
|
|
1551
2032
|
// packages/typescript/src/lib/http/send-request.txt
|
|
1552
|
-
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";
|
|
1553
2034
|
|
|
1554
2035
|
// packages/typescript/src/lib/generate.ts
|
|
1555
2036
|
function security(spec) {
|
|
@@ -1573,6 +2054,15 @@ function security(spec) {
|
|
|
1573
2054
|
return options;
|
|
1574
2055
|
}
|
|
1575
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
|
+
);
|
|
1576
2066
|
settings.useTsExtension ??= true;
|
|
1577
2067
|
const makeImport = (moduleSpecifier) => {
|
|
1578
2068
|
return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
|
|
@@ -1580,11 +2070,11 @@ async function generate(spec, settings) {
|
|
|
1580
2070
|
const { commonSchemas, endpoints, groups, outputs, commonZod } = generateCode(
|
|
1581
2071
|
{
|
|
1582
2072
|
spec,
|
|
1583
|
-
style
|
|
2073
|
+
style,
|
|
1584
2074
|
makeImport
|
|
1585
2075
|
}
|
|
1586
2076
|
);
|
|
1587
|
-
const output = settings.mode === "full" ?
|
|
2077
|
+
const output = settings.mode === "full" ? join3(settings.output, "src") : settings.output;
|
|
1588
2078
|
const options = security(spec);
|
|
1589
2079
|
const clientName = settings.name || "Client";
|
|
1590
2080
|
const inputFiles = generateInputs(groups, commonZod, makeImport);
|
|
@@ -1594,9 +2084,9 @@ async function generate(spec, settings) {
|
|
|
1594
2084
|
"models/.getkeep": ""
|
|
1595
2085
|
// 'README.md': readme,
|
|
1596
2086
|
});
|
|
1597
|
-
await writeFiles(
|
|
2087
|
+
await writeFiles(join3(output, "http"), {
|
|
1598
2088
|
"interceptors.ts": `
|
|
1599
|
-
import {
|
|
2089
|
+
import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
|
|
1600
2090
|
${interceptors_default}`,
|
|
1601
2091
|
"parse-response.ts": parse_response_default,
|
|
1602
2092
|
"send-request.ts": `import z from 'zod';
|
|
@@ -1606,12 +2096,12 @@ import { parseInput } from './${makeImport("parser")}';
|
|
|
1606
2096
|
import type { RequestConfig } from './${makeImport("request")}';
|
|
1607
2097
|
import { APIError, APIResponse } from './${makeImport("response")}';
|
|
1608
2098
|
|
|
1609
|
-
${
|
|
2099
|
+
${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputType: style.outputType })}`,
|
|
1610
2100
|
"response.ts": response_default,
|
|
1611
2101
|
"parser.ts": parser_default,
|
|
1612
2102
|
"request.ts": request_default
|
|
1613
2103
|
});
|
|
1614
|
-
await writeFiles(
|
|
2104
|
+
await writeFiles(join3(output, "outputs"), outputs);
|
|
1615
2105
|
const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
|
|
1616
2106
|
await writeFiles(output, {
|
|
1617
2107
|
"client.ts": client_default(
|
|
@@ -1621,7 +2111,7 @@ ${template(send_request_default, {})({ throwError: settings.throwError })}`,
|
|
|
1621
2111
|
options,
|
|
1622
2112
|
makeImport
|
|
1623
2113
|
},
|
|
1624
|
-
|
|
2114
|
+
style
|
|
1625
2115
|
),
|
|
1626
2116
|
...inputFiles,
|
|
1627
2117
|
...endpoints,
|
|
@@ -1639,24 +2129,24 @@ ${template(send_request_default, {})({ throwError: settings.throwError })}`,
|
|
|
1639
2129
|
)
|
|
1640
2130
|
});
|
|
1641
2131
|
const folders = [
|
|
1642
|
-
getFolderExports(
|
|
2132
|
+
getFolderExports(join3(output, "outputs"), settings.useTsExtension),
|
|
1643
2133
|
getFolderExports(
|
|
1644
|
-
|
|
2134
|
+
join3(output, "inputs"),
|
|
1645
2135
|
settings.useTsExtension,
|
|
1646
2136
|
["ts"],
|
|
1647
2137
|
(dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
|
|
1648
2138
|
),
|
|
1649
|
-
getFolderExports(
|
|
2139
|
+
getFolderExports(join3(output, "api"), settings.useTsExtension),
|
|
1650
2140
|
getFolderExports(
|
|
1651
|
-
|
|
2141
|
+
join3(output, "http"),
|
|
1652
2142
|
settings.useTsExtension,
|
|
1653
2143
|
["ts"],
|
|
1654
|
-
(dirent) =>
|
|
2144
|
+
(dirent) => !["response.ts", "parser.ts"].includes(dirent.name)
|
|
1655
2145
|
)
|
|
1656
2146
|
];
|
|
1657
2147
|
if (modelsImports.length) {
|
|
1658
2148
|
folders.push(
|
|
1659
|
-
getFolderExports(
|
|
2149
|
+
getFolderExports(join3(output, "models"), settings.useTsExtension)
|
|
1660
2150
|
);
|
|
1661
2151
|
}
|
|
1662
2152
|
const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|