typedapi-client-helpers 0.3.0 → 0.3.1
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/CHANGELOG.md +72 -68
- package/README.md +20 -10
- package/dist/cjs/utils/handleApiResponse.js +9 -1
- package/dist/cjs/utils/handleApiResponse.js.map +1 -1
- package/dist/esm/utils/handleApiResponse.js +8 -1
- package/dist/esm/utils/handleApiResponse.js.map +1 -1
- package/dist/types/interfaces/ApiResult.d.ts +9 -0
- package/dist/types/interfaces/ApiResult.d.ts.map +1 -1
- package/dist/types/utils/handleApiResponse.d.ts +3 -2
- package/dist/types/utils/handleApiResponse.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/openapi-generator/index.mjs +0 -9
- package/scripts/openapi-generator/method-template.mjs +405 -397
- package/scripts/openapi-generator/schema.mjs +737 -735
|
@@ -1,397 +1,405 @@
|
|
|
1
|
-
import { getOperationTypes, generatedFileHeader } from "./schema.mjs";
|
|
2
|
-
import { pascalCase, typePropertyName } from "./names.mjs";
|
|
3
|
-
|
|
4
|
-
function unique(values) {
|
|
5
|
-
return [...new Set(values.filter(Boolean))];
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
function createImport(names, from, typeOnly = false) {
|
|
9
|
-
const cleanNames = unique(names).sort((a, b) => a.localeCompare(b));
|
|
10
|
-
if (cleanNames.length === 0) return "";
|
|
11
|
-
const keyword = typeOnly ? "import type" : "import";
|
|
12
|
-
if (cleanNames.length <= 3) return `${keyword} { ${cleanNames.join(", ")} } from ${JSON.stringify(from)};`;
|
|
13
|
-
return `${keyword} {\n${cleanNames.map((name) => ` ${name},`).join("\n")}\n} from ${JSON.stringify(from)};`;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function isPrimitiveType(type) {
|
|
17
|
-
return /^(void|any|unknown|string|number|boolean|Blob|File|Record<)/.test(type);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function extractTopLevelTypeNames(type) {
|
|
21
|
-
const withoutLiterals = String(type ?? "").replace(/"(?:\\.|[^"])*"|'(?:\\.|[^'])*'/g, "");
|
|
22
|
-
const matches = withoutLiterals.match(/\b[A-Z][A-Za-z0-9_]*\b/g) ?? [];
|
|
23
|
-
return matches.filter(
|
|
24
|
-
(name) => ![
|
|
25
|
-
"Array", "Record", "Promise", "HttpResponse", "Blob", "File", "ApiResult",
|
|
26
|
-
"RequestParams", "FormData", "ApiClientError",
|
|
27
|
-
].includes(name),
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function localPropertyAccess(baseExpression, wireName) {
|
|
32
|
-
return `${baseExpression}[${JSON.stringify(typePropertyName(wireName))}]`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parameterValueExpression(sourceExpression, parameter) {
|
|
36
|
-
return localPropertyAccess(sourceExpression, parameter.name);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function parameterObjectExpression(sourceExpression, parameters) {
|
|
40
|
-
const properties = parameters.map(
|
|
41
|
-
(parameter) => `${JSON.stringify(parameter.name)}: ${parameterValueExpression(
|
|
42
|
-
sourceExpression,
|
|
43
|
-
parameter,
|
|
44
|
-
)}`,
|
|
45
|
-
);
|
|
46
|
-
return properties.length <= 2
|
|
47
|
-
? `{ ${properties.join(", ")} }`
|
|
48
|
-
: `{
|
|
49
|
-
${properties.map((property) => ` ${property},`).join("\n")}
|
|
50
|
-
}`;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function pathExpression(routePath, pathParams, inputName = "input") {
|
|
54
|
-
const parts = [];
|
|
55
|
-
let cursor = 0;
|
|
56
|
-
const regex = /\{([^}]+)\}/g;
|
|
57
|
-
let match;
|
|
58
|
-
|
|
59
|
-
while ((match = regex.exec(routePath))) {
|
|
60
|
-
const [placeholder, paramName] = match;
|
|
61
|
-
const before = routePath.slice(cursor, match.index);
|
|
62
|
-
if (before) parts.push(JSON.stringify(before));
|
|
63
|
-
const parameter = pathParams.find((item) => item.name === paramName);
|
|
64
|
-
const valueExpression = parameter
|
|
65
|
-
? parameterValueExpression(inputName, parameter)
|
|
66
|
-
: localPropertyAccess(inputName, paramName);
|
|
67
|
-
parts.push(`encodeURIComponent(String(${valueExpression}))`);
|
|
68
|
-
cursor = match.index + placeholder.length;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const after = routePath.slice(cursor);
|
|
72
|
-
if (after) parts.push(JSON.stringify(after));
|
|
73
|
-
if (parts.length === 0) return JSON.stringify(routePath);
|
|
74
|
-
|
|
75
|
-
return parts
|
|
76
|
-
.map((part) => /^"/.test(part) ? part.slice(1, -1).replace(/`/g, "\\`") : `\${${part}}`)
|
|
77
|
-
.join("")
|
|
78
|
-
.replace(/^/, "`")
|
|
79
|
-
.replace(/$/, "`");
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function requestFormat(responseContentType, responseType) {
|
|
83
|
-
if (responseType === "void" || !responseContentType) return undefined;
|
|
84
|
-
if (responseContentType.includes("json")) return "json";
|
|
85
|
-
if (responseContentType.startsWith("text/")) return "text";
|
|
86
|
-
if (responseContentType === "application/octet-stream" || responseType === "Blob") return "blob";
|
|
87
|
-
return undefined;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function contentTypeEnum(contentType) {
|
|
91
|
-
switch (contentType) {
|
|
92
|
-
case "application/json": return "ContentType.Json";
|
|
93
|
-
case "application/vnd.api+json": return "ContentType.JsonApi";
|
|
94
|
-
case "multipart/form-data": return "ContentType.FormData";
|
|
95
|
-
case "application/x-www-form-urlencoded": return "ContentType.UrlEncoded";
|
|
96
|
-
case "text/plain": return "ContentType.Text";
|
|
97
|
-
default: return contentType?.includes("json") ? "ContentType.Json" : undefined;
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function toWireExpression(valueExpression, schemaKey) {
|
|
102
|
-
if (!schemaKey) return valueExpression;
|
|
103
|
-
return `toWireValue(${valueExpression}, typedApiWireSchemas[${JSON.stringify(schemaKey)}], typedApiWireSchemas)`;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
function requestItems(operationInfo, operationTypes, options = {}) {
|
|
107
|
-
const inputName = options.inputName ?? "input";
|
|
108
|
-
const items = [
|
|
109
|
-
"...params",
|
|
110
|
-
`path: ${pathExpression(
|
|
111
|
-
operationInfo.routePath,
|
|
112
|
-
operationTypes.parametersByLocation.path,
|
|
113
|
-
inputName,
|
|
114
|
-
)}`,
|
|
115
|
-
`method: ${JSON.stringify(operationInfo.method)}`,
|
|
116
|
-
];
|
|
117
|
-
|
|
118
|
-
if (options.queryValueName) {
|
|
119
|
-
items.push(`query: ${options.queryValueName}`);
|
|
120
|
-
} else if (operationTypes.hasQueryParams) {
|
|
121
|
-
items.push(
|
|
122
|
-
`query: ${parameterObjectExpression(
|
|
123
|
-
inputName,
|
|
124
|
-
operationTypes.parametersByLocation.query,
|
|
125
|
-
)}`,
|
|
126
|
-
);
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (operationTypes.bodyType) {
|
|
130
|
-
const bodyExpression = options.bodyValueName ?? "data";
|
|
131
|
-
items.push(`body: ${toWireExpression(bodyExpression, operationTypes.bodyWireSchemaKey)}`);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
if (operationTypes.hasHeaderParams || operationTypes.hasCookieParams) {
|
|
135
|
-
const headerSources = ["params.headers"];
|
|
136
|
-
if (operationTypes.hasHeaderParams) {
|
|
137
|
-
const headersExpression = parameterObjectExpression(
|
|
138
|
-
inputName,
|
|
139
|
-
operationTypes.parametersByLocation.header,
|
|
140
|
-
);
|
|
141
|
-
headerSources.push(`toRequestHeaders(${headersExpression})`);
|
|
142
|
-
}
|
|
143
|
-
if (operationTypes.hasCookieParams) {
|
|
144
|
-
const cookiesExpression = parameterObjectExpression(
|
|
145
|
-
inputName,
|
|
146
|
-
operationTypes.parametersByLocation.cookie,
|
|
147
|
-
);
|
|
148
|
-
const cookieCondition = operationTypes.parametersByLocation.cookie
|
|
149
|
-
.map((parameter) => `${parameterValueExpression(inputName, parameter)} !== undefined`)
|
|
150
|
-
.join(" || ") || "false";
|
|
151
|
-
headerSources.push(
|
|
152
|
-
`${cookieCondition} ? { Cookie: toCookieHeader(${cookiesExpression}) } : undefined`,
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
items.push(`headers: mergeHeaders(${headerSources.join(", ")})`);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const type = contentTypeEnum(operationTypes.contentType);
|
|
159
|
-
if (type) items.push(`type: ${type}`);
|
|
160
|
-
const format = requestFormat(operationTypes.responseContentType, operationTypes.responseType);
|
|
161
|
-
if (format) items.push(`format: ${JSON.stringify(format)}`);
|
|
162
|
-
return items;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function callbackOptions(operationTypes, defaultHandlers) {
|
|
166
|
-
const items = [];
|
|
167
|
-
if (defaultHandlers) {
|
|
168
|
-
items.push("onSuccess: onSuccess ?? typedApiDefaultSuccessHandler");
|
|
169
|
-
items.push("onError: onError ?? typedApiDefaultErrorHandler");
|
|
170
|
-
} else {
|
|
171
|
-
items.push("onSuccess");
|
|
172
|
-
items.push("onError");
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
if (operationTypes.responseWireSchemaKey) {
|
|
176
|
-
items.push(
|
|
177
|
-
`transformResponse: (value) => fromWireValue(value, typedApiWireSchemas[${JSON.stringify(
|
|
178
|
-
operationTypes.responseWireSchemaKey,
|
|
179
|
-
)}], typedApiWireSchemas) as ${operationTypes.responseType}`,
|
|
180
|
-
);
|
|
181
|
-
}
|
|
182
|
-
if (operationTypes.errorWireSchemaKey) {
|
|
183
|
-
items.push(
|
|
184
|
-
`transformError: (value) => fromWireValue(value, typedApiWireSchemas[${JSON.stringify(
|
|
185
|
-
operationTypes.errorWireSchemaKey,
|
|
186
|
-
)}], typedApiWireSchemas) as ${operationTypes.errorType}`,
|
|
187
|
-
);
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
function
|
|
200
|
-
return `
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
function
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
return
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
export function
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
if (
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
if (
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
if (
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
) {
|
|
363
|
-
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
1
|
+
import { getOperationTypes, generatedFileHeader } from "./schema.mjs";
|
|
2
|
+
import { pascalCase, typePropertyName } from "./names.mjs";
|
|
3
|
+
|
|
4
|
+
function unique(values) {
|
|
5
|
+
return [...new Set(values.filter(Boolean))];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function createImport(names, from, typeOnly = false) {
|
|
9
|
+
const cleanNames = unique(names).sort((a, b) => a.localeCompare(b));
|
|
10
|
+
if (cleanNames.length === 0) return "";
|
|
11
|
+
const keyword = typeOnly ? "import type" : "import";
|
|
12
|
+
if (cleanNames.length <= 3) return `${keyword} { ${cleanNames.join(", ")} } from ${JSON.stringify(from)};`;
|
|
13
|
+
return `${keyword} {\n${cleanNames.map((name) => ` ${name},`).join("\n")}\n} from ${JSON.stringify(from)};`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function isPrimitiveType(type) {
|
|
17
|
+
return /^(void|any|unknown|string|number|boolean|Blob|File|Record<)/.test(type);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function extractTopLevelTypeNames(type) {
|
|
21
|
+
const withoutLiterals = String(type ?? "").replace(/"(?:\\.|[^"])*"|'(?:\\.|[^'])*'/g, "");
|
|
22
|
+
const matches = withoutLiterals.match(/\b[A-Z][A-Za-z0-9_]*\b/g) ?? [];
|
|
23
|
+
return matches.filter(
|
|
24
|
+
(name) => ![
|
|
25
|
+
"Array", "Record", "Promise", "HttpResponse", "Blob", "File", "ApiResult",
|
|
26
|
+
"RequestParams", "FormData", "ApiClientError", "ApiHttpError",
|
|
27
|
+
].includes(name),
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function localPropertyAccess(baseExpression, wireName) {
|
|
32
|
+
return `${baseExpression}[${JSON.stringify(typePropertyName(wireName))}]`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function parameterValueExpression(sourceExpression, parameter) {
|
|
36
|
+
return localPropertyAccess(sourceExpression, parameter.name);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function parameterObjectExpression(sourceExpression, parameters) {
|
|
40
|
+
const properties = parameters.map(
|
|
41
|
+
(parameter) => `${JSON.stringify(parameter.name)}: ${parameterValueExpression(
|
|
42
|
+
sourceExpression,
|
|
43
|
+
parameter,
|
|
44
|
+
)}`,
|
|
45
|
+
);
|
|
46
|
+
return properties.length <= 2
|
|
47
|
+
? `{ ${properties.join(", ")} }`
|
|
48
|
+
: `{
|
|
49
|
+
${properties.map((property) => ` ${property},`).join("\n")}
|
|
50
|
+
}`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function pathExpression(routePath, pathParams, inputName = "input") {
|
|
54
|
+
const parts = [];
|
|
55
|
+
let cursor = 0;
|
|
56
|
+
const regex = /\{([^}]+)\}/g;
|
|
57
|
+
let match;
|
|
58
|
+
|
|
59
|
+
while ((match = regex.exec(routePath))) {
|
|
60
|
+
const [placeholder, paramName] = match;
|
|
61
|
+
const before = routePath.slice(cursor, match.index);
|
|
62
|
+
if (before) parts.push(JSON.stringify(before));
|
|
63
|
+
const parameter = pathParams.find((item) => item.name === paramName);
|
|
64
|
+
const valueExpression = parameter
|
|
65
|
+
? parameterValueExpression(inputName, parameter)
|
|
66
|
+
: localPropertyAccess(inputName, paramName);
|
|
67
|
+
parts.push(`encodeURIComponent(String(${valueExpression}))`);
|
|
68
|
+
cursor = match.index + placeholder.length;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const after = routePath.slice(cursor);
|
|
72
|
+
if (after) parts.push(JSON.stringify(after));
|
|
73
|
+
if (parts.length === 0) return JSON.stringify(routePath);
|
|
74
|
+
|
|
75
|
+
return parts
|
|
76
|
+
.map((part) => /^"/.test(part) ? part.slice(1, -1).replace(/`/g, "\\`") : `\${${part}}`)
|
|
77
|
+
.join("")
|
|
78
|
+
.replace(/^/, "`")
|
|
79
|
+
.replace(/$/, "`");
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function requestFormat(responseContentType, responseType) {
|
|
83
|
+
if (responseType === "void" || !responseContentType) return undefined;
|
|
84
|
+
if (responseContentType.includes("json")) return "json";
|
|
85
|
+
if (responseContentType.startsWith("text/")) return "text";
|
|
86
|
+
if (responseContentType === "application/octet-stream" || responseType === "Blob") return "blob";
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function contentTypeEnum(contentType) {
|
|
91
|
+
switch (contentType) {
|
|
92
|
+
case "application/json": return "ContentType.Json";
|
|
93
|
+
case "application/vnd.api+json": return "ContentType.JsonApi";
|
|
94
|
+
case "multipart/form-data": return "ContentType.FormData";
|
|
95
|
+
case "application/x-www-form-urlencoded": return "ContentType.UrlEncoded";
|
|
96
|
+
case "text/plain": return "ContentType.Text";
|
|
97
|
+
default: return contentType?.includes("json") ? "ContentType.Json" : undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function toWireExpression(valueExpression, schemaKey) {
|
|
102
|
+
if (!schemaKey) return valueExpression;
|
|
103
|
+
return `toWireValue(${valueExpression}, typedApiWireSchemas[${JSON.stringify(schemaKey)}], typedApiWireSchemas)`;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function requestItems(operationInfo, operationTypes, options = {}) {
|
|
107
|
+
const inputName = options.inputName ?? "input";
|
|
108
|
+
const items = [
|
|
109
|
+
"...params",
|
|
110
|
+
`path: ${pathExpression(
|
|
111
|
+
operationInfo.routePath,
|
|
112
|
+
operationTypes.parametersByLocation.path,
|
|
113
|
+
inputName,
|
|
114
|
+
)}`,
|
|
115
|
+
`method: ${JSON.stringify(operationInfo.method)}`,
|
|
116
|
+
];
|
|
117
|
+
|
|
118
|
+
if (options.queryValueName) {
|
|
119
|
+
items.push(`query: ${options.queryValueName}`);
|
|
120
|
+
} else if (operationTypes.hasQueryParams) {
|
|
121
|
+
items.push(
|
|
122
|
+
`query: ${parameterObjectExpression(
|
|
123
|
+
inputName,
|
|
124
|
+
operationTypes.parametersByLocation.query,
|
|
125
|
+
)}`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (operationTypes.bodyType) {
|
|
130
|
+
const bodyExpression = options.bodyValueName ?? "data";
|
|
131
|
+
items.push(`body: ${toWireExpression(bodyExpression, operationTypes.bodyWireSchemaKey)}`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (operationTypes.hasHeaderParams || operationTypes.hasCookieParams) {
|
|
135
|
+
const headerSources = ["params.headers"];
|
|
136
|
+
if (operationTypes.hasHeaderParams) {
|
|
137
|
+
const headersExpression = parameterObjectExpression(
|
|
138
|
+
inputName,
|
|
139
|
+
operationTypes.parametersByLocation.header,
|
|
140
|
+
);
|
|
141
|
+
headerSources.push(`toRequestHeaders(${headersExpression})`);
|
|
142
|
+
}
|
|
143
|
+
if (operationTypes.hasCookieParams) {
|
|
144
|
+
const cookiesExpression = parameterObjectExpression(
|
|
145
|
+
inputName,
|
|
146
|
+
operationTypes.parametersByLocation.cookie,
|
|
147
|
+
);
|
|
148
|
+
const cookieCondition = operationTypes.parametersByLocation.cookie
|
|
149
|
+
.map((parameter) => `${parameterValueExpression(inputName, parameter)} !== undefined`)
|
|
150
|
+
.join(" || ") || "false";
|
|
151
|
+
headerSources.push(
|
|
152
|
+
`${cookieCondition} ? { Cookie: toCookieHeader(${cookiesExpression}) } : undefined`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
items.push(`headers: mergeHeaders(${headerSources.join(", ")})`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const type = contentTypeEnum(operationTypes.contentType);
|
|
159
|
+
if (type) items.push(`type: ${type}`);
|
|
160
|
+
const format = requestFormat(operationTypes.responseContentType, operationTypes.responseType);
|
|
161
|
+
if (format) items.push(`format: ${JSON.stringify(format)}`);
|
|
162
|
+
return items;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function callbackOptions(operationTypes, defaultHandlers) {
|
|
166
|
+
const items = [];
|
|
167
|
+
if (defaultHandlers) {
|
|
168
|
+
items.push("onSuccess: onSuccess ?? typedApiDefaultSuccessHandler");
|
|
169
|
+
items.push("onError: onError ?? typedApiDefaultErrorHandler");
|
|
170
|
+
} else {
|
|
171
|
+
items.push("onSuccess");
|
|
172
|
+
items.push("onError");
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (operationTypes.responseWireSchemaKey) {
|
|
176
|
+
items.push(
|
|
177
|
+
`transformResponse: (value) => fromWireValue(value, typedApiWireSchemas[${JSON.stringify(
|
|
178
|
+
operationTypes.responseWireSchemaKey,
|
|
179
|
+
)}], typedApiWireSchemas) as ${operationTypes.responseType}`,
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
if (operationTypes.errorWireSchemaKey) {
|
|
183
|
+
items.push(
|
|
184
|
+
`transformError: (value) => fromWireValue(value, typedApiWireSchemas[${JSON.stringify(
|
|
185
|
+
operationTypes.errorWireSchemaKey,
|
|
186
|
+
)}], typedApiWireSchemas) as ${operationTypes.errorType}`,
|
|
187
|
+
);
|
|
188
|
+
} else if (operationTypes.usesHttpErrorFallback) {
|
|
189
|
+
items.push(
|
|
190
|
+
"transformError: (value, response) => createApiHttpError(response.status, value)",
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return items.length <= 2
|
|
195
|
+
? `{ ${items.join(", ")} }`
|
|
196
|
+
: `{\n ${items.join(",\n ")},\n }`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function optionsType(operationTypes) {
|
|
200
|
+
return `ApiMethodOptions<${operationTypes.responseType}, ${operationTypes.errorType}, RequestParams>`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function promiseType(operationTypes) {
|
|
204
|
+
return `Promise<ApiResult<${operationTypes.responseType}, ${operationTypes.errorType}>>`;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isPaginated(operationTypes) {
|
|
208
|
+
return Boolean(operationTypes.paginationMetadata) ||
|
|
209
|
+
/(?:Api)?Pagination(?:Sort)?Response\b|Paged(?:Response|Result)?\b|Paginated(?:Response|Result)?\b/.test(
|
|
210
|
+
operationTypes.responseType,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function methodDescription(operationInfo, operationTypes) {
|
|
215
|
+
const description = operationInfo.operation.description || operationInfo.operation.summary || "No description";
|
|
216
|
+
const tags = (operationInfo.operation.tags ?? []).join(", ");
|
|
217
|
+
return `/**\n * ${description.replace(/\n/g, "\n * ")}\n *\n * @tags ${tags}\n * @name ${operationTypes.methodNameSource}\n * @request ${operationInfo.method}:${operationInfo.routePath}\n */`;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function createRequestCall(operationInfo, operationTypes, options = {}) {
|
|
221
|
+
const items = requestItems(operationInfo, operationTypes, options)
|
|
222
|
+
.map((item) => ` ${item},`)
|
|
223
|
+
.join("\n");
|
|
224
|
+
return `request<${operationTypes.responseType}, ${operationTypes.errorType}>({\n${items}\n })`;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function createPaginatedMethod(operationInfo, operationTypes, defaultHandlers) {
|
|
228
|
+
const queryTypeName = operationTypes.parameterTypeNames.query;
|
|
229
|
+
const requestCall = createRequestCall(operationInfo, operationTypes, { queryValueName: "builtQuery" });
|
|
230
|
+
return `${methodDescription(operationInfo, operationTypes)}
|
|
231
|
+
export async function ${operationTypes.methodName}(
|
|
232
|
+
filters: FilterFormValues<${queryTypeName}>[] = [],
|
|
233
|
+
page = 1,
|
|
234
|
+
pageSize = 100,
|
|
235
|
+
sortBy: SortableKeys<${operationTypes.responseType}> | null = null,
|
|
236
|
+
sortDirection?: SortDirection,
|
|
237
|
+
options: ${optionsType(operationTypes)} = {},
|
|
238
|
+
): ${promiseType(operationTypes)} {
|
|
239
|
+
const { onSuccess, onError, params = {} } = options;
|
|
240
|
+
const builtQuery = buildQuery<
|
|
241
|
+
${queryTypeName},
|
|
242
|
+
UnwrapArray<ExtractDataIfPaginated<${operationTypes.responseType}>>
|
|
243
|
+
>(filters, page, pageSize, sortBy, sortDirection);
|
|
244
|
+
|
|
245
|
+
return handleApiResponse<${operationTypes.responseType}, ${operationTypes.errorType}>(
|
|
246
|
+
() => ${requestCall},
|
|
247
|
+
${callbackOptions(operationTypes, defaultHandlers)},
|
|
248
|
+
);
|
|
249
|
+
}`;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function regularParameterArgumentName(operationTypes) {
|
|
253
|
+
if (operationTypes.hasPathParams) return "pathParams";
|
|
254
|
+
if (
|
|
255
|
+
operationTypes.hasQueryParams &&
|
|
256
|
+
!operationTypes.hasHeaderParams &&
|
|
257
|
+
!operationTypes.hasCookieParams
|
|
258
|
+
) return "query";
|
|
259
|
+
return "requestParams";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function createRegularMethod(operationInfo, operationTypes, defaultHandlers) {
|
|
263
|
+
const args = [];
|
|
264
|
+
const parameterArgumentName = operationTypes.paramsTypeName
|
|
265
|
+
? regularParameterArgumentName(operationTypes)
|
|
266
|
+
: undefined;
|
|
267
|
+
|
|
268
|
+
if (operationTypes.paramsTypeName) {
|
|
269
|
+
const canOmitParams = operationTypes.allParametersOptional && !operationTypes.bodyType;
|
|
270
|
+
args.push(
|
|
271
|
+
`${parameterArgumentName}: ${operationTypes.paramsTypeName}${canOmitParams ? " = {}" : ""}`,
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (operationTypes.bodyType) {
|
|
276
|
+
args.push(`data: ${operationTypes.bodyType}`);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
args.push(`options: ${optionsType(operationTypes)} = {}`);
|
|
280
|
+
const requestCall = createRequestCall(operationInfo, operationTypes, {
|
|
281
|
+
inputName: parameterArgumentName ?? "undefined",
|
|
282
|
+
bodyValueName: operationTypes.bodyType ? "data" : undefined,
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
return `${methodDescription(operationInfo, operationTypes)}
|
|
286
|
+
export async function ${operationTypes.methodName}(
|
|
287
|
+
${args.join(",\n ")}
|
|
288
|
+
): ${promiseType(operationTypes)} {
|
|
289
|
+
const { onSuccess, onError, params = {} } = options;
|
|
290
|
+
|
|
291
|
+
return handleApiResponse<${operationTypes.responseType}, ${operationTypes.errorType}>(
|
|
292
|
+
() => ${requestCall},
|
|
293
|
+
${callbackOptions(operationTypes, defaultHandlers)},
|
|
294
|
+
);
|
|
295
|
+
}`;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function createMethod(operationInfo, operationTypes, defaultHandlers, options) {
|
|
299
|
+
if (
|
|
300
|
+
operationTypes.hasQueryParams &&
|
|
301
|
+
!operationTypes.hasPathParams &&
|
|
302
|
+
!operationTypes.bodyType &&
|
|
303
|
+
!operationTypes.hasHeaderParams &&
|
|
304
|
+
!operationTypes.hasCookieParams &&
|
|
305
|
+
options.useFilterFormValues !== false &&
|
|
306
|
+
isPaginated(operationTypes)
|
|
307
|
+
) {
|
|
308
|
+
return createPaginatedMethod(operationInfo, operationTypes, defaultHandlers);
|
|
309
|
+
}
|
|
310
|
+
return createRegularMethod(operationInfo, operationTypes, defaultHandlers);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export function methodFileNameForController(controllerName) {
|
|
314
|
+
return `${controllerName}.api.ts`;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function controllerNameForOperation(operationInfo, options = {}) {
|
|
318
|
+
if (options.moduleNameFirstTag !== false) {
|
|
319
|
+
const [tag] = operationInfo.operation.tags ?? [];
|
|
320
|
+
if (tag) return pascalCase(tag, "Default");
|
|
321
|
+
}
|
|
322
|
+
const parts = operationInfo.routePath.split("/").filter(Boolean);
|
|
323
|
+
return pascalCase(parts[1] ?? parts[0] ?? "Default", "Default");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
export function generateMethodFile(openApi, controllerName, operations, options = {}) {
|
|
327
|
+
const dataContractImports = [];
|
|
328
|
+
const dataContractValueImports = [];
|
|
329
|
+
const helperTypeImports = ["ApiResult", "ApiMethodOptions"];
|
|
330
|
+
const helperValueImports = ["handleApiResponse"];
|
|
331
|
+
const methods = [];
|
|
332
|
+
let needsContentType = false;
|
|
333
|
+
let needsPaginationTypes = false;
|
|
334
|
+
let needsHeaderHelpers = false;
|
|
335
|
+
let needsWireMappings = false;
|
|
336
|
+
|
|
337
|
+
for (const operationInfo of operations) {
|
|
338
|
+
const operationTypes = getOperationTypes(openApi, operationInfo, options);
|
|
339
|
+
|
|
340
|
+
for (const typeName of Object.values(operationTypes.parameterTypeNames)) dataContractImports.push(typeName);
|
|
341
|
+
if (operationTypes.paramsTypeName) dataContractImports.push(operationTypes.paramsTypeName);
|
|
342
|
+
if (operationTypes.bodyType && !isPrimitiveType(operationTypes.bodyType)) {
|
|
343
|
+
dataContractImports.push(...extractTopLevelTypeNames(operationTypes.bodyType));
|
|
344
|
+
}
|
|
345
|
+
if (!isPrimitiveType(operationTypes.responseType)) {
|
|
346
|
+
dataContractImports.push(...extractTopLevelTypeNames(operationTypes.responseType));
|
|
347
|
+
}
|
|
348
|
+
if (!isPrimitiveType(operationTypes.errorType)) {
|
|
349
|
+
dataContractImports.push(...extractTopLevelTypeNames(operationTypes.errorType));
|
|
350
|
+
}
|
|
351
|
+
if (operationTypes.usesHttpErrorFallback) {
|
|
352
|
+
helperTypeImports.push("ApiHttpError");
|
|
353
|
+
helperValueImports.push("createApiHttpError");
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
if (contentTypeEnum(operationTypes.contentType)) needsContentType = true;
|
|
357
|
+
if (operationTypes.hasHeaderParams || operationTypes.hasCookieParams) needsHeaderHelpers = true;
|
|
358
|
+
if (
|
|
359
|
+
operationTypes.bodyWireSchemaKey ||
|
|
360
|
+
operationTypes.responseWireSchemaKey ||
|
|
361
|
+
operationTypes.errorWireSchemaKey
|
|
362
|
+
) {
|
|
363
|
+
needsWireMappings = true;
|
|
364
|
+
}
|
|
365
|
+
if (
|
|
366
|
+
operationTypes.hasQueryParams &&
|
|
367
|
+
!operationTypes.hasPathParams && !operationTypes.bodyType &&
|
|
368
|
+
!operationTypes.hasHeaderParams && !operationTypes.hasCookieParams &&
|
|
369
|
+
options.useFilterFormValues !== false && isPaginated(operationTypes)
|
|
370
|
+
) {
|
|
371
|
+
needsPaginationTypes = true;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
methods.push(createMethod(operationInfo, operationTypes, options.defaultHandlers, options));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (needsHeaderHelpers) helperValueImports.push("mergeHeaders", "toCookieHeader", "toRequestHeaders");
|
|
378
|
+
if (needsWireMappings) {
|
|
379
|
+
dataContractValueImports.push("typedApiWireSchemas");
|
|
380
|
+
helperValueImports.push("fromWireValue", "toWireValue");
|
|
381
|
+
}
|
|
382
|
+
if (needsPaginationTypes) {
|
|
383
|
+
helperValueImports.push("buildQuery");
|
|
384
|
+
helperTypeImports.push(
|
|
385
|
+
"ExtractDataIfPaginated", "FilterFormValues", "SortableKeys", "SortDirection", "UnwrapArray",
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const useTypeOnlyImports = options.useTypeOnlyImports !== false;
|
|
390
|
+
const defaultHandlers = options.defaultHandlers;
|
|
391
|
+
const imports = [
|
|
392
|
+
generatedFileHeader(),
|
|
393
|
+
createImport(["request", ...(needsContentType ? ["ContentType"] : [])], "../generated/http-client"),
|
|
394
|
+
createImport(["RequestParams"], "../generated/http-client", useTypeOnlyImports),
|
|
395
|
+
createImport(dataContractValueImports, "../generated/data-contracts"),
|
|
396
|
+
createImport(dataContractImports, "../generated/data-contracts", useTypeOnlyImports),
|
|
397
|
+
createImport(helperValueImports, options.runtimePackageName ?? "typedapi-client-helpers"),
|
|
398
|
+
createImport(helperTypeImports, options.runtimePackageName ?? "typedapi-client-helpers", useTypeOnlyImports),
|
|
399
|
+
defaultHandlers
|
|
400
|
+
? `import { ${defaultHandlers.success} as typedApiDefaultSuccessHandler, ${defaultHandlers.error} as typedApiDefaultErrorHandler } from ${JSON.stringify(defaultHandlers.path)};`
|
|
401
|
+
: "",
|
|
402
|
+
].filter(Boolean);
|
|
403
|
+
|
|
404
|
+
return `${imports.join("\n")}\n\n${methods.join("\n\n")}\n`;
|
|
405
|
+
}
|