@sdk-it/typescript 0.15.0 → 0.17.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 +1098 -703
- package/dist/index.js.map +4 -4
- package/dist/lib/client.d.ts +1 -1
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/emitters/interface.d.ts.map +1 -1
- package/dist/lib/emitters/zod.d.ts +4 -4
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts +2 -1
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/readme-generator.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts +18 -13
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/utils.d.ts +7 -14
- package/dist/lib/utils.d.ts.map +1 -1
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,25 +1,9 @@
|
|
|
1
1
|
// packages/typescript/src/lib/generate.ts
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join as join2 } from "node:path";
|
|
3
3
|
import { npmRunPathEnv } from "npm-run-path";
|
|
4
4
|
import { spinalcase as spinalcase3 } from "stringcase";
|
|
5
5
|
import { getFolderExports, methods, writeFiles } from "@sdk-it/core";
|
|
6
6
|
|
|
7
|
-
// packages/typescript/src/lib/generator.ts
|
|
8
|
-
import { get as get2, merge } from "lodash-es";
|
|
9
|
-
import { pascalcase, spinalcase as spinalcase2 } from "stringcase";
|
|
10
|
-
import {
|
|
11
|
-
forEachOperation,
|
|
12
|
-
removeDuplicates as removeDuplicates3
|
|
13
|
-
} from "@sdk-it/core";
|
|
14
|
-
|
|
15
|
-
// packages/typescript/src/lib/utils.ts
|
|
16
|
-
import { get } from "lodash-es";
|
|
17
|
-
import { removeDuplicates as removeDuplicates2 } from "@sdk-it/core";
|
|
18
|
-
|
|
19
|
-
// packages/typescript/src/lib/sdk.ts
|
|
20
|
-
import { camelcase, spinalcase } from "stringcase";
|
|
21
|
-
import { removeDuplicates, toLitObject as toLitObject2 } from "@sdk-it/core";
|
|
22
|
-
|
|
23
7
|
// packages/typescript/src/lib/client.ts
|
|
24
8
|
import { toLitObject } from "@sdk-it/core";
|
|
25
9
|
var client_default = (spec) => {
|
|
@@ -44,15 +28,18 @@ var client_default = (spec) => {
|
|
|
44
28
|
}
|
|
45
29
|
};
|
|
46
30
|
return `
|
|
47
|
-
import {
|
|
31
|
+
import type { RequestConfig } from './http/${spec.makeImport("request")}';
|
|
32
|
+
import { fetchType, sendRequest, parse } from './http/${spec.makeImport("send-request")}';
|
|
48
33
|
import z from 'zod';
|
|
49
|
-
import type { Endpoints } from '
|
|
50
|
-
import schemas from '
|
|
34
|
+
import type { Endpoints } from './api/${spec.makeImport("endpoints")}';
|
|
35
|
+
import schemas from './api/${spec.makeImport("schemas")}';
|
|
51
36
|
import {
|
|
52
37
|
createBaseUrlInterceptor,
|
|
53
38
|
createHeadersInterceptor,
|
|
54
39
|
} from './http/${spec.makeImport("interceptors")}';
|
|
55
40
|
|
|
41
|
+
import { parseInput, type ParseError } from './http/${spec.makeImport("parser")}';
|
|
42
|
+
|
|
56
43
|
${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, null, 2)} as const` : ""}
|
|
57
44
|
const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
|
|
58
45
|
${spec.servers.length ? `export type Servers = typeof servers[number];` : ""}
|
|
@@ -81,6 +68,44 @@ export class ${spec.name} {
|
|
|
81
68
|
});
|
|
82
69
|
}
|
|
83
70
|
|
|
71
|
+
async prepare<E extends keyof Endpoints>(
|
|
72
|
+
endpoint: E,
|
|
73
|
+
input: Endpoints[E]['input'],
|
|
74
|
+
options?: { headers?: HeadersInit },
|
|
75
|
+
): Promise<
|
|
76
|
+
readonly [
|
|
77
|
+
RequestConfig & {
|
|
78
|
+
parse: (response: Response) => ReturnType<typeof parse>;
|
|
79
|
+
},
|
|
80
|
+
ParseError<(typeof schemas)[E]['schema']> | null,
|
|
81
|
+
]
|
|
82
|
+
> {
|
|
83
|
+
const route = schemas[endpoint];
|
|
84
|
+
|
|
85
|
+
const interceptors = [
|
|
86
|
+
createHeadersInterceptor(
|
|
87
|
+
() => this.defaultHeaders,
|
|
88
|
+
options?.headers ?? {},
|
|
89
|
+
),
|
|
90
|
+
createBaseUrlInterceptor(() => this.options.baseUrl),
|
|
91
|
+
];
|
|
92
|
+
const [parsedInput, parseError] = parseInput(route.schema, input);
|
|
93
|
+
if (parseError) {
|
|
94
|
+
return [null as never, parseError as never] as const;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
let config = route.toRequest(parsedInput as never);
|
|
98
|
+
for (const interceptor of interceptors) {
|
|
99
|
+
if (interceptor.before) {
|
|
100
|
+
config = await interceptor.before(config);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return [
|
|
104
|
+
{ ...config, parse: (response: Response) => parse(route, response) },
|
|
105
|
+
null as never,
|
|
106
|
+
] as const;
|
|
107
|
+
}
|
|
108
|
+
|
|
84
109
|
get defaultHeaders() {
|
|
85
110
|
return ${defaultHeaders}
|
|
86
111
|
}
|
|
@@ -101,536 +126,276 @@ export class ${spec.name} {
|
|
|
101
126
|
}`;
|
|
102
127
|
};
|
|
103
128
|
|
|
104
|
-
// packages/typescript/src/lib/
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
return
|
|
127
|
-
export default {
|
|
128
|
-
${this.#endpoints.join("\n")}
|
|
129
|
-
}`;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
var Emitter = class {
|
|
133
|
-
#makeImport;
|
|
134
|
-
imports = [];
|
|
135
|
-
constructor(makeImport) {
|
|
136
|
-
this.#makeImport = makeImport;
|
|
137
|
-
this.imports = [
|
|
138
|
-
`import type z from 'zod';`,
|
|
139
|
-
`import type { ParseError } from '${this.#makeImport("./http/parser")}';`
|
|
140
|
-
];
|
|
141
|
-
}
|
|
142
|
-
endpoints = [];
|
|
143
|
-
addEndpoint(endpoint, operation) {
|
|
144
|
-
this.endpoints.push(` "${endpoint}": ${operation};`);
|
|
145
|
-
}
|
|
146
|
-
addImport(value) {
|
|
147
|
-
this.imports.push(value);
|
|
148
|
-
}
|
|
149
|
-
complete() {
|
|
150
|
-
return `${this.imports.join("\n")}
|
|
151
|
-
export interface Endpoints {
|
|
152
|
-
${this.endpoints.join("\n")}
|
|
153
|
-
}`;
|
|
129
|
+
// packages/typescript/src/lib/generator.ts
|
|
130
|
+
import { merge } from "lodash-es";
|
|
131
|
+
import { join } from "node:path";
|
|
132
|
+
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
|
+
|
|
135
|
+
// packages/spec/dist/lib/operation.js
|
|
136
|
+
import { camelcase } from "stringcase";
|
|
137
|
+
var defaults = {
|
|
138
|
+
operationId: (operation, path, method) => {
|
|
139
|
+
if (operation.operationId) {
|
|
140
|
+
return camelcase(operation.operationId);
|
|
141
|
+
}
|
|
142
|
+
const metadata = operation["x-oaiMeta"];
|
|
143
|
+
if (metadata && metadata.name) {
|
|
144
|
+
return camelcase(metadata.name);
|
|
145
|
+
}
|
|
146
|
+
return camelcase(
|
|
147
|
+
[method, ...path.replace(/[\\/\\{\\}]/g, " ").split(" ")].filter(Boolean).join(" ").trim()
|
|
148
|
+
);
|
|
149
|
+
},
|
|
150
|
+
tag: (operation, path) => {
|
|
151
|
+
return operation.tags?.[0] || determineGenericTag(path, operation);
|
|
154
152
|
}
|
|
155
153
|
};
|
|
156
|
-
function
|
|
157
|
-
const
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
const
|
|
161
|
-
const
|
|
162
|
-
|
|
163
|
-
const
|
|
164
|
-
const
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
if (preciseMatch.test(content) && schema2 !== name) {
|
|
183
|
-
output.push(
|
|
184
|
-
`import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
|
|
185
|
-
);
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
output.push(content);
|
|
189
|
-
return [
|
|
190
|
-
[`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
|
|
191
|
-
...acc
|
|
192
|
-
];
|
|
193
|
-
}, []);
|
|
194
|
-
return {
|
|
195
|
-
...Object.fromEntries(schemas),
|
|
196
|
-
...inputs
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
function generateSDK(spec) {
|
|
200
|
-
const emitter = new Emitter(spec.makeImport);
|
|
201
|
-
const schemaEndpoint = new SchemaEndpoint(spec.makeImport);
|
|
202
|
-
const errors = [];
|
|
203
|
-
for (const [name, operations] of Object.entries(spec.operations)) {
|
|
204
|
-
emitter.addImport(
|
|
205
|
-
`import type * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
|
|
206
|
-
);
|
|
207
|
-
schemaEndpoint.addImport(
|
|
208
|
-
`import * as ${camelcase(name)} from './inputs/${spec.makeImport(spinalcase(name))}';`
|
|
209
|
-
);
|
|
210
|
-
for (const operation of operations) {
|
|
211
|
-
const schemaName = camelcase(`${operation.name} schema`);
|
|
212
|
-
const schemaRef = `${camelcase(name)}.${schemaName}`;
|
|
213
|
-
const output = operation.formatOutput();
|
|
214
|
-
const inputHeaders = [];
|
|
215
|
-
const inputQuery = [];
|
|
216
|
-
const inputBody = [];
|
|
217
|
-
const inputParams = [];
|
|
218
|
-
for (const [name2, prop] of Object.entries(operation.inputs)) {
|
|
219
|
-
if (prop.in === "headers" || prop.in === "header") {
|
|
220
|
-
inputHeaders.push(`"${name2}"`);
|
|
221
|
-
} else if (prop.in === "query") {
|
|
222
|
-
inputQuery.push(`"${name2}"`);
|
|
223
|
-
} else if (prop.in === "body") {
|
|
224
|
-
inputBody.push(`"${name2}"`);
|
|
225
|
-
} else if (prop.in === "path") {
|
|
226
|
-
inputParams.push(`"${name2}"`);
|
|
227
|
-
} else if (prop.in === "internal") {
|
|
228
|
-
continue;
|
|
229
|
-
} else {
|
|
230
|
-
throw new Error(
|
|
231
|
-
`Unknown source ${prop.in} in ${name2} ${JSON.stringify(
|
|
232
|
-
prop
|
|
233
|
-
)} in ${operation.name}`
|
|
234
|
-
);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
emitter.addImport(
|
|
238
|
-
`import type {${output.import}} from './outputs/${spec.makeImport(spinalcase(operation.name))}';`
|
|
154
|
+
function forEachOperation(config, callback) {
|
|
155
|
+
const result = [];
|
|
156
|
+
for (const [path, pathItem] of Object.entries(config.spec.paths ?? {})) {
|
|
157
|
+
const { parameters = [], ...methods2 } = pathItem;
|
|
158
|
+
const fixedPath = path.replace(/:([^/]+)/g, "{$1}");
|
|
159
|
+
for (const [method, operation] of Object.entries(methods2)) {
|
|
160
|
+
const formatOperationId = config.operationId ?? defaults.operationId;
|
|
161
|
+
const formatTag = config.tag ?? defaults.tag;
|
|
162
|
+
const operationName = formatOperationId(operation, fixedPath, method);
|
|
163
|
+
const operationTag = formatTag(operation, fixedPath);
|
|
164
|
+
const metadata = operation["x-oaiMeta"] ?? {};
|
|
165
|
+
result.push(
|
|
166
|
+
callback(
|
|
167
|
+
{
|
|
168
|
+
name: metadata.name,
|
|
169
|
+
method,
|
|
170
|
+
path: fixedPath,
|
|
171
|
+
groupName: operationTag,
|
|
172
|
+
tag: operationTag
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
...operation,
|
|
176
|
+
parameters: [...parameters, ...operation.parameters ?? []],
|
|
177
|
+
operationId: operationName
|
|
178
|
+
}
|
|
179
|
+
)
|
|
239
180
|
);
|
|
240
|
-
errors.push(...operation.errors ?? []);
|
|
241
|
-
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
242
|
-
for (const type in operation.schemas ?? {}) {
|
|
243
|
-
let typePrefix = "";
|
|
244
|
-
if (addTypeParser && type !== "json") {
|
|
245
|
-
typePrefix = `${type} `;
|
|
246
|
-
}
|
|
247
|
-
const input = `typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}`;
|
|
248
|
-
const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
|
|
249
|
-
emitter.addEndpoint(
|
|
250
|
-
endpoint,
|
|
251
|
-
`{input: z.infer<${input}>; output: ${output.use}; error: ${(operation.errors ?? ["ServerError"]).concat(`ParseError<${input}>`).join("|")}}`
|
|
252
|
-
);
|
|
253
|
-
schemaEndpoint.addEndpoint(
|
|
254
|
-
endpoint,
|
|
255
|
-
`{
|
|
256
|
-
schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
|
|
257
|
-
deserializer: ${operation.parser === "chunked" ? "chunked" : "buffered"},
|
|
258
|
-
toRequest(input: Endpoints['${endpoint}']['input']) {
|
|
259
|
-
const endpoint = '${endpoint}';
|
|
260
|
-
return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
|
|
261
|
-
inputHeaders: [${inputHeaders}],
|
|
262
|
-
inputQuery: [${inputQuery}],
|
|
263
|
-
inputBody: [${inputBody}],
|
|
264
|
-
inputParams: [${inputParams}],
|
|
265
|
-
}));
|
|
266
|
-
},
|
|
267
|
-
}`
|
|
268
|
-
);
|
|
269
|
-
}
|
|
270
181
|
}
|
|
271
182
|
}
|
|
272
|
-
|
|
273
|
-
`import type { ${removeDuplicates(errors, (it) => it).join(", ")} } from '${spec.makeImport("./http/response")}';`
|
|
274
|
-
);
|
|
275
|
-
return {
|
|
276
|
-
"client.ts": client_default(spec),
|
|
277
|
-
"schemas.ts": schemaEndpoint.complete(),
|
|
278
|
-
"endpoints.ts": emitter.complete()
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// packages/typescript/src/lib/utils.ts
|
|
283
|
-
function isRef(obj) {
|
|
284
|
-
return obj && "$ref" in obj;
|
|
285
|
-
}
|
|
286
|
-
function cleanRef(ref) {
|
|
287
|
-
return ref.replace(/^#\//, "");
|
|
183
|
+
return result;
|
|
288
184
|
}
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
185
|
+
var reservedKeywords = /* @__PURE__ */ new Set([
|
|
186
|
+
"abstract",
|
|
187
|
+
"arguments",
|
|
188
|
+
"await",
|
|
189
|
+
"boolean",
|
|
190
|
+
"break",
|
|
191
|
+
"byte",
|
|
192
|
+
"case",
|
|
193
|
+
"catch",
|
|
194
|
+
"char",
|
|
195
|
+
"class",
|
|
196
|
+
"const",
|
|
197
|
+
"continue",
|
|
198
|
+
"debugger",
|
|
199
|
+
"default",
|
|
200
|
+
"delete",
|
|
201
|
+
"do",
|
|
202
|
+
"double",
|
|
203
|
+
"else",
|
|
204
|
+
"enum",
|
|
205
|
+
"eval",
|
|
206
|
+
"export",
|
|
207
|
+
"extends",
|
|
208
|
+
"false",
|
|
209
|
+
"final",
|
|
210
|
+
"finally",
|
|
211
|
+
"float",
|
|
212
|
+
"for",
|
|
213
|
+
"function",
|
|
214
|
+
"goto",
|
|
215
|
+
"if",
|
|
216
|
+
"implements",
|
|
217
|
+
"import",
|
|
218
|
+
"in",
|
|
219
|
+
"instanceof",
|
|
220
|
+
"int",
|
|
221
|
+
"interface",
|
|
222
|
+
"let",
|
|
223
|
+
"long",
|
|
224
|
+
"native",
|
|
225
|
+
"new",
|
|
226
|
+
"null",
|
|
227
|
+
"package",
|
|
228
|
+
"private",
|
|
229
|
+
"protected",
|
|
230
|
+
"public",
|
|
231
|
+
"return",
|
|
232
|
+
"short",
|
|
233
|
+
"static",
|
|
234
|
+
"super",
|
|
235
|
+
"switch",
|
|
236
|
+
"synchronized",
|
|
237
|
+
"this",
|
|
238
|
+
"throw",
|
|
239
|
+
"throws",
|
|
240
|
+
"transient",
|
|
241
|
+
"true",
|
|
242
|
+
"try",
|
|
243
|
+
"typeof",
|
|
244
|
+
"var",
|
|
245
|
+
"void",
|
|
246
|
+
"volatile",
|
|
247
|
+
"while",
|
|
248
|
+
"with",
|
|
249
|
+
"yield",
|
|
250
|
+
// Potentially problematic identifiers / Common Verbs used as tags
|
|
251
|
+
"object",
|
|
252
|
+
"string",
|
|
253
|
+
"number",
|
|
254
|
+
"any",
|
|
255
|
+
"unknown",
|
|
256
|
+
"never",
|
|
257
|
+
"get",
|
|
258
|
+
"list",
|
|
259
|
+
"create",
|
|
260
|
+
"update",
|
|
261
|
+
"delete",
|
|
262
|
+
"post",
|
|
263
|
+
"put",
|
|
264
|
+
"patch",
|
|
265
|
+
"do",
|
|
266
|
+
"send",
|
|
267
|
+
"add",
|
|
268
|
+
"remove",
|
|
269
|
+
"set",
|
|
270
|
+
"find",
|
|
271
|
+
"search",
|
|
272
|
+
"check",
|
|
273
|
+
"make"
|
|
274
|
+
// Added make, check
|
|
275
|
+
]);
|
|
276
|
+
function sanitizeTag(camelCasedTag) {
|
|
277
|
+
if (/^\d/.test(camelCasedTag)) {
|
|
278
|
+
return `_${camelCasedTag}`;
|
|
299
279
|
}
|
|
300
|
-
return
|
|
280
|
+
return reservedKeywords.has(camelCasedTag) ? `${camelCasedTag}_` : camelCasedTag;
|
|
301
281
|
}
|
|
302
|
-
function
|
|
303
|
-
|
|
304
|
-
const
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
return options;
|
|
337
|
-
}
|
|
338
|
-
function mergeImports(imports) {
|
|
339
|
-
const merged = {};
|
|
340
|
-
for (const i of imports) {
|
|
341
|
-
merged[i.moduleSpecifier] = merged[i.moduleSpecifier] ?? {
|
|
342
|
-
moduleSpecifier: i.moduleSpecifier,
|
|
343
|
-
defaultImport: i.defaultImport,
|
|
344
|
-
namespaceImport: i.namespaceImport,
|
|
345
|
-
namedImports: []
|
|
346
|
-
};
|
|
347
|
-
if (i.namedImports) {
|
|
348
|
-
merged[i.moduleSpecifier].namedImports.push(...i.namedImports);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
return Object.values(merged);
|
|
352
|
-
}
|
|
353
|
-
function importsToString(...imports) {
|
|
354
|
-
return imports.map((it) => {
|
|
355
|
-
if (it.defaultImport) {
|
|
356
|
-
return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
|
|
357
|
-
}
|
|
358
|
-
if (it.namespaceImport) {
|
|
359
|
-
return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
|
|
360
|
-
}
|
|
361
|
-
if (it.namedImports) {
|
|
362
|
-
return `import {${removeDuplicates2(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
|
|
363
|
-
}
|
|
364
|
-
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
365
|
-
});
|
|
366
|
-
}
|
|
367
|
-
function exclude(list, exclude2) {
|
|
368
|
-
return list.filter((it) => !exclude2.includes(it));
|
|
369
|
-
}
|
|
370
|
-
function useImports(content, imports) {
|
|
371
|
-
const output = [];
|
|
372
|
-
for (const it of mergeImports(imports)) {
|
|
373
|
-
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
374
|
-
if (singleImport && content.includes(singleImport)) {
|
|
375
|
-
output.push(importsToString(it).join("\n"));
|
|
376
|
-
} else if (it.namedImports.length) {
|
|
377
|
-
for (const namedImport of it.namedImports) {
|
|
378
|
-
if (content.includes(namedImport.name)) {
|
|
379
|
-
output.push(importsToString(it).join("\n"));
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
return output;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// packages/typescript/src/lib/emitters/interface.ts
|
|
388
|
-
var TypeScriptDeserialzer = class {
|
|
389
|
-
circularRefTracker = /* @__PURE__ */ new Set();
|
|
390
|
-
#spec;
|
|
391
|
-
#onRef;
|
|
392
|
-
constructor(spec, onRef) {
|
|
393
|
-
this.#spec = spec;
|
|
394
|
-
this.#onRef = onRef;
|
|
395
|
-
}
|
|
396
|
-
#stringifyKey = (key) => {
|
|
397
|
-
const reservedWords = [
|
|
398
|
-
"constructor",
|
|
399
|
-
"prototype",
|
|
400
|
-
"break",
|
|
401
|
-
"case",
|
|
402
|
-
"catch",
|
|
403
|
-
"class",
|
|
404
|
-
"const",
|
|
405
|
-
"continue",
|
|
406
|
-
"debugger",
|
|
407
|
-
"default",
|
|
408
|
-
"delete",
|
|
409
|
-
"do",
|
|
410
|
-
"else",
|
|
411
|
-
"export",
|
|
412
|
-
"extends",
|
|
413
|
-
"false",
|
|
414
|
-
"finally",
|
|
415
|
-
"for",
|
|
416
|
-
"function",
|
|
417
|
-
"if",
|
|
418
|
-
"import",
|
|
419
|
-
"in",
|
|
420
|
-
"instanceof",
|
|
421
|
-
"new",
|
|
422
|
-
"null",
|
|
423
|
-
"return",
|
|
424
|
-
"super",
|
|
425
|
-
"switch",
|
|
426
|
-
"this",
|
|
427
|
-
"throw",
|
|
428
|
-
"true",
|
|
429
|
-
"try",
|
|
430
|
-
"typeof",
|
|
431
|
-
"var",
|
|
432
|
-
"void",
|
|
433
|
-
"while",
|
|
434
|
-
"with",
|
|
435
|
-
"yield"
|
|
436
|
-
];
|
|
437
|
-
if (reservedWords.includes(key)) {
|
|
438
|
-
return `'${key}'`;
|
|
439
|
-
}
|
|
440
|
-
if (key.trim() === "") {
|
|
441
|
-
return `'${key}'`;
|
|
442
|
-
}
|
|
443
|
-
const firstChar = key.charAt(0);
|
|
444
|
-
const validFirstChar = firstChar >= "a" && firstChar <= "z" || firstChar >= "A" && firstChar <= "Z" || firstChar === "_" || firstChar === "$";
|
|
445
|
-
if (!validFirstChar) {
|
|
446
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
447
|
-
}
|
|
448
|
-
for (let i = 1; i < key.length; i++) {
|
|
449
|
-
const char = key.charAt(i);
|
|
450
|
-
const validChar = char >= "a" && char <= "z" || char >= "A" && char <= "Z" || char >= "0" && char <= "9" || char === "_" || char === "$";
|
|
451
|
-
if (!validChar) {
|
|
452
|
-
return `'${key.replace(/'/g, "\\'")}'`;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
return key;
|
|
456
|
-
};
|
|
457
|
-
#stringifyKeyV2 = (value) => {
|
|
458
|
-
return `'${value}'`;
|
|
459
|
-
};
|
|
460
|
-
/**
|
|
461
|
-
* Handle objects (properties)
|
|
462
|
-
*/
|
|
463
|
-
object(schema, required = false) {
|
|
464
|
-
const properties = schema.properties || {};
|
|
465
|
-
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
466
|
-
const isRequired = (schema.required ?? []).includes(key);
|
|
467
|
-
const tsType = this.handle(propSchema, isRequired);
|
|
468
|
-
return `${this.#stringifyKeyV2(key)}: ${tsType}`;
|
|
469
|
-
});
|
|
470
|
-
if (schema.additionalProperties) {
|
|
471
|
-
if (typeof schema.additionalProperties === "object") {
|
|
472
|
-
const indexType = this.handle(schema.additionalProperties, true);
|
|
473
|
-
propEntries.push(`[key: string]: ${indexType}`);
|
|
474
|
-
} else if (schema.additionalProperties === true) {
|
|
475
|
-
propEntries.push("[key: string]: any");
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
return `{ ${propEntries.join("; ")} }`;
|
|
479
|
-
}
|
|
480
|
-
/**
|
|
481
|
-
* Handle arrays (items could be a single schema or a tuple)
|
|
482
|
-
*/
|
|
483
|
-
array(schema, required = false) {
|
|
484
|
-
const { items } = schema;
|
|
485
|
-
if (!items) {
|
|
486
|
-
return "any[]";
|
|
487
|
-
}
|
|
488
|
-
if (Array.isArray(items)) {
|
|
489
|
-
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
490
|
-
return `[${tupleItems.join(", ")}]`;
|
|
491
|
-
}
|
|
492
|
-
const itemsType = this.handle(items, true);
|
|
493
|
-
return `${itemsType}[]`;
|
|
494
|
-
}
|
|
495
|
-
/**
|
|
496
|
-
* Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
|
|
497
|
-
*/
|
|
498
|
-
normal(type, schema, required = false) {
|
|
499
|
-
switch (type) {
|
|
500
|
-
case "string":
|
|
501
|
-
return this.string(schema, required);
|
|
502
|
-
case "number":
|
|
503
|
-
case "integer":
|
|
504
|
-
return this.number(schema, required);
|
|
505
|
-
case "boolean":
|
|
506
|
-
return appendOptional("boolean", required);
|
|
507
|
-
case "object":
|
|
508
|
-
return this.object(schema, required);
|
|
509
|
-
case "array":
|
|
510
|
-
return this.array(schema, required);
|
|
511
|
-
case "null":
|
|
512
|
-
return "null";
|
|
513
|
-
default:
|
|
514
|
-
return appendOptional("any", required);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
517
|
-
ref($ref, required) {
|
|
518
|
-
const schemaName = cleanRef($ref).split("/").pop();
|
|
519
|
-
if (this.circularRefTracker.has(schemaName)) {
|
|
520
|
-
return schemaName;
|
|
521
|
-
}
|
|
522
|
-
this.circularRefTracker.add(schemaName);
|
|
523
|
-
this.#onRef(schemaName, this.handle(followRef(this.#spec, $ref), true));
|
|
524
|
-
this.circularRefTracker.delete(schemaName);
|
|
525
|
-
return appendOptional(schemaName, required);
|
|
526
|
-
}
|
|
527
|
-
allOf(schemas) {
|
|
528
|
-
const allOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
529
|
-
return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
|
|
530
|
-
}
|
|
531
|
-
anyOf(schemas, required) {
|
|
532
|
-
const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
533
|
-
return appendOptional(
|
|
534
|
-
anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
|
|
535
|
-
required
|
|
536
|
-
);
|
|
537
|
-
}
|
|
538
|
-
oneOf(schemas, required) {
|
|
539
|
-
const oneOfTypes = schemas.map((sub) => {
|
|
540
|
-
if (isRef(sub)) {
|
|
541
|
-
const { model } = parseRef(sub.$ref);
|
|
542
|
-
if (this.circularRefTracker.has(model)) {
|
|
543
|
-
return model;
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
return this.handle(sub, false);
|
|
547
|
-
});
|
|
548
|
-
return appendOptional(
|
|
549
|
-
oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
|
|
550
|
-
required
|
|
551
|
-
);
|
|
552
|
-
}
|
|
553
|
-
enum(values, required) {
|
|
554
|
-
const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
|
|
555
|
-
return appendOptional(enumValues, required);
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Handle string type with formats
|
|
559
|
-
*/
|
|
560
|
-
string(schema, required) {
|
|
561
|
-
let type;
|
|
562
|
-
switch (schema.format) {
|
|
563
|
-
case "date-time":
|
|
564
|
-
case "datetime":
|
|
565
|
-
case "date":
|
|
566
|
-
type = "Date";
|
|
567
|
-
break;
|
|
568
|
-
case "binary":
|
|
569
|
-
case "byte":
|
|
570
|
-
type = "Blob";
|
|
571
|
-
break;
|
|
572
|
-
case "int64":
|
|
573
|
-
type = "bigint";
|
|
574
|
-
break;
|
|
575
|
-
default:
|
|
576
|
-
type = "string";
|
|
282
|
+
function determineGenericTag(pathString, operation) {
|
|
283
|
+
const operationId = operation.operationId || "";
|
|
284
|
+
const VERSION_REGEX = /^[vV]\d+$/;
|
|
285
|
+
const commonVerbs = /* @__PURE__ */ new Set([
|
|
286
|
+
// Verbs to potentially strip from operationId prefix
|
|
287
|
+
"get",
|
|
288
|
+
"list",
|
|
289
|
+
"create",
|
|
290
|
+
"update",
|
|
291
|
+
"delete",
|
|
292
|
+
"post",
|
|
293
|
+
"put",
|
|
294
|
+
"patch",
|
|
295
|
+
"do",
|
|
296
|
+
"send",
|
|
297
|
+
"add",
|
|
298
|
+
"remove",
|
|
299
|
+
"set",
|
|
300
|
+
"find",
|
|
301
|
+
"search",
|
|
302
|
+
"check",
|
|
303
|
+
"make"
|
|
304
|
+
// Added make
|
|
305
|
+
]);
|
|
306
|
+
const segments = pathString.split("/").filter(Boolean);
|
|
307
|
+
const potentialCandidates = segments.filter(
|
|
308
|
+
(segment) => segment && !segment.startsWith("{") && !segment.endsWith("}") && !VERSION_REGEX.test(segment)
|
|
309
|
+
);
|
|
310
|
+
for (let i = potentialCandidates.length - 1; i >= 0; i--) {
|
|
311
|
+
const segment = potentialCandidates[i];
|
|
312
|
+
if (!segment.startsWith("@")) {
|
|
313
|
+
return sanitizeTag(camelcase(segment));
|
|
577
314
|
}
|
|
578
|
-
return appendOptional(type, required);
|
|
579
|
-
}
|
|
580
|
-
/**
|
|
581
|
-
* Handle number/integer types with formats
|
|
582
|
-
*/
|
|
583
|
-
number(schema, required) {
|
|
584
|
-
const type = schema.format === "int64" ? "bigint" : "number";
|
|
585
|
-
return appendOptional(type, required);
|
|
586
315
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
316
|
+
const canFallbackToPathSegment = potentialCandidates.length > 0;
|
|
317
|
+
if (operationId) {
|
|
318
|
+
const lowerOpId = operationId.toLowerCase();
|
|
319
|
+
const parts = operationId.replace(/([a-z])([A-Z])/g, "$1_$2").replace(/([A-Z])([A-Z][a-z])/g, "$1_$2").replace(/([a-zA-Z])(\d)/g, "$1_$2").replace(/(\d)([a-zA-Z])/g, "$1_$2").toLowerCase().split(/[_-\s]+/);
|
|
320
|
+
const validParts = parts.filter(Boolean);
|
|
321
|
+
if (commonVerbs.has(lowerOpId) && validParts.length === 1 && canFallbackToPathSegment) {
|
|
322
|
+
} else if (validParts.length > 0) {
|
|
323
|
+
const firstPart = validParts[0];
|
|
324
|
+
const isFirstPartVerb = commonVerbs.has(firstPart);
|
|
325
|
+
if (isFirstPartVerb && validParts.length > 1) {
|
|
326
|
+
const verbPrefixLength = firstPart.length;
|
|
327
|
+
let nextPartStartIndex = -1;
|
|
328
|
+
if (operationId.length > verbPrefixLength) {
|
|
329
|
+
const charAfterPrefix = operationId[verbPrefixLength];
|
|
330
|
+
if (charAfterPrefix >= "A" && charAfterPrefix <= "Z") {
|
|
331
|
+
nextPartStartIndex = verbPrefixLength;
|
|
332
|
+
} else if (charAfterPrefix >= "0" && charAfterPrefix <= "9") {
|
|
333
|
+
nextPartStartIndex = verbPrefixLength;
|
|
334
|
+
} else if (["_", "-"].includes(charAfterPrefix)) {
|
|
335
|
+
nextPartStartIndex = verbPrefixLength + 1;
|
|
336
|
+
} else {
|
|
337
|
+
const match = operationId.substring(verbPrefixLength).match(/[A-Z0-9]/);
|
|
338
|
+
if (match && match.index !== void 0) {
|
|
339
|
+
nextPartStartIndex = verbPrefixLength + match.index;
|
|
340
|
+
}
|
|
341
|
+
if (nextPartStartIndex === -1 && operationId.length > verbPrefixLength) {
|
|
342
|
+
nextPartStartIndex = verbPrefixLength;
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (nextPartStartIndex !== -1 && nextPartStartIndex < operationId.length) {
|
|
347
|
+
const remainingOriginalSubstring = operationId.substring(nextPartStartIndex);
|
|
348
|
+
const potentialTag = camelcase(remainingOriginalSubstring);
|
|
349
|
+
if (potentialTag) {
|
|
350
|
+
return sanitizeTag(potentialTag);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
const potentialTagJoined = camelcase(validParts.slice(1).join("_"));
|
|
354
|
+
if (potentialTagJoined) {
|
|
355
|
+
return sanitizeTag(potentialTagJoined);
|
|
356
|
+
}
|
|
607
357
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
358
|
+
const potentialTagFull = camelcase(operationId);
|
|
359
|
+
if (potentialTagFull) {
|
|
360
|
+
const isResultSingleVerb = validParts.length === 1 && isFirstPartVerb;
|
|
361
|
+
if (!(isResultSingleVerb && canFallbackToPathSegment)) {
|
|
362
|
+
if (potentialTagFull.length > 0) {
|
|
363
|
+
return sanitizeTag(potentialTagFull);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
const firstPartCamel = camelcase(firstPart);
|
|
368
|
+
if (firstPartCamel) {
|
|
369
|
+
const isFirstPartCamelVerb = commonVerbs.has(firstPartCamel);
|
|
370
|
+
if (!isFirstPartCamelVerb || validParts.length === 1 || !canFallbackToPathSegment) {
|
|
371
|
+
return sanitizeTag(firstPartCamel);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (isFirstPartVerb && validParts.length > 1 && validParts[1] && canFallbackToPathSegment) {
|
|
375
|
+
const secondPartCamel = camelcase(validParts[1]);
|
|
376
|
+
if (secondPartCamel) {
|
|
377
|
+
return sanitizeTag(secondPartCamel);
|
|
378
|
+
}
|
|
615
379
|
}
|
|
616
|
-
const typeResults = types.map((t) => this.normal(t, schema, false));
|
|
617
|
-
return appendOptional(typeResults.join(" | "), required);
|
|
618
380
|
}
|
|
619
|
-
return this.normal(types[0], schema, required);
|
|
620
381
|
}
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
382
|
+
if (potentialCandidates.length > 0) {
|
|
383
|
+
let firstCandidate = potentialCandidates[0];
|
|
384
|
+
if (firstCandidate.startsWith("@")) {
|
|
385
|
+
firstCandidate = firstCandidate.substring(1);
|
|
386
|
+
}
|
|
387
|
+
if (firstCandidate) {
|
|
388
|
+
return sanitizeTag(camelcase(firstCandidate));
|
|
389
|
+
}
|
|
627
390
|
}
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
391
|
+
console.warn(
|
|
392
|
+
`Could not determine a suitable tag for path: ${pathString}, operationId: ${operationId}. Using 'unknown'.`
|
|
393
|
+
);
|
|
394
|
+
return "unknown";
|
|
631
395
|
}
|
|
632
396
|
|
|
633
397
|
// packages/typescript/src/lib/emitters/zod.ts
|
|
398
|
+
import { cleanRef, followRef, isRef, parseRef } from "@sdk-it/core";
|
|
634
399
|
var ZodDeserialzer = class {
|
|
635
400
|
circularRefTracker = /* @__PURE__ */ new Set();
|
|
636
401
|
#spec;
|
|
@@ -642,12 +407,11 @@ var ZodDeserialzer = class {
|
|
|
642
407
|
/**
|
|
643
408
|
* Handle objects (properties, additionalProperties).
|
|
644
409
|
*/
|
|
645
|
-
object(schema
|
|
410
|
+
object(schema) {
|
|
646
411
|
const properties = schema.properties || {};
|
|
647
412
|
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
648
413
|
const isRequired = (schema.required ?? []).includes(key);
|
|
649
|
-
|
|
650
|
-
return `'${key}': ${zodPart}`;
|
|
414
|
+
return `'${key}': ${this.handle(propSchema, isRequired)}`;
|
|
651
415
|
});
|
|
652
416
|
let additionalProps = "";
|
|
653
417
|
if (schema.additionalProperties) {
|
|
@@ -658,8 +422,7 @@ var ZodDeserialzer = class {
|
|
|
658
422
|
additionalProps = `.catchall(z.unknown())`;
|
|
659
423
|
}
|
|
660
424
|
}
|
|
661
|
-
|
|
662
|
-
return `${objectSchema}${appendOptional2(required)}`;
|
|
425
|
+
return `z.object({${propEntries.join(", ")}})${additionalProps}`;
|
|
663
426
|
}
|
|
664
427
|
/**
|
|
665
428
|
* Handle arrays (items could be a single schema or a tuple (array of schemas)).
|
|
@@ -668,18 +431,18 @@ var ZodDeserialzer = class {
|
|
|
668
431
|
array(schema, required = false) {
|
|
669
432
|
const { items } = schema;
|
|
670
433
|
if (!items) {
|
|
671
|
-
return `z.array(z.unknown())${
|
|
434
|
+
return `z.array(z.unknown())${appendOptional(required)}`;
|
|
672
435
|
}
|
|
673
436
|
if (Array.isArray(items)) {
|
|
674
437
|
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
675
438
|
const base = `z.tuple([${tupleItems.join(", ")}])`;
|
|
676
|
-
return `${base}${
|
|
439
|
+
return `${base}${appendOptional(required)}`;
|
|
677
440
|
}
|
|
678
441
|
const itemsSchema = this.handle(items, true);
|
|
679
|
-
return `z.array(${itemsSchema})${
|
|
442
|
+
return `z.array(${itemsSchema})${appendOptional(required)}`;
|
|
680
443
|
}
|
|
681
444
|
#suffixes = (defaultValue, required, nullable) => {
|
|
682
|
-
return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${
|
|
445
|
+
return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional(required)}`;
|
|
683
446
|
};
|
|
684
447
|
/**
|
|
685
448
|
* Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
|
|
@@ -697,13 +460,13 @@ var ZodDeserialzer = class {
|
|
|
697
460
|
case "boolean":
|
|
698
461
|
return `z.boolean()${this.#suffixes(schema.default, required, nullable)}`;
|
|
699
462
|
case "object":
|
|
700
|
-
return this.object(schema,
|
|
463
|
+
return `${this.object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
|
|
701
464
|
case "array":
|
|
702
465
|
return this.array(schema, required);
|
|
703
466
|
case "null":
|
|
704
|
-
return `z.null()${
|
|
467
|
+
return `z.null()${appendOptional(required)}`;
|
|
705
468
|
default:
|
|
706
|
-
return `z.unknown()${
|
|
469
|
+
return `z.unknown()${appendOptional(required)}`;
|
|
707
470
|
}
|
|
708
471
|
}
|
|
709
472
|
ref($ref, required) {
|
|
@@ -719,15 +482,15 @@ var ZodDeserialzer = class {
|
|
|
719
482
|
this.circularRefTracker.delete(schemaName);
|
|
720
483
|
return schemaName;
|
|
721
484
|
}
|
|
722
|
-
allOf(schemas) {
|
|
485
|
+
allOf(schemas, required) {
|
|
723
486
|
const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
724
487
|
if (allOfSchemas.length === 0) {
|
|
725
488
|
return `z.unknown()`;
|
|
726
489
|
}
|
|
727
490
|
if (allOfSchemas.length === 1) {
|
|
728
|
-
return allOfSchemas[0]
|
|
491
|
+
return `${allOfSchemas[0]}${appendOptional(required)}`;
|
|
729
492
|
}
|
|
730
|
-
return this.#toIntersection(allOfSchemas)
|
|
493
|
+
return `${this.#toIntersection(allOfSchemas)}${appendOptional(required)}`;
|
|
731
494
|
}
|
|
732
495
|
#toIntersection(schemas) {
|
|
733
496
|
const [left, ...right] = schemas;
|
|
@@ -737,39 +500,35 @@ var ZodDeserialzer = class {
|
|
|
737
500
|
return `z.intersection(${left}, ${this.#toIntersection(right)})`;
|
|
738
501
|
}
|
|
739
502
|
anyOf(schemas, required) {
|
|
740
|
-
const anyOfSchemas = schemas.map((sub) => this.handle(sub,
|
|
503
|
+
const anyOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
741
504
|
if (anyOfSchemas.length === 1) {
|
|
742
|
-
return anyOfSchemas[0]
|
|
505
|
+
return `${anyOfSchemas[0]}${appendOptional(required)}`;
|
|
743
506
|
}
|
|
744
|
-
return
|
|
745
|
-
// Handle an invalid anyOf with one schema
|
|
746
|
-
anyOfSchemas[0]
|
|
747
|
-
);
|
|
507
|
+
return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
|
|
748
508
|
}
|
|
749
509
|
oneOf(schemas, required) {
|
|
750
510
|
const oneOfSchemas = schemas.map((sub) => {
|
|
751
511
|
if (isRef(sub)) {
|
|
752
512
|
const { model } = parseRef(sub.$ref);
|
|
753
513
|
if (this.circularRefTracker.has(model)) {
|
|
754
|
-
return model
|
|
514
|
+
return `${model}${appendOptional(required)}`;
|
|
755
515
|
}
|
|
756
516
|
}
|
|
757
517
|
return this.handle(sub, true);
|
|
758
518
|
});
|
|
759
519
|
if (oneOfSchemas.length === 1) {
|
|
760
|
-
return oneOfSchemas[0]
|
|
520
|
+
return `${oneOfSchemas[0]}${appendOptional(required)}`;
|
|
761
521
|
}
|
|
762
|
-
return
|
|
763
|
-
// Handle an invalid oneOf with one schema
|
|
764
|
-
oneOfSchemas[0]
|
|
765
|
-
);
|
|
522
|
+
return `z.union([${oneOfSchemas.join(", ")}])${appendOptional(required)}`;
|
|
766
523
|
}
|
|
767
|
-
enum(
|
|
768
|
-
const enumVals = values.map((val) => JSON.stringify(val)).join(", ");
|
|
524
|
+
enum(type, values) {
|
|
769
525
|
if (values.length === 1) {
|
|
770
|
-
return `z.literal(${
|
|
526
|
+
return `z.literal(${values.join(", ")})`;
|
|
771
527
|
}
|
|
772
|
-
|
|
528
|
+
if (type === "integer") {
|
|
529
|
+
return `z.union([${values.map((val) => `z.literal(${val})`).join(", ")}])`;
|
|
530
|
+
}
|
|
531
|
+
return `z.enum([${values.join(", ")}])`;
|
|
773
532
|
}
|
|
774
533
|
/**
|
|
775
534
|
* Handle a `string` schema with possible format keywords (JSON Schema).
|
|
@@ -808,7 +567,7 @@ var ZodDeserialzer = class {
|
|
|
808
567
|
break;
|
|
809
568
|
case "byte":
|
|
810
569
|
case "binary":
|
|
811
|
-
base = "z.instanceof(Blob)
|
|
570
|
+
base = "z.instanceof(Blob)";
|
|
812
571
|
break;
|
|
813
572
|
case "int64":
|
|
814
573
|
base = "z.string() /* or z.bigint() if your app can handle it */";
|
|
@@ -850,51 +609,522 @@ var ZodDeserialzer = class {
|
|
|
850
609
|
if (typeof schema.multipleOf === "number") {
|
|
851
610
|
base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
|
|
852
611
|
}
|
|
853
|
-
return { base, defaultValue };
|
|
612
|
+
return { base, defaultValue };
|
|
613
|
+
}
|
|
614
|
+
handle(schema, required) {
|
|
615
|
+
if (isRef(schema)) {
|
|
616
|
+
return `${this.ref(schema.$ref, true)}${appendOptional(required)}`;
|
|
617
|
+
}
|
|
618
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
619
|
+
return this.allOf(schema.allOf ?? [], required);
|
|
620
|
+
}
|
|
621
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
622
|
+
return this.anyOf(schema.anyOf ?? [], required);
|
|
623
|
+
}
|
|
624
|
+
if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
|
|
625
|
+
return this.oneOf(schema.oneOf ?? [], required);
|
|
626
|
+
}
|
|
627
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
628
|
+
const enumVals = schema.enum.map((val) => JSON.stringify(val));
|
|
629
|
+
const defaultValue = enumVals.includes(JSON.stringify(schema.default)) ? JSON.stringify(schema.default) : void 0;
|
|
630
|
+
return `${this.enum(schema.type, enumVals)}${this.#suffixes(defaultValue, required, false)}`;
|
|
631
|
+
}
|
|
632
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
633
|
+
if (!types.length) {
|
|
634
|
+
return `z.unknown()${appendOptional(required)}`;
|
|
635
|
+
}
|
|
636
|
+
if ("nullable" in schema && schema.nullable) {
|
|
637
|
+
types.push("null");
|
|
638
|
+
} else if (schema.default === null) {
|
|
639
|
+
types.push("null");
|
|
640
|
+
}
|
|
641
|
+
if (types.length > 1) {
|
|
642
|
+
const realTypes = types.filter((t) => t !== "null");
|
|
643
|
+
if (realTypes.length === 1 && types.includes("null")) {
|
|
644
|
+
return this.normal(realTypes[0], schema, required, true);
|
|
645
|
+
}
|
|
646
|
+
const subSchemas = types.map((t) => this.normal(t, schema, false));
|
|
647
|
+
return `z.union([${subSchemas.join(", ")}])${appendOptional(required)}`;
|
|
648
|
+
}
|
|
649
|
+
return this.normal(types[0], schema, required, false);
|
|
650
|
+
}
|
|
651
|
+
};
|
|
652
|
+
function appendOptional(isRequired) {
|
|
653
|
+
return isRequired ? "" : ".optional()";
|
|
654
|
+
}
|
|
655
|
+
function appendDefault(defaultValue) {
|
|
656
|
+
return defaultValue !== void 0 || typeof defaultValue !== "undefined" ? `.default(${defaultValue})` : "";
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// packages/typescript/src/lib/sdk.ts
|
|
660
|
+
import { get } from "lodash-es";
|
|
661
|
+
import { camelcase as camelcase2, pascalcase, spinalcase } from "stringcase";
|
|
662
|
+
import { followRef as followRef3, isRef as isRef4, toLitObject as toLitObject2 } from "@sdk-it/core";
|
|
663
|
+
|
|
664
|
+
// 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
|
+
var TypeScriptDeserialzer = class {
|
|
667
|
+
circularRefTracker = /* @__PURE__ */ new Set();
|
|
668
|
+
#spec;
|
|
669
|
+
#onRef;
|
|
670
|
+
constructor(spec, onRef) {
|
|
671
|
+
this.#spec = spec;
|
|
672
|
+
this.#onRef = onRef;
|
|
673
|
+
}
|
|
674
|
+
#stringifyKey = (key) => {
|
|
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) => {
|
|
736
|
+
return `'${value}'`;
|
|
737
|
+
};
|
|
738
|
+
/**
|
|
739
|
+
* Handle objects (properties)
|
|
740
|
+
*/
|
|
741
|
+
object(schema, required = false) {
|
|
742
|
+
const properties = schema.properties || {};
|
|
743
|
+
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
744
|
+
const isRequired = (schema.required ?? []).includes(key);
|
|
745
|
+
const tsType = this.handle(propSchema, isRequired);
|
|
746
|
+
return `${this.#stringifyKeyV2(key)}: ${tsType}`;
|
|
747
|
+
});
|
|
748
|
+
if (schema.additionalProperties) {
|
|
749
|
+
if (typeof schema.additionalProperties === "object") {
|
|
750
|
+
const indexType = this.handle(schema.additionalProperties, true);
|
|
751
|
+
propEntries.push(`[key: string]: ${indexType}`);
|
|
752
|
+
} else if (schema.additionalProperties === true) {
|
|
753
|
+
propEntries.push("[key: string]: any");
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
return `{ ${propEntries.join("; ")} }`;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Handle arrays (items could be a single schema or a tuple)
|
|
760
|
+
*/
|
|
761
|
+
array(schema, required = false) {
|
|
762
|
+
const { items } = schema;
|
|
763
|
+
if (!items) {
|
|
764
|
+
return "any[]";
|
|
765
|
+
}
|
|
766
|
+
if (Array.isArray(items)) {
|
|
767
|
+
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
768
|
+
return `[${tupleItems.join(", ")}]`;
|
|
769
|
+
}
|
|
770
|
+
const itemsType = this.handle(items, true);
|
|
771
|
+
return `${itemsType}[]`;
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
|
|
775
|
+
*/
|
|
776
|
+
normal(type, schema, required = false) {
|
|
777
|
+
switch (type) {
|
|
778
|
+
case "string":
|
|
779
|
+
return this.string(schema, required);
|
|
780
|
+
case "number":
|
|
781
|
+
case "integer":
|
|
782
|
+
return this.number(schema, required);
|
|
783
|
+
case "boolean":
|
|
784
|
+
return appendOptional2("boolean", required);
|
|
785
|
+
case "object":
|
|
786
|
+
return this.object(schema, required);
|
|
787
|
+
case "array":
|
|
788
|
+
return this.array(schema, required);
|
|
789
|
+
case "null":
|
|
790
|
+
return "null";
|
|
791
|
+
default:
|
|
792
|
+
console.warn(`Unknown type: ${type}`);
|
|
793
|
+
return appendOptional2("any", required);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
ref($ref, required) {
|
|
797
|
+
const schemaName = cleanRef2($ref).split("/").pop();
|
|
798
|
+
if (this.circularRefTracker.has(schemaName)) {
|
|
799
|
+
return schemaName;
|
|
800
|
+
}
|
|
801
|
+
this.circularRefTracker.add(schemaName);
|
|
802
|
+
this.#onRef?.(
|
|
803
|
+
schemaName,
|
|
804
|
+
this.handle(followRef2(this.#spec, $ref), required)
|
|
805
|
+
);
|
|
806
|
+
this.circularRefTracker.delete(schemaName);
|
|
807
|
+
return appendOptional2(schemaName, required);
|
|
808
|
+
}
|
|
809
|
+
allOf(schemas) {
|
|
810
|
+
const allOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
811
|
+
return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
|
|
812
|
+
}
|
|
813
|
+
anyOf(schemas, required) {
|
|
814
|
+
const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
815
|
+
return appendOptional2(
|
|
816
|
+
anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
|
|
817
|
+
required
|
|
818
|
+
);
|
|
819
|
+
}
|
|
820
|
+
oneOf(schemas, required) {
|
|
821
|
+
const oneOfTypes = schemas.map((sub) => {
|
|
822
|
+
if (isRef2(sub)) {
|
|
823
|
+
const { model } = parseRef2(sub.$ref);
|
|
824
|
+
if (this.circularRefTracker.has(model)) {
|
|
825
|
+
return model;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
return this.handle(sub, false);
|
|
829
|
+
});
|
|
830
|
+
return appendOptional2(
|
|
831
|
+
oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
|
|
832
|
+
required
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
enum(values, required) {
|
|
836
|
+
const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
|
|
837
|
+
return appendOptional2(enumValues, required);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
840
|
+
* Handle string type with formats
|
|
841
|
+
*/
|
|
842
|
+
string(schema, required) {
|
|
843
|
+
let type;
|
|
844
|
+
switch (schema.format) {
|
|
845
|
+
case "date-time":
|
|
846
|
+
case "datetime":
|
|
847
|
+
case "date":
|
|
848
|
+
type = "Date";
|
|
849
|
+
break;
|
|
850
|
+
case "binary":
|
|
851
|
+
case "byte":
|
|
852
|
+
type = "Blob";
|
|
853
|
+
break;
|
|
854
|
+
case "int64":
|
|
855
|
+
type = "bigint";
|
|
856
|
+
break;
|
|
857
|
+
default:
|
|
858
|
+
type = "string";
|
|
859
|
+
}
|
|
860
|
+
return appendOptional2(type, required);
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Handle number/integer types with formats
|
|
864
|
+
*/
|
|
865
|
+
number(schema, required) {
|
|
866
|
+
const type = schema.format === "int64" ? "bigint" : "number";
|
|
867
|
+
return appendOptional2(type, required);
|
|
854
868
|
}
|
|
855
869
|
handle(schema, required) {
|
|
856
|
-
if (
|
|
870
|
+
if (isRef2(schema)) {
|
|
857
871
|
return this.ref(schema.$ref, required);
|
|
858
872
|
}
|
|
859
873
|
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
860
|
-
return this.allOf(schema.allOf
|
|
874
|
+
return this.allOf(schema.allOf);
|
|
861
875
|
}
|
|
862
876
|
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
863
|
-
return this.anyOf(schema.anyOf
|
|
877
|
+
return this.anyOf(schema.anyOf, required);
|
|
864
878
|
}
|
|
865
|
-
if (schema.oneOf && Array.isArray(schema.oneOf)
|
|
866
|
-
return this.oneOf(schema.oneOf
|
|
879
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
880
|
+
return this.oneOf(schema.oneOf, required);
|
|
867
881
|
}
|
|
868
882
|
if (schema.enum && Array.isArray(schema.enum)) {
|
|
869
883
|
return this.enum(schema.enum, required);
|
|
870
884
|
}
|
|
871
885
|
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
872
886
|
if (!types.length) {
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
887
|
+
if ("properties" in schema) {
|
|
888
|
+
return this.object(schema, required);
|
|
889
|
+
}
|
|
890
|
+
return appendOptional2("any", required);
|
|
877
891
|
}
|
|
878
892
|
if (types.length > 1) {
|
|
879
893
|
const realTypes = types.filter((t) => t !== "null");
|
|
880
894
|
if (realTypes.length === 1 && types.includes("null")) {
|
|
881
|
-
|
|
895
|
+
const tsType = this.normal(realTypes[0], schema, false);
|
|
896
|
+
return appendOptional2(`${tsType} | null`, required);
|
|
882
897
|
}
|
|
883
|
-
const
|
|
884
|
-
return
|
|
898
|
+
const typeResults = types.map((t) => this.normal(t, schema, false));
|
|
899
|
+
return appendOptional2(typeResults.join(" | "), required);
|
|
885
900
|
}
|
|
886
|
-
return this.normal(types[0], schema, required
|
|
901
|
+
return this.normal(types[0], schema, required);
|
|
902
|
+
}
|
|
903
|
+
/**
|
|
904
|
+
* Generate an interface declaration
|
|
905
|
+
*/
|
|
906
|
+
generateInterface(name, schema) {
|
|
907
|
+
const content = this.handle(schema, true);
|
|
908
|
+
return `interface ${name} ${content}`;
|
|
887
909
|
}
|
|
888
910
|
};
|
|
889
|
-
function appendOptional2(isRequired) {
|
|
890
|
-
return isRequired ?
|
|
911
|
+
function appendOptional2(type, isRequired) {
|
|
912
|
+
return isRequired ? type : `${type} | undefined`;
|
|
891
913
|
}
|
|
892
|
-
|
|
893
|
-
|
|
914
|
+
|
|
915
|
+
// packages/typescript/src/lib/utils.ts
|
|
916
|
+
import { isRef as isRef3, removeDuplicates } from "@sdk-it/core";
|
|
917
|
+
function securityToOptions(security2, securitySchemes, staticIn) {
|
|
918
|
+
securitySchemes ??= {};
|
|
919
|
+
const options = {};
|
|
920
|
+
for (const it of security2) {
|
|
921
|
+
const [name] = Object.keys(it);
|
|
922
|
+
if (!name) {
|
|
923
|
+
continue;
|
|
924
|
+
}
|
|
925
|
+
const schema = securitySchemes[name];
|
|
926
|
+
if (isRef3(schema)) {
|
|
927
|
+
throw new Error(`Ref security schemas are not supported`);
|
|
928
|
+
}
|
|
929
|
+
if (schema.type === "http") {
|
|
930
|
+
options["authorization"] = {
|
|
931
|
+
in: staticIn ?? "header",
|
|
932
|
+
schema: "z.string().optional().transform((val) => (val ? `Bearer ${val}` : undefined))",
|
|
933
|
+
optionName: "token"
|
|
934
|
+
};
|
|
935
|
+
continue;
|
|
936
|
+
}
|
|
937
|
+
if (schema.type === "apiKey") {
|
|
938
|
+
if (!schema.in) {
|
|
939
|
+
throw new Error(`apiKey security schema must have an "in" field`);
|
|
940
|
+
}
|
|
941
|
+
if (!schema.name) {
|
|
942
|
+
throw new Error(`apiKey security schema must have a "name" field`);
|
|
943
|
+
}
|
|
944
|
+
options[schema.name] = {
|
|
945
|
+
in: staticIn ?? schema.in,
|
|
946
|
+
schema: "z.string().optional()"
|
|
947
|
+
};
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
return options;
|
|
952
|
+
}
|
|
953
|
+
function mergeImports(...imports) {
|
|
954
|
+
const merged = {};
|
|
955
|
+
for (const it of imports) {
|
|
956
|
+
merged[it.moduleSpecifier] = merged[it.moduleSpecifier] ?? {
|
|
957
|
+
moduleSpecifier: it.moduleSpecifier,
|
|
958
|
+
defaultImport: it.defaultImport,
|
|
959
|
+
namespaceImport: it.namespaceImport,
|
|
960
|
+
namedImports: []
|
|
961
|
+
};
|
|
962
|
+
for (const named of it.namedImports) {
|
|
963
|
+
if (!merged[it.moduleSpecifier].namedImports.some(
|
|
964
|
+
(x) => x.name === named.name
|
|
965
|
+
)) {
|
|
966
|
+
merged[it.moduleSpecifier].namedImports.push(named);
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
return Object.values(merged);
|
|
971
|
+
}
|
|
972
|
+
function importsToString(...imports) {
|
|
973
|
+
return imports.map((it) => {
|
|
974
|
+
if (it.defaultImport) {
|
|
975
|
+
return `import ${it.defaultImport} from '${it.moduleSpecifier}'`;
|
|
976
|
+
}
|
|
977
|
+
if (it.namespaceImport) {
|
|
978
|
+
return `import * as ${it.namespaceImport} from '${it.moduleSpecifier}'`;
|
|
979
|
+
}
|
|
980
|
+
if (it.namedImports) {
|
|
981
|
+
return `import {${removeDuplicates(it.namedImports, (it2) => it2.name).map((n) => `${n.isTypeOnly ? "type" : ""} ${n.name}`).join(", ")}} from '${it.moduleSpecifier}'`;
|
|
982
|
+
}
|
|
983
|
+
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
function exclude(list, exclude2) {
|
|
987
|
+
return list.filter((it) => !exclude2.includes(it));
|
|
988
|
+
}
|
|
989
|
+
function useImports(content, ...imports) {
|
|
990
|
+
const output = [];
|
|
991
|
+
for (const it of mergeImports(...imports)) {
|
|
992
|
+
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
993
|
+
if (singleImport && content.includes(singleImport)) {
|
|
994
|
+
output.push(importsToString(it).join("\n"));
|
|
995
|
+
} else if (it.namedImports.length) {
|
|
996
|
+
for (const namedImport of it.namedImports) {
|
|
997
|
+
if (content.includes(namedImport.name)) {
|
|
998
|
+
output.push(importsToString(it).join("\n"));
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
return output;
|
|
894
1004
|
}
|
|
895
1005
|
|
|
896
|
-
// packages/typescript/src/lib/
|
|
897
|
-
|
|
1006
|
+
// packages/typescript/src/lib/sdk.ts
|
|
1007
|
+
function generateInputs(operationsSet, commonZod, makeImport) {
|
|
1008
|
+
const commonImports = commonZod.keys().toArray();
|
|
1009
|
+
const inputs = {};
|
|
1010
|
+
for (const [name, operations] of Object.entries(operationsSet)) {
|
|
1011
|
+
const output = [];
|
|
1012
|
+
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
1013
|
+
for (const operation of operations) {
|
|
1014
|
+
const schemaName = camelcase2(`${operation.name} schema`);
|
|
1015
|
+
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
|
|
1016
|
+
const inputContent = schema;
|
|
1017
|
+
for (const schema2 of commonImports) {
|
|
1018
|
+
if (inputContent.includes(schema2)) {
|
|
1019
|
+
imports.add(
|
|
1020
|
+
`import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
|
|
1021
|
+
);
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
output.push(inputContent);
|
|
1025
|
+
}
|
|
1026
|
+
inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
|
|
1027
|
+
}
|
|
1028
|
+
const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
|
|
1029
|
+
const output = [`import { z } from 'zod';`];
|
|
1030
|
+
const content = `export const ${name} = ${schema};`;
|
|
1031
|
+
for (const schema2 of commonImports) {
|
|
1032
|
+
const preciseMatch = new RegExp(`\\b${schema2}\\b`);
|
|
1033
|
+
if (preciseMatch.test(content) && schema2 !== name) {
|
|
1034
|
+
output.push(
|
|
1035
|
+
`import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
|
|
1036
|
+
);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
output.push(content);
|
|
1040
|
+
return [
|
|
1041
|
+
[`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
|
|
1042
|
+
...acc
|
|
1043
|
+
];
|
|
1044
|
+
}, []);
|
|
1045
|
+
return {
|
|
1046
|
+
...Object.fromEntries(schemas),
|
|
1047
|
+
...inputs
|
|
1048
|
+
};
|
|
1049
|
+
}
|
|
1050
|
+
function toEndpoint(groupName, spec, specOperation, operation, utils) {
|
|
1051
|
+
const schemaName = camelcase2(`${operation.name} schema`);
|
|
1052
|
+
const schemaRef = `${camelcase2(groupName)}.${schemaName}`;
|
|
1053
|
+
const inputHeaders = [];
|
|
1054
|
+
const inputQuery = [];
|
|
1055
|
+
const inputBody = [];
|
|
1056
|
+
const inputParams = [];
|
|
1057
|
+
const schemas = [];
|
|
1058
|
+
const responses = [];
|
|
1059
|
+
for (const [name, prop] of Object.entries(operation.inputs)) {
|
|
1060
|
+
if (prop.in === "headers" || prop.in === "header") {
|
|
1061
|
+
inputHeaders.push(`"${name}"`);
|
|
1062
|
+
} else if (prop.in === "query") {
|
|
1063
|
+
inputQuery.push(`"${name}"`);
|
|
1064
|
+
} else if (prop.in === "body") {
|
|
1065
|
+
inputBody.push(`"${name}"`);
|
|
1066
|
+
} else if (prop.in === "path") {
|
|
1067
|
+
inputParams.push(`"${name}"`);
|
|
1068
|
+
} else if (prop.in === "internal") {
|
|
1069
|
+
continue;
|
|
1070
|
+
} else {
|
|
1071
|
+
throw new Error(
|
|
1072
|
+
`Unknown source ${prop.in} in ${name} ${JSON.stringify(
|
|
1073
|
+
prop
|
|
1074
|
+
)} in ${operation.name}`
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
specOperation.responses ??= {};
|
|
1079
|
+
const outputs = [];
|
|
1080
|
+
const statusesCount = Object.keys(specOperation.responses).filter((status) => {
|
|
1081
|
+
const statusCode = +status;
|
|
1082
|
+
return statusCode >= 200 && statusCode < 300;
|
|
1083
|
+
}).length > 1;
|
|
1084
|
+
for (const status in specOperation.responses) {
|
|
1085
|
+
const response = isRef4(specOperation.responses[status]) ? followRef3(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
|
|
1086
|
+
const handled = handleResponse(
|
|
1087
|
+
spec,
|
|
1088
|
+
operation.name,
|
|
1089
|
+
status,
|
|
1090
|
+
response,
|
|
1091
|
+
utils,
|
|
1092
|
+
true
|
|
1093
|
+
// statusesCount,
|
|
1094
|
+
);
|
|
1095
|
+
responses.push(handled);
|
|
1096
|
+
outputs.push(...handled.outputs);
|
|
1097
|
+
}
|
|
1098
|
+
const addTypeParser = Object.keys(operation.schemas).length > 1;
|
|
1099
|
+
for (const type in operation.schemas ?? {}) {
|
|
1100
|
+
let typePrefix = "";
|
|
1101
|
+
if (addTypeParser && type !== "json") {
|
|
1102
|
+
typePrefix = `${type} `;
|
|
1103
|
+
}
|
|
1104
|
+
const endpoint = `${typePrefix}${operation.trigger.method.toUpperCase()} ${operation.trigger.path}`;
|
|
1105
|
+
schemas.push(
|
|
1106
|
+
`"${endpoint}": {
|
|
1107
|
+
schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
|
|
1108
|
+
output:[${outputs.join(",")}],
|
|
1109
|
+
toRequest(input: z.infer<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>) {
|
|
1110
|
+
const endpoint = '${endpoint}';
|
|
1111
|
+
return toRequest(endpoint, ${operation.outgoingContentType || "nobody"}(input, {
|
|
1112
|
+
inputHeaders: [${inputHeaders}],
|
|
1113
|
+
inputQuery: [${inputQuery}],
|
|
1114
|
+
inputBody: [${inputBody}],
|
|
1115
|
+
inputParams: [${inputParams}],
|
|
1116
|
+
}));
|
|
1117
|
+
},
|
|
1118
|
+
}`
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
return { responses, schemas };
|
|
1122
|
+
}
|
|
1123
|
+
var statusCodeToResponseMap = {
|
|
1124
|
+
"200": "Ok",
|
|
1125
|
+
"201": "Created",
|
|
1126
|
+
"202": "Accepted",
|
|
1127
|
+
"204": "NoContent",
|
|
898
1128
|
"400": "BadRequest",
|
|
899
1129
|
"401": "Unauthorized",
|
|
900
1130
|
"402": "PaymentRequired",
|
|
@@ -913,8 +1143,97 @@ var statusCdeToMessageMap = {
|
|
|
913
1143
|
"503": "ServiceUnavailable",
|
|
914
1144
|
"504": "GatewayTimeout"
|
|
915
1145
|
};
|
|
1146
|
+
function handleResponse(spec, operationName, status, response, utils, numbered) {
|
|
1147
|
+
const schemas = {};
|
|
1148
|
+
const imports = {};
|
|
1149
|
+
const endpointImports = {
|
|
1150
|
+
ParseError: {
|
|
1151
|
+
defaultImport: void 0,
|
|
1152
|
+
isTypeOnly: false,
|
|
1153
|
+
moduleSpecifier: utils.makeImport(`../http/parser`),
|
|
1154
|
+
namedImports: [{ isTypeOnly: false, name: "ParseError" }],
|
|
1155
|
+
namespaceImport: void 0
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
const responses = [];
|
|
1159
|
+
const outputs = [];
|
|
1160
|
+
const typeScriptDeserialzer = new TypeScriptDeserialzer(
|
|
1161
|
+
spec,
|
|
1162
|
+
(schemaName, zod) => {
|
|
1163
|
+
schemas[schemaName] = zod;
|
|
1164
|
+
imports[schemaName] = {
|
|
1165
|
+
defaultImport: void 0,
|
|
1166
|
+
isTypeOnly: true,
|
|
1167
|
+
moduleSpecifier: `../models/${utils.makeImport(schemaName)}`,
|
|
1168
|
+
namedImports: [{ isTypeOnly: true, name: schemaName }],
|
|
1169
|
+
namespaceImport: void 0
|
|
1170
|
+
};
|
|
1171
|
+
}
|
|
1172
|
+
);
|
|
1173
|
+
const statusCode = +status;
|
|
1174
|
+
const parser = (response.headers ?? {})["Transfer-Encoding"] ? "chunked" : "buffered";
|
|
1175
|
+
const statusName = statusCodeToResponseMap[status] || "APIResponse";
|
|
1176
|
+
const interfaceName = pascalcase(
|
|
1177
|
+
operationName + ` output${numbered ? status : ""}`
|
|
1178
|
+
);
|
|
1179
|
+
if (statusCode === 204) {
|
|
1180
|
+
outputs.push(statusName);
|
|
1181
|
+
} else {
|
|
1182
|
+
if (status.endsWith("XX")) {
|
|
1183
|
+
outputs.push(`APIError<${interfaceName}>`);
|
|
1184
|
+
} else {
|
|
1185
|
+
outputs.push(
|
|
1186
|
+
parser !== "buffered" ? `{type: ${statusName}<${interfaceName}>, parser: ${parser}}` : `${statusName}<${interfaceName}>`
|
|
1187
|
+
);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
const responseContent = get(response, ["content"]);
|
|
1191
|
+
const isJson = responseContent && responseContent["application/json"];
|
|
1192
|
+
const responseSchema = isJson ? typeScriptDeserialzer.handle(
|
|
1193
|
+
responseContent["application/json"].schema,
|
|
1194
|
+
true
|
|
1195
|
+
) : "void";
|
|
1196
|
+
responses.push({
|
|
1197
|
+
name: interfaceName,
|
|
1198
|
+
schema: responseSchema
|
|
1199
|
+
});
|
|
1200
|
+
const statusGroup = +status.slice(0, 1);
|
|
1201
|
+
if (statusCode >= 400 || statusGroup >= 4) {
|
|
1202
|
+
endpointImports[statusCodeToResponseMap[status] ?? "APIError"] = {
|
|
1203
|
+
moduleSpecifier: utils.makeImport("../http/response"),
|
|
1204
|
+
namedImports: [{ name: statusCodeToResponseMap[status] ?? "APIError" }]
|
|
1205
|
+
};
|
|
1206
|
+
endpointImports[interfaceName] = {
|
|
1207
|
+
isTypeOnly: true,
|
|
1208
|
+
moduleSpecifier: `../outputs/${utils.makeImport(spinalcase(operationName))}`,
|
|
1209
|
+
namedImports: [{ isTypeOnly: true, name: interfaceName }]
|
|
1210
|
+
};
|
|
1211
|
+
} 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
|
+
endpointImports[interfaceName] = {
|
|
1222
|
+
defaultImport: void 0,
|
|
1223
|
+
isTypeOnly: true,
|
|
1224
|
+
moduleSpecifier: `../outputs/${utils.makeImport(spinalcase(operationName))}`,
|
|
1225
|
+
namedImports: [{ isTypeOnly: true, name: interfaceName }],
|
|
1226
|
+
namespaceImport: void 0
|
|
1227
|
+
};
|
|
1228
|
+
}
|
|
1229
|
+
return { schemas, imports, endpointImports, responses, outputs };
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// packages/typescript/src/lib/styles/github/endpoints.txt
|
|
1233
|
+
var endpoints_default = "\ntype 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: EndpointOutput<K>;\n error: EndpointError<K> | ParseError<(typeof schemas)[K]['schema']>;\n };\n};";
|
|
1234
|
+
|
|
1235
|
+
// packages/typescript/src/lib/generator.ts
|
|
916
1236
|
function generateCode(config) {
|
|
917
|
-
const commonSchemas = {};
|
|
918
1237
|
const commonZod = /* @__PURE__ */ new Map();
|
|
919
1238
|
const commonZodImports = [];
|
|
920
1239
|
const zodDeserialzer = new ZodDeserialzer(config.spec, (model, schema) => {
|
|
@@ -929,14 +1248,15 @@ function generateCode(config) {
|
|
|
929
1248
|
});
|
|
930
1249
|
const groups = {};
|
|
931
1250
|
const outputs = {};
|
|
1251
|
+
const endpoints = {};
|
|
932
1252
|
forEachOperation(config, (entry, operation) => {
|
|
933
1253
|
console.log(`Processing ${entry.method} ${entry.path}`);
|
|
934
|
-
|
|
935
|
-
|
|
1254
|
+
groups[entry.groupName] ??= [];
|
|
1255
|
+
endpoints[entry.groupName] ??= [];
|
|
936
1256
|
const inputs = {};
|
|
937
1257
|
const additionalProperties = [];
|
|
938
1258
|
for (const param of operation.parameters ?? []) {
|
|
939
|
-
if (
|
|
1259
|
+
if (isRef5(param)) {
|
|
940
1260
|
throw new Error(`Found reference in parameter ${param.$ref}`);
|
|
941
1261
|
}
|
|
942
1262
|
if (!param.schema) {
|
|
@@ -964,24 +1284,40 @@ function generateCode(config) {
|
|
|
964
1284
|
})
|
|
965
1285
|
)
|
|
966
1286
|
);
|
|
967
|
-
const
|
|
1287
|
+
const schemas = {};
|
|
968
1288
|
const shortContenTypeMap = {
|
|
969
1289
|
"application/json": "json",
|
|
1290
|
+
"application/*+json": "json",
|
|
1291
|
+
// type specific of json like application/vnd.api+json (from the generation pov it shouldn't matter)
|
|
1292
|
+
"text/json": "json",
|
|
1293
|
+
// non standard - later standardized to application/json
|
|
970
1294
|
"application/x-www-form-urlencoded": "urlencoded",
|
|
971
1295
|
"multipart/form-data": "formdata",
|
|
972
1296
|
"application/xml": "xml",
|
|
973
1297
|
"text/plain": "text"
|
|
974
1298
|
};
|
|
975
1299
|
let outgoingContentType;
|
|
976
|
-
if (
|
|
977
|
-
const
|
|
978
|
-
for (const type in content) {
|
|
979
|
-
const ctSchema =
|
|
1300
|
+
if (!isEmpty(operation.requestBody)) {
|
|
1301
|
+
const requestBody = isRef5(operation.requestBody) ? followRef4(config.spec, operation.requestBody.$ref) : operation.requestBody;
|
|
1302
|
+
for (const type in requestBody.content) {
|
|
1303
|
+
const ctSchema = isRef5(requestBody.content[type].schema) ? followRef4(config.spec, requestBody.content[type].schema.$ref) : requestBody.content[type].schema;
|
|
980
1304
|
if (!ctSchema) {
|
|
981
|
-
console.warn(
|
|
1305
|
+
console.warn(
|
|
1306
|
+
`Schema not found for ${type} in ${entry.method} ${entry.path}`
|
|
1307
|
+
);
|
|
982
1308
|
continue;
|
|
983
1309
|
}
|
|
984
|
-
|
|
1310
|
+
let objectSchema = ctSchema;
|
|
1311
|
+
if (objectSchema.type !== "object") {
|
|
1312
|
+
objectSchema = {
|
|
1313
|
+
type: "object",
|
|
1314
|
+
required: [requestBody.required ? "$body" : ""],
|
|
1315
|
+
properties: {
|
|
1316
|
+
$body: ctSchema
|
|
1317
|
+
}
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
const schema = merge({}, objectSchema, {
|
|
985
1321
|
required: additionalProperties.filter((p) => p.required).map((p) => p.name),
|
|
986
1322
|
properties: additionalProperties.reduce(
|
|
987
1323
|
(acc, p) => ({
|
|
@@ -991,14 +1327,14 @@ function generateCode(config) {
|
|
|
991
1327
|
{}
|
|
992
1328
|
)
|
|
993
1329
|
});
|
|
994
|
-
Object.assign(inputs, bodyInputs(config,
|
|
995
|
-
|
|
1330
|
+
Object.assign(inputs, bodyInputs(config, objectSchema));
|
|
1331
|
+
schemas[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
|
|
996
1332
|
}
|
|
997
|
-
if (content["application/json"]) {
|
|
1333
|
+
if (requestBody.content["application/json"]) {
|
|
998
1334
|
outgoingContentType = "json";
|
|
999
|
-
} else if (content["application/x-www-form-urlencoded"]) {
|
|
1335
|
+
} else if (requestBody.content["application/x-www-form-urlencoded"]) {
|
|
1000
1336
|
outgoingContentType = "urlencoded";
|
|
1001
|
-
} else if (content["multipart/form-data"]) {
|
|
1337
|
+
} else if (requestBody.content["multipart/form-data"]) {
|
|
1002
1338
|
outgoingContentType = "formdata";
|
|
1003
1339
|
} else {
|
|
1004
1340
|
outgoingContentType = "json";
|
|
@@ -1011,7 +1347,7 @@ function generateCode(config) {
|
|
|
1011
1347
|
}),
|
|
1012
1348
|
{}
|
|
1013
1349
|
);
|
|
1014
|
-
|
|
1350
|
+
schemas[shortContenTypeMap["application/json"]] = zodDeserialzer.handle(
|
|
1015
1351
|
{
|
|
1016
1352
|
type: "object",
|
|
1017
1353
|
required: additionalProperties.filter((p) => p.required).map((p) => p.name),
|
|
@@ -1020,90 +1356,137 @@ function generateCode(config) {
|
|
|
1020
1356
|
true
|
|
1021
1357
|
);
|
|
1022
1358
|
}
|
|
1023
|
-
const
|
|
1024
|
-
|
|
1025
|
-
|
|
1359
|
+
const endpoint = toEndpoint(
|
|
1360
|
+
entry.groupName,
|
|
1361
|
+
config.spec,
|
|
1362
|
+
operation,
|
|
1363
|
+
{
|
|
1364
|
+
outgoingContentType,
|
|
1365
|
+
name: operation.operationId,
|
|
1366
|
+
type: "http",
|
|
1367
|
+
trigger: entry,
|
|
1368
|
+
schemas,
|
|
1369
|
+
inputs
|
|
1370
|
+
},
|
|
1371
|
+
{ makeImport: config.makeImport }
|
|
1372
|
+
);
|
|
1026
1373
|
const output = [`import z from 'zod';`];
|
|
1027
|
-
|
|
1028
|
-
const
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
operation.responses[status]
|
|
1033
|
-
) ? followRef(
|
|
1034
|
-
config.spec,
|
|
1035
|
-
operation.responses[status].$ref
|
|
1036
|
-
) : operation.responses[status];
|
|
1037
|
-
const statusCode = +status;
|
|
1038
|
-
if (statusCode >= 400) {
|
|
1039
|
-
errors.push(statusCdeToMessageMap[status] ?? "ProblematicResponse");
|
|
1040
|
-
}
|
|
1041
|
-
if (statusCode >= 200 && statusCode < 300) {
|
|
1042
|
-
foundResponse = true;
|
|
1043
|
-
const responseContent = get2(response, ["content"]);
|
|
1044
|
-
const isJson = responseContent && responseContent["application/json"];
|
|
1045
|
-
if ((response.headers ?? {})["Transfer-Encoding"]) {
|
|
1046
|
-
parser = "chunked";
|
|
1047
|
-
}
|
|
1048
|
-
const typeScriptDeserialzer = new TypeScriptDeserialzer(
|
|
1049
|
-
config.spec,
|
|
1050
|
-
(schemaName, zod) => {
|
|
1051
|
-
commonSchemas[schemaName] = zod;
|
|
1052
|
-
responsesImports[schemaName] = {
|
|
1053
|
-
defaultImport: void 0,
|
|
1054
|
-
isTypeOnly: true,
|
|
1055
|
-
moduleSpecifier: `../models/${config.makeImport(schemaName)}`,
|
|
1056
|
-
namedImports: [{ isTypeOnly: true, name: schemaName }],
|
|
1057
|
-
namespaceImport: void 0
|
|
1058
|
-
};
|
|
1059
|
-
}
|
|
1060
|
-
);
|
|
1061
|
-
const responseSchema = isJson ? typeScriptDeserialzer.handle(
|
|
1062
|
-
responseContent["application/json"].schema,
|
|
1063
|
-
true
|
|
1064
|
-
) : statusCode === 204 ? "void" : "ReadableStream";
|
|
1065
|
-
responses.push(responseSchema);
|
|
1066
|
-
}
|
|
1067
|
-
}
|
|
1068
|
-
if (responses.length > 1) {
|
|
1374
|
+
const responses = endpoint.responses.flatMap((it) => it.responses);
|
|
1375
|
+
const responsesImports = endpoint.responses.flatMap(
|
|
1376
|
+
(it) => Object.values(it.imports)
|
|
1377
|
+
);
|
|
1378
|
+
if (responses.length) {
|
|
1069
1379
|
output.push(
|
|
1070
|
-
`export type ${
|
|
1071
|
-
responses,
|
|
1072
|
-
(it) => it
|
|
1073
|
-
).join(" | ")};`
|
|
1380
|
+
...responses.map((it) => `export type ${it.name} = ${it.schema};`)
|
|
1074
1381
|
);
|
|
1075
1382
|
} else {
|
|
1076
1383
|
output.push(
|
|
1077
|
-
`export type ${
|
|
1384
|
+
`export type ${pascalcase2(operation.operationId + " output")} = void;`
|
|
1078
1385
|
);
|
|
1079
1386
|
}
|
|
1080
|
-
output.
|
|
1081
|
-
|
|
1082
|
-
);
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
}
|
|
1086
|
-
outputs[`${spinalcase2(entry.name)}.ts`] = output.join("\n");
|
|
1087
|
-
groups[groupName].push({
|
|
1088
|
-
name: entry.name,
|
|
1387
|
+
output.unshift(...useImports(output.join(""), ...responsesImports));
|
|
1388
|
+
outputs[`${spinalcase2(operation.operationId)}.ts`] = output.join("\n");
|
|
1389
|
+
endpoints[entry.groupName].push(endpoint);
|
|
1390
|
+
groups[entry.groupName].push({
|
|
1391
|
+
name: operation.operationId,
|
|
1089
1392
|
type: "http",
|
|
1090
1393
|
inputs,
|
|
1091
|
-
errors: errors.length ? errors : ["ServerError"],
|
|
1092
1394
|
outgoingContentType,
|
|
1093
|
-
schemas
|
|
1094
|
-
parser,
|
|
1095
|
-
formatOutput: () => ({
|
|
1096
|
-
import: pascalcase(entry.name + " output"),
|
|
1097
|
-
use: pascalcase(entry.name + " output")
|
|
1098
|
-
}),
|
|
1395
|
+
schemas,
|
|
1099
1396
|
trigger: entry
|
|
1100
1397
|
});
|
|
1101
1398
|
});
|
|
1102
|
-
|
|
1399
|
+
const commonSchemas = Object.values(endpoints).reduce(
|
|
1400
|
+
(acc, endpoint) => ({
|
|
1401
|
+
...acc,
|
|
1402
|
+
...endpoint.reduce(
|
|
1403
|
+
(acc2, { responses }) => ({
|
|
1404
|
+
...acc2,
|
|
1405
|
+
...responses.reduce(
|
|
1406
|
+
(acc3, it) => ({ ...acc3, ...it.schemas }),
|
|
1407
|
+
{}
|
|
1408
|
+
)
|
|
1409
|
+
}),
|
|
1410
|
+
{}
|
|
1411
|
+
)
|
|
1412
|
+
}),
|
|
1413
|
+
{}
|
|
1414
|
+
);
|
|
1415
|
+
const allSchemas = Object.keys(endpoints).map((it) => ({
|
|
1416
|
+
import: `import ${camelcase3(it)} from './${config.makeImport(spinalcase2(it))}';`,
|
|
1417
|
+
use: ` ...${camelcase3(it)}`
|
|
1418
|
+
}));
|
|
1419
|
+
const imports = [
|
|
1420
|
+
'import z from "zod";',
|
|
1421
|
+
`import type { ParseError } from '${config.makeImport("../http/parser")}';`,
|
|
1422
|
+
`import type { ServerError } from '${config.makeImport("../http/response")}';`,
|
|
1423
|
+
`import type { OutputType, Parser, Type } from '../http/send-request.ts';`
|
|
1424
|
+
];
|
|
1425
|
+
return {
|
|
1426
|
+
groups,
|
|
1427
|
+
commonSchemas,
|
|
1428
|
+
commonZod,
|
|
1429
|
+
outputs,
|
|
1430
|
+
endpoints: {
|
|
1431
|
+
[join("api", "endpoints.ts")]: `
|
|
1432
|
+
|
|
1433
|
+
|
|
1434
|
+
import type z from 'zod';
|
|
1435
|
+
import type { ParseError } from '${config.makeImport("../http/parser")}';
|
|
1436
|
+
import type { ProblematicResponse, SuccessfulResponse } from '${config.makeImport(
|
|
1437
|
+
"../http/response"
|
|
1438
|
+
)}';
|
|
1439
|
+
import type { OutputType, Parser, Type } from '${config.makeImport(
|
|
1440
|
+
"../http/send-request"
|
|
1441
|
+
)}';
|
|
1442
|
+
|
|
1443
|
+
import schemas from '${config.makeImport("./schemas")}';
|
|
1444
|
+
|
|
1445
|
+
${endpoints_default}`,
|
|
1446
|
+
[`${join("api", "schemas.ts")}`]: `${allSchemas.map((it) => it.import).join("\n")}
|
|
1447
|
+
|
|
1448
|
+
export default {
|
|
1449
|
+
${allSchemas.map((it) => it.use).join(",\n")}
|
|
1450
|
+
};
|
|
1451
|
+
|
|
1452
|
+
`.trim(),
|
|
1453
|
+
...Object.fromEntries(
|
|
1454
|
+
Object.entries(endpoints).map(([name, endpoint]) => {
|
|
1455
|
+
const imps = importsToString(
|
|
1456
|
+
...mergeImports(
|
|
1457
|
+
...endpoint.flatMap(
|
|
1458
|
+
(it) => it.responses.flatMap(
|
|
1459
|
+
(it2) => Object.values(it2.endpointImports)
|
|
1460
|
+
)
|
|
1461
|
+
)
|
|
1462
|
+
)
|
|
1463
|
+
);
|
|
1464
|
+
return [
|
|
1465
|
+
[
|
|
1466
|
+
join("api", `${spinalcase2(name)}.ts`),
|
|
1467
|
+
`${[
|
|
1468
|
+
...imps,
|
|
1469
|
+
// ...imports,
|
|
1470
|
+
`import z from 'zod';`,
|
|
1471
|
+
`import { toRequest, json, urlencoded, nobody, formdata, createUrl } from '${config.makeImport("../http/request")}';`,
|
|
1472
|
+
`import { chunked, buffered } from "${config.makeImport("../http/parse-response")}";`,
|
|
1473
|
+
`import * as ${camelcase3(name)} from '../inputs/${config.makeImport(spinalcase2(name))}';`
|
|
1474
|
+
].join(
|
|
1475
|
+
"\n"
|
|
1476
|
+
)}
|
|
1477
|
+
export default {
|
|
1478
|
+
${endpoint.flatMap((it) => it.schemas).join(",\n")}
|
|
1479
|
+
}`
|
|
1480
|
+
]
|
|
1481
|
+
];
|
|
1482
|
+
}).flat()
|
|
1483
|
+
)
|
|
1484
|
+
}
|
|
1485
|
+
};
|
|
1103
1486
|
}
|
|
1104
1487
|
function toProps(spec, schemaOrRef, aggregator = []) {
|
|
1105
|
-
if (
|
|
1106
|
-
const schema =
|
|
1488
|
+
if (isRef5(schemaOrRef)) {
|
|
1489
|
+
const schema = followRef4(spec, schemaOrRef.$ref);
|
|
1107
1490
|
return toProps(spec, schema, aggregator);
|
|
1108
1491
|
} else if (schemaOrRef.type === "object") {
|
|
1109
1492
|
for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
|
|
@@ -1148,22 +1531,22 @@ function bodyInputs(config, ctSchema) {
|
|
|
1148
1531
|
}
|
|
1149
1532
|
|
|
1150
1533
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1151
|
-
var interceptors_default = "
|
|
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.dir('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";
|
|
1152
1535
|
|
|
1153
1536
|
// packages/typescript/src/lib/http/parse-response.txt
|
|
1154
|
-
var parse_response_default = 'import { parse } from "fast-content-type-parse";\n\
|
|
1537
|
+
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';
|
|
1155
1538
|
|
|
1156
1539
|
// packages/typescript/src/lib/http/parser.txt
|
|
1157
|
-
var parser_default = "import { z } from 'zod';\n\nexport
|
|
1540
|
+
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";
|
|
1158
1541
|
|
|
1159
1542
|
// packages/typescript/src/lib/http/request.txt
|
|
1160
|
-
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';\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 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 }\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 }\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";
|
|
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";
|
|
1161
1544
|
|
|
1162
1545
|
// packages/typescript/src/lib/http/response.txt
|
|
1163
|
-
var response_default = "export
|
|
1546
|
+
var response_default = "export class APIResponse<Body = unknown, Status extends number = number> {\n static status: number;\n status: Status;\n data: Body;\n\n constructor(status: Status, data: Body) {\n this.status = status;\n this.data = data;\n }\n\n static create<Body = unknown>(status: number, data: Body) {\n return new this(status, data);\n }\n}\n\nexport class APIError<Body, Status extends number = number> extends APIResponse<\n Body,\n Status\n> {\n static override create<T>(status: number, data: T) {\n return new this(status, data);\n }\n}\n\n// 2xx Success\nexport class Ok<T> extends APIResponse<T, 200> {\n static override status = 200 as const;\n constructor(data: T) {\n super(Ok.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class Created<T> extends APIResponse<T, 201> {\n static override status = 201 as const;\n constructor(data: T) {\n super(Created.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class Accepted<T> extends APIResponse<T, 202> {\n static override status = 202 as const;\n constructor(data: T) {\n super(Accepted.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class NoContent extends APIResponse<never, 204> {\n static override status = 204 as const;\n constructor() {\n super(NoContent.status, null as never);\n }\n static override create(status: number, data: never): NoContent {\n return new this();\n }\n}\n\n// 4xx Client Errors\nexport class BadRequest<T> extends APIError<T, 400> {\n static override status = 400 as const;\n constructor(data: T) {\n super(BadRequest.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class Unauthorized<T = { message: string }> extends APIError<T, 401> {\n static override status = 401 as const;\n constructor(data: T) {\n super(Unauthorized.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class PaymentRequired<T = { message: string }> extends APIError<T, 402> {\n static override status = 402 as const;\n constructor(data: T) {\n super(PaymentRequired.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class Forbidden<T = { message: string }> extends APIError<T, 403> {\n static override status = 403 as const;\n constructor(data: T) {\n super(Forbidden.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class NotFound<T = { message: string }> extends APIError<T, 404> {\n static override status = 404 as const;\n constructor(data: T) {\n super(NotFound.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class MethodNotAllowed<T = { message: string }> extends APIError<\n T,\n 405\n> {\n static override status = 405 as const;\n constructor(data: T) {\n super(MethodNotAllowed.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class NotAcceptable<T = { message: string }> extends APIError<T, 406> {\n static override status = 406 as const;\n constructor(data: T) {\n super(NotAcceptable.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class Conflict<T = { message: string }> extends APIError<T, 409> {\n static override status = 409 as const;\n constructor(data: T) {\n super(Conflict.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class Gone<T = { message: string }> extends APIError<T, 410> {\n static override status = 410 as const;\n constructor(data: T) {\n super(Gone.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class UnprocessableEntity<\n T = { message: string; errors?: Record<string, string[]> },\n> extends APIError<T, 422> {\n static override status = 422 as const;\n constructor(data: T) {\n super(UnprocessableEntity.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class TooManyRequests<\n T = { message: string; retryAfter?: string },\n> extends APIError<T, 429> {\n static override status = 429 as const;\n constructor(data: T) {\n super(TooManyRequests.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class PayloadTooLarge<T = { message: string }> extends APIError<T, 413> {\n static override status = 413 as const;\n constructor(data: T) {\n super(PayloadTooLarge.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class UnsupportedMediaType<T = { message: string }> extends APIError<\n T,\n 415\n> {\n static override status = 415 as const;\n constructor(data: T) {\n super(UnsupportedMediaType.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\n\n// 5xx Server Errors\nexport class InternalServerError<T = { message: string }> extends APIError<\n T,\n 500\n> {\n static override status = 500 as const;\n constructor(data: T) {\n super(InternalServerError.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class NotImplemented<T = { message: string }> extends APIError<T, 501> {\n static override status = 501 as const;\n constructor(data: T) {\n super(NotImplemented.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class BadGateway<T = { message: string }> extends APIError<T, 502> {\n static override status = 502 as const;\n constructor(data: T) {\n super(BadGateway.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class ServiceUnavailable<\n T = { message: string; retryAfter?: string },\n> extends APIError<T, 503> {\n static override status = 503 as const;\n constructor(data: T) {\n super(ServiceUnavailable.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\nexport class GatewayTimeout<T = { message: string }> extends APIError<T, 504> {\n static override status = 504 as const;\n constructor(data: T) {\n super(GatewayTimeout.status, data);\n }\n static override create<T>(status: number, data: T) {\n return new this(data);\n }\n}\n\nexport type ClientError =\n | BadRequest<{ message: string }>\n | Unauthorized<unknown>\n | PaymentRequired<unknown>\n | Forbidden<unknown>\n | NotFound<unknown>\n | MethodNotAllowed<unknown>\n | NotAcceptable<unknown>\n | Conflict<unknown>\n | Gone<unknown>\n | UnprocessableEntity<unknown>\n | TooManyRequests<unknown>;\n\nexport type ServerError =\n | InternalServerError<unknown>\n | NotImplemented<unknown>\n | BadGateway<unknown>\n | ServiceUnavailable<unknown>\n | GatewayTimeout<unknown>;\n\nexport type ProblematicResponse = ClientError | ServerError;\n\nexport type SuccessfulResponse = Ok<unknown> | Created<unknown> | Accepted<unknown> | NoContent;";
|
|
1164
1547
|
|
|
1165
1548
|
// packages/typescript/src/lib/http/send-request.txt
|
|
1166
|
-
var send_request_default = "\nexport interface RequestSchema {\n schema: z.ZodType;\n toRequest: (input: any) => RequestConfig;\n
|
|
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 sendRequest(\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 return [null as never, parseError as never] as const;\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 data = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n return [data as never, null] as const;\n }\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\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";
|
|
1167
1550
|
|
|
1168
1551
|
// packages/typescript/src/lib/generate.ts
|
|
1169
1552
|
function security(spec) {
|
|
@@ -1191,21 +1574,16 @@ async function generate(spec, settings) {
|
|
|
1191
1574
|
const makeImport = (moduleSpecifier) => {
|
|
1192
1575
|
return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
|
|
1193
1576
|
};
|
|
1194
|
-
const { commonSchemas, groups, outputs, commonZod } = generateCode(
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1577
|
+
const { commonSchemas, endpoints, groups, outputs, commonZod } = generateCode(
|
|
1578
|
+
{
|
|
1579
|
+
spec,
|
|
1580
|
+
style: "github",
|
|
1581
|
+
makeImport
|
|
1582
|
+
}
|
|
1583
|
+
);
|
|
1584
|
+
const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
|
|
1200
1585
|
const options = security(spec);
|
|
1201
1586
|
const clientName = settings.name || "Client";
|
|
1202
|
-
const clientFiles = generateSDK({
|
|
1203
|
-
name: clientName,
|
|
1204
|
-
operations: groups,
|
|
1205
|
-
servers: spec.servers?.map((server) => server.url) || [],
|
|
1206
|
-
options,
|
|
1207
|
-
makeImport
|
|
1208
|
-
});
|
|
1209
1587
|
const inputFiles = generateInputs(groups, commonZod, makeImport);
|
|
1210
1588
|
await writeFiles(output, {
|
|
1211
1589
|
"outputs/.gitkeep": "",
|
|
@@ -1213,24 +1591,34 @@ async function generate(spec, settings) {
|
|
|
1213
1591
|
"models/.getkeep": ""
|
|
1214
1592
|
// 'README.md': readme,
|
|
1215
1593
|
});
|
|
1216
|
-
await writeFiles(
|
|
1217
|
-
"interceptors.ts":
|
|
1594
|
+
await writeFiles(join2(output, "http"), {
|
|
1595
|
+
"interceptors.ts": `
|
|
1596
|
+
import { type RequestConfig } from './${makeImport("request")}';
|
|
1597
|
+
${interceptors_default}`,
|
|
1218
1598
|
"parse-response.ts": parse_response_default,
|
|
1219
1599
|
"send-request.ts": `import z from 'zod';
|
|
1220
1600
|
import type { Interceptor } from './${makeImport("interceptors")}';
|
|
1221
|
-
import {
|
|
1222
|
-
import {
|
|
1223
|
-
import type { RequestConfig } from '
|
|
1601
|
+
import { buffered } from './${makeImport("parse-response")}';
|
|
1602
|
+
import { parseInput } from './${makeImport("parser")}';
|
|
1603
|
+
import type { RequestConfig } from './${makeImport("request")}';
|
|
1604
|
+
import { APIError, APIResponse } from './${makeImport("response")}';
|
|
1605
|
+
|
|
1224
1606
|
${send_request_default}`,
|
|
1225
1607
|
"response.ts": response_default,
|
|
1226
1608
|
"parser.ts": parser_default,
|
|
1227
1609
|
"request.ts": request_default
|
|
1228
1610
|
});
|
|
1229
|
-
await writeFiles(
|
|
1611
|
+
await writeFiles(join2(output, "outputs"), outputs);
|
|
1230
1612
|
const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
|
|
1231
1613
|
await writeFiles(output, {
|
|
1232
|
-
|
|
1614
|
+
"client.ts": client_default({
|
|
1615
|
+
name: clientName,
|
|
1616
|
+
servers: (spec.servers ?? []).map((server) => server.url) || [],
|
|
1617
|
+
options,
|
|
1618
|
+
makeImport
|
|
1619
|
+
}),
|
|
1233
1620
|
...inputFiles,
|
|
1621
|
+
...endpoints,
|
|
1234
1622
|
...Object.fromEntries(
|
|
1235
1623
|
Object.entries(commonSchemas).map(([name, schema]) => [
|
|
1236
1624
|
`models/${name}.ts`,
|
|
@@ -1245,29 +1633,36 @@ ${send_request_default}`,
|
|
|
1245
1633
|
)
|
|
1246
1634
|
});
|
|
1247
1635
|
const folders = [
|
|
1248
|
-
getFolderExports(
|
|
1636
|
+
getFolderExports(join2(output, "outputs"), settings.useTsExtension),
|
|
1249
1637
|
getFolderExports(
|
|
1250
|
-
|
|
1638
|
+
join2(output, "inputs"),
|
|
1251
1639
|
settings.useTsExtension,
|
|
1252
1640
|
["ts"],
|
|
1253
|
-
(dirent) => dirent.isDirectory() && dirent.name
|
|
1641
|
+
(dirent) => dirent.isDirectory() && ["schemas"].includes(dirent.name)
|
|
1254
1642
|
),
|
|
1255
|
-
getFolderExports(
|
|
1643
|
+
getFolderExports(join2(output, "api"), settings.useTsExtension),
|
|
1644
|
+
getFolderExports(
|
|
1645
|
+
join2(output, "http"),
|
|
1646
|
+
settings.useTsExtension,
|
|
1647
|
+
["ts"],
|
|
1648
|
+
(dirent) => dirent.name !== "response.ts"
|
|
1649
|
+
)
|
|
1256
1650
|
];
|
|
1257
1651
|
if (modelsImports.length) {
|
|
1258
1652
|
folders.push(
|
|
1259
|
-
getFolderExports(
|
|
1653
|
+
getFolderExports(join2(output, "models"), settings.useTsExtension)
|
|
1260
1654
|
);
|
|
1261
1655
|
}
|
|
1262
|
-
const [outputIndex, inputsIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|
|
1656
|
+
const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|
|
1263
1657
|
await writeFiles(output, {
|
|
1658
|
+
"api/index.ts": apiIndex,
|
|
1264
1659
|
"outputs/index.ts": outputIndex,
|
|
1265
1660
|
"inputs/index.ts": inputsIndex || null,
|
|
1266
1661
|
"http/index.ts": httpIndex,
|
|
1267
1662
|
...modelsImports.length ? { "models/index.ts": modelsIndex } : {}
|
|
1268
1663
|
});
|
|
1269
1664
|
await writeFiles(output, {
|
|
1270
|
-
"index.ts": await getFolderExports(output, settings.useTsExtension)
|
|
1665
|
+
"index.ts": await getFolderExports(output, settings.useTsExtension, ["ts"])
|
|
1271
1666
|
});
|
|
1272
1667
|
if (settings.mode === "full") {
|
|
1273
1668
|
await writeFiles(settings.output, {
|