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.
@@ -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
- return items.length <= 2
191
- ? `{ ${items.join(", ")} }`
192
- : `{\n ${items.join(",\n ")},\n }`;
193
- }
194
-
195
- function optionsType(operationTypes) {
196
- return `ApiMethodOptions<${operationTypes.responseType}, ${operationTypes.errorType}, RequestParams>`;
197
- }
198
-
199
- function promiseType(operationTypes) {
200
- return `Promise<ApiResult<${operationTypes.responseType}, ${operationTypes.errorType}>>`;
201
- }
202
-
203
- function isPaginated(operationTypes) {
204
- return Boolean(operationTypes.paginationMetadata) ||
205
- /(?:Api)?Pagination(?:Sort)?Response\b|Paged(?:Response|Result)?\b|Paginated(?:Response|Result)?\b/.test(
206
- operationTypes.responseType,
207
- );
208
- }
209
-
210
- function methodDescription(operationInfo, operationTypes) {
211
- const description = operationInfo.operation.description || operationInfo.operation.summary || "No description";
212
- const tags = (operationInfo.operation.tags ?? []).join(", ");
213
- return `/**\n * ${description.replace(/\n/g, "\n * ")}\n *\n * @tags ${tags}\n * @name ${operationTypes.methodNameSource}\n * @request ${operationInfo.method}:${operationInfo.routePath}\n */`;
214
- }
215
-
216
- function createRequestCall(operationInfo, operationTypes, options = {}) {
217
- const items = requestItems(operationInfo, operationTypes, options)
218
- .map((item) => ` ${item},`)
219
- .join("\n");
220
- return `request<${operationTypes.responseType}, ${operationTypes.errorType}>({\n${items}\n })`;
221
- }
222
-
223
- function createPaginatedMethod(operationInfo, operationTypes, defaultHandlers) {
224
- const queryTypeName = operationTypes.parameterTypeNames.query;
225
- const requestCall = createRequestCall(operationInfo, operationTypes, { queryValueName: "builtQuery" });
226
- return `${methodDescription(operationInfo, operationTypes)}
227
- export async function ${operationTypes.methodName}(
228
- filters: FilterFormValues<${queryTypeName}>[] = [],
229
- page = 1,
230
- pageSize = 100,
231
- sortBy: SortableKeys<${operationTypes.responseType}> | null = null,
232
- sortDirection?: SortDirection,
233
- options: ${optionsType(operationTypes)} = {},
234
- ): ${promiseType(operationTypes)} {
235
- const { onSuccess, onError, params = {} } = options;
236
- const builtQuery = buildQuery<
237
- ${queryTypeName},
238
- UnwrapArray<ExtractDataIfPaginated<${operationTypes.responseType}>>
239
- >(filters, page, pageSize, sortBy, sortDirection);
240
-
241
- return handleApiResponse<${operationTypes.responseType}, ${operationTypes.errorType}>(
242
- () => ${requestCall},
243
- ${callbackOptions(operationTypes, defaultHandlers)},
244
- );
245
- }`;
246
- }
247
-
248
- function regularParameterArgumentName(operationTypes) {
249
- if (operationTypes.hasPathParams) return "pathParams";
250
- if (
251
- operationTypes.hasQueryParams &&
252
- !operationTypes.hasHeaderParams &&
253
- !operationTypes.hasCookieParams
254
- ) return "query";
255
- return "requestParams";
256
- }
257
-
258
- function createRegularMethod(operationInfo, operationTypes, defaultHandlers) {
259
- const args = [];
260
- const parameterArgumentName = operationTypes.paramsTypeName
261
- ? regularParameterArgumentName(operationTypes)
262
- : undefined;
263
-
264
- if (operationTypes.paramsTypeName) {
265
- const canOmitParams = operationTypes.allParametersOptional && !operationTypes.bodyType;
266
- args.push(
267
- `${parameterArgumentName}: ${operationTypes.paramsTypeName}${canOmitParams ? " = {}" : ""}`,
268
- );
269
- }
270
-
271
- if (operationTypes.bodyType) {
272
- args.push(`data: ${operationTypes.bodyType}`);
273
- }
274
-
275
- args.push(`options: ${optionsType(operationTypes)} = {}`);
276
- const requestCall = createRequestCall(operationInfo, operationTypes, {
277
- inputName: parameterArgumentName ?? "undefined",
278
- bodyValueName: operationTypes.bodyType ? "data" : undefined,
279
- });
280
-
281
- return `${methodDescription(operationInfo, operationTypes)}
282
- export async function ${operationTypes.methodName}(
283
- ${args.join(",\n ")}
284
- ): ${promiseType(operationTypes)} {
285
- const { onSuccess, onError, params = {} } = options;
286
-
287
- return handleApiResponse<${operationTypes.responseType}, ${operationTypes.errorType}>(
288
- () => ${requestCall},
289
- ${callbackOptions(operationTypes, defaultHandlers)},
290
- );
291
- }`;
292
- }
293
-
294
- function createMethod(operationInfo, operationTypes, defaultHandlers, options) {
295
- if (
296
- operationTypes.hasQueryParams &&
297
- !operationTypes.hasPathParams &&
298
- !operationTypes.bodyType &&
299
- !operationTypes.hasHeaderParams &&
300
- !operationTypes.hasCookieParams &&
301
- options.useFilterFormValues !== false &&
302
- isPaginated(operationTypes)
303
- ) {
304
- return createPaginatedMethod(operationInfo, operationTypes, defaultHandlers);
305
- }
306
- return createRegularMethod(operationInfo, operationTypes, defaultHandlers);
307
- }
308
-
309
- export function methodFileNameForController(controllerName) {
310
- return `${controllerName}.api.ts`;
311
- }
312
-
313
- export function controllerNameForOperation(operationInfo, options = {}) {
314
- if (options.moduleNameFirstTag !== false) {
315
- const [tag] = operationInfo.operation.tags ?? [];
316
- if (tag) return pascalCase(tag, "Default");
317
- }
318
- const parts = operationInfo.routePath.split("/").filter(Boolean);
319
- return pascalCase(parts[1] ?? parts[0] ?? "Default", "Default");
320
- }
321
-
322
- export function generateMethodFile(openApi, controllerName, operations, options = {}) {
323
- const dataContractImports = [];
324
- const dataContractValueImports = [];
325
- const helperTypeImports = ["ApiResult", "ApiMethodOptions"];
326
- const helperValueImports = ["handleApiResponse"];
327
- const methods = [];
328
- let needsContentType = false;
329
- let needsPaginationTypes = false;
330
- let needsHeaderHelpers = false;
331
- let needsWireMappings = false;
332
-
333
- for (const operationInfo of operations) {
334
- const operationTypes = getOperationTypes(openApi, operationInfo, options);
335
-
336
- for (const typeName of Object.values(operationTypes.parameterTypeNames)) dataContractImports.push(typeName);
337
- if (operationTypes.paramsTypeName) dataContractImports.push(operationTypes.paramsTypeName);
338
- if (operationTypes.bodyType && !isPrimitiveType(operationTypes.bodyType)) {
339
- dataContractImports.push(...extractTopLevelTypeNames(operationTypes.bodyType));
340
- }
341
- if (!isPrimitiveType(operationTypes.responseType)) {
342
- dataContractImports.push(...extractTopLevelTypeNames(operationTypes.responseType));
343
- }
344
- if (!isPrimitiveType(operationTypes.errorType)) {
345
- dataContractImports.push(...extractTopLevelTypeNames(operationTypes.errorType));
346
- }
347
-
348
- if (contentTypeEnum(operationTypes.contentType)) needsContentType = true;
349
- if (operationTypes.hasHeaderParams || operationTypes.hasCookieParams) needsHeaderHelpers = true;
350
- if (
351
- operationTypes.bodyWireSchemaKey ||
352
- operationTypes.responseWireSchemaKey ||
353
- operationTypes.errorWireSchemaKey
354
- ) {
355
- needsWireMappings = true;
356
- }
357
- if (
358
- operationTypes.hasQueryParams &&
359
- !operationTypes.hasPathParams && !operationTypes.bodyType &&
360
- !operationTypes.hasHeaderParams && !operationTypes.hasCookieParams &&
361
- options.useFilterFormValues !== false && isPaginated(operationTypes)
362
- ) {
363
- needsPaginationTypes = true;
364
- }
365
-
366
- methods.push(createMethod(operationInfo, operationTypes, options.defaultHandlers, options));
367
- }
368
-
369
- if (needsHeaderHelpers) helperValueImports.push("mergeHeaders", "toCookieHeader", "toRequestHeaders");
370
- if (needsWireMappings) {
371
- dataContractValueImports.push("typedApiWireSchemas");
372
- helperValueImports.push("fromWireValue", "toWireValue");
373
- }
374
- if (needsPaginationTypes) {
375
- helperValueImports.push("buildQuery");
376
- helperTypeImports.push(
377
- "ExtractDataIfPaginated", "FilterFormValues", "SortableKeys", "SortDirection", "UnwrapArray",
378
- );
379
- }
380
-
381
- const useTypeOnlyImports = options.useTypeOnlyImports !== false;
382
- const defaultHandlers = options.defaultHandlers;
383
- const imports = [
384
- generatedFileHeader(),
385
- createImport(["request", ...(needsContentType ? ["ContentType"] : [])], "../generated/http-client"),
386
- createImport(["RequestParams"], "../generated/http-client", useTypeOnlyImports),
387
- createImport(dataContractValueImports, "../generated/data-contracts"),
388
- createImport(dataContractImports, "../generated/data-contracts", useTypeOnlyImports),
389
- createImport(helperValueImports, options.runtimePackageName ?? "typedapi-client-helpers"),
390
- createImport(helperTypeImports, options.runtimePackageName ?? "typedapi-client-helpers", useTypeOnlyImports),
391
- defaultHandlers
392
- ? `import { ${defaultHandlers.success} as typedApiDefaultSuccessHandler, ${defaultHandlers.error} as typedApiDefaultErrorHandler } from ${JSON.stringify(defaultHandlers.path)};`
393
- : "",
394
- ].filter(Boolean);
395
-
396
- return `${imports.join("\n")}\n\n${methods.join("\n\n")}\n`;
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
+ }