@sdk-it/typescript 0.20.0 → 0.22.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/README.md +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1086 -1136
- package/dist/index.js.map +4 -4
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/emitters/interface.d.ts +4 -18
- package/dist/lib/emitters/interface.d.ts.map +1 -1
- package/dist/lib/emitters/snippet.d.ts +24 -0
- package/dist/lib/emitters/snippet.d.ts.map +1 -0
- package/dist/lib/emitters/zod.d.ts +1 -8
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts +5 -18
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts +5 -8
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/options.d.ts +27 -0
- package/dist/lib/options.d.ts.map +1 -0
- package/dist/lib/sdk.d.ts +8 -6
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/status-map.d.ts +3 -0
- package/dist/lib/status-map.d.ts.map +1 -0
- package/dist/lib/statusMap.d.ts +2 -0
- package/dist/lib/statusMap.d.ts.map +1 -0
- package/dist/lib/style.d.ts +2 -1
- package/dist/lib/style.d.ts.map +1 -1
- package/dist/lib/typescript-snippet.d.ts +20 -0
- package/dist/lib/typescript-snippet.d.ts.map +1 -0
- package/dist/lib/utils.d.ts +2 -2
- package/dist/lib/utils.d.ts.map +1 -1
- package/package.json +4 -4
- package/dist/lib/readme.d.ts +0 -19
- package/dist/lib/readme.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
// packages/typescript/src/lib/generate.ts
|
|
2
2
|
import { template as template2 } from "lodash-es";
|
|
3
|
+
import { readdir } from "node:fs/promises";
|
|
3
4
|
import { join as join2 } from "node:path";
|
|
4
5
|
import { npmRunPathEnv } from "npm-run-path";
|
|
5
|
-
import { spinalcase as spinalcase3 } from "stringcase";
|
|
6
|
-
import { methods, pascalcase as
|
|
6
|
+
import { camelcase as camelcase4, spinalcase as spinalcase3 } from "stringcase";
|
|
7
|
+
import { methods, pascalcase as pascalcase5, toLitObject as toLitObject2 } from "@sdk-it/core";
|
|
7
8
|
import {
|
|
9
|
+
createWriterProxy,
|
|
8
10
|
getFolderExports,
|
|
9
11
|
writeFiles
|
|
10
12
|
} from "@sdk-it/core/file-system.js";
|
|
13
|
+
import { toReadme } from "@sdk-it/readme";
|
|
14
|
+
import {
|
|
15
|
+
augmentSpec,
|
|
16
|
+
cleanFiles,
|
|
17
|
+
readWriteMetadata,
|
|
18
|
+
sanitizeTag as sanitizeTag4
|
|
19
|
+
} from "@sdk-it/spec";
|
|
11
20
|
|
|
12
21
|
// packages/typescript/src/lib/client.ts
|
|
13
22
|
import { toLitObject } from "@sdk-it/core";
|
|
@@ -32,11 +41,9 @@ var client_default = (spec, style) => {
|
|
|
32
41
|
schema: spec.servers.length ? `z.enum(servers).default(servers[0])` : "z.string()"
|
|
33
42
|
}
|
|
34
43
|
};
|
|
35
|
-
return `
|
|
44
|
+
return `import z from 'zod';
|
|
36
45
|
import type { HeadersInit, RequestConfig } from './http/${spec.makeImport("request")}';
|
|
37
|
-
import { fetchType,
|
|
38
|
-
import z from 'zod';
|
|
39
|
-
import type { Endpoints } from './api/${spec.makeImport("endpoints")}';
|
|
46
|
+
import { fetchType, parse } from './http/${spec.makeImport("dispatcher")}';
|
|
40
47
|
import schemas from './api/${spec.makeImport("schemas")}';
|
|
41
48
|
import {
|
|
42
49
|
createBaseUrlInterceptor,
|
|
@@ -57,13 +64,18 @@ export class ${spec.name} {
|
|
|
57
64
|
this.options = optionsSchema.parse(options);
|
|
58
65
|
}
|
|
59
66
|
|
|
60
|
-
async request<E extends keyof
|
|
67
|
+
async request<const E extends keyof typeof schemas>(
|
|
61
68
|
endpoint: E,
|
|
62
|
-
input:
|
|
69
|
+
input: z.infer<(typeof schemas)[E]['schema']>,
|
|
63
70
|
options?: { signal?: AbortSignal, headers?: HeadersInit },
|
|
64
|
-
) ${style.errorAsValue ? `: Promise<
|
|
71
|
+
) ${style.errorAsValue ? `: Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>| [never, ParseError<(typeof schemas)[E]['schema']>]>` : `: Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>>`} {
|
|
65
72
|
const route = schemas[endpoint];
|
|
66
|
-
const
|
|
73
|
+
const withDefaultInputs = Object.assign({}, this.#defaultInputs, input);
|
|
74
|
+
const [parsedInput, parseError] = parseInput(route.schema, withDefaultInputs);
|
|
75
|
+
if (parseError) {
|
|
76
|
+
${style.errorAsValue ? "return [null as never, parseError as never] as const;" : "throw parseError;"}
|
|
77
|
+
}
|
|
78
|
+
const result = await route.dispatch(parsedInput as never, {
|
|
67
79
|
fetch: this.options.fetch,
|
|
68
80
|
interceptors: [
|
|
69
81
|
createHeadersInterceptor(() => this.defaultHeaders, options?.headers ?? {}),
|
|
@@ -71,12 +83,12 @@ export class ${spec.name} {
|
|
|
71
83
|
],
|
|
72
84
|
signal: options?.signal,
|
|
73
85
|
});
|
|
74
|
-
return
|
|
86
|
+
return result as Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>;
|
|
75
87
|
}
|
|
76
88
|
|
|
77
|
-
async prepare<E extends keyof
|
|
89
|
+
async prepare<const E extends keyof typeof schemas>(
|
|
78
90
|
endpoint: E,
|
|
79
|
-
input:
|
|
91
|
+
input: z.infer<(typeof schemas)[E]['schema']>,
|
|
80
92
|
options?: { headers?: HeadersInit },
|
|
81
93
|
): ${style.errorAsValue ? `Promise<
|
|
82
94
|
readonly [
|
|
@@ -108,8 +120,8 @@ export class ${spec.name} {
|
|
|
108
120
|
config = await interceptor.before(config);
|
|
109
121
|
}
|
|
110
122
|
}
|
|
111
|
-
const prepared = { ...config, parse: (response: Response) => parse(route, response) };
|
|
112
|
-
return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared"}
|
|
123
|
+
const prepared = { ...config, parse: (response: Response) => parse(route.output, response) as never };
|
|
124
|
+
return ${style.errorAsValue ? "[prepared, null as never] as const;" : "prepared as any"}
|
|
113
125
|
}
|
|
114
126
|
|
|
115
127
|
get defaultHeaders() {
|
|
@@ -132,258 +144,185 @@ export class ${spec.name} {
|
|
|
132
144
|
}`;
|
|
133
145
|
};
|
|
134
146
|
|
|
135
|
-
// packages/typescript/src/lib/
|
|
136
|
-
import {
|
|
137
|
-
import {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
import { camelcase } from "stringcase";
|
|
143
|
-
import { followRef, isRef } from "@sdk-it/core";
|
|
144
|
-
var defaults = {
|
|
145
|
-
operationId: (operation, path, method) => {
|
|
146
|
-
if (operation.operationId) {
|
|
147
|
-
return camelcase(operation.operationId);
|
|
148
|
-
}
|
|
149
|
-
const metadata = operation["x-oaiMeta"];
|
|
150
|
-
if (metadata && metadata.name) {
|
|
151
|
-
return camelcase(metadata.name);
|
|
152
|
-
}
|
|
153
|
-
return camelcase(
|
|
154
|
-
[method, ...path.replace(/[\\/\\{\\}]/g, " ").split(" ")].filter(Boolean).join(" ").trim()
|
|
155
|
-
);
|
|
156
|
-
},
|
|
157
|
-
tag: (operation, path) => {
|
|
158
|
-
return operation.tags?.[0] ? sanitizeTag(operation.tags?.[0]) : determineGenericTag(path, operation);
|
|
147
|
+
// packages/typescript/src/lib/emitters/interface.ts
|
|
148
|
+
import { followRef, isRef, parseRef, pascalcase } from "@sdk-it/core";
|
|
149
|
+
import { isPrimitiveSchema, sanitizeTag } from "@sdk-it/spec";
|
|
150
|
+
var TypeScriptEmitter = class {
|
|
151
|
+
#spec;
|
|
152
|
+
constructor(spec) {
|
|
153
|
+
this.#spec = spec;
|
|
159
154
|
}
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const formatTag = config.tag ?? defaults.tag;
|
|
178
|
-
const operationName = formatOperationId(operation, fixedPath, method);
|
|
179
|
-
const operationTag = formatTag(operation, fixedPath);
|
|
180
|
-
const metadata = operation["x-oaiMeta"] ?? {};
|
|
181
|
-
result.push(
|
|
182
|
-
callback(
|
|
183
|
-
{
|
|
184
|
-
name: metadata.name,
|
|
185
|
-
method,
|
|
186
|
-
path: fixedPath,
|
|
187
|
-
groupName: operationTag,
|
|
188
|
-
tag: operationTag
|
|
189
|
-
},
|
|
190
|
-
{
|
|
191
|
-
...operation,
|
|
192
|
-
parameters: [...parameters, ...operation.parameters ?? []],
|
|
193
|
-
operationId: operationName,
|
|
194
|
-
responses: resolveResponses(config.spec, operation)
|
|
195
|
-
}
|
|
196
|
-
)
|
|
197
|
-
);
|
|
155
|
+
#stringifyKey = (value) => {
|
|
156
|
+
return `'${value}'`;
|
|
157
|
+
};
|
|
158
|
+
object(schema, required = false) {
|
|
159
|
+
const properties = schema.properties || {};
|
|
160
|
+
const propEntries = Object.entries(properties).map(([key, propSchema]) => {
|
|
161
|
+
const isRequired = (schema.required ?? []).includes(key);
|
|
162
|
+
const tsType = this.handle(propSchema, isRequired);
|
|
163
|
+
return `${this.#stringifyKey(key)}: ${tsType}`;
|
|
164
|
+
});
|
|
165
|
+
if (schema.additionalProperties) {
|
|
166
|
+
if (typeof schema.additionalProperties === "object") {
|
|
167
|
+
const indexType = this.handle(schema.additionalProperties, true);
|
|
168
|
+
propEntries.push(`[key: string]: ${indexType}`);
|
|
169
|
+
} else if (schema.additionalProperties === true) {
|
|
170
|
+
propEntries.push("[key: string]: any");
|
|
171
|
+
}
|
|
198
172
|
}
|
|
173
|
+
return `${propEntries.length ? `{ ${propEntries.join("; ")} }` : "unknown"}`;
|
|
199
174
|
}
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
"catch",
|
|
208
|
-
"class",
|
|
209
|
-
"const",
|
|
210
|
-
"continue",
|
|
211
|
-
"debugger",
|
|
212
|
-
"default",
|
|
213
|
-
"delete",
|
|
214
|
-
"do",
|
|
215
|
-
"else",
|
|
216
|
-
"enum",
|
|
217
|
-
"export",
|
|
218
|
-
"extends",
|
|
219
|
-
"false",
|
|
220
|
-
"finally",
|
|
221
|
-
"for",
|
|
222
|
-
"function",
|
|
223
|
-
"if",
|
|
224
|
-
"implements",
|
|
225
|
-
// Strict mode
|
|
226
|
-
"import",
|
|
227
|
-
"in",
|
|
228
|
-
"instanceof",
|
|
229
|
-
"interface",
|
|
230
|
-
// Strict mode
|
|
231
|
-
"let",
|
|
232
|
-
// Strict mode
|
|
233
|
-
"new",
|
|
234
|
-
"null",
|
|
235
|
-
"package",
|
|
236
|
-
// Strict mode
|
|
237
|
-
"private",
|
|
238
|
-
// Strict mode
|
|
239
|
-
"protected",
|
|
240
|
-
// Strict mode
|
|
241
|
-
"public",
|
|
242
|
-
// Strict mode
|
|
243
|
-
"return",
|
|
244
|
-
"static",
|
|
245
|
-
// Strict mode
|
|
246
|
-
"super",
|
|
247
|
-
"switch",
|
|
248
|
-
"this",
|
|
249
|
-
"throw",
|
|
250
|
-
"true",
|
|
251
|
-
"try",
|
|
252
|
-
"typeof",
|
|
253
|
-
"var",
|
|
254
|
-
"void",
|
|
255
|
-
"while",
|
|
256
|
-
"with",
|
|
257
|
-
"yield",
|
|
258
|
-
// Strict mode / Generator functions
|
|
259
|
-
// 'arguments' is not technically a reserved word, but it's a special identifier within functions
|
|
260
|
-
// and assigning to it or declaring it can cause issues or unexpected behavior.
|
|
261
|
-
"arguments"
|
|
262
|
-
]);
|
|
263
|
-
function sanitizeTag(camelCasedTag) {
|
|
264
|
-
if (/^\d/.test(camelCasedTag)) {
|
|
265
|
-
return `_${camelCasedTag}`;
|
|
266
|
-
}
|
|
267
|
-
return reservedKeywords.has(camelcase(camelCasedTag)) ? `${camelCasedTag}_` : camelCasedTag;
|
|
268
|
-
}
|
|
269
|
-
function determineGenericTag(pathString, operation) {
|
|
270
|
-
const operationId = operation.operationId || "";
|
|
271
|
-
const VERSION_REGEX = /^[vV]\d+$/;
|
|
272
|
-
const commonVerbs = /* @__PURE__ */ new Set([
|
|
273
|
-
// Verbs to potentially strip from operationId prefix
|
|
274
|
-
"get",
|
|
275
|
-
"list",
|
|
276
|
-
"create",
|
|
277
|
-
"update",
|
|
278
|
-
"delete",
|
|
279
|
-
"post",
|
|
280
|
-
"put",
|
|
281
|
-
"patch",
|
|
282
|
-
"do",
|
|
283
|
-
"send",
|
|
284
|
-
"add",
|
|
285
|
-
"remove",
|
|
286
|
-
"set",
|
|
287
|
-
"find",
|
|
288
|
-
"search",
|
|
289
|
-
"check",
|
|
290
|
-
"make"
|
|
291
|
-
]);
|
|
292
|
-
const segments = pathString.split("/").filter(Boolean);
|
|
293
|
-
const potentialCandidates = segments.filter(
|
|
294
|
-
(segment) => segment && !segment.startsWith("{") && !segment.endsWith("}") && !VERSION_REGEX.test(segment)
|
|
295
|
-
);
|
|
296
|
-
for (let i = potentialCandidates.length - 1; i >= 0; i--) {
|
|
297
|
-
const segment = potentialCandidates[i];
|
|
298
|
-
if (!segment.startsWith("@")) {
|
|
299
|
-
return sanitizeTag(camelcase(segment));
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
const canFallbackToPathSegment = potentialCandidates.length > 0;
|
|
303
|
-
if (operationId) {
|
|
304
|
-
const lowerOpId = operationId.toLowerCase();
|
|
305
|
-
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]+/);
|
|
306
|
-
const validParts = parts.filter(Boolean);
|
|
307
|
-
if (commonVerbs.has(lowerOpId) && validParts.length === 1 && canFallbackToPathSegment) {
|
|
308
|
-
} else if (validParts.length > 0) {
|
|
309
|
-
const firstPart = validParts[0];
|
|
310
|
-
const isFirstPartVerb = commonVerbs.has(firstPart);
|
|
311
|
-
if (isFirstPartVerb && validParts.length > 1) {
|
|
312
|
-
const verbPrefixLength = firstPart.length;
|
|
313
|
-
let nextPartStartIndex = -1;
|
|
314
|
-
if (operationId.length > verbPrefixLength) {
|
|
315
|
-
const charAfterPrefix = operationId[verbPrefixLength];
|
|
316
|
-
if (charAfterPrefix >= "A" && charAfterPrefix <= "Z") {
|
|
317
|
-
nextPartStartIndex = verbPrefixLength;
|
|
318
|
-
} else if (charAfterPrefix >= "0" && charAfterPrefix <= "9") {
|
|
319
|
-
nextPartStartIndex = verbPrefixLength;
|
|
320
|
-
} else if (["_", "-"].includes(charAfterPrefix)) {
|
|
321
|
-
nextPartStartIndex = verbPrefixLength + 1;
|
|
322
|
-
} else {
|
|
323
|
-
const match = operationId.substring(verbPrefixLength).match(/[A-Z0-9]/);
|
|
324
|
-
if (match && match.index !== void 0) {
|
|
325
|
-
nextPartStartIndex = verbPrefixLength + match.index;
|
|
326
|
-
}
|
|
327
|
-
if (nextPartStartIndex === -1 && operationId.length > verbPrefixLength) {
|
|
328
|
-
nextPartStartIndex = verbPrefixLength;
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
if (nextPartStartIndex !== -1 && nextPartStartIndex < operationId.length) {
|
|
333
|
-
const remainingOriginalSubstring = operationId.substring(nextPartStartIndex);
|
|
334
|
-
const potentialTag = camelcase(remainingOriginalSubstring);
|
|
335
|
-
if (potentialTag) {
|
|
336
|
-
return sanitizeTag(potentialTag);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
const potentialTagJoined = camelcase(validParts.slice(1).join("_"));
|
|
340
|
-
if (potentialTagJoined) {
|
|
341
|
-
return sanitizeTag(potentialTagJoined);
|
|
342
|
-
}
|
|
343
|
-
}
|
|
344
|
-
const potentialTagFull = camelcase(operationId);
|
|
345
|
-
if (potentialTagFull) {
|
|
346
|
-
const isResultSingleVerb = validParts.length === 1 && isFirstPartVerb;
|
|
347
|
-
if (!(isResultSingleVerb && canFallbackToPathSegment)) {
|
|
348
|
-
if (potentialTagFull.length > 0) {
|
|
349
|
-
return sanitizeTag(potentialTagFull);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
const firstPartCamel = camelcase(firstPart);
|
|
354
|
-
if (firstPartCamel) {
|
|
355
|
-
const isFirstPartCamelVerb = commonVerbs.has(firstPartCamel);
|
|
356
|
-
if (!isFirstPartCamelVerb || validParts.length === 1 || !canFallbackToPathSegment) {
|
|
357
|
-
return sanitizeTag(firstPartCamel);
|
|
358
|
-
}
|
|
359
|
-
}
|
|
360
|
-
if (isFirstPartVerb && validParts.length > 1 && validParts[1] && canFallbackToPathSegment) {
|
|
361
|
-
const secondPartCamel = camelcase(validParts[1]);
|
|
362
|
-
if (secondPartCamel) {
|
|
363
|
-
return sanitizeTag(secondPartCamel);
|
|
364
|
-
}
|
|
365
|
-
}
|
|
175
|
+
/**
|
|
176
|
+
* Handle arrays (items could be a single schema or a tuple)
|
|
177
|
+
*/
|
|
178
|
+
#array(schema, required = false) {
|
|
179
|
+
const { items } = schema;
|
|
180
|
+
if (!items) {
|
|
181
|
+
return "any[]";
|
|
366
182
|
}
|
|
183
|
+
if (Array.isArray(items)) {
|
|
184
|
+
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
185
|
+
return `[${tupleItems.join(", ")}]`;
|
|
186
|
+
}
|
|
187
|
+
const itemsType = this.handle(items, true);
|
|
188
|
+
return itemsType.length > 1 ? `(${itemsType})[]` : `${itemsType}[]`;
|
|
367
189
|
}
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
|
|
192
|
+
*/
|
|
193
|
+
normal(type, schema, required = false) {
|
|
194
|
+
switch (type) {
|
|
195
|
+
case "string":
|
|
196
|
+
return this.string(schema, required);
|
|
197
|
+
case "number":
|
|
198
|
+
case "integer":
|
|
199
|
+
return this.number(schema, required);
|
|
200
|
+
case "boolean":
|
|
201
|
+
return appendOptional("boolean", required);
|
|
202
|
+
case "object":
|
|
203
|
+
return this.object(schema, required);
|
|
204
|
+
case "array":
|
|
205
|
+
return this.#array(schema, required);
|
|
206
|
+
case "null":
|
|
207
|
+
return "null";
|
|
208
|
+
default:
|
|
209
|
+
console.warn(`Unknown type: ${type}`);
|
|
210
|
+
return appendOptional("any", required);
|
|
372
211
|
}
|
|
373
|
-
|
|
374
|
-
|
|
212
|
+
}
|
|
213
|
+
#ref($ref, required) {
|
|
214
|
+
const schemaName = pascalcase(sanitizeTag(parseRef($ref).model));
|
|
215
|
+
const schema = followRef(this.#spec, $ref);
|
|
216
|
+
if (isPrimitiveSchema(schema)) {
|
|
217
|
+
return this.handle(schema, required);
|
|
375
218
|
}
|
|
219
|
+
return `models.${appendOptional(schemaName, required)}`;
|
|
376
220
|
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
221
|
+
allOf(schemas) {
|
|
222
|
+
const allOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
223
|
+
return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
|
|
224
|
+
}
|
|
225
|
+
oneOf(schemas, required) {
|
|
226
|
+
const oneOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
227
|
+
return appendOptional(
|
|
228
|
+
oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
|
|
229
|
+
required
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
anyOf(schemas, required) {
|
|
233
|
+
return this.oneOf(schemas, required);
|
|
234
|
+
}
|
|
235
|
+
enum(values, required) {
|
|
236
|
+
const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
|
|
237
|
+
return appendOptional(enumValues, required);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Handle string type with formats
|
|
241
|
+
*/
|
|
242
|
+
string(schema, required) {
|
|
243
|
+
let type;
|
|
244
|
+
if (schema.contentEncoding === "binary") {
|
|
245
|
+
return appendOptional("Blob", required);
|
|
246
|
+
}
|
|
247
|
+
switch (schema.format) {
|
|
248
|
+
case "date-time":
|
|
249
|
+
case "datetime":
|
|
250
|
+
case "date":
|
|
251
|
+
type = "Date";
|
|
252
|
+
break;
|
|
253
|
+
case "binary":
|
|
254
|
+
case "byte":
|
|
255
|
+
type = "Blob";
|
|
256
|
+
break;
|
|
257
|
+
case "int64":
|
|
258
|
+
type = "bigint";
|
|
259
|
+
break;
|
|
260
|
+
default:
|
|
261
|
+
type = "string";
|
|
262
|
+
}
|
|
263
|
+
return appendOptional(type, required);
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Handle number/integer types with formats
|
|
267
|
+
*/
|
|
268
|
+
number(schema, required) {
|
|
269
|
+
const type = schema.format === "int64" ? "bigint" : "number";
|
|
270
|
+
return appendOptional(type, required);
|
|
271
|
+
}
|
|
272
|
+
handle(schema, required) {
|
|
273
|
+
if (isRef(schema)) {
|
|
274
|
+
return this.#ref(schema.$ref, required);
|
|
275
|
+
}
|
|
276
|
+
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
277
|
+
return this.allOf(schema.allOf);
|
|
278
|
+
}
|
|
279
|
+
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
280
|
+
return this.anyOf(schema.anyOf, required);
|
|
281
|
+
}
|
|
282
|
+
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
283
|
+
return this.oneOf(schema.oneOf, required);
|
|
284
|
+
}
|
|
285
|
+
if (schema.enum && Array.isArray(schema.enum)) {
|
|
286
|
+
return this.enum(schema.enum, required);
|
|
287
|
+
}
|
|
288
|
+
if (schema.const) {
|
|
289
|
+
return this.enum([schema.const], true);
|
|
290
|
+
}
|
|
291
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
292
|
+
if (!types.length) {
|
|
293
|
+
if ("properties" in schema) {
|
|
294
|
+
return this.object(schema, required);
|
|
295
|
+
}
|
|
296
|
+
return appendOptional("any", required);
|
|
297
|
+
}
|
|
298
|
+
if (types.length > 1) {
|
|
299
|
+
const realTypes = types.filter((t) => t !== "null");
|
|
300
|
+
if (realTypes.length === 1 && types.includes("null")) {
|
|
301
|
+
const tsType = this.normal(realTypes[0], schema, false);
|
|
302
|
+
return appendOptional(`${tsType} | null`, required);
|
|
303
|
+
}
|
|
304
|
+
const typeResults = types.map((t) => this.normal(t, schema, false));
|
|
305
|
+
return appendOptional(typeResults.join(" | "), required);
|
|
306
|
+
}
|
|
307
|
+
return this.normal(types[0], schema, required);
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
function appendOptional(type, isRequired) {
|
|
311
|
+
return isRequired ? type : `${type} | undefined`;
|
|
381
312
|
}
|
|
382
313
|
|
|
314
|
+
// packages/typescript/src/lib/generator.ts
|
|
315
|
+
import { merge, template } from "lodash-es";
|
|
316
|
+
import { join } from "node:path";
|
|
317
|
+
import { camelcase as camelcase2, spinalcase } from "stringcase";
|
|
318
|
+
import { followRef as followRef4, isEmpty as isEmpty2, isRef as isRef4, resolveRef } from "@sdk-it/core";
|
|
319
|
+
import { forEachOperation } from "@sdk-it/spec";
|
|
320
|
+
|
|
383
321
|
// packages/typescript/src/lib/emitters/zod.ts
|
|
384
|
-
import {
|
|
385
|
-
|
|
386
|
-
|
|
322
|
+
import { followRef as followRef2, isRef as isRef2, parseRef as parseRef2, pascalcase as pascalcase2 } from "@sdk-it/core";
|
|
323
|
+
import { isPrimitiveSchema as isPrimitiveSchema2, sanitizeTag as sanitizeTag2 } from "@sdk-it/spec";
|
|
324
|
+
var ZodEmitter = class {
|
|
325
|
+
#generatedRefs = /* @__PURE__ */ new Set();
|
|
387
326
|
#spec;
|
|
388
327
|
#onRef;
|
|
389
328
|
constructor(spec, onRef) {
|
|
@@ -410,25 +349,21 @@ var ZodDeserialzer = class {
|
|
|
410
349
|
}
|
|
411
350
|
return `z.object({${propEntries.join(", ")}})${additionalProps}`;
|
|
412
351
|
}
|
|
413
|
-
|
|
414
|
-
* Handle arrays (items could be a single schema or a tuple (array of schemas)).
|
|
415
|
-
* In JSON Schema 2020-12, `items` can be an array → tuple style.
|
|
416
|
-
*/
|
|
417
|
-
array(schema, required = false) {
|
|
352
|
+
#array(schema, required = false) {
|
|
418
353
|
const { items } = schema;
|
|
419
354
|
if (!items) {
|
|
420
|
-
return `z.array(z.unknown())${
|
|
355
|
+
return `z.array(z.unknown())${appendOptional2(required)}`;
|
|
421
356
|
}
|
|
422
357
|
if (Array.isArray(items)) {
|
|
423
358
|
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
424
359
|
const base = `z.tuple([${tupleItems.join(", ")}])`;
|
|
425
|
-
return `${base}${
|
|
360
|
+
return `${base}${appendOptional2(required)}`;
|
|
426
361
|
}
|
|
427
362
|
const itemsSchema = this.handle(items, true);
|
|
428
|
-
return `z.array(${itemsSchema})${
|
|
363
|
+
return `z.array(${itemsSchema})${this.#suffixes(JSON.stringify(schema.default), required, false)}`;
|
|
429
364
|
}
|
|
430
365
|
#suffixes = (defaultValue, required, nullable) => {
|
|
431
|
-
return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${
|
|
366
|
+
return `${nullable ? ".nullable()" : ""}${appendDefault(defaultValue)}${appendOptional2(required)}`;
|
|
432
367
|
};
|
|
433
368
|
/**
|
|
434
369
|
* Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
|
|
@@ -448,55 +383,58 @@ var ZodDeserialzer = class {
|
|
|
448
383
|
case "object":
|
|
449
384
|
return `${this.object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
|
|
450
385
|
case "array":
|
|
451
|
-
return this
|
|
386
|
+
return this.#array(schema, required);
|
|
452
387
|
case "null":
|
|
453
|
-
return `z.null()${
|
|
388
|
+
return `z.null()${appendOptional2(required)}`;
|
|
454
389
|
default:
|
|
455
|
-
return `z.unknown()${
|
|
390
|
+
return `z.unknown()${appendOptional2(required)}`;
|
|
456
391
|
}
|
|
457
392
|
}
|
|
458
|
-
ref($ref, required) {
|
|
459
|
-
const schemaName =
|
|
460
|
-
|
|
393
|
+
#ref($ref, required) {
|
|
394
|
+
const schemaName = pascalcase2(sanitizeTag2(parseRef2($ref).model));
|
|
395
|
+
const schema = followRef2(this.#spec, $ref);
|
|
396
|
+
if (isPrimitiveSchema2(schema)) {
|
|
397
|
+
const result = this.handle(schema, required);
|
|
398
|
+
this.#onRef?.(schemaName, result);
|
|
399
|
+
return result;
|
|
400
|
+
}
|
|
401
|
+
if (this.#generatedRefs.has(schemaName)) {
|
|
461
402
|
return schemaName;
|
|
462
403
|
}
|
|
463
|
-
this
|
|
464
|
-
this.#onRef?.(
|
|
465
|
-
schemaName,
|
|
466
|
-
this.handle(followRef2(this.#spec, $ref), required)
|
|
467
|
-
);
|
|
404
|
+
this.#generatedRefs.add(schemaName);
|
|
405
|
+
this.#onRef?.(schemaName, this.handle(schema, required));
|
|
468
406
|
return schemaName;
|
|
469
407
|
}
|
|
408
|
+
#toIntersection(schemas) {
|
|
409
|
+
const [left, ...right] = schemas;
|
|
410
|
+
if (!right.length) {
|
|
411
|
+
return left;
|
|
412
|
+
}
|
|
413
|
+
return `z.intersection(${left}, ${this.#toIntersection(right)})`;
|
|
414
|
+
}
|
|
470
415
|
allOf(schemas, required) {
|
|
471
416
|
const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
472
417
|
if (allOfSchemas.length === 0) {
|
|
473
418
|
return `z.unknown()`;
|
|
474
419
|
}
|
|
475
420
|
if (allOfSchemas.length === 1) {
|
|
476
|
-
return `${allOfSchemas[0]}${
|
|
421
|
+
return `${allOfSchemas[0]}${appendOptional2(required)}`;
|
|
477
422
|
}
|
|
478
|
-
return `${this.#toIntersection(allOfSchemas)}${
|
|
479
|
-
}
|
|
480
|
-
#toIntersection(schemas) {
|
|
481
|
-
const [left, ...right] = schemas;
|
|
482
|
-
if (!right.length) {
|
|
483
|
-
return left;
|
|
484
|
-
}
|
|
485
|
-
return `z.intersection(${left}, ${this.#toIntersection(right)})`;
|
|
423
|
+
return `${this.#toIntersection(allOfSchemas)}${appendOptional2(required)}`;
|
|
486
424
|
}
|
|
487
425
|
anyOf(schemas, required) {
|
|
488
426
|
const anyOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
489
427
|
if (anyOfSchemas.length === 1) {
|
|
490
|
-
return `${anyOfSchemas[0]}${
|
|
428
|
+
return `${anyOfSchemas[0]}${appendOptional2(required)}`;
|
|
491
429
|
}
|
|
492
|
-
return `z.union([${anyOfSchemas.join(", ")}])${
|
|
430
|
+
return `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}`;
|
|
493
431
|
}
|
|
494
432
|
oneOf(schemas, required) {
|
|
495
433
|
const oneOfSchemas = schemas.map((sub) => this.handle(sub, true));
|
|
496
434
|
if (oneOfSchemas.length === 1) {
|
|
497
|
-
return `${oneOfSchemas[0]}${
|
|
435
|
+
return `${oneOfSchemas[0]}${appendOptional2(required)}`;
|
|
498
436
|
}
|
|
499
|
-
return `z.union([${oneOfSchemas.join(", ")}])${
|
|
437
|
+
return `z.union([${oneOfSchemas.join(", ")}])${appendOptional2(required)}`;
|
|
500
438
|
}
|
|
501
439
|
enum(type, values) {
|
|
502
440
|
if (values.length === 1) {
|
|
@@ -594,7 +532,7 @@ var ZodDeserialzer = class {
|
|
|
594
532
|
}
|
|
595
533
|
handle(schema, required) {
|
|
596
534
|
if (isRef2(schema)) {
|
|
597
|
-
return `${this
|
|
535
|
+
return `${this.#ref(schema.$ref, true)}${appendOptional2(required)}`;
|
|
598
536
|
}
|
|
599
537
|
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
600
538
|
return this.allOf(schema.allOf ?? [], required);
|
|
@@ -612,7 +550,7 @@ var ZodDeserialzer = class {
|
|
|
612
550
|
}
|
|
613
551
|
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
614
552
|
if (!types.length) {
|
|
615
|
-
return `z.unknown()${
|
|
553
|
+
return `z.unknown()${appendOptional2(required)}`;
|
|
616
554
|
}
|
|
617
555
|
if ("nullable" in schema && schema.nullable) {
|
|
618
556
|
types.push("null");
|
|
@@ -625,12 +563,12 @@ var ZodDeserialzer = class {
|
|
|
625
563
|
return this.normal(realTypes[0], schema, required, true);
|
|
626
564
|
}
|
|
627
565
|
const subSchemas = types.map((t) => this.normal(t, schema, false));
|
|
628
|
-
return `z.union([${subSchemas.join(", ")}])${
|
|
566
|
+
return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
|
|
629
567
|
}
|
|
630
568
|
return this.normal(types[0], schema, required, false);
|
|
631
569
|
}
|
|
632
570
|
};
|
|
633
|
-
function
|
|
571
|
+
function appendOptional2(isRequired) {
|
|
634
572
|
return isRequired ? "" : ".optional()";
|
|
635
573
|
}
|
|
636
574
|
function appendDefault(defaultValue) {
|
|
@@ -638,201 +576,46 @@ function appendDefault(defaultValue) {
|
|
|
638
576
|
}
|
|
639
577
|
|
|
640
578
|
// packages/typescript/src/lib/sdk.ts
|
|
641
|
-
import {
|
|
642
|
-
import {
|
|
643
|
-
import {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
propEntries.push("[key: string]: any");
|
|
677
|
-
}
|
|
678
|
-
}
|
|
679
|
-
return `{ ${propEntries.join("; ")} }`;
|
|
680
|
-
}
|
|
681
|
-
/**
|
|
682
|
-
* Handle arrays (items could be a single schema or a tuple)
|
|
683
|
-
*/
|
|
684
|
-
array(schema, required = false) {
|
|
685
|
-
const { items } = schema;
|
|
686
|
-
if (!items) {
|
|
687
|
-
return "any[]";
|
|
688
|
-
}
|
|
689
|
-
if (Array.isArray(items)) {
|
|
690
|
-
const tupleItems = items.map((sub) => this.handle(sub, true));
|
|
691
|
-
return `[${tupleItems.join(", ")}]`;
|
|
692
|
-
}
|
|
693
|
-
const itemsType = this.handle(items, true);
|
|
694
|
-
return `${itemsType}[]`;
|
|
695
|
-
}
|
|
696
|
-
/**
|
|
697
|
-
* Convert a basic type (string | number | boolean | object | array, etc.) to TypeScript
|
|
698
|
-
*/
|
|
699
|
-
normal(type, schema, required = false) {
|
|
700
|
-
switch (type) {
|
|
701
|
-
case "string":
|
|
702
|
-
return this.string(schema, required);
|
|
703
|
-
case "number":
|
|
704
|
-
case "integer":
|
|
705
|
-
return this.number(schema, required);
|
|
706
|
-
case "boolean":
|
|
707
|
-
return appendOptional2("boolean", required);
|
|
708
|
-
case "object":
|
|
709
|
-
return this.object(schema, required);
|
|
710
|
-
case "array":
|
|
711
|
-
return this.array(schema, required);
|
|
712
|
-
case "null":
|
|
713
|
-
return "null";
|
|
714
|
-
default:
|
|
715
|
-
console.warn(`Unknown type: ${type}`);
|
|
716
|
-
return appendOptional2("any", required);
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
ref($ref, required) {
|
|
720
|
-
const schemaName = cleanRef2($ref).split("/").pop();
|
|
721
|
-
if (this.generatedRefs.has(schemaName)) {
|
|
722
|
-
return schemaName;
|
|
723
|
-
}
|
|
724
|
-
this.generatedRefs.add(schemaName);
|
|
725
|
-
this.#onRef?.(
|
|
726
|
-
schemaName,
|
|
727
|
-
this.handle(followRef3(this.#spec, $ref), required)
|
|
728
|
-
);
|
|
729
|
-
return appendOptional2(schemaName, required);
|
|
730
|
-
}
|
|
731
|
-
allOf(schemas) {
|
|
732
|
-
const allOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
733
|
-
return allOfTypes.length > 1 ? `${allOfTypes.join(" & ")}` : allOfTypes[0];
|
|
734
|
-
}
|
|
735
|
-
anyOf(schemas, required) {
|
|
736
|
-
const anyOfTypes = schemas.map((sub) => this.handle(sub, true));
|
|
737
|
-
return appendOptional2(
|
|
738
|
-
anyOfTypes.length > 1 ? `${anyOfTypes.join(" | ")}` : anyOfTypes[0],
|
|
739
|
-
required
|
|
740
|
-
);
|
|
741
|
-
}
|
|
742
|
-
oneOf(schemas, required) {
|
|
743
|
-
const oneOfTypes = schemas.map((sub) => {
|
|
744
|
-
return this.handle(sub, false);
|
|
745
|
-
});
|
|
746
|
-
return appendOptional2(
|
|
747
|
-
oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
|
|
748
|
-
required
|
|
749
|
-
);
|
|
750
|
-
}
|
|
751
|
-
enum(values, required) {
|
|
752
|
-
const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
|
|
753
|
-
return appendOptional2(enumValues, required);
|
|
754
|
-
}
|
|
755
|
-
/**
|
|
756
|
-
* Handle string type with formats
|
|
757
|
-
*/
|
|
758
|
-
string(schema, required) {
|
|
759
|
-
let type;
|
|
760
|
-
if (schema.contentEncoding === "binary") {
|
|
761
|
-
return appendOptional2("Blob", required);
|
|
762
|
-
}
|
|
763
|
-
switch (schema.format) {
|
|
764
|
-
case "date-time":
|
|
765
|
-
case "datetime":
|
|
766
|
-
case "date":
|
|
767
|
-
type = "Date";
|
|
768
|
-
break;
|
|
769
|
-
case "binary":
|
|
770
|
-
case "byte":
|
|
771
|
-
type = "Blob";
|
|
772
|
-
break;
|
|
773
|
-
case "int64":
|
|
774
|
-
type = "bigint";
|
|
775
|
-
break;
|
|
776
|
-
default:
|
|
777
|
-
type = "string";
|
|
778
|
-
}
|
|
779
|
-
return appendOptional2(type, required);
|
|
780
|
-
}
|
|
781
|
-
/**
|
|
782
|
-
* Handle number/integer types with formats
|
|
783
|
-
*/
|
|
784
|
-
number(schema, required) {
|
|
785
|
-
const type = schema.format === "int64" ? "bigint" : "number";
|
|
786
|
-
return appendOptional2(type, required);
|
|
787
|
-
}
|
|
788
|
-
handle(schema, required) {
|
|
789
|
-
if (isRef3(schema)) {
|
|
790
|
-
return this.ref(schema.$ref, required);
|
|
791
|
-
}
|
|
792
|
-
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
793
|
-
return this.allOf(schema.allOf);
|
|
794
|
-
}
|
|
795
|
-
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
796
|
-
return this.anyOf(schema.anyOf, required);
|
|
797
|
-
}
|
|
798
|
-
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
799
|
-
return this.oneOf(schema.oneOf, required);
|
|
800
|
-
}
|
|
801
|
-
if (schema.enum && Array.isArray(schema.enum)) {
|
|
802
|
-
return this.enum(schema.enum, required);
|
|
803
|
-
}
|
|
804
|
-
if (schema.const) {
|
|
805
|
-
if (schema["x-internal"]) {
|
|
806
|
-
return `${schema.const}`;
|
|
807
|
-
}
|
|
808
|
-
return this.enum([schema.const], required);
|
|
809
|
-
}
|
|
810
|
-
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
811
|
-
if (!types.length) {
|
|
812
|
-
if ("properties" in schema) {
|
|
813
|
-
return this.object(schema, required);
|
|
814
|
-
}
|
|
815
|
-
return appendOptional2("any", required);
|
|
816
|
-
}
|
|
817
|
-
if (types.length > 1) {
|
|
818
|
-
const realTypes = types.filter((t) => t !== "null");
|
|
819
|
-
if (realTypes.length === 1 && types.includes("null")) {
|
|
820
|
-
const tsType = this.normal(realTypes[0], schema, false);
|
|
821
|
-
return appendOptional2(`${tsType} | null`, required);
|
|
822
|
-
}
|
|
823
|
-
const typeResults = types.map((t) => this.normal(t, schema, false));
|
|
824
|
-
return appendOptional2(typeResults.join(" | "), required);
|
|
825
|
-
}
|
|
826
|
-
return this.normal(types[0], schema, required);
|
|
827
|
-
}
|
|
579
|
+
import { camelcase } from "stringcase";
|
|
580
|
+
import { isEmpty, pascalcase as pascalcase3 } from "@sdk-it/core";
|
|
581
|
+
import {
|
|
582
|
+
isErrorStatusCode,
|
|
583
|
+
isStreamingContentType,
|
|
584
|
+
isSuccessStatusCode,
|
|
585
|
+
isTextContentType,
|
|
586
|
+
parseJsonContentType,
|
|
587
|
+
sanitizeTag as sanitizeTag3
|
|
588
|
+
} from "@sdk-it/spec";
|
|
589
|
+
|
|
590
|
+
// packages/typescript/src/lib/status-map.ts
|
|
591
|
+
var status_map_default = {
|
|
592
|
+
"200": "Ok",
|
|
593
|
+
"201": "Created",
|
|
594
|
+
"202": "Accepted",
|
|
595
|
+
"204": "NoContent",
|
|
596
|
+
"400": "BadRequest",
|
|
597
|
+
"401": "Unauthorized",
|
|
598
|
+
"402": "PaymentRequired",
|
|
599
|
+
"403": "Forbidden",
|
|
600
|
+
"404": "NotFound",
|
|
601
|
+
"405": "MethodNotAllowed",
|
|
602
|
+
"406": "NotAcceptable",
|
|
603
|
+
"409": "Conflict",
|
|
604
|
+
"412": "PreconditionFailed",
|
|
605
|
+
"413": "PayloadTooLarge",
|
|
606
|
+
"410": "Gone",
|
|
607
|
+
"422": "UnprocessableEntity",
|
|
608
|
+
"429": "TooManyRequests",
|
|
609
|
+
"500": "InternalServerError",
|
|
610
|
+
"501": "NotImplemented",
|
|
611
|
+
"502": "BadGateway",
|
|
612
|
+
"503": "ServiceUnavailable",
|
|
613
|
+
"504": "GatewayTimeout"
|
|
828
614
|
};
|
|
829
|
-
function appendOptional2(type, isRequired) {
|
|
830
|
-
return isRequired ? type : `${type} | undefined`;
|
|
831
|
-
}
|
|
832
615
|
|
|
833
616
|
// packages/typescript/src/lib/utils.ts
|
|
834
|
-
import { isRef as
|
|
835
|
-
function securityToOptions(security2, securitySchemes, staticIn) {
|
|
617
|
+
import { followRef as followRef3, isRef as isRef3, removeDuplicates } from "@sdk-it/core";
|
|
618
|
+
function securityToOptions(spec, security2, securitySchemes, staticIn) {
|
|
836
619
|
securitySchemes ??= {};
|
|
837
620
|
const options = {};
|
|
838
621
|
for (const it of security2) {
|
|
@@ -840,10 +623,7 @@ function securityToOptions(security2, securitySchemes, staticIn) {
|
|
|
840
623
|
if (!name) {
|
|
841
624
|
continue;
|
|
842
625
|
}
|
|
843
|
-
const schema = securitySchemes[name];
|
|
844
|
-
if (isRef4(schema)) {
|
|
845
|
-
throw new Error(`Ref security schemas are not supported`);
|
|
846
|
-
}
|
|
626
|
+
const schema = isRef3(securitySchemes[name]) ? followRef3(spec, securitySchemes[name].$ref) : securitySchemes[name];
|
|
847
627
|
if (schema.type === "http") {
|
|
848
628
|
options["authorization"] = {
|
|
849
629
|
in: staticIn ?? "header",
|
|
@@ -901,73 +681,11 @@ function importsToString(...imports) {
|
|
|
901
681
|
throw new Error(`Invalid import ${JSON.stringify(it)}`);
|
|
902
682
|
});
|
|
903
683
|
}
|
|
904
|
-
function exclude(list, exclude2) {
|
|
905
|
-
return list.filter((it) => !exclude2.includes(it));
|
|
906
|
-
}
|
|
907
|
-
function useImports(content, ...imports) {
|
|
908
|
-
const output = [];
|
|
909
|
-
for (const it of mergeImports(...imports)) {
|
|
910
|
-
const singleImport = it.defaultImport ?? it.namespaceImport;
|
|
911
|
-
if (singleImport && content.includes(singleImport)) {
|
|
912
|
-
output.push(importsToString(it).join("\n"));
|
|
913
|
-
} else if (it.namedImports.length) {
|
|
914
|
-
for (const namedImport of it.namedImports) {
|
|
915
|
-
if (content.includes(namedImport.name)) {
|
|
916
|
-
output.push(importsToString(it).join("\n"));
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
return output;
|
|
922
|
-
}
|
|
923
684
|
|
|
924
685
|
// packages/typescript/src/lib/sdk.ts
|
|
925
|
-
function generateInputs(operationsSet, commonZod, makeImport) {
|
|
926
|
-
const commonImports = commonZod.keys().toArray();
|
|
927
|
-
const inputs = {};
|
|
928
|
-
for (const [name, operations] of Object.entries(operationsSet)) {
|
|
929
|
-
const output = [];
|
|
930
|
-
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
931
|
-
for (const operation of operations) {
|
|
932
|
-
const schemaName = camelcase2(`${operation.name} schema`);
|
|
933
|
-
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
|
|
934
|
-
const inputContent = schema;
|
|
935
|
-
for (const schema2 of commonImports) {
|
|
936
|
-
if (inputContent.includes(schema2)) {
|
|
937
|
-
imports.add(
|
|
938
|
-
`import { ${schema2} } from './schemas/${makeImport(spinalcase(schema2))}';`
|
|
939
|
-
);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
output.push(inputContent);
|
|
943
|
-
}
|
|
944
|
-
inputs[`inputs/${spinalcase(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
|
|
945
|
-
}
|
|
946
|
-
const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
|
|
947
|
-
const output = [`import { z } from 'zod';`];
|
|
948
|
-
const content = `export const ${name} = ${schema};`;
|
|
949
|
-
for (const schema2 of commonImports) {
|
|
950
|
-
const preciseMatch = new RegExp(`\\b${schema2}\\b`);
|
|
951
|
-
if (preciseMatch.test(content) && schema2 !== name) {
|
|
952
|
-
output.push(
|
|
953
|
-
`import { ${schema2} } from './${makeImport(spinalcase(schema2))}';`
|
|
954
|
-
);
|
|
955
|
-
}
|
|
956
|
-
}
|
|
957
|
-
output.push(content);
|
|
958
|
-
return [
|
|
959
|
-
[`inputs/schemas/${spinalcase(name)}.ts`, output.join("\n")],
|
|
960
|
-
...acc
|
|
961
|
-
];
|
|
962
|
-
}, []);
|
|
963
|
-
return {
|
|
964
|
-
...Object.fromEntries(schemas),
|
|
965
|
-
...inputs
|
|
966
|
-
};
|
|
967
|
-
}
|
|
968
686
|
function toEndpoint(groupName, spec, specOperation, operation, utils) {
|
|
969
|
-
const schemaName =
|
|
970
|
-
const schemaRef = `${
|
|
687
|
+
const schemaName = camelcase(`${operation.name} schema`);
|
|
688
|
+
const schemaRef = `${camelcase(groupName)}.${schemaName}`;
|
|
971
689
|
const inputHeaders = [];
|
|
972
690
|
const inputQuery = [];
|
|
973
691
|
const inputBody = [];
|
|
@@ -995,20 +713,13 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
|
|
|
995
713
|
}
|
|
996
714
|
specOperation.responses ??= {};
|
|
997
715
|
const outputs = [];
|
|
998
|
-
const statusesCount = Object.keys(specOperation.responses).filter((status) => {
|
|
999
|
-
const statusCode = +status;
|
|
1000
|
-
return statusCode >= 200 && statusCode < 300;
|
|
1001
|
-
}).length > 1;
|
|
1002
716
|
for (const status in specOperation.responses) {
|
|
1003
|
-
const response = isRef5(specOperation.responses[status]) ? followRef4(spec, specOperation.responses[status].$ref) : specOperation.responses[status];
|
|
1004
717
|
const handled = handleResponse(
|
|
1005
718
|
spec,
|
|
1006
719
|
operation.name,
|
|
1007
720
|
status,
|
|
1008
|
-
|
|
1009
|
-
utils
|
|
1010
|
-
true
|
|
1011
|
-
// statusesCount,
|
|
721
|
+
specOperation.responses[status],
|
|
722
|
+
utils
|
|
1012
723
|
);
|
|
1013
724
|
responses.push(handled);
|
|
1014
725
|
outputs.push(...handled.outputs);
|
|
@@ -1019,49 +730,112 @@ function toEndpoint(groupName, spec, specOperation, operation, utils) {
|
|
|
1019
730
|
if (addTypeParser && type !== "json") {
|
|
1020
731
|
typePrefix = `${type} `;
|
|
1021
732
|
}
|
|
1022
|
-
const endpoint = `${typePrefix}${operation.
|
|
733
|
+
const endpoint = `${typePrefix}${operation.method.toUpperCase()} ${operation.path}`;
|
|
1023
734
|
schemas.push(
|
|
1024
735
|
`"${endpoint}": {
|
|
1025
736
|
schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
|
|
1026
737
|
output:[${outputs.join(",")}],
|
|
1027
738
|
toRequest(input: z.infer<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>) {
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
739
|
+
return toRequest('${endpoint}', ${operation.outgoingContentType || "empty"}(input, {
|
|
740
|
+
inputHeaders: [${inputHeaders}],
|
|
741
|
+
inputQuery: [${inputQuery}],
|
|
742
|
+
inputBody: [${inputBody}],
|
|
743
|
+
inputParams: [${inputParams}],
|
|
744
|
+
}));},
|
|
745
|
+
async dispatch(input: z.infer<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>,options: {
|
|
746
|
+
signal?: AbortSignal;
|
|
747
|
+
interceptors: Interceptor[];
|
|
748
|
+
fetch: z.infer<typeof fetchType>;
|
|
749
|
+
})${specOperation["x-pagination"] ? paginationOperation(specOperation, utils.style) : normalOperation(utils.style)}`
|
|
1037
750
|
);
|
|
1038
751
|
}
|
|
1039
752
|
return { responses, schemas };
|
|
1040
753
|
}
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
"
|
|
1052
|
-
"
|
|
1053
|
-
"
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
754
|
+
function normalOperation(style) {
|
|
755
|
+
return `{
|
|
756
|
+
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
757
|
+
const result = await dispatcher.send(this.toRequest(input), this.output);
|
|
758
|
+
return ${style?.outputType === "status" ? "result" : style?.errorAsValue ? `result` : "result.data;"}
|
|
759
|
+
},
|
|
760
|
+
}`;
|
|
761
|
+
}
|
|
762
|
+
function paginationOperation(operation, style) {
|
|
763
|
+
const pagination = operation["x-pagination"];
|
|
764
|
+
const data = `${style?.errorAsValue ? `result[0]${style.outputType === "status" ? "" : ""}` : `${style?.outputType === "default" ? "result.data" : "result.data"}`}`;
|
|
765
|
+
const returnValue = `${style?.errorAsValue ? `[${style?.outputType === "status" ? "new http.Ok(pagination)" : "pagination"}, null]` : `${style?.outputType === "status" ? "new http.Ok(pagination);" : "pagination"}`}`;
|
|
766
|
+
if (pagination.type === "offset") {
|
|
767
|
+
const sameInputNames = pagination.limitParamName === "limit" && pagination.offsetParamName === "offset";
|
|
768
|
+
const initialParams = sameInputNames ? "input" : `{...input, limit: input.${pagination.limitParamName}, offset: input.${pagination.offsetParamName}}`;
|
|
769
|
+
const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.offsetParamName}: nextPageParams.offset, ${pagination.limitParamName}: nextPageParams.limit`;
|
|
770
|
+
const logic = `const pagination = new OffsetPagination(${initialParams}, async (nextPageParams) => {
|
|
771
|
+
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
772
|
+
const result = await dispatcher.send(
|
|
773
|
+
this.toRequest({...input, ${nextPageParams}}),
|
|
774
|
+
this.output,
|
|
775
|
+
);
|
|
776
|
+
return {
|
|
777
|
+
data: ${data}.${pagination.items},
|
|
778
|
+
meta: {
|
|
779
|
+
hasMore: Boolean(${data}.${pagination.hasMore}),
|
|
780
|
+
},
|
|
781
|
+
};
|
|
782
|
+
});
|
|
783
|
+
await pagination.getNextPage();
|
|
784
|
+
return ${returnValue}
|
|
785
|
+
`;
|
|
786
|
+
return style?.errorAsValue ? `{try {${logic}} catch (error) {return [null as never, error] as const;}}}` : `{${logic}}}`;
|
|
787
|
+
}
|
|
788
|
+
if (pagination.type === "cursor") {
|
|
789
|
+
const sameInputNames = pagination.cursorParamName === "cursor";
|
|
790
|
+
const initialParams = sameInputNames ? "input" : `{...input, cursor: input.${pagination.cursorParamName}}`;
|
|
791
|
+
const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.cursorParamName}: nextPageParams.cursor`;
|
|
792
|
+
const logic = `
|
|
793
|
+
const pagination = new CursorPagination(${initialParams}, async (nextPageParams) => {
|
|
794
|
+
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
795
|
+
const result = await dispatcher.send(
|
|
796
|
+
this.toRequest({...input, ${nextPageParams}}),
|
|
797
|
+
this.output,
|
|
798
|
+
);
|
|
799
|
+
${style?.errorAsValue ? `if (result[1]) {throw result[1];}` : ""}
|
|
800
|
+
return {
|
|
801
|
+
data: ${data}.${pagination.items},
|
|
802
|
+
meta: {
|
|
803
|
+
hasMore: Boolean(${data}.${pagination.hasMore}),
|
|
804
|
+
},
|
|
805
|
+
};
|
|
806
|
+
});
|
|
807
|
+
await pagination.getNextPage();
|
|
808
|
+
return ${returnValue}
|
|
809
|
+
`;
|
|
810
|
+
return style?.errorAsValue ? `{try {${logic}} catch (error) {return [null as never, error] as const;}}}` : `{${logic}}}`;
|
|
811
|
+
}
|
|
812
|
+
if (pagination.type === "page") {
|
|
813
|
+
const sameInputNames = pagination.pageNumberParamName === "page" && pagination.pageSizeParamName === "pageSize";
|
|
814
|
+
const initialParams = sameInputNames ? "input" : `{...input, page: input.${pagination.pageNumberParamName}, pageSize: input.${pagination.pageSizeParamName}}`;
|
|
815
|
+
const nextPageParams = sameInputNames ? "...nextPageParams" : `${pagination.pageNumberParamName}: nextPageParams.page, ${pagination.pageSizeParamName}: nextPageParams.pageSize`;
|
|
816
|
+
const logic = `
|
|
817
|
+
const pagination = new Pagination(${initialParams}, async (nextPageParams) => {
|
|
818
|
+
const dispatcher = new Dispatcher(options.interceptors, options.fetch);
|
|
819
|
+
const result = await dispatcher.send(
|
|
820
|
+
this.toRequest({...input, ${nextPageParams}}),
|
|
821
|
+
this.output,
|
|
822
|
+
);
|
|
823
|
+
${style?.errorAsValue ? `if (result[1]) {throw result[1];}` : ""}
|
|
824
|
+
return {
|
|
825
|
+
data: ${data}.${pagination.items},
|
|
826
|
+
meta: {
|
|
827
|
+
hasMore: Boolean(${data}.${pagination.hasMore}),
|
|
828
|
+
},
|
|
829
|
+
};
|
|
830
|
+
});
|
|
831
|
+
await pagination.getNextPage();
|
|
832
|
+
return ${returnValue}
|
|
833
|
+
`;
|
|
834
|
+
return style?.errorAsValue ? `{try {${logic}} catch (error) {return [null as never, error] as const;}}}` : `{${logic}}}`;
|
|
835
|
+
}
|
|
836
|
+
return normalOperation(style);
|
|
837
|
+
}
|
|
838
|
+
function handleResponse(spec, operationName, status, response, utils) {
|
|
1065
839
|
const schemas = {};
|
|
1066
840
|
const imports = {};
|
|
1067
841
|
const endpointImports = {
|
|
@@ -1075,89 +849,94 @@ function handleResponse(spec, operationName, status, response, utils, numbered)
|
|
|
1075
849
|
};
|
|
1076
850
|
const responses = [];
|
|
1077
851
|
const outputs = [];
|
|
1078
|
-
const typeScriptDeserialzer = new
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
852
|
+
const typeScriptDeserialzer = new TypeScriptEmitter(spec);
|
|
853
|
+
const statusCode = +status;
|
|
854
|
+
const statusName = `http.${status_map_default[status] || "APIResponse"}`;
|
|
855
|
+
const interfaceName = pascalcase3(sanitizeTag3(response["x-response-name"]));
|
|
856
|
+
let parser = "buffered";
|
|
857
|
+
if (isEmpty(response.content)) {
|
|
858
|
+
responses.push({
|
|
859
|
+
name: interfaceName,
|
|
860
|
+
schema: "void",
|
|
861
|
+
description: response.description
|
|
862
|
+
});
|
|
863
|
+
} else {
|
|
864
|
+
const contentTypeResult = fromContentType(
|
|
865
|
+
spec,
|
|
866
|
+
typeScriptDeserialzer,
|
|
867
|
+
response
|
|
868
|
+
);
|
|
869
|
+
if (!contentTypeResult) {
|
|
870
|
+
throw new Error(
|
|
871
|
+
`No recognizable content type for response ${status} in operation ${operationName}`
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
parser = contentTypeResult.parser;
|
|
875
|
+
const responseSchema = contentTypeResult.responseSchema;
|
|
876
|
+
responses.push({
|
|
877
|
+
name: interfaceName,
|
|
878
|
+
schema: responseSchema,
|
|
879
|
+
description: response.description
|
|
880
|
+
});
|
|
881
|
+
if (isErrorStatusCode(statusCode)) {
|
|
882
|
+
endpointImports[status_map_default[status] ?? "APIError"] = {
|
|
883
|
+
moduleSpecifier: utils.makeImport("../http/response"),
|
|
884
|
+
namedImports: [{ name: status_map_default[status] ?? "APIError" }]
|
|
1088
885
|
};
|
|
886
|
+
} else if (isSuccessStatusCode(statusCode)) {
|
|
1089
887
|
}
|
|
1090
|
-
|
|
1091
|
-
const statusCode = +status;
|
|
1092
|
-
const parser = (response.headers ?? {})["Transfer-Encoding"] ? "chunked" : "buffered";
|
|
1093
|
-
const statusName = `http.${statusCodeToResponseMap[status] || "APIResponse"}`;
|
|
1094
|
-
const interfaceName = pascalcase(
|
|
1095
|
-
operationName + ` output${numbered ? status : ""}`
|
|
1096
|
-
);
|
|
888
|
+
}
|
|
1097
889
|
if (statusCode === 204) {
|
|
1098
890
|
outputs.push(statusName);
|
|
1099
891
|
} else {
|
|
1100
892
|
if (status.endsWith("XX")) {
|
|
1101
|
-
outputs.push(`http.APIError
|
|
893
|
+
outputs.push(`http.APIError<outputs.${interfaceName}>`);
|
|
1102
894
|
} else {
|
|
1103
895
|
outputs.push(
|
|
1104
|
-
parser !== "buffered" ? `{type: ${statusName}
|
|
896
|
+
parser !== "buffered" ? `{type: ${statusName}<outputs.${interfaceName}>, parser: ${parser}}` : `${statusName}<outputs.${interfaceName}>`
|
|
1105
897
|
);
|
|
1106
898
|
}
|
|
1107
899
|
}
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
if (
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
900
|
+
return { schemas, imports, endpointImports, responses, outputs };
|
|
901
|
+
}
|
|
902
|
+
function fromContentType(spec, typeScriptDeserialzer, response) {
|
|
903
|
+
if ((response.headers ?? {})["Transfer-Encoding"]) {
|
|
904
|
+
return streamedOutput();
|
|
905
|
+
}
|
|
906
|
+
for (const type in response.content) {
|
|
907
|
+
if (isStreamingContentType(type)) {
|
|
908
|
+
return streamedOutput();
|
|
909
|
+
}
|
|
910
|
+
if (parseJsonContentType(type)) {
|
|
911
|
+
return {
|
|
912
|
+
parser: "buffered",
|
|
913
|
+
responseSchema: response.content[type].schema ? typeScriptDeserialzer.handle(response.content[type].schema, true) : "void"
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
if (isTextContentType(type)) {
|
|
917
|
+
return {
|
|
918
|
+
parser: "buffered",
|
|
919
|
+
responseSchema: response.content[type].schema ? typeScriptDeserialzer.handle(response.content[type].schema, true) : "void"
|
|
1119
920
|
};
|
|
1120
|
-
schema.required ??= [];
|
|
1121
|
-
schema.required.push("[http.KIND]");
|
|
1122
921
|
}
|
|
1123
|
-
responseSchema = typeScriptDeserialzer.handle(schema, true);
|
|
1124
|
-
}
|
|
1125
|
-
responses.push({
|
|
1126
|
-
name: interfaceName,
|
|
1127
|
-
schema: responseSchema,
|
|
1128
|
-
description: response.description
|
|
1129
|
-
});
|
|
1130
|
-
const statusGroup = +status.slice(0, 1);
|
|
1131
|
-
if (statusCode >= 400 || statusGroup >= 4) {
|
|
1132
|
-
endpointImports[statusCodeToResponseMap[status] ?? "APIError"] = {
|
|
1133
|
-
moduleSpecifier: utils.makeImport("../http/response"),
|
|
1134
|
-
namedImports: [{ name: statusCodeToResponseMap[status] ?? "APIError" }]
|
|
1135
|
-
};
|
|
1136
|
-
endpointImports[interfaceName] = {
|
|
1137
|
-
isTypeOnly: true,
|
|
1138
|
-
moduleSpecifier: `../outputs/${utils.makeImport(spinalcase(operationName))}`,
|
|
1139
|
-
namedImports: [{ isTypeOnly: true, name: interfaceName }]
|
|
1140
|
-
};
|
|
1141
|
-
} else if (statusCode >= 200 && statusCode < 300 || statusCode >= 2 || statusGroup <= 3) {
|
|
1142
|
-
endpointImports[interfaceName] = {
|
|
1143
|
-
defaultImport: void 0,
|
|
1144
|
-
isTypeOnly: true,
|
|
1145
|
-
moduleSpecifier: `../outputs/${utils.makeImport(spinalcase(operationName))}`,
|
|
1146
|
-
namedImports: [{ isTypeOnly: true, name: interfaceName }],
|
|
1147
|
-
namespaceImport: void 0
|
|
1148
|
-
};
|
|
1149
922
|
}
|
|
1150
|
-
return
|
|
923
|
+
return streamedOutput();
|
|
924
|
+
}
|
|
925
|
+
function streamedOutput() {
|
|
926
|
+
return {
|
|
927
|
+
parser: "chunked",
|
|
928
|
+
responseSchema: "ReadableStream"
|
|
929
|
+
};
|
|
1151
930
|
}
|
|
1152
931
|
|
|
1153
932
|
// packages/typescript/src/lib/styles/github/endpoints.txt
|
|
1154
|
-
var endpoints_default = "type
|
|
933
|
+
var endpoints_default = "type EndpointOutput<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n SuccessfulResponse\n>;\n\ntype EndpointError<K extends keyof typeof schemas> = Extract<\n Unionize<(typeof schemas)[K]['output']>,\n ProblematicResponse\n>;\n\nexport type Endpoints = {\n [K in keyof typeof schemas]: {\n input: z.infer<(typeof schemas)[K]['schema']>;\n output: <% if (outputType === 'default') { %>EndpointOutput<K>['data']<% } else { %>EndpointOutput<K><% } %>;\n error: EndpointError<K> | ParseError<(typeof schemas)[K]['schema']>;\n };\n};";
|
|
1155
934
|
|
|
1156
935
|
// packages/typescript/src/lib/generator.ts
|
|
1157
936
|
function generateCode(config) {
|
|
1158
937
|
const commonZod = /* @__PURE__ */ new Map();
|
|
1159
938
|
const commonZodImports = [];
|
|
1160
|
-
const zodDeserialzer = new
|
|
939
|
+
const zodDeserialzer = new ZodEmitter(config.spec, (model, schema) => {
|
|
1161
940
|
commonZod.set(model, schema);
|
|
1162
941
|
commonZodImports.push({
|
|
1163
942
|
defaultImport: void 0,
|
|
@@ -1168,20 +947,18 @@ function generateCode(config) {
|
|
|
1168
947
|
});
|
|
1169
948
|
});
|
|
1170
949
|
const groups = {};
|
|
1171
|
-
const outputs = {};
|
|
1172
950
|
const endpoints = {};
|
|
1173
|
-
forEachOperation(config, (entry, operation) => {
|
|
951
|
+
forEachOperation(config.spec, (entry, operation) => {
|
|
1174
952
|
console.log(`Processing ${entry.method} ${entry.path}`);
|
|
1175
953
|
groups[entry.groupName] ??= [];
|
|
1176
954
|
endpoints[entry.groupName] ??= [];
|
|
1177
955
|
const inputs = {};
|
|
1178
956
|
const additionalProperties = {};
|
|
1179
|
-
for (const param of operation.parameters
|
|
1180
|
-
if (isRef6(param)) {
|
|
1181
|
-
throw new Error(`Found reference in parameter ${param.$ref}`);
|
|
1182
|
-
}
|
|
957
|
+
for (const param of operation.parameters) {
|
|
1183
958
|
if (!param.schema) {
|
|
1184
|
-
|
|
959
|
+
param.schema = {
|
|
960
|
+
type: "string"
|
|
961
|
+
};
|
|
1185
962
|
}
|
|
1186
963
|
inputs[param.name] = {
|
|
1187
964
|
in: param.in,
|
|
@@ -1189,9 +966,12 @@ function generateCode(config) {
|
|
|
1189
966
|
};
|
|
1190
967
|
additionalProperties[param.name] = param;
|
|
1191
968
|
}
|
|
1192
|
-
const security2 = operation.security ?? [];
|
|
1193
969
|
const securitySchemes = config.spec.components?.securitySchemes ?? {};
|
|
1194
|
-
const securityOptions = securityToOptions(
|
|
970
|
+
const securityOptions = securityToOptions(
|
|
971
|
+
config.spec,
|
|
972
|
+
operation.security ?? [],
|
|
973
|
+
securitySchemes
|
|
974
|
+
);
|
|
1195
975
|
Object.assign(inputs, securityOptions);
|
|
1196
976
|
Object.entries(securityOptions).forEach(([name, value]) => {
|
|
1197
977
|
additionalProperties[name] = {
|
|
@@ -1215,65 +995,43 @@ function generateCode(config) {
|
|
|
1215
995
|
"application/xml": "xml",
|
|
1216
996
|
"text/plain": "text"
|
|
1217
997
|
};
|
|
1218
|
-
let outgoingContentType;
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
998
|
+
let outgoingContentType = "empty";
|
|
999
|
+
for (const type in operation.requestBody.content) {
|
|
1000
|
+
let objectSchema = resolveRef(
|
|
1001
|
+
config.spec,
|
|
1002
|
+
operation.requestBody.content[type].schema
|
|
1003
|
+
);
|
|
1004
|
+
if (type === "application/empty") {
|
|
1005
|
+
objectSchema = {
|
|
1006
|
+
type: "object",
|
|
1007
|
+
// properties: objectSchema['x-properties'],
|
|
1008
|
+
additionalProperties: isEmpty2(objectSchema["x-properties"])
|
|
1009
|
+
};
|
|
1010
|
+
} else {
|
|
1230
1011
|
if (objectSchema.type !== "object") {
|
|
1231
1012
|
objectSchema = {
|
|
1232
1013
|
type: "object",
|
|
1233
|
-
required: [requestBody.required ? "$body" : ""],
|
|
1014
|
+
required: [operation.requestBody.required ? "$body" : ""],
|
|
1234
1015
|
properties: {
|
|
1235
|
-
$body:
|
|
1016
|
+
$body: objectSchema
|
|
1017
|
+
// ...objectSchema['x-properties'],
|
|
1236
1018
|
}
|
|
1237
1019
|
};
|
|
1238
1020
|
}
|
|
1239
|
-
const schema = merge({}, objectSchema, {
|
|
1240
|
-
required: Object.values(additionalProperties).filter((p) => p.required).map((p) => p.name),
|
|
1241
|
-
properties: Object.entries(additionalProperties).reduce(
|
|
1242
|
-
(acc, [, p]) => ({
|
|
1243
|
-
...acc,
|
|
1244
|
-
[p.name]: p.schema
|
|
1245
|
-
}),
|
|
1246
|
-
{}
|
|
1247
|
-
)
|
|
1248
|
-
});
|
|
1249
|
-
Object.assign(inputs, bodyInputs(config, objectSchema));
|
|
1250
|
-
schemas[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
|
|
1251
|
-
}
|
|
1252
|
-
if (requestBody.content["application/json"]) {
|
|
1253
|
-
outgoingContentType = "json";
|
|
1254
|
-
} else if (requestBody.content["application/x-www-form-urlencoded"]) {
|
|
1255
|
-
outgoingContentType = "urlencoded";
|
|
1256
|
-
} else if (requestBody.content["multipart/form-data"]) {
|
|
1257
|
-
outgoingContentType = "formdata";
|
|
1258
|
-
} else {
|
|
1259
|
-
outgoingContentType = "json";
|
|
1260
1021
|
}
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
(acc, [, p]) => ({
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
},
|
|
1275
|
-
true
|
|
1276
|
-
);
|
|
1022
|
+
const schema = merge({}, objectSchema, {
|
|
1023
|
+
required: Object.values(additionalProperties).filter((p) => p.required).map((p) => p.name),
|
|
1024
|
+
properties: Object.entries(additionalProperties).reduce((acc, [, p]) => ({ ...acc, [p.name]: p.schema }), {})
|
|
1025
|
+
});
|
|
1026
|
+
Object.assign(inputs, bodyInputs(config.spec, objectSchema));
|
|
1027
|
+
schemas[shortContenTypeMap[type]] = zodDeserialzer.handle(schema, true);
|
|
1028
|
+
}
|
|
1029
|
+
if (operation.requestBody.content["application/json"]) {
|
|
1030
|
+
outgoingContentType = "json";
|
|
1031
|
+
} else if (operation.requestBody.content["application/x-www-form-urlencoded"]) {
|
|
1032
|
+
outgoingContentType = "urlencoded";
|
|
1033
|
+
} else if (operation.requestBody.content["multipart/form-data"]) {
|
|
1034
|
+
outgoingContentType = "formdata";
|
|
1277
1035
|
}
|
|
1278
1036
|
const endpoint = toEndpoint(
|
|
1279
1037
|
entry.groupName,
|
|
@@ -1282,79 +1040,30 @@ function generateCode(config) {
|
|
|
1282
1040
|
{
|
|
1283
1041
|
outgoingContentType,
|
|
1284
1042
|
name: operation.operationId,
|
|
1285
|
-
|
|
1286
|
-
|
|
1043
|
+
method: entry.method,
|
|
1044
|
+
path: entry.path,
|
|
1287
1045
|
schemas,
|
|
1288
1046
|
inputs
|
|
1289
1047
|
},
|
|
1290
|
-
{ makeImport: config.makeImport }
|
|
1291
|
-
);
|
|
1292
|
-
const output = [
|
|
1293
|
-
`import z from 'zod';`,
|
|
1294
|
-
`import type * as http from '../http';`
|
|
1295
|
-
];
|
|
1296
|
-
const responses = endpoint.responses.flatMap((it) => it.responses);
|
|
1297
|
-
const responsesImports = endpoint.responses.flatMap(
|
|
1298
|
-
(it) => Object.values(it.imports)
|
|
1048
|
+
{ makeImport: config.makeImport, style: config.style }
|
|
1299
1049
|
);
|
|
1300
|
-
if (responses.length) {
|
|
1301
|
-
output.push(
|
|
1302
|
-
...responses.map(
|
|
1303
|
-
(it) => `${it.description ? `
|
|
1304
|
-
/**
|
|
1305
|
-
* ${it.description}
|
|
1306
|
-
*/
|
|
1307
|
-
` : ""} export type ${it.name} = ${it.schema};`
|
|
1308
|
-
)
|
|
1309
|
-
);
|
|
1310
|
-
} else {
|
|
1311
|
-
output.push(
|
|
1312
|
-
`export type ${pascalcase2(operation.operationId + " output")} = void;`
|
|
1313
|
-
);
|
|
1314
|
-
}
|
|
1315
|
-
output.unshift(...useImports(output.join(""), ...responsesImports));
|
|
1316
|
-
outputs[`${spinalcase2(operation.operationId)}.ts`] = output.join("\n");
|
|
1317
1050
|
endpoints[entry.groupName].push(endpoint);
|
|
1318
1051
|
groups[entry.groupName].push({
|
|
1319
1052
|
name: operation.operationId,
|
|
1320
|
-
type: "http",
|
|
1321
1053
|
inputs,
|
|
1322
1054
|
outgoingContentType,
|
|
1323
1055
|
schemas,
|
|
1324
|
-
|
|
1056
|
+
method: entry.method,
|
|
1057
|
+
path: entry.path
|
|
1325
1058
|
});
|
|
1326
1059
|
});
|
|
1327
|
-
const commonSchemas = Object.values(endpoints).reduce(
|
|
1328
|
-
(acc, endpoint) => ({
|
|
1329
|
-
...acc,
|
|
1330
|
-
...endpoint.reduce(
|
|
1331
|
-
(acc2, { responses }) => ({
|
|
1332
|
-
...acc2,
|
|
1333
|
-
...responses.reduce(
|
|
1334
|
-
(acc3, it) => ({ ...acc3, ...it.schemas }),
|
|
1335
|
-
{}
|
|
1336
|
-
)
|
|
1337
|
-
}),
|
|
1338
|
-
{}
|
|
1339
|
-
)
|
|
1340
|
-
}),
|
|
1341
|
-
{}
|
|
1342
|
-
);
|
|
1343
1060
|
const allSchemas = Object.keys(endpoints).map((it) => ({
|
|
1344
|
-
import: `import ${
|
|
1345
|
-
use: ` ...${
|
|
1061
|
+
import: `import ${camelcase2(it)} from './${config.makeImport(spinalcase(it))}';`,
|
|
1062
|
+
use: ` ...${camelcase2(it)}`
|
|
1346
1063
|
}));
|
|
1347
|
-
const imports = [
|
|
1348
|
-
'import z from "zod";',
|
|
1349
|
-
`import type { ParseError } from '${config.makeImport("../http/parser")}';`,
|
|
1350
|
-
`import type { ServerError } from '${config.makeImport("../http/response")}';`,
|
|
1351
|
-
`import type { OutputType, Parser, Type } from '../http/send-request.ts';`
|
|
1352
|
-
];
|
|
1353
1064
|
return {
|
|
1354
1065
|
groups,
|
|
1355
|
-
commonSchemas,
|
|
1356
1066
|
commonZod,
|
|
1357
|
-
outputs,
|
|
1358
1067
|
endpoints: {
|
|
1359
1068
|
[join("api", "endpoints.ts")]: `
|
|
1360
1069
|
|
|
@@ -1364,12 +1073,9 @@ import type { ParseError } from '${config.makeImport("../http/parser")}';
|
|
|
1364
1073
|
import type { ProblematicResponse, SuccessfulResponse } from '${config.makeImport(
|
|
1365
1074
|
"../http/response"
|
|
1366
1075
|
)}';
|
|
1367
|
-
import type { OutputType, Parser, Type } from '${config.makeImport(
|
|
1368
|
-
"../http/send-request"
|
|
1369
|
-
)}';
|
|
1370
1076
|
|
|
1371
1077
|
import schemas from '${config.makeImport("./schemas")}';
|
|
1372
|
-
|
|
1078
|
+
import type { Unionize } from '${config.makeImport("../http/dispatcher")}';
|
|
1373
1079
|
${template(endpoints_default)({ outputType: config.style?.outputType })}`,
|
|
1374
1080
|
[`${join("api", "schemas.ts")}`]: `${allSchemas.map((it) => it.import).join("\n")}
|
|
1375
1081
|
import { KIND } from "${config.makeImport("../http/index")}";
|
|
@@ -1391,14 +1097,18 @@ ${allSchemas.map((it) => it.use).join(",\n")}
|
|
|
1391
1097
|
);
|
|
1392
1098
|
return [
|
|
1393
1099
|
[
|
|
1394
|
-
join("api", `${
|
|
1100
|
+
join("api", `${spinalcase(name)}.ts`),
|
|
1395
1101
|
`${[
|
|
1396
1102
|
...imps,
|
|
1397
1103
|
`import z from 'zod';`,
|
|
1398
1104
|
`import * as http from '${config.makeImport("../http/response")}';`,
|
|
1399
|
-
`import
|
|
1105
|
+
`import * as outputs from '${config.makeImport("../outputs/index")}';`,
|
|
1106
|
+
`import { toRequest, json, urlencoded, empty, formdata, createUrl, type HeadersInit } from '${config.makeImport("../http/request")}';`,
|
|
1400
1107
|
`import { chunked, buffered } from "${config.makeImport("../http/parse-response")}";`,
|
|
1401
|
-
`import * as ${
|
|
1108
|
+
`import * as ${camelcase2(name)} from '../inputs/${config.makeImport(spinalcase(name))}';`,
|
|
1109
|
+
`import { createBaseUrlInterceptor, createHeadersInterceptor, type Interceptor } from '${config.makeImport("../http/interceptors")}';`,
|
|
1110
|
+
`import { Dispatcher, fetchType, type InstanceType } from '${config.makeImport("../http/dispatcher")}';`,
|
|
1111
|
+
`import { Pagination, OffsetPagination, CursorPagination } from "${config.makeImport("../pagination/index")}";`
|
|
1402
1112
|
].join(
|
|
1403
1113
|
"\n"
|
|
1404
1114
|
)}
|
|
@@ -1413,8 +1123,8 @@ ${endpoint.flatMap((it) => it.schemas).join(",\n")}
|
|
|
1413
1123
|
};
|
|
1414
1124
|
}
|
|
1415
1125
|
function toProps(spec, schemaOrRef, aggregator = []) {
|
|
1416
|
-
if (
|
|
1417
|
-
const schema =
|
|
1126
|
+
if (isRef4(schemaOrRef)) {
|
|
1127
|
+
const schema = followRef4(spec, schemaOrRef.$ref);
|
|
1418
1128
|
return toProps(spec, schema, aggregator);
|
|
1419
1129
|
} else if (schemaOrRef.type === "object") {
|
|
1420
1130
|
for (const [name] of Object.entries(schemaOrRef.properties ?? {})) {
|
|
@@ -1443,9 +1153,9 @@ function toProps(spec, schemaOrRef, aggregator = []) {
|
|
|
1443
1153
|
console.warn("Unknown schema in body", schemaOrRef);
|
|
1444
1154
|
return void 0;
|
|
1445
1155
|
}
|
|
1446
|
-
function bodyInputs(
|
|
1156
|
+
function bodyInputs(spec, ctSchema) {
|
|
1447
1157
|
const props = [];
|
|
1448
|
-
toProps(
|
|
1158
|
+
toProps(spec, ctSchema, props);
|
|
1449
1159
|
return props.reduce(
|
|
1450
1160
|
(acc, prop) => ({
|
|
1451
1161
|
...acc,
|
|
@@ -1458,6 +1168,9 @@ function bodyInputs(config, ctSchema) {
|
|
|
1458
1168
|
);
|
|
1459
1169
|
}
|
|
1460
1170
|
|
|
1171
|
+
// packages/typescript/src/lib/http/dispatcher.txt
|
|
1172
|
+
var dispatcher_default = "export type Unionize<T> = T extends [infer Single extends OutputType]\n ? InstanceType<Single>\n : T extends readonly [...infer Tuple extends OutputType[]]\n ? { [I in keyof Tuple]: InstanceType<Tuple[I]> }[number]\n : never;\n\nexport type InstanceType<T> =\n T extends Type<infer U>\n ? U\n : T extends { type: Type<infer U> }\n ? U\n : T extends Array<unknown>\n ? Unionize<T>\n : never;\n\nexport 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 const fetchType = z\n .function()\n .args(z.instanceof(Request))\n .returns(z.promise(z.instanceof(Response)))\n .optional();\n\nexport async function parse<T extends OutputType[]>(\n outputs: T,\n response: Response,\n) <% if(!throwError) { %>\n: Promise<\n [\n Extract<InstanceType<T>, SuccessfulResponse>['data'],\n Extract<InstanceType<T>, ProblematicResponse>['data'],\n ]\n>\n <% } %>\n\n\n\n {\n let output: typeof APIResponse | null = null;\n let parser: Parser = buffered;\n for (const outputType of outputs) {\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\n if (response.ok) {\n const apiresponse = (output || APIResponse).create(\n response.status,\n await parser(response),\n );\n <% if(throwError) { %>\n return <% if (outputType === 'default') { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse><% } else { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse>;<% } %>;\n <% } else { %>\n return [<% if (outputType === 'default') { %>apiresponse.data as Extract<InstanceType<T>, SuccessfulResponse>['data']<% } else { %>apiresponse as Extract<InstanceType<T>, SuccessfulResponse><% } %>, null] as const;\n <% } %>\n }\n<% if(throwError) { %>\n throw (output || APIError).create(\n response.status,\n await parser(response),\n );\n<% } else { %>\n const data = (output || APIError).create(\n response.status,\n await parser(response),\n );\n return [null, data] as const;\n<% } %>\n}\n\nexport function isTypeOf<T extends Type<APIResponse>>(\n instance: any,\n baseType: T,\n): instance is T {\n if (instance === baseType) {\n return true;\n }\n const prototype = Object.getPrototypeOf(instance);\n if (prototype === null) {\n return false;\n }\n return isTypeOf(prototype, baseType);\n}\n\nexport class Dispatcher {\n #interceptors: Interceptor[] = [];\n #fetch: z.infer<typeof fetchType>;\n constructor(interceptors: Interceptor[], fetch?: z.infer<typeof fetchType>) {\n this.#interceptors = interceptors;\n this.#fetch = fetch;\n }\n\n async send<T extends OutputType[]>(\n config: RequestConfig,\n outputs: T,\n signal?: AbortSignal,\n ) {\n for (const interceptor of this.#interceptors) {\n if (interceptor.before) {\n config = await interceptor.before(config);\n }\n }\n\n let response = await (this.#fetch ?? fetch)(\n new Request(config.url, config.init),\n {\n ...config.init,\n signal: signal,\n },\n );\n\n for (let i = this.#interceptors.length - 1; i >= 0; i--) {\n const interceptor = this.#interceptors[i];\n if (interceptor.after) {\n response = await interceptor.after(response.clone());\n }\n }\n\n return await parse(outputs, response);\n }\n}\n";
|
|
1173
|
+
|
|
1461
1174
|
// packages/typescript/src/lib/http/interceptors.txt
|
|
1462
1175
|
var interceptors_default = "export interface Interceptor {\n before?: (config: RequestConfig) => Promise<RequestConfig> | RequestConfig;\n after?: (response: Response) => Promise<Response> | Response;\n}\n\nexport const createHeadersInterceptor = (\n defaultHeaders: () => Record<string, string | undefined>,\n requestHeaders: HeadersInit,\n):Interceptor => {\n return {\n before({init, url}) {\n // Priority Levels\n // 1. Headers Input\n // 2. Request Headers\n // 3. Default Headers\n const headers = defaultHeaders();\n\n for (const [key, value] of new Headers(requestHeaders)) {\n // Only set the header if it doesn't already exist and has a value\n // even though these headers are passed at operation level\n // still they are lower priority compared to the headers input\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n for (const [key, value] of Object.entries(headers)) {\n // Only set the header if it doesn't already exist and has a value\n if (value !== undefined && !init.headers.has(key)) {\n init.headers.set(key, value);\n }\n }\n\n return {init, url};\n },\n };\n};\n\nexport const createBaseUrlInterceptor = (\n getBaseUrl: () => string,\n): Interceptor => {\n return {\n before({ init, url }) {\n const baseUrl = getBaseUrl();\n if (url.protocol === 'local:') {\n return {\n init,\n url: new URL(url.href.replace('local://', baseUrl))\n };\n }\n return { init, url };\n },\n };\n};\n\nexport const logInterceptor: Interceptor = {\n before({ url, init }) {\n console.log('Request:', { url, init });\n return { url, init };\n },\n after(response) {\n console.log('Response:', response);\n return response;\n },\n};\n\n/**\n * Creates an interceptor that logs detailed information about requests and responses.\n * @param options Configuration options for the logger\n * @returns An interceptor object with before and after handlers\n */\nexport const createDetailedLogInterceptor = (options?: {\n logLevel?: 'debug' | 'info' | 'warn' | 'error';\n includeRequestBody?: boolean;\n includeResponseBody?: boolean;\n}) => {\n const logLevel = options?.logLevel || 'info';\n const includeRequestBody = options?.includeRequestBody || false;\n const includeResponseBody = options?.includeResponseBody || false;\n\n return {\n async before(request: Request) {\n const logData = {\n url: request.url,\n method: request.method,\n contentType: request.headers.get('Content-Type'),\n headers: Object.fromEntries([...request.headers.entries()]),\n };\n\n console[logLevel]('\u{1F680} Outgoing Request:', logData);\n\n if (includeRequestBody) {\n try {\n // Clone the request to avoid consuming the body stream\n const clonedRequest = request.clone();\n if (clonedRequest.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedRequest.json().catch(() => null);\n console[logLevel]('Request Body:', body);\n } else {\n const body = await clonedRequest.text().catch(() => null);\n console[logLevel]('Request Body:', body);\n }\n } catch (error) {\n console.error('Could not log request body:', error);\n }\n }\n\n return request;\n },\n\n async after(response: Response) {\n const logData = {\n status: response.status,\n statusText: response.statusText,\n url: response.url,\n headers: Object.fromEntries([...response.headers.entries()]),\n };\n\n console[logLevel]('\u{1F4E5} Incoming Response:', logData);\n\n if (includeResponseBody && response.body) {\n try {\n // Clone the response to avoid consuming the body stream\n const clonedResponse = response.clone();\n if (clonedResponse.headers.get('Content-Type')?.includes('application/json')) {\n const body = await clonedResponse.json().catch(() => null);\n console[logLevel]('Response Body:', body);\n } else {\n const body = await clonedResponse.text().catch(() => null);\n if (body) {\n console[logLevel]('Response Body:', body.substring(0, 500) + (body.length > 500 ? '...' : ''));\n } else {\n console[logLevel]('No response body');\n }\n }\n } catch (error) {\n console.error('Could not log response body:', error);\n }\n }\n\n return response;\n },\n };\n};\n";
|
|
1463
1176
|
|
|
@@ -1468,7 +1181,7 @@ var parse_response_default = 'import { parse } from "fast-content-type-parse";\n
|
|
|
1468
1181
|
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";
|
|
1469
1182
|
|
|
1470
1183
|
// packages/typescript/src/lib/http/request.txt
|
|
1471
|
-
var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type HeadersInit = [string, string][] | Record<string, string>;\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\n\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\n\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Record<string, string>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Record<string, string>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n if (\n this.props.inputBody.length === 1 &&\n this.props.inputBody[0] === '$body'\n ) {\n return JSON.stringify(this.input.$body);\n }\n\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n };\n }\n}\n\nclass
|
|
1184
|
+
var request_default = "type Init = Omit<RequestInit, 'headers'> & { headers: Headers; };\nexport type RequestConfig = { init: Init; url: URL };\nexport type Method = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE' | 'HEAD' | 'OPTIONS';\nexport type ContentType = 'xml' | 'json' | 'urlencoded' | 'multipart' | 'formdata';\nexport type HeadersInit = [string, string][] | Record<string, string>;\nexport type Endpoint =\n | `${ContentType} ${Method} ${string}`\n | `${Method} ${string}`;\n\nexport type BodyInit =\n | ArrayBuffer\n | Blob\n | FormData\n | URLSearchParams\n | null\n | string;\n\nexport function createUrl(path: string, query: URLSearchParams) {\n const url = new URL(path, `local://`);\n url.search = query.toString();\n return url;\n}\n\nfunction template(\n templateString: string,\n templateVariables: Record<string, any>,\n): string {\n const nargs = /{([0-9a-zA-Z_]+)}/g;\n return templateString.replace(nargs, (match, key: string, index: number) => {\n // Handle escaped double braces\n if (\n templateString[index - 1] === '{' &&\n templateString[index + match.length] === '}'\n ) {\n return key;\n }\n\n const result = key in templateVariables ? templateVariables[key] : null;\n return result === null || result === undefined ? '' : String(result);\n });\n}\n\ntype Input = Record<string, any>;\ntype Props = {\n inputHeaders: string[];\n inputQuery: string[];\n inputBody: string[];\n inputParams: string[];\n};\n\nabstract class Serializer {\n protected input: Input;\n protected props: Props;\n\n constructor(\n input: Input,\n props: Props,\n ) {\n this.input = input;\n this.props = props;\n }\n\n abstract getBody(): BodyInit | null;\n abstract getHeaders(): Record<string, string>;\n serialize(): Serialized {\n const headers = new Headers({});\n for (const header of this.props.inputHeaders) {\n headers.set(header, this.input[header]);\n }\n\n const query = new URLSearchParams();\n for (const key of this.props.inputQuery) {\n const value = this.input[key];\n if (value !== undefined) {\n query.set(key, String(value));\n }\n }\n\n const params = this.props.inputParams.reduce<Record<string, any>>(\n (acc, key) => {\n acc[key] = this.input[key];\n return acc;\n },\n {},\n );\n\n return {\n body: this.getBody(),\n query,\n params,\n headers: this.getHeaders(),\n };\n }\n}\n\ninterface Serialized {\n body: BodyInit | null;\n query: URLSearchParams;\n params: Record<string, any>;\n headers: Record<string, string>;\n}\n\nclass JsonSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body: Record<string, any> = {};\n if (\n this.props.inputBody.length === 1 &&\n this.props.inputBody[0] === '$body'\n ) {\n return JSON.stringify(this.input.$body);\n }\n\n for (const prop of this.props.inputBody) {\n body[prop] = this.input[prop];\n }\n return JSON.stringify(body);\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/json',\n Accept: 'application/json',\n };\n }\n}\n\nclass UrlencodedSerializer extends Serializer {\n getBody(): BodyInit | null {\n const body = new URLSearchParams();\n for (const prop of this.props.inputBody) {\n body.set(prop, this.input[prop]);\n }\n return body;\n }\n getHeaders(): Record<string, string> {\n return {\n 'Content-Type': 'application/x-www-form-urlencoded',\n Accept: 'application/json',\n };\n }\n}\n\nclass EmptySerializer 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 empty(input: Input, props: Props) {\n return new EmptySerializer(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";
|
|
1472
1185
|
|
|
1473
1186
|
// packages/typescript/src/lib/http/response.txt
|
|
1474
1187
|
var response_default = `export const KIND = Symbol('APIDATA');
|
|
@@ -1702,6 +1415,28 @@ export class Gone<T = { message: string }> extends APIError<T, 410> {
|
|
|
1702
1415
|
return typeof value === 'object' && value !== null && KIND in value && value[KIND] === this.kind;
|
|
1703
1416
|
}
|
|
1704
1417
|
}
|
|
1418
|
+
export class PreconditionFailed<T = { message: string }> extends APIError<T, 412> {
|
|
1419
|
+
static override readonly kind = Symbol.for('PreconditionFailed');
|
|
1420
|
+
static override status = 412 as const;
|
|
1421
|
+
constructor(data: T) {
|
|
1422
|
+
super(PreconditionFailed.status, data);
|
|
1423
|
+
}
|
|
1424
|
+
static override create<T>(status: number, data: T) {
|
|
1425
|
+
Object.defineProperty(data, KIND, { value: this.kind });
|
|
1426
|
+
return new this(data);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
static is<T extends { [KIND]: (typeof PreconditionFailed)['kind'] }>(
|
|
1430
|
+
value: unknown,
|
|
1431
|
+
): value is T {
|
|
1432
|
+
return (
|
|
1433
|
+
typeof value === 'object' &&
|
|
1434
|
+
value !== null &&
|
|
1435
|
+
KIND in value &&
|
|
1436
|
+
value[KIND] === this.kind
|
|
1437
|
+
);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1705
1440
|
export class UnprocessableEntity<
|
|
1706
1441
|
T = { message: string; errors?: Record<string, string[]> },
|
|
1707
1442
|
> extends APIError<T, 422> {
|
|
@@ -1853,7 +1588,7 @@ export class GatewayTimeout<T = { message: string }> extends APIError<T, 504> {
|
|
|
1853
1588
|
}
|
|
1854
1589
|
|
|
1855
1590
|
export type ClientError =
|
|
1856
|
-
| BadRequest<
|
|
1591
|
+
| BadRequest<unknown>
|
|
1857
1592
|
| Unauthorized<unknown>
|
|
1858
1593
|
| PaymentRequired<unknown>
|
|
1859
1594
|
| Forbidden<unknown>
|
|
@@ -1862,6 +1597,9 @@ export type ClientError =
|
|
|
1862
1597
|
| NotAcceptable<unknown>
|
|
1863
1598
|
| Conflict<unknown>
|
|
1864
1599
|
| Gone<unknown>
|
|
1600
|
+
| PreconditionFailed<unknown>
|
|
1601
|
+
| PayloadTooLarge<unknown>
|
|
1602
|
+
| UnsupportedMediaType<unknown>
|
|
1865
1603
|
| UnprocessableEntity<unknown>
|
|
1866
1604
|
| TooManyRequests<unknown>;
|
|
1867
1605
|
|
|
@@ -1878,315 +1616,396 @@ export type SuccessfulResponse =
|
|
|
1878
1616
|
| Ok<unknown>
|
|
1879
1617
|
| Created<unknown>
|
|
1880
1618
|
| Accepted<unknown>
|
|
1881
|
-
| NoContent
|
|
1882
|
-
`;
|
|
1619
|
+
| NoContent;`;
|
|
1883
1620
|
|
|
1884
|
-
// packages/typescript/src/lib/
|
|
1885
|
-
var
|
|
1621
|
+
// packages/typescript/src/lib/paginations/cursor-pagination.txt
|
|
1622
|
+
var cursor_pagination_default = "type CursorPaginationParams = {\n cursor?: string;\n};\n\ninterface CursorMetadata extends Metadata {\n nextCursor?: string;\n}\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends CursorMetadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends CursorMetadata> = (\n input: CursorPaginationParams,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class CursorPagination<T, M extends CursorMetadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: CursorPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n\n constructor(\n initialParams: PartialNullable<CursorPaginationParams>,\n fetchFn: FetchFn<T, M>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = {\n cursor: initialParams.cursor ?? undefined,\n };\n }\n\n async getNextPage() {\n const result = await this.#fetchFn(this.#params);\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n cursor: result.meta.nextCursor,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter() {\n if (!this.#currentPage) {\n yield await this.getNextPage();\n }\n\n while (this.hasMore) {\n yield await this.getNextPage();\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n\ntype PartialNullable<T> = {\n [K in keyof T]?: T[K] | null;\n};\n";
|
|
1886
1623
|
|
|
1887
|
-
// packages/typescript/src/lib/
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1624
|
+
// packages/typescript/src/lib/paginations/offset-pagination.txt
|
|
1625
|
+
var offset_pagination_default = "type OffsetPaginationParams = {\n offset: number;\n limit: number;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<T, M extends Metadata> = (\n input: OffsetPaginationParams,\n) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class OffsetPagination<T, M extends Metadata> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: OffsetPaginationParams;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M>;\n\n constructor(\n initialParams: Partial<OffsetPaginationParams>,\n fetchFn: FetchFn<T, M>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = {\n limit: initialParams.limit ?? 0,\n offset: initialParams.offset ?? 0,\n };\n }\n\n async getNextPage() {\n const result = await this.#fetchFn(this.#params);\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n offset: this.#params.offset + this.#params.limit,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter() {\n if (!this.#currentPage) {\n yield await this.getNextPage();\n }\n\n while (this.hasMore) {\n yield await this.getNextPage();\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n\n reset(params?: Partial<OffsetPaginationParams>) {\n this.#meta = null;\n this.#currentPage = null;\n if (params) {\n this.#params = { ...this.#params, ...params };\n } else {\n this.#params.offset = 0;\n }\n return this;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
|
|
1626
|
+
|
|
1627
|
+
// packages/typescript/src/lib/paginations/page-pagination.txt
|
|
1628
|
+
var page_pagination_default = "type InferPage<T> = T extends Page<infer U> ? U : never;\ntype PaginationParams<P extends number | bigint, S extends number | bigint> = {\n page?: P;\n pageSize?: S;\n};\n\ninterface Metadata {\n hasMore?: boolean;\n}\n\ntype PaginationResult<T, M extends Metadata> = {\n data: T[];\n meta: M;\n};\n\ntype FetchFn<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> = (input: Partial<PaginationParams<P, S>>) => Promise<PaginationResult<T, M>>;\n\n/**\n * @experimental\n */\nexport class Pagination<\n T,\n M extends Metadata,\n P extends number | bigint,\n S extends number | bigint,\n> {\n #meta: PaginationResult<T, M>['meta'] | null = null;\n #params: PaginationParams<P, S>;\n #currentPage: Page<T> | null = null;\n readonly #fetchFn: FetchFn<T, M, P, S>;\n\n constructor(\n initialParams: Partial<PaginationParams<P, S>>,\n fetchFn: FetchFn<T, M, P, S>,\n ) {\n this.#fetchFn = fetchFn;\n this.#params = { ...initialParams, page: initialParams.page };\n }\n\n async getNextPage() {\n const result = await this.#fetchFn(this.#params);\n this.#currentPage = new Page(result.data);\n this.#meta = result.meta;\n this.#params = {\n ...this.#params,\n page: ((this.#params.page as number) || 0 + 1) as never,\n };\n return this;\n }\n\n getCurrentPage() {\n if (!this.#currentPage) {\n throw new Error(\n 'No page data available. Please call getNextPage() first.',\n );\n }\n return this.#currentPage;\n }\n\n get hasMore() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta.hasMore;\n }\n\n async *[Symbol.asyncIterator]() {\n for await (const page of this.iter()) {\n yield page.getCurrentPage();\n }\n }\n\n async *iter() {\n if (!this.#currentPage) {\n yield await this.getNextPage();\n }\n\n while (this.hasMore) {\n yield await this.getNextPage();\n }\n }\n\n get metadata() {\n if (!this.#meta) {\n throw new Error(\n 'No meta data available. Please call getNextPage() first.',\n );\n }\n return this.#meta;\n }\n}\n\nclass Page<T> {\n data: T[];\n constructor(data: T[]) {\n this.data = data;\n }\n}\n";
|
|
1629
|
+
|
|
1630
|
+
// packages/typescript/src/lib/typescript-snippet.ts
|
|
1631
|
+
import { camelcase as camelcase3, spinalcase as spinalcase2 } from "stringcase";
|
|
1632
|
+
import { isEmpty as isEmpty3, pascalcase as pascalcase4, resolveRef as resolveRef2 } from "@sdk-it/core";
|
|
1633
|
+
import {
|
|
1634
|
+
patchParameters,
|
|
1635
|
+
securityToOptions as securityToOptions2
|
|
1636
|
+
} from "@sdk-it/spec";
|
|
1637
|
+
|
|
1638
|
+
// packages/typescript/src/lib/emitters/snippet.ts
|
|
1639
|
+
import { followRef as followRef5, isRef as isRef5 } from "@sdk-it/core";
|
|
1640
|
+
var SnippetEmitter = class {
|
|
1641
|
+
spec;
|
|
1642
|
+
generatedRefs = /* @__PURE__ */ new Set();
|
|
1643
|
+
cache = /* @__PURE__ */ new Map();
|
|
1891
1644
|
constructor(spec) {
|
|
1892
|
-
this
|
|
1645
|
+
this.spec = spec;
|
|
1893
1646
|
}
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
const
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
const isRequired = (schema.required ?? []).includes(propName);
|
|
1904
|
-
lines.push(...this.#property(propName, propSchema, isRequired));
|
|
1647
|
+
object(schema) {
|
|
1648
|
+
const schemaObj = isRef5(schema) ? followRef5(this.spec, schema.$ref) : schema;
|
|
1649
|
+
const result = {};
|
|
1650
|
+
const properties = schemaObj.properties || {};
|
|
1651
|
+
for (const [propName, propSchema] of Object.entries(properties)) {
|
|
1652
|
+
const isRequired = (schemaObj.required ?? []).includes(propName);
|
|
1653
|
+
const resolvedProp = isRef5(propSchema) ? followRef5(this.spec, propSchema.$ref) : propSchema;
|
|
1654
|
+
if (isRequired || resolvedProp.example !== void 0 || resolvedProp.default !== void 0 || Math.random() > 0.5) {
|
|
1655
|
+
result[propName] = this.handle(propSchema);
|
|
1905
1656
|
}
|
|
1906
1657
|
}
|
|
1907
|
-
if (
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
} else {
|
|
1912
|
-
lines.push(
|
|
1913
|
-
...this.handle(schema.additionalProperties).map((l) => ` ${l}`)
|
|
1914
|
-
);
|
|
1915
|
-
}
|
|
1658
|
+
if (schemaObj.additionalProperties && typeof schemaObj.additionalProperties === "object") {
|
|
1659
|
+
result["additionalPropExample"] = this.handle(
|
|
1660
|
+
schemaObj.additionalProperties
|
|
1661
|
+
);
|
|
1916
1662
|
}
|
|
1917
|
-
return
|
|
1918
|
-
}
|
|
1919
|
-
/**
|
|
1920
|
-
* Format a property with its type and description
|
|
1921
|
-
*/
|
|
1922
|
-
#property(name, schema, required) {
|
|
1923
|
-
const requiredMark = required ? " (required)" : "";
|
|
1924
|
-
const propNameLine = `- \`${name}\`${requiredMark}:`;
|
|
1925
|
-
const lines = [propNameLine];
|
|
1926
|
-
const schemaDocs = this.handle(schema);
|
|
1927
|
-
lines.push(...schemaDocs.map((line) => ` ${line}`));
|
|
1928
|
-
return lines;
|
|
1663
|
+
return result;
|
|
1929
1664
|
}
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
lines.push(`**Array items:**`);
|
|
1936
|
-
if (schema.items) {
|
|
1937
|
-
const itemDocs = this.handle(schema.items);
|
|
1938
|
-
lines.push(...itemDocs.map((line) => ` ${line}`));
|
|
1939
|
-
} else {
|
|
1940
|
-
lines.push(` **Type:** \`unknown\``);
|
|
1941
|
-
}
|
|
1942
|
-
if (schema.minItems !== void 0)
|
|
1943
|
-
lines.push(`- Minimum items: ${schema.minItems}`);
|
|
1944
|
-
if (schema.maxItems !== void 0)
|
|
1945
|
-
lines.push(`- Maximum items: ${schema.maxItems}`);
|
|
1946
|
-
if (schema.uniqueItems)
|
|
1947
|
-
lines.push(`- Items must be unique.`);
|
|
1948
|
-
return lines;
|
|
1949
|
-
}
|
|
1950
|
-
#ref($ref) {
|
|
1951
|
-
const schemaName = $ref.split("/").pop() || "object";
|
|
1952
|
-
const resolved = followRef6(this.#spec, $ref);
|
|
1953
|
-
const lines = [
|
|
1954
|
-
`**Type:** [\`${schemaName}\`](#${schemaName.toLowerCase()})`
|
|
1955
|
-
];
|
|
1956
|
-
if (resolved.description) {
|
|
1957
|
-
lines.push(resolved.description);
|
|
1665
|
+
array(schema) {
|
|
1666
|
+
const schemaObj = isRef5(schema) ? followRef5(this.spec, schema.$ref) : schema;
|
|
1667
|
+
const itemsSchema = schemaObj.items;
|
|
1668
|
+
if (!itemsSchema) {
|
|
1669
|
+
return [];
|
|
1958
1670
|
}
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
schemas.forEach((subSchema, index) => {
|
|
1964
|
-
lines.push(`- **Constraint ${index + 1}:**`);
|
|
1965
|
-
const subLines = this.handle(subSchema);
|
|
1966
|
-
lines.push(...subLines.map((l) => ` ${l}`));
|
|
1967
|
-
});
|
|
1968
|
-
return lines;
|
|
1969
|
-
}
|
|
1970
|
-
#anyOf(schemas) {
|
|
1971
|
-
const lines = ["**Any of (Union):**"];
|
|
1972
|
-
schemas.forEach((subSchema, index) => {
|
|
1973
|
-
lines.push(`- **Option ${index + 1}:**`);
|
|
1974
|
-
const subLines = this.handle(subSchema);
|
|
1975
|
-
lines.push(...subLines.map((l) => ` ${l}`));
|
|
1976
|
-
});
|
|
1977
|
-
return lines;
|
|
1978
|
-
}
|
|
1979
|
-
#oneOf(schemas) {
|
|
1980
|
-
const lines = ["**One of (Exclusive Union):**"];
|
|
1981
|
-
schemas.forEach((subSchema, index) => {
|
|
1982
|
-
lines.push(`- **Option ${index + 1}:**`);
|
|
1983
|
-
const subLines = this.handle(subSchema);
|
|
1984
|
-
lines.push(...subLines.map((l) => ` ${l}`));
|
|
1985
|
-
});
|
|
1986
|
-
return lines;
|
|
1987
|
-
}
|
|
1988
|
-
#enum(schema) {
|
|
1989
|
-
const lines = [`**Type:** \`${schema.type || "unknown"}\` (enum)`];
|
|
1990
|
-
if (schema.description)
|
|
1991
|
-
lines.push(schema.description);
|
|
1992
|
-
lines.push("**Allowed values:**");
|
|
1993
|
-
lines.push(
|
|
1994
|
-
...(schema.enum || []).map((val) => `- \`${JSON.stringify(val)}\``)
|
|
1995
|
-
);
|
|
1996
|
-
if (schema.default !== void 0) {
|
|
1997
|
-
lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
1671
|
+
const count = Math.min(schemaObj.minItems ?? 1, 2);
|
|
1672
|
+
const result = [];
|
|
1673
|
+
for (let i = 0; i < count; i++) {
|
|
1674
|
+
result.push(this.handle(itemsSchema));
|
|
1998
1675
|
}
|
|
1999
|
-
return
|
|
1676
|
+
return result;
|
|
2000
1677
|
}
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
);
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
case "
|
|
2019
|
-
case "
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
lines.push(
|
|
2031
|
-
`- Must be strictly greater than: ${schema.exclusiveMinimum}`
|
|
2032
|
-
);
|
|
2033
|
-
}
|
|
2034
|
-
} else if (typeof schema.exclusiveMinimum === "number") {
|
|
2035
|
-
lines.push(
|
|
2036
|
-
`- Must be strictly greater than: ${schema.exclusiveMinimum}`
|
|
2037
|
-
);
|
|
2038
|
-
}
|
|
2039
|
-
if (schema.maximum !== void 0) {
|
|
2040
|
-
const exclusiveMax = typeof schema.exclusiveMaximum === "number";
|
|
2041
|
-
lines.push(
|
|
2042
|
-
`- Maximum: ${schema.maximum}${exclusiveMax ? " (exclusive)" : ""}`
|
|
2043
|
-
);
|
|
2044
|
-
if (exclusiveMax) {
|
|
2045
|
-
lines.push(
|
|
2046
|
-
`- Must be strictly less than: ${schema.exclusiveMaximum}`
|
|
2047
|
-
);
|
|
2048
|
-
}
|
|
2049
|
-
} else if (typeof schema.exclusiveMaximum === "number") {
|
|
2050
|
-
lines.push(
|
|
2051
|
-
`- Must be strictly less than: ${schema.exclusiveMaximum}`
|
|
2052
|
-
);
|
|
2053
|
-
}
|
|
2054
|
-
if (schema.multipleOf !== void 0)
|
|
2055
|
-
lines.push(`- Must be a multiple of: ${schema.multipleOf}`);
|
|
2056
|
-
break;
|
|
2057
|
-
case "boolean":
|
|
2058
|
-
lines.push(`**Type:** \`boolean\`${nullableSuffix}`);
|
|
2059
|
-
lines.push(...description);
|
|
2060
|
-
break;
|
|
2061
|
-
case "object":
|
|
2062
|
-
lines.push(`**Type:** \`object\`${nullableSuffix}`);
|
|
2063
|
-
lines.push(...description);
|
|
2064
|
-
lines.push(...this.#object(schema));
|
|
2065
|
-
break;
|
|
2066
|
-
case "array":
|
|
2067
|
-
lines.push(`**Type:** \`array\`${nullableSuffix}`);
|
|
2068
|
-
lines.push(...description);
|
|
2069
|
-
lines.push(...this.#array(schema));
|
|
2070
|
-
break;
|
|
2071
|
-
case "null":
|
|
2072
|
-
lines.push(`**Type:** \`null\``);
|
|
2073
|
-
lines.push(...description);
|
|
2074
|
-
break;
|
|
1678
|
+
string(schema) {
|
|
1679
|
+
if (schema.example !== void 0)
|
|
1680
|
+
return String(schema.example);
|
|
1681
|
+
if (schema.default !== void 0)
|
|
1682
|
+
return String(schema.default);
|
|
1683
|
+
switch (schema.format) {
|
|
1684
|
+
case "date-time":
|
|
1685
|
+
case "datetime":
|
|
1686
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
1687
|
+
case "date":
|
|
1688
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
1689
|
+
case "time":
|
|
1690
|
+
return (/* @__PURE__ */ new Date()).toISOString().split("T")[1];
|
|
1691
|
+
case "email":
|
|
1692
|
+
return "user@example.com";
|
|
1693
|
+
case "uuid":
|
|
1694
|
+
return "123e4567-e89b-12d3-a456-426614174000";
|
|
1695
|
+
case "uri":
|
|
1696
|
+
case "url":
|
|
1697
|
+
return "https://example.com";
|
|
1698
|
+
case "ipv4":
|
|
1699
|
+
return "192.168.1.1";
|
|
1700
|
+
case "ipv6":
|
|
1701
|
+
return "2001:0db8:85a3:0000:0000:8a2e:0370:7334";
|
|
1702
|
+
case "hostname":
|
|
1703
|
+
return "example.com";
|
|
1704
|
+
case "binary":
|
|
1705
|
+
case "byte":
|
|
1706
|
+
return `new Blob(['example'], { type: 'text/plain' })`;
|
|
2075
1707
|
default:
|
|
2076
|
-
|
|
2077
|
-
|
|
1708
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
1709
|
+
return String(schema.enum[0]);
|
|
1710
|
+
}
|
|
1711
|
+
return schema.pattern ? `string matching ${schema.pattern}` : "example";
|
|
2078
1712
|
}
|
|
2079
|
-
|
|
2080
|
-
|
|
1713
|
+
}
|
|
1714
|
+
number(schema) {
|
|
1715
|
+
if (schema.example !== void 0)
|
|
1716
|
+
return Number(schema.example);
|
|
1717
|
+
if (schema.default !== void 0)
|
|
1718
|
+
return Number(schema.default);
|
|
1719
|
+
let value;
|
|
1720
|
+
if (typeof schema.exclusiveMinimum === "number") {
|
|
1721
|
+
value = schema.exclusiveMinimum + 1;
|
|
1722
|
+
} else if (typeof schema.minimum === "number") {
|
|
1723
|
+
value = schema.minimum;
|
|
1724
|
+
} else {
|
|
1725
|
+
value = schema.type === "integer" ? 42 : 42.42;
|
|
1726
|
+
}
|
|
1727
|
+
if (typeof schema.exclusiveMaximum === "number" && value >= schema.exclusiveMaximum) {
|
|
1728
|
+
value = schema.exclusiveMaximum - 1;
|
|
1729
|
+
} else if (typeof schema.maximum === "number" && value > schema.maximum) {
|
|
1730
|
+
value = schema.maximum;
|
|
2081
1731
|
}
|
|
2082
|
-
|
|
1732
|
+
if (typeof schema.multipleOf === "number" && value % schema.multipleOf !== 0) {
|
|
1733
|
+
value = Math.floor(value / schema.multipleOf) * schema.multipleOf;
|
|
1734
|
+
}
|
|
1735
|
+
return schema.type === "integer" ? Math.floor(value) : value;
|
|
1736
|
+
}
|
|
1737
|
+
boolean(schema) {
|
|
1738
|
+
if (schema.example !== void 0)
|
|
1739
|
+
return Boolean(schema.example);
|
|
1740
|
+
if (schema.default !== void 0)
|
|
1741
|
+
return Boolean(schema.default);
|
|
1742
|
+
return true;
|
|
1743
|
+
}
|
|
1744
|
+
null() {
|
|
1745
|
+
return null;
|
|
1746
|
+
}
|
|
1747
|
+
ref($ref) {
|
|
1748
|
+
const parts = $ref.split("/");
|
|
1749
|
+
const refKey = parts[parts.length - 1] || "";
|
|
1750
|
+
if (this.cache.has($ref)) {
|
|
1751
|
+
return this.cache.get($ref);
|
|
1752
|
+
}
|
|
1753
|
+
this.cache.set($ref, { _ref: refKey });
|
|
1754
|
+
const resolved = followRef5(this.spec, $ref);
|
|
1755
|
+
const result = this.handle(resolved);
|
|
1756
|
+
this.cache.set($ref, result);
|
|
1757
|
+
return result;
|
|
1758
|
+
}
|
|
1759
|
+
allOf(schemas) {
|
|
1760
|
+
const initial = {};
|
|
1761
|
+
return schemas.reduce((result, schema) => {
|
|
1762
|
+
const example = this.handle(schema);
|
|
1763
|
+
if (typeof example === "object" && example !== null) {
|
|
1764
|
+
return { ...result, ...example };
|
|
1765
|
+
}
|
|
1766
|
+
return result;
|
|
1767
|
+
}, initial);
|
|
1768
|
+
}
|
|
1769
|
+
anyOf(schemas) {
|
|
1770
|
+
if (schemas.length === 0)
|
|
1771
|
+
return {};
|
|
1772
|
+
return this.handle(schemas[0]);
|
|
1773
|
+
}
|
|
1774
|
+
oneOf(schemas) {
|
|
1775
|
+
if (schemas.length === 0)
|
|
1776
|
+
return {};
|
|
1777
|
+
return this.handle(schemas[0]);
|
|
1778
|
+
}
|
|
1779
|
+
enum(schema) {
|
|
1780
|
+
return Array.isArray(schema.enum) && schema.enum.length > 0 ? schema.enum[0] : void 0;
|
|
2083
1781
|
}
|
|
2084
|
-
/**
|
|
2085
|
-
* Handle schemas by resolving references and delegating to appropriate handler
|
|
2086
|
-
*/
|
|
2087
1782
|
handle(schemaOrRef) {
|
|
2088
|
-
if (
|
|
2089
|
-
return this
|
|
1783
|
+
if (isRef5(schemaOrRef)) {
|
|
1784
|
+
return this.ref(schemaOrRef.$ref);
|
|
1785
|
+
}
|
|
1786
|
+
const schema = isRef5(schemaOrRef) ? followRef5(this.spec, schemaOrRef.$ref) : schemaOrRef;
|
|
1787
|
+
if (schema.example !== void 0) {
|
|
1788
|
+
return schema.example;
|
|
1789
|
+
}
|
|
1790
|
+
if (schema.default !== void 0) {
|
|
1791
|
+
return schema.default;
|
|
2090
1792
|
}
|
|
2091
|
-
const schema = schemaOrRef;
|
|
2092
1793
|
if (schema.allOf && Array.isArray(schema.allOf)) {
|
|
2093
|
-
return this
|
|
1794
|
+
return this.allOf(schema.allOf);
|
|
2094
1795
|
}
|
|
2095
1796
|
if (schema.anyOf && Array.isArray(schema.anyOf)) {
|
|
2096
|
-
return this
|
|
1797
|
+
return this.anyOf(schema.anyOf);
|
|
2097
1798
|
}
|
|
2098
1799
|
if (schema.oneOf && Array.isArray(schema.oneOf)) {
|
|
2099
|
-
return this
|
|
2100
|
-
}
|
|
2101
|
-
if (schema.enum && Array.isArray(schema.enum)) {
|
|
2102
|
-
return this.#enum(schema);
|
|
1800
|
+
return this.oneOf(schema.oneOf);
|
|
2103
1801
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
if (types.includes("null")) {
|
|
2107
|
-
nullable = true;
|
|
2108
|
-
types = types.filter((t) => t !== "null");
|
|
1802
|
+
if (schema.enum && Array.isArray(schema.enum) && schema.enum.length > 0) {
|
|
1803
|
+
return this.enum(schema);
|
|
2109
1804
|
}
|
|
1805
|
+
const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
|
|
2110
1806
|
if (types.length === 0) {
|
|
2111
1807
|
if (schema.properties || schema.additionalProperties) {
|
|
2112
|
-
|
|
1808
|
+
return this.object(schema);
|
|
2113
1809
|
} else if (schema.items) {
|
|
2114
|
-
|
|
1810
|
+
return this.array(schema);
|
|
2115
1811
|
}
|
|
1812
|
+
return "example";
|
|
1813
|
+
}
|
|
1814
|
+
const primaryType = types.find((t) => t !== "null") || types[0];
|
|
1815
|
+
switch (primaryType) {
|
|
1816
|
+
case "string":
|
|
1817
|
+
return this.string(schema);
|
|
1818
|
+
case "number":
|
|
1819
|
+
case "integer":
|
|
1820
|
+
return this.number(schema);
|
|
1821
|
+
case "boolean":
|
|
1822
|
+
return this.boolean(schema);
|
|
1823
|
+
case "object":
|
|
1824
|
+
return this.object(schema);
|
|
1825
|
+
case "array":
|
|
1826
|
+
return this.array(schema);
|
|
1827
|
+
case "null":
|
|
1828
|
+
return this.null();
|
|
1829
|
+
default:
|
|
1830
|
+
return "unknown";
|
|
2116
1831
|
}
|
|
2117
|
-
if (types.length === 0) {
|
|
2118
|
-
const lines2 = ["**Type:** `unknown`"];
|
|
2119
|
-
if (schema.description)
|
|
2120
|
-
lines2.push(schema.description);
|
|
2121
|
-
if (schema.default !== void 0)
|
|
2122
|
-
lines2.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
2123
|
-
return lines2;
|
|
2124
|
-
}
|
|
2125
|
-
if (types.length === 1) {
|
|
2126
|
-
return this.#normal(types[0], schema, nullable);
|
|
2127
|
-
}
|
|
2128
|
-
const typeString = types.join(" | ");
|
|
2129
|
-
const nullableSuffix = nullable ? " (nullable)" : "";
|
|
2130
|
-
const lines = [`**Type:** \`${typeString}\`${nullableSuffix}`];
|
|
2131
|
-
if (schema.description)
|
|
2132
|
-
lines.push(schema.description);
|
|
2133
|
-
if (schema.default !== void 0)
|
|
2134
|
-
lines.push(`**Default:** \`${JSON.stringify(schema.default)}\``);
|
|
2135
|
-
return lines;
|
|
2136
1832
|
}
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
}
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
1833
|
+
};
|
|
1834
|
+
|
|
1835
|
+
// packages/typescript/src/lib/typescript-snippet.ts
|
|
1836
|
+
var TypeScriptGenerator = class {
|
|
1837
|
+
#spec;
|
|
1838
|
+
#settings;
|
|
1839
|
+
#snippetEmitter;
|
|
1840
|
+
#clientName;
|
|
1841
|
+
#packageName;
|
|
1842
|
+
constructor(spec, settings) {
|
|
1843
|
+
this.#spec = spec;
|
|
1844
|
+
this.#settings = settings;
|
|
1845
|
+
this.#snippetEmitter = new SnippetEmitter(spec);
|
|
1846
|
+
this.#clientName = settings.name?.trim() ? pascalcase4(settings.name) : "Client";
|
|
1847
|
+
this.#packageName = settings.name ? `@${spinalcase2(this.#clientName.toLowerCase())}/sdk` : "sdk";
|
|
1848
|
+
}
|
|
1849
|
+
succinct(entry, operation, values) {
|
|
1850
|
+
let payload = "{}";
|
|
1851
|
+
if (!isEmpty3(operation.requestBody)) {
|
|
1852
|
+
const contentTypes = Object.keys(operation.requestBody.content || {});
|
|
1853
|
+
const schema = resolveRef2(
|
|
1854
|
+
this.#spec,
|
|
1855
|
+
operation.requestBody.content[contentTypes[0]].schema
|
|
1856
|
+
);
|
|
1857
|
+
const examplePayload = this.#snippetEmitter.handle({
|
|
1858
|
+
...schema,
|
|
1859
|
+
properties: Object.assign({}, schema.properties, schema.properties)
|
|
1860
|
+
});
|
|
1861
|
+
Object.assign(
|
|
1862
|
+
examplePayload,
|
|
1863
|
+
values.requestBody ?? {},
|
|
1864
|
+
values.pathParameters ?? {},
|
|
1865
|
+
values.queryParameters ?? {},
|
|
1866
|
+
values.headers ?? {},
|
|
1867
|
+
values.cookies ?? {}
|
|
1868
|
+
);
|
|
1869
|
+
payload = examplePayload;
|
|
1870
|
+
} else {
|
|
1871
|
+
const requestBody = { type: "object", properties: {} };
|
|
1872
|
+
patchParameters(
|
|
1873
|
+
this.#spec,
|
|
1874
|
+
requestBody,
|
|
1875
|
+
operation.parameters,
|
|
1876
|
+
operation.security ?? []
|
|
1877
|
+
);
|
|
1878
|
+
const examplePayload = this.#snippetEmitter.handle(requestBody);
|
|
1879
|
+
Object.assign(
|
|
1880
|
+
examplePayload,
|
|
1881
|
+
values.pathParameters ?? {},
|
|
1882
|
+
values.queryParameters ?? {},
|
|
1883
|
+
values.headers ?? {},
|
|
1884
|
+
values.cookies ?? {}
|
|
1885
|
+
);
|
|
1886
|
+
payload = examplePayload;
|
|
1887
|
+
}
|
|
1888
|
+
payload = JSON.stringify(
|
|
1889
|
+
payload,
|
|
1890
|
+
(key, value) => {
|
|
1891
|
+
if (value?.startsWith && value.startsWith("new")) {
|
|
1892
|
+
return `__REPLACE_${Math.random().toString(36).substring(2, 11)}__${value}__REPLACE_END__`;
|
|
2160
1893
|
}
|
|
1894
|
+
return value;
|
|
1895
|
+
},
|
|
1896
|
+
2
|
|
1897
|
+
).replace(/"__REPLACE_[^"]*__([^"]*?)__REPLACE_END__"/g, "$1");
|
|
1898
|
+
let successResponse;
|
|
1899
|
+
for (const status in operation.responses) {
|
|
1900
|
+
if (status.startsWith("2")) {
|
|
1901
|
+
successResponse = operation.responses[status];
|
|
1902
|
+
break;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
if (successResponse) {
|
|
1906
|
+
if (successResponse.headers?.["Transfer-Encoding"]) {
|
|
1907
|
+
return this.#httpStreaming(entry, payload);
|
|
1908
|
+
}
|
|
1909
|
+
if (successResponse.content && successResponse.content["application/octet-stream"]) {
|
|
1910
|
+
return this.#streamDownload(entry, payload);
|
|
2161
1911
|
}
|
|
2162
1912
|
}
|
|
2163
|
-
|
|
1913
|
+
if (!isEmpty3(operation["x-pagination"])) {
|
|
1914
|
+
return this.#pagination(operation, entry, payload);
|
|
1915
|
+
}
|
|
1916
|
+
return this.#normal(entry, payload);
|
|
2164
1917
|
}
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
1918
|
+
#pagination(opeartion, entry, payload) {
|
|
1919
|
+
const pagination = opeartion["x-pagination"];
|
|
1920
|
+
switch (pagination.type) {
|
|
1921
|
+
case "page":
|
|
1922
|
+
return {
|
|
1923
|
+
content: `const result = ${this.#ddd(entry, payload)}`,
|
|
1924
|
+
footer: `for await (const page of result) {
|
|
1925
|
+
console.log(page);
|
|
1926
|
+
}`
|
|
1927
|
+
};
|
|
1928
|
+
case "offset":
|
|
1929
|
+
return {
|
|
1930
|
+
content: `const result = ${this.#ddd(entry, payload)}`,
|
|
1931
|
+
footer: `for await (const page of result) {
|
|
1932
|
+
console.log(page);
|
|
1933
|
+
}`
|
|
1934
|
+
};
|
|
1935
|
+
case "cursor":
|
|
1936
|
+
return {
|
|
1937
|
+
content: `const result = ${this.#ddd(entry, payload)}`,
|
|
1938
|
+
footer: `for await (const page of result) {
|
|
1939
|
+
console.log(page);
|
|
1940
|
+
}`
|
|
1941
|
+
};
|
|
2180
1942
|
}
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
1943
|
+
return this.#normal(entry, payload);
|
|
1944
|
+
}
|
|
1945
|
+
#normal(entry, payload) {
|
|
1946
|
+
return {
|
|
1947
|
+
content: `const result = ${this.#ddd(entry, payload)};`,
|
|
1948
|
+
footer: "console.log(result.data)"
|
|
1949
|
+
};
|
|
1950
|
+
}
|
|
1951
|
+
#streamDownload(entry, payload) {
|
|
1952
|
+
return {
|
|
1953
|
+
content: `const stream = ${this.#ddd(entry, payload)}`,
|
|
1954
|
+
footer: `await writeFile('./report.pdf', stream);`
|
|
1955
|
+
};
|
|
1956
|
+
}
|
|
1957
|
+
#httpStreaming(entry, payload) {
|
|
1958
|
+
return {
|
|
1959
|
+
content: `const stream = ${this.#ddd(entry, payload)}`,
|
|
1960
|
+
footer: `for await (const chunk of stream) {
|
|
1961
|
+
console.log(chunk);
|
|
1962
|
+
}`
|
|
1963
|
+
};
|
|
1964
|
+
}
|
|
1965
|
+
#ddd(entry, payload) {
|
|
1966
|
+
return `await ${camelcase3(this.#clientName)}.request('${entry.method.toUpperCase()} ${entry.path}', ${payload});`;
|
|
1967
|
+
}
|
|
1968
|
+
snippet(entry, operation, config = {}) {
|
|
1969
|
+
const payload = this.succinct(entry, operation, config);
|
|
1970
|
+
const content = [
|
|
1971
|
+
this.client(),
|
|
1972
|
+
"",
|
|
1973
|
+
payload.content,
|
|
1974
|
+
"",
|
|
1975
|
+
payload.footer
|
|
1976
|
+
];
|
|
1977
|
+
if (config.frame !== false) {
|
|
1978
|
+
content.unshift("```typescript");
|
|
1979
|
+
content.push("```");
|
|
2186
1980
|
}
|
|
2187
|
-
|
|
2188
|
-
}
|
|
2189
|
-
|
|
1981
|
+
return content.join("\n");
|
|
1982
|
+
}
|
|
1983
|
+
#authentication() {
|
|
1984
|
+
return securityToOptions2(
|
|
1985
|
+
this.#spec,
|
|
1986
|
+
this.#spec.security ?? [],
|
|
1987
|
+
this.#spec.components?.securitySchemes ?? {}
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
client() {
|
|
1991
|
+
const inputs = [
|
|
1992
|
+
`baseUrl: '${this.#spec.servers?.[0]?.url ?? "http://localhost:3000"}'`
|
|
1993
|
+
];
|
|
1994
|
+
const authOptions = this.#authentication();
|
|
1995
|
+
if (!isEmpty3(authOptions)) {
|
|
1996
|
+
const [firstAuth] = authOptions;
|
|
1997
|
+
inputs.push(`${firstAuth.name}: ${firstAuth.example}`);
|
|
1998
|
+
}
|
|
1999
|
+
return `import { ${this.#clientName} } from '${this.#packageName}';
|
|
2000
|
+
|
|
2001
|
+
const ${camelcase3(this.#clientName)} = new ${this.#clientName}({
|
|
2002
|
+
${inputs.join(",\n ")}
|
|
2003
|
+
});`;
|
|
2004
|
+
}
|
|
2005
|
+
};
|
|
2006
|
+
function generateSnippet(spec, settings, entry, operation, config = {}) {
|
|
2007
|
+
const generator = new TypeScriptGenerator(spec, settings);
|
|
2008
|
+
return generator.snippet(entry, operation, config);
|
|
2190
2009
|
}
|
|
2191
2010
|
|
|
2192
2011
|
// packages/typescript/src/lib/generate.ts
|
|
@@ -2195,7 +2014,7 @@ function security(spec) {
|
|
|
2195
2014
|
const components = spec.components || {};
|
|
2196
2015
|
const securitySchemes = components.securitySchemes || {};
|
|
2197
2016
|
const paths = Object.values(spec.paths ?? {});
|
|
2198
|
-
const options = securityToOptions(security2, securitySchemes);
|
|
2017
|
+
const options = securityToOptions(spec, security2, securitySchemes);
|
|
2199
2018
|
for (const it of paths) {
|
|
2200
2019
|
for (const method of methods) {
|
|
2201
2020
|
const operation = it[method];
|
|
@@ -2204,64 +2023,82 @@ function security(spec) {
|
|
|
2204
2023
|
}
|
|
2205
2024
|
Object.assign(
|
|
2206
2025
|
options,
|
|
2207
|
-
securityToOptions(
|
|
2026
|
+
securityToOptions(
|
|
2027
|
+
spec,
|
|
2028
|
+
operation.security || [],
|
|
2029
|
+
securitySchemes,
|
|
2030
|
+
"input"
|
|
2031
|
+
)
|
|
2208
2032
|
);
|
|
2209
2033
|
}
|
|
2210
2034
|
}
|
|
2211
2035
|
return options;
|
|
2212
2036
|
}
|
|
2213
|
-
async function generate(
|
|
2037
|
+
async function generate(openapi, settings) {
|
|
2038
|
+
const spec = augmentSpec(
|
|
2039
|
+
{ spec: openapi, responses: { flattenErrorResponses: true } },
|
|
2040
|
+
false
|
|
2041
|
+
);
|
|
2042
|
+
const generator = new TypeScriptGenerator(spec, settings);
|
|
2214
2043
|
const style = Object.assign(
|
|
2215
2044
|
{},
|
|
2216
2045
|
{
|
|
2217
|
-
errorAsValue:
|
|
2046
|
+
errorAsValue: false,
|
|
2218
2047
|
name: "github",
|
|
2219
2048
|
outputType: "default"
|
|
2220
2049
|
},
|
|
2221
2050
|
settings.style ?? {}
|
|
2222
2051
|
);
|
|
2052
|
+
const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
|
|
2223
2053
|
settings.useTsExtension ??= true;
|
|
2054
|
+
const { writer, files: writtenFiles } = createWriterProxy(
|
|
2055
|
+
settings.writer ?? writeFiles,
|
|
2056
|
+
output
|
|
2057
|
+
);
|
|
2058
|
+
settings.writer = writer;
|
|
2059
|
+
settings.readFolder ??= async (folder) => {
|
|
2060
|
+
const files = await readdir(folder, { withFileTypes: true });
|
|
2061
|
+
return files.map((file) => ({
|
|
2062
|
+
fileName: file.name,
|
|
2063
|
+
filePath: join2(file.parentPath, file.name),
|
|
2064
|
+
isFolder: file.isDirectory()
|
|
2065
|
+
}));
|
|
2066
|
+
};
|
|
2224
2067
|
const makeImport = (moduleSpecifier) => {
|
|
2225
2068
|
return settings.useTsExtension ? `${moduleSpecifier}.ts` : moduleSpecifier;
|
|
2226
2069
|
};
|
|
2227
|
-
const {
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
}
|
|
2233
|
-
);
|
|
2234
|
-
const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
|
|
2070
|
+
const { endpoints, groups, commonZod } = generateCode({
|
|
2071
|
+
spec,
|
|
2072
|
+
style,
|
|
2073
|
+
makeImport
|
|
2074
|
+
});
|
|
2235
2075
|
const options = security(spec);
|
|
2236
|
-
const clientName =
|
|
2237
|
-
const
|
|
2238
|
-
const
|
|
2239
|
-
|
|
2240
|
-
await
|
|
2076
|
+
const clientName = pascalcase5((settings.name || "client").trim());
|
|
2077
|
+
const packageName = settings.name ? `@${spinalcase3(settings.name.trim().toLowerCase())}/sdk` : "sdk";
|
|
2078
|
+
const inputs = toInputs(groups, commonZod, makeImport);
|
|
2079
|
+
const models = serializeModels(spec);
|
|
2080
|
+
await settings.writer(output, {
|
|
2241
2081
|
"outputs/.gitkeep": "",
|
|
2242
2082
|
"inputs/.gitkeep": "",
|
|
2243
2083
|
"models/.getkeep": ""
|
|
2244
2084
|
});
|
|
2245
|
-
await
|
|
2246
|
-
"interceptors.ts": `
|
|
2247
|
-
import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
|
|
2248
|
-
${interceptors_default}`,
|
|
2085
|
+
await settings.writer(join2(output, "http"), {
|
|
2249
2086
|
"parse-response.ts": parse_response_default,
|
|
2250
|
-
"send-request.ts": `import z from 'zod';
|
|
2251
|
-
import type { Interceptor } from './${makeImport("interceptors")}';
|
|
2252
|
-
import { buffered } from './${makeImport("parse-response")}';
|
|
2253
|
-
import { parseInput } from './${makeImport("parser")}';
|
|
2254
|
-
import type { RequestConfig } from './${makeImport("request")}';
|
|
2255
|
-
import { APIError, APIResponse } from './${makeImport("response")}';
|
|
2256
|
-
|
|
2257
|
-
${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputType: style.outputType })}`,
|
|
2258
2087
|
"response.ts": response_default,
|
|
2259
2088
|
"parser.ts": parser_default,
|
|
2260
|
-
"request.ts": request_default
|
|
2089
|
+
"request.ts": request_default,
|
|
2090
|
+
"dispatcher.ts": `import z from 'zod';
|
|
2091
|
+
import { type Interceptor } from '${makeImport("../http/interceptors")}';
|
|
2092
|
+
import { type RequestConfig } from '${makeImport("../http/request")}';
|
|
2093
|
+
import { buffered } from '${makeImport("./parse-response")}';
|
|
2094
|
+
import { APIError, APIResponse, type SuccessfulResponse, type ProblematicResponse } from '${makeImport("./response")}';
|
|
2095
|
+
|
|
2096
|
+
${template2(dispatcher_default, {})({ throwError: !style.errorAsValue, outputType: style.outputType })}`,
|
|
2097
|
+
"interceptors.ts": `
|
|
2098
|
+
import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
|
|
2099
|
+
${interceptors_default}`
|
|
2261
2100
|
});
|
|
2262
|
-
await
|
|
2263
|
-
const modelsImports = Object.entries(commonSchemas).map(([name]) => name);
|
|
2264
|
-
await writeFiles(output, {
|
|
2101
|
+
await settings.writer(output, {
|
|
2265
2102
|
"client.ts": client_default(
|
|
2266
2103
|
{
|
|
2267
2104
|
name: clientName,
|
|
@@ -2271,52 +2108,80 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
|
|
|
2271
2108
|
},
|
|
2272
2109
|
style
|
|
2273
2110
|
),
|
|
2274
|
-
...
|
|
2275
|
-
...endpoints
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
(it) => `import type { ${it} } from './${it}.ts';`
|
|
2283
|
-
),
|
|
2284
|
-
`export type ${name} = ${schema};`
|
|
2285
|
-
].join("\n")
|
|
2286
|
-
])
|
|
2287
|
-
)
|
|
2111
|
+
...inputs,
|
|
2112
|
+
...endpoints
|
|
2113
|
+
});
|
|
2114
|
+
await settings.writer(output, models);
|
|
2115
|
+
await settings.writer(join2(output, "pagination"), {
|
|
2116
|
+
"cursor-pagination.ts": cursor_pagination_default,
|
|
2117
|
+
"offset-pagination.ts": offset_pagination_default,
|
|
2118
|
+
"page-pagination.ts": page_pagination_default
|
|
2288
2119
|
});
|
|
2120
|
+
const metadata = await readWriteMetadata(output, Array.from(writtenFiles));
|
|
2121
|
+
if (settings.cleanup !== false && writtenFiles.size > 0) {
|
|
2122
|
+
await cleanFiles(metadata.content, output, [
|
|
2123
|
+
"/tsconfig*.json",
|
|
2124
|
+
"/package.json",
|
|
2125
|
+
"/metadata.json",
|
|
2126
|
+
"/**/index.ts"
|
|
2127
|
+
]);
|
|
2128
|
+
}
|
|
2289
2129
|
const folders = [
|
|
2290
|
-
getFolderExports(
|
|
2130
|
+
getFolderExports(
|
|
2131
|
+
join2(output, "outputs"),
|
|
2132
|
+
settings.readFolder,
|
|
2133
|
+
settings.useTsExtension
|
|
2134
|
+
),
|
|
2291
2135
|
getFolderExports(
|
|
2292
2136
|
join2(output, "inputs"),
|
|
2137
|
+
settings.readFolder,
|
|
2293
2138
|
settings.useTsExtension,
|
|
2294
2139
|
["ts"],
|
|
2295
|
-
(dirent) => dirent.
|
|
2140
|
+
(dirent) => dirent.isFolder && ["schemas"].includes(dirent.fileName)
|
|
2141
|
+
),
|
|
2142
|
+
getFolderExports(
|
|
2143
|
+
join2(output, "api"),
|
|
2144
|
+
settings.readFolder,
|
|
2145
|
+
settings.useTsExtension
|
|
2296
2146
|
),
|
|
2297
|
-
getFolderExports(join2(output, "api"), settings.useTsExtension),
|
|
2298
2147
|
getFolderExports(
|
|
2299
2148
|
join2(output, "http"),
|
|
2149
|
+
settings.readFolder,
|
|
2300
2150
|
settings.useTsExtension,
|
|
2301
2151
|
["ts"],
|
|
2302
|
-
(dirent) => !["response.ts", "parser.ts"].includes(dirent.
|
|
2152
|
+
(dirent) => !["response.ts", "parser.ts"].includes(dirent.fileName)
|
|
2153
|
+
),
|
|
2154
|
+
getFolderExports(
|
|
2155
|
+
join2(output, "models"),
|
|
2156
|
+
settings.readFolder,
|
|
2157
|
+
settings.useTsExtension
|
|
2303
2158
|
)
|
|
2304
2159
|
];
|
|
2305
|
-
if (modelsImports.length) {
|
|
2306
|
-
folders.push(
|
|
2307
|
-
getFolderExports(join2(output, "models"), settings.useTsExtension)
|
|
2308
|
-
);
|
|
2309
|
-
}
|
|
2310
2160
|
const [outputIndex, inputsIndex, apiIndex, httpIndex, modelsIndex] = await Promise.all(folders);
|
|
2311
|
-
await
|
|
2161
|
+
await settings.writer(join2(output, "pagination"), {
|
|
2162
|
+
"index.ts": await getFolderExports(
|
|
2163
|
+
join2(output, "pagination"),
|
|
2164
|
+
settings.readFolder,
|
|
2165
|
+
settings.useTsExtension,
|
|
2166
|
+
["ts"]
|
|
2167
|
+
)
|
|
2168
|
+
});
|
|
2169
|
+
await settings.writer(output, {
|
|
2312
2170
|
"api/index.ts": apiIndex,
|
|
2313
2171
|
"outputs/index.ts": outputIndex,
|
|
2314
2172
|
"inputs/index.ts": inputsIndex || null,
|
|
2315
2173
|
"http/index.ts": httpIndex,
|
|
2316
|
-
|
|
2174
|
+
"models/index.ts": modelsIndex
|
|
2175
|
+
// ...(modelsImports.length ? { 'models/index.ts': modelsIndex } : {}),
|
|
2317
2176
|
});
|
|
2318
|
-
await
|
|
2319
|
-
"index.ts": await getFolderExports(
|
|
2177
|
+
await settings.writer(output, {
|
|
2178
|
+
"index.ts": await getFolderExports(
|
|
2179
|
+
output,
|
|
2180
|
+
settings.readFolder,
|
|
2181
|
+
settings.useTsExtension,
|
|
2182
|
+
["ts"],
|
|
2183
|
+
(config) => config.fileName.endsWith("pagination")
|
|
2184
|
+
)
|
|
2320
2185
|
});
|
|
2321
2186
|
if (settings.mode === "full") {
|
|
2322
2187
|
const configFiles = {
|
|
@@ -2324,9 +2189,23 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
|
|
|
2324
2189
|
ignoreIfExists: true,
|
|
2325
2190
|
content: JSON.stringify(
|
|
2326
2191
|
{
|
|
2327
|
-
name:
|
|
2192
|
+
name: packageName,
|
|
2193
|
+
version: "0.0.1",
|
|
2328
2194
|
type: "module",
|
|
2329
2195
|
main: "./src/index.ts",
|
|
2196
|
+
module: "./src/index.ts",
|
|
2197
|
+
types: "./src/index.ts",
|
|
2198
|
+
publishConfig: {
|
|
2199
|
+
access: "public"
|
|
2200
|
+
},
|
|
2201
|
+
exports: {
|
|
2202
|
+
"./package.json": "./package.json",
|
|
2203
|
+
".": {
|
|
2204
|
+
types: "./src/index.ts",
|
|
2205
|
+
import: "./src/index.ts",
|
|
2206
|
+
default: "./src/index.ts"
|
|
2207
|
+
}
|
|
2208
|
+
},
|
|
2330
2209
|
dependencies: {
|
|
2331
2210
|
"fast-content-type-parse": "^3.0.0",
|
|
2332
2211
|
zod: "^3.24.2"
|
|
@@ -2359,33 +2238,104 @@ ${template2(send_request_default, {})({ throwError: !style.errorAsValue, outputT
|
|
|
2359
2238
|
)
|
|
2360
2239
|
}
|
|
2361
2240
|
};
|
|
2362
|
-
if (readme) {
|
|
2241
|
+
if (settings.readme) {
|
|
2363
2242
|
configFiles["README.md"] = {
|
|
2364
|
-
ignoreIfExists:
|
|
2365
|
-
content:
|
|
2243
|
+
ignoreIfExists: false,
|
|
2244
|
+
content: toReadme(spec, {
|
|
2245
|
+
generateSnippet: (...args) => generator.snippet(...args)
|
|
2246
|
+
})
|
|
2366
2247
|
};
|
|
2367
2248
|
}
|
|
2368
|
-
await
|
|
2249
|
+
await settings.writer(settings.output, configFiles);
|
|
2369
2250
|
}
|
|
2370
2251
|
await settings.formatCode?.({
|
|
2371
2252
|
output,
|
|
2372
2253
|
env: npmRunPathEnv()
|
|
2373
2254
|
});
|
|
2374
2255
|
}
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2256
|
+
function serializeModels(spec) {
|
|
2257
|
+
const filesMap = {};
|
|
2258
|
+
const files = {};
|
|
2259
|
+
for (const [name, schema] of Object.entries(spec.components.schemas)) {
|
|
2260
|
+
const isResponseBody = schema["x-responsebody"];
|
|
2261
|
+
const isRequestBody = schema["x-requestbody"];
|
|
2262
|
+
const responseGroup = schema["x-response-group"];
|
|
2263
|
+
const stream = schema["x-stream"];
|
|
2264
|
+
const folder = isResponseBody ? "outputs" : "models";
|
|
2265
|
+
let typeContent = "ReadableStream";
|
|
2266
|
+
if (!stream) {
|
|
2267
|
+
const serializer = new TypeScriptEmitter(spec);
|
|
2268
|
+
typeContent = serializer.handle(schema, true);
|
|
2269
|
+
}
|
|
2270
|
+
const fileContent = [
|
|
2271
|
+
`
|
|
2272
|
+
${schema.description ? `
|
|
2273
|
+
/**
|
|
2274
|
+
* ${schema.description}
|
|
2275
|
+
*/
|
|
2276
|
+
` : ""}`,
|
|
2277
|
+
`export type ${pascalcase5(sanitizeTag4(name))} = ${typeContent};`
|
|
2278
|
+
];
|
|
2279
|
+
const fileName = responseGroup ? join2(folder, `${spinalcase3(responseGroup)}.ts`) : join2(folder, `${spinalcase3(name)}.ts`);
|
|
2280
|
+
filesMap[fileName] ??= [];
|
|
2281
|
+
filesMap[fileName].push(fileContent.join("\n"));
|
|
2282
|
+
}
|
|
2283
|
+
for (const [group, contents] of Object.entries(filesMap)) {
|
|
2284
|
+
let fileContent = contents.join("\n");
|
|
2285
|
+
if (fileContent.includes("models.")) {
|
|
2286
|
+
fileContent = `import type * as models from '../index.ts';
|
|
2287
|
+
${fileContent}`;
|
|
2288
|
+
}
|
|
2289
|
+
files[group] = fileContent;
|
|
2290
|
+
}
|
|
2291
|
+
return files;
|
|
2292
|
+
}
|
|
2293
|
+
function toInputs(operationsSet, commonZod, makeImport) {
|
|
2294
|
+
const commonImports = commonZod.keys().toArray();
|
|
2295
|
+
const inputs = {};
|
|
2296
|
+
for (const [name, operations] of Object.entries(operationsSet)) {
|
|
2297
|
+
const output = [];
|
|
2298
|
+
const imports = /* @__PURE__ */ new Set(['import { z } from "zod";']);
|
|
2299
|
+
for (const operation of operations) {
|
|
2300
|
+
const schemaName = camelcase4(`${operation.name} schema`);
|
|
2301
|
+
const schema = `export const ${schemaName} = ${Object.keys(operation.schemas).length === 1 ? Object.values(operation.schemas)[0] : toLitObject2(operation.schemas)};`;
|
|
2302
|
+
for (const it of commonImports) {
|
|
2303
|
+
if (schema.includes(it)) {
|
|
2304
|
+
imports.add(
|
|
2305
|
+
`import { ${it} } from './schemas/${makeImport(spinalcase3(it))}';`
|
|
2306
|
+
);
|
|
2307
|
+
}
|
|
2308
|
+
}
|
|
2309
|
+
output.push(schema);
|
|
2310
|
+
}
|
|
2311
|
+
inputs[`inputs/${spinalcase3(name)}.ts`] = [...imports, ...output].join("\n") + "\n";
|
|
2312
|
+
}
|
|
2313
|
+
const schemas = commonZod.entries().reduce((acc, [name, schema]) => {
|
|
2314
|
+
const output = [`import { z } from 'zod';`];
|
|
2315
|
+
const content = `export const ${name} = ${schema};`;
|
|
2316
|
+
for (const schema2 of commonImports) {
|
|
2317
|
+
const preciseMatch = new RegExp(`\\b${schema2}\\b`);
|
|
2318
|
+
if (preciseMatch.test(content) && schema2 !== name) {
|
|
2319
|
+
output.push(
|
|
2320
|
+
`import { ${schema2} } from './${makeImport(spinalcase3(schema2))}';`
|
|
2321
|
+
);
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2324
|
+
output.push(content);
|
|
2325
|
+
return [
|
|
2326
|
+
[`inputs/schemas/${spinalcase3(name)}.ts`, output.join("\n")],
|
|
2327
|
+
...acc
|
|
2328
|
+
];
|
|
2329
|
+
}, []);
|
|
2330
|
+
return {
|
|
2331
|
+
...Object.fromEntries(schemas),
|
|
2332
|
+
...inputs
|
|
2333
|
+
};
|
|
2386
2334
|
}
|
|
2387
2335
|
export {
|
|
2336
|
+
TypeScriptGenerator,
|
|
2388
2337
|
generate,
|
|
2389
|
-
|
|
2338
|
+
generateSnippet,
|
|
2339
|
+
toInputs
|
|
2390
2340
|
};
|
|
2391
2341
|
//# sourceMappingURL=index.js.map
|