typedapi-client-helpers 0.3.4 → 0.3.6

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.
@@ -9,22 +9,38 @@ function createImport(names, from, typeOnly = false) {
9
9
  const cleanNames = unique(names).sort((a, b) => a.localeCompare(b));
10
10
  if (cleanNames.length === 0) return "";
11
11
  const keyword = typeOnly ? "import type" : "import";
12
- if (cleanNames.length <= 3) return `${keyword} { ${cleanNames.join(", ")} } from ${JSON.stringify(from)};`;
12
+ if (cleanNames.length <= 3)
13
+ return `${keyword} { ${cleanNames.join(", ")} } from ${JSON.stringify(from)};`;
13
14
  return `${keyword} {\n${cleanNames.map((name) => ` ${name},`).join("\n")}\n} from ${JSON.stringify(from)};`;
14
15
  }
15
16
 
16
17
  function isPrimitiveType(type) {
17
- return /^(void|any|unknown|string|number|boolean|Blob|File|Record<)/.test(type);
18
+ return /^(void|any|unknown|string|number|boolean|Blob|File|Record<)/.test(
19
+ type,
20
+ );
18
21
  }
19
22
 
20
23
  function extractTopLevelTypeNames(type) {
21
- const withoutLiterals = String(type ?? "").replace(/"(?:\\.|[^"])*"|'(?:\\.|[^'])*'/g, "");
24
+ const withoutLiterals = String(type ?? "").replace(
25
+ /"(?:\\.|[^"])*"|'(?:\\.|[^'])*'/g,
26
+ "",
27
+ );
22
28
  const matches = withoutLiterals.match(/\b[A-Z][A-Za-z0-9_]*\b/g) ?? [];
23
29
  return matches.filter(
24
- (name) => ![
25
- "Array", "Record", "Promise", "HttpResponse", "Blob", "File", "ApiResult",
26
- "RequestParams", "FormData", "ApiClientError", "ApiHttpError",
27
- ].includes(name),
30
+ (name) =>
31
+ ![
32
+ "Array",
33
+ "Record",
34
+ "Promise",
35
+ "HttpResponse",
36
+ "Blob",
37
+ "File",
38
+ "ApiResult",
39
+ "RequestParams",
40
+ "FormData",
41
+ "ApiClientError",
42
+ "ApiHttpError",
43
+ ].includes(name),
28
44
  );
29
45
  }
30
46
 
@@ -38,10 +54,11 @@ function parameterValueExpression(sourceExpression, parameter) {
38
54
 
39
55
  function parameterObjectExpression(sourceExpression, parameters) {
40
56
  const properties = parameters.map(
41
- (parameter) => `${JSON.stringify(parameter.name)}: ${parameterValueExpression(
42
- sourceExpression,
43
- parameter,
44
- )}`,
57
+ (parameter) =>
58
+ `${JSON.stringify(parameter.name)}: ${parameterValueExpression(
59
+ sourceExpression,
60
+ parameter,
61
+ )}`,
45
62
  );
46
63
  return properties.length <= 2
47
64
  ? `{ ${properties.join(", ")} }`
@@ -73,7 +90,9 @@ function pathExpression(routePath, pathParams, inputName = "input") {
73
90
  if (parts.length === 0) return JSON.stringify(routePath);
74
91
 
75
92
  return parts
76
- .map((part) => /^"/.test(part) ? part.slice(1, -1).replace(/`/g, "\\`") : `\${${part}}`)
93
+ .map((part) =>
94
+ /^"/.test(part) ? part.slice(1, -1).replace(/`/g, "\\`") : `\${${part}}`,
95
+ )
77
96
  .join("")
78
97
  .replace(/^/, "`")
79
98
  .replace(/$/, "`");
@@ -83,22 +102,31 @@ function requestFormat(responseContentType, responseType) {
83
102
  if (responseType === "void" || !responseContentType) return undefined;
84
103
  if (responseContentType.includes("json")) return "json";
85
104
  if (responseContentType.startsWith("text/")) return "text";
86
- if (responseContentType === "application/octet-stream" || responseType === "Blob") return "blob";
105
+ if (
106
+ responseContentType === "application/octet-stream" ||
107
+ responseType === "Blob"
108
+ )
109
+ return "blob";
87
110
  return undefined;
88
111
  }
89
112
 
90
113
  function contentTypeEnum(contentType) {
91
114
  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;
115
+ case "application/json":
116
+ return "ContentType.Json";
117
+ case "application/vnd.api+json":
118
+ return "ContentType.JsonApi";
119
+ case "multipart/form-data":
120
+ return "ContentType.FormData";
121
+ case "application/x-www-form-urlencoded":
122
+ return "ContentType.UrlEncoded";
123
+ case "text/plain":
124
+ return "ContentType.Text";
125
+ default:
126
+ return contentType?.includes("json") ? "ContentType.Json" : undefined;
98
127
  }
99
128
  }
100
129
 
101
-
102
130
  function requestItems(operationInfo, operationTypes, options = {}) {
103
131
  const inputName = options.inputName ?? "input";
104
132
  const items = [
@@ -114,7 +142,11 @@ function requestItems(operationInfo, operationTypes, options = {}) {
114
142
  if (options.queryValueName) {
115
143
  items.push(`query: ${options.queryValueName}`);
116
144
  } else if (operationTypes.hasQueryParams) {
117
- if (!operationTypes.hasPathParams && !operationTypes.hasHeaderParams && !operationTypes.hasCookieParams) {
145
+ if (
146
+ !operationTypes.hasPathParams &&
147
+ !operationTypes.hasHeaderParams &&
148
+ !operationTypes.hasCookieParams
149
+ ) {
118
150
  items.push(`query: ${inputName}`);
119
151
  } else {
120
152
  items.push(
@@ -145,9 +177,13 @@ function requestItems(operationInfo, operationTypes, options = {}) {
145
177
  inputName,
146
178
  operationTypes.parametersByLocation.cookie,
147
179
  );
148
- const cookieCondition = operationTypes.parametersByLocation.cookie
149
- .map((parameter) => `${parameterValueExpression(inputName, parameter)} !== undefined`)
150
- .join(" || ") || "false";
180
+ const cookieCondition =
181
+ operationTypes.parametersByLocation.cookie
182
+ .map(
183
+ (parameter) =>
184
+ `${parameterValueExpression(inputName, parameter)} !== undefined`,
185
+ )
186
+ .join(" || ") || "false";
151
187
  headerSources.push(
152
188
  `${cookieCondition} ? { Cookie: toCookieHeader(${cookiesExpression}) } : undefined`,
153
189
  );
@@ -157,7 +193,10 @@ function requestItems(operationInfo, operationTypes, options = {}) {
157
193
 
158
194
  const type = contentTypeEnum(operationTypes.contentType);
159
195
  if (type) items.push(`type: ${type}`);
160
- const format = requestFormat(operationTypes.responseContentType, operationTypes.responseType);
196
+ const format = requestFormat(
197
+ operationTypes.responseContentType,
198
+ operationTypes.responseType,
199
+ );
161
200
  if (format) items.push(`format: ${JSON.stringify(format)}`);
162
201
  return items;
163
202
  }
@@ -193,14 +232,33 @@ function promiseType(operationTypes) {
193
232
  }
194
233
 
195
234
  function isPaginated(operationTypes) {
196
- return Boolean(operationTypes.paginationMetadata) ||
235
+ return (
236
+ Boolean(operationTypes.paginationMetadata) ||
197
237
  /(?:Api)?Pagination(?:Sort)?Response\b|Paged(?:Response|Result)?\b|Paginated(?:Response|Result)?\b/.test(
198
238
  operationTypes.responseType,
199
- );
239
+ )
240
+ );
241
+ }
242
+
243
+ function isFilterForm(operationTypes) {
244
+ return Boolean(operationTypes.filterFormMetadata);
245
+ }
246
+
247
+ function isPureQueryMethod(operationTypes) {
248
+ return (
249
+ operationTypes.hasQueryParams &&
250
+ !operationTypes.hasPathParams &&
251
+ !operationTypes.bodyType &&
252
+ !operationTypes.hasHeaderParams &&
253
+ !operationTypes.hasCookieParams
254
+ );
200
255
  }
201
256
 
202
257
  function methodDescription(operationInfo, operationTypes) {
203
- const description = operationInfo.operation.description || operationInfo.operation.summary || "No description";
258
+ const description =
259
+ operationInfo.operation.description ||
260
+ operationInfo.operation.summary ||
261
+ "No description";
204
262
  const tags = (operationInfo.operation.tags ?? []).join(", ");
205
263
  return `/**\n * ${description.replace(/\n/g, "\n * ")}\n *\n * @tags ${tags}\n * @name ${operationTypes.methodNameSource}\n * @request ${operationInfo.method}:${operationInfo.routePath}\n */`;
206
264
  }
@@ -214,7 +272,9 @@ function createRequestCall(operationInfo, operationTypes, options = {}) {
214
272
 
215
273
  function createPaginatedMethod(operationInfo, operationTypes, defaultHandlers) {
216
274
  const queryTypeName = operationTypes.parameterTypeNames.query;
217
- const requestCall = createRequestCall(operationInfo, operationTypes, { queryValueName: "builtQuery" });
275
+ const requestCall = createRequestCall(operationInfo, operationTypes, {
276
+ queryValueName: "builtQuery",
277
+ });
218
278
  return `${methodDescription(operationInfo, operationTypes)}
219
279
  export async function ${operationTypes.methodName}(
220
280
  filters: FilterFormValues<${queryTypeName}>[] = [],
@@ -237,13 +297,47 @@ export async function ${operationTypes.methodName}(
237
297
  }`;
238
298
  }
239
299
 
300
+ function createFilterFormMethod(
301
+ operationInfo,
302
+ operationTypes,
303
+ defaultHandlers,
304
+ ) {
305
+ const queryTypeName = operationTypes.parameterTypeNames.query;
306
+ const queryPropertyOptional = operationTypes.allParametersOptional ? "?" : "";
307
+ const filterFormDefault = operationTypes.allParametersOptional ? " = {}" : "";
308
+ const requestCall = createRequestCall(operationInfo, operationTypes, {
309
+ queryValueName: "builtQuery",
310
+ });
311
+
312
+ return `${methodDescription(operationInfo, operationTypes)}
313
+ export async function ${operationTypes.methodName}(
314
+ filterForm: {
315
+ query${queryPropertyOptional}: ${queryTypeName};
316
+ filters?: FilterFormValues<${queryTypeName}>[];
317
+ }${filterFormDefault},
318
+ options: ${optionsType(operationTypes)} = {},
319
+ ): ${promiseType(operationTypes)} {
320
+ const { onSuccess, onError, params = {} } = options;
321
+ const builtQuery = {
322
+ ...(filterForm.query ?? {}),
323
+ ...buildFilterQuery<${queryTypeName}>(filterForm.filters ?? []),
324
+ };
325
+
326
+ return handleApiResponse<${operationTypes.responseType}, ${operationTypes.errorType}>(
327
+ () => ${requestCall},
328
+ ${callbackOptions(operationTypes, defaultHandlers)},
329
+ );
330
+ }`;
331
+ }
332
+
240
333
  function regularParameterArgumentName(operationTypes) {
241
334
  if (operationTypes.hasPathParams) return "pathParams";
242
335
  if (
243
336
  operationTypes.hasQueryParams &&
244
337
  !operationTypes.hasHeaderParams &&
245
338
  !operationTypes.hasCookieParams
246
- ) return "query";
339
+ )
340
+ return "query";
247
341
  return "requestParams";
248
342
  }
249
343
 
@@ -254,7 +348,8 @@ function createRegularMethod(operationInfo, operationTypes, defaultHandlers) {
254
348
  : undefined;
255
349
 
256
350
  if (operationTypes.paramsTypeName) {
257
- const canOmitParams = operationTypes.allParametersOptional && !operationTypes.bodyType;
351
+ const canOmitParams =
352
+ operationTypes.allParametersOptional && !operationTypes.bodyType;
258
353
  args.push(
259
354
  `${parameterArgumentName}: ${operationTypes.paramsTypeName}${canOmitParams ? " = {}" : ""}`,
260
355
  );
@@ -285,15 +380,26 @@ export async function ${operationTypes.methodName}(
285
380
 
286
381
  function createMethod(operationInfo, operationTypes, defaultHandlers, options) {
287
382
  if (
288
- operationTypes.hasQueryParams &&
289
- !operationTypes.hasPathParams &&
290
- !operationTypes.bodyType &&
291
- !operationTypes.hasHeaderParams &&
292
- !operationTypes.hasCookieParams &&
383
+ isPureQueryMethod(operationTypes) &&
293
384
  options.useFilterFormValues !== false &&
294
385
  isPaginated(operationTypes)
295
386
  ) {
296
- return createPaginatedMethod(operationInfo, operationTypes, defaultHandlers);
387
+ return createPaginatedMethod(
388
+ operationInfo,
389
+ operationTypes,
390
+ defaultHandlers,
391
+ );
392
+ }
393
+ if (
394
+ isPureQueryMethod(operationTypes) &&
395
+ options.useFilterFormValues !== false &&
396
+ isFilterForm(operationTypes)
397
+ ) {
398
+ return createFilterFormMethod(
399
+ operationInfo,
400
+ operationTypes,
401
+ defaultHandlers,
402
+ );
297
403
  }
298
404
  return createRegularMethod(operationInfo, operationTypes, defaultHandlers);
299
405
  }
@@ -311,28 +417,43 @@ export function controllerNameForOperation(operationInfo, options = {}) {
311
417
  return pascalCase(parts[1] ?? parts[0] ?? "Default", "Default");
312
418
  }
313
419
 
314
- export function generateMethodFile(openApi, controllerName, operations, options = {}) {
420
+ export function generateMethodFile(
421
+ openApi,
422
+ controllerName,
423
+ operations,
424
+ options = {},
425
+ ) {
315
426
  const dataContractImports = [];
316
427
  const helperTypeImports = ["ApiResult", "ApiMethodOptions"];
317
428
  const helperValueImports = ["handleApiResponse"];
318
429
  const methods = [];
319
430
  let needsContentType = false;
320
431
  let needsPaginationTypes = false;
321
- let needsHeaderHelpers = false;
432
+ let needsFilterFormTypes = false;
433
+ let needsHeaderParams = false;
434
+ let needsCookieParams = false;
322
435
 
323
436
  for (const operationInfo of operations) {
324
437
  const operationTypes = getOperationTypes(openApi, operationInfo, options);
325
438
 
326
- for (const typeName of Object.values(operationTypes.parameterTypeNames)) dataContractImports.push(typeName);
327
- if (operationTypes.paramsTypeName) dataContractImports.push(operationTypes.paramsTypeName);
439
+ for (const typeName of Object.values(operationTypes.parameterTypeNames))
440
+ dataContractImports.push(typeName);
441
+ if (operationTypes.paramsTypeName)
442
+ dataContractImports.push(operationTypes.paramsTypeName);
328
443
  if (operationTypes.bodyType && !isPrimitiveType(operationTypes.bodyType)) {
329
- dataContractImports.push(...extractTopLevelTypeNames(operationTypes.bodyType));
444
+ dataContractImports.push(
445
+ ...extractTopLevelTypeNames(operationTypes.bodyType),
446
+ );
330
447
  }
331
448
  if (!isPrimitiveType(operationTypes.responseType)) {
332
- dataContractImports.push(...extractTopLevelTypeNames(operationTypes.responseType));
449
+ dataContractImports.push(
450
+ ...extractTopLevelTypeNames(operationTypes.responseType),
451
+ );
333
452
  }
334
453
  if (!isPrimitiveType(operationTypes.errorType)) {
335
- dataContractImports.push(...extractTopLevelTypeNames(operationTypes.errorType));
454
+ dataContractImports.push(
455
+ ...extractTopLevelTypeNames(operationTypes.errorType),
456
+ );
336
457
  }
337
458
  if (operationTypes.usesHttpErrorFallback) {
338
459
  helperTypeImports.push("ApiHttpError");
@@ -340,36 +461,78 @@ export function generateMethodFile(openApi, controllerName, operations, options
340
461
  }
341
462
 
342
463
  if (contentTypeEnum(operationTypes.contentType)) needsContentType = true;
343
- if (operationTypes.hasHeaderParams || operationTypes.hasCookieParams) needsHeaderHelpers = true;
464
+ if (operationTypes.hasHeaderParams) needsHeaderParams = true;
465
+ if (operationTypes.hasCookieParams) needsCookieParams = true;
344
466
  if (
345
- operationTypes.hasQueryParams &&
346
- !operationTypes.hasPathParams && !operationTypes.bodyType &&
347
- !operationTypes.hasHeaderParams && !operationTypes.hasCookieParams &&
348
- options.useFilterFormValues !== false && isPaginated(operationTypes)
467
+ isPureQueryMethod(operationTypes) &&
468
+ options.useFilterFormValues !== false &&
469
+ isPaginated(operationTypes)
349
470
  ) {
350
471
  needsPaginationTypes = true;
472
+ } else if (
473
+ isPureQueryMethod(operationTypes) &&
474
+ options.useFilterFormValues !== false &&
475
+ isFilterForm(operationTypes)
476
+ ) {
477
+ needsFilterFormTypes = true;
351
478
  }
352
479
 
353
- methods.push(createMethod(operationInfo, operationTypes, options.defaultHandlers, options));
480
+ methods.push(
481
+ createMethod(
482
+ operationInfo,
483
+ operationTypes,
484
+ options.defaultHandlers,
485
+ options,
486
+ ),
487
+ );
354
488
  }
355
489
 
356
- if (needsHeaderHelpers) helperValueImports.push("mergeHeaders", "toCookieHeader", "toRequestHeaders");
490
+ if (needsHeaderParams || needsCookieParams)
491
+ helperValueImports.push("mergeHeaders");
492
+ if (needsHeaderParams) helperValueImports.push("toRequestHeaders");
493
+ if (needsCookieParams) helperValueImports.push("toCookieHeader");
357
494
  if (needsPaginationTypes) {
358
495
  helperValueImports.push("buildQuery");
359
496
  helperTypeImports.push(
360
- "ExtractDataIfPaginated", "FilterFormValues", "SortableKeys", "SortDirection", "UnwrapArray",
497
+ "ExtractDataIfPaginated",
498
+ "FilterFormValues",
499
+ "SortableKeys",
500
+ "SortDirection",
501
+ "UnwrapArray",
361
502
  );
362
503
  }
504
+ if (needsFilterFormTypes) {
505
+ helperValueImports.push("buildFilterQuery");
506
+ helperTypeImports.push("FilterFormValues");
507
+ }
363
508
 
364
509
  const useTypeOnlyImports = options.useTypeOnlyImports !== false;
365
510
  const defaultHandlers = options.defaultHandlers;
366
511
  const imports = [
367
512
  generatedFileHeader(),
368
- createImport(["request", ...(needsContentType ? ["ContentType"] : [])], "../generated/http-client"),
369
- createImport(["RequestParams"], "../generated/http-client", useTypeOnlyImports),
370
- createImport(dataContractImports, "../generated/data-contracts", useTypeOnlyImports),
371
- createImport(helperValueImports, options.runtimePackageName ?? "typedapi-client-helpers"),
372
- createImport(helperTypeImports, options.runtimePackageName ?? "typedapi-client-helpers", useTypeOnlyImports),
513
+ createImport(
514
+ ["request", ...(needsContentType ? ["ContentType"] : [])],
515
+ "../generated/http-client",
516
+ ),
517
+ createImport(
518
+ ["RequestParams"],
519
+ "../generated/http-client",
520
+ useTypeOnlyImports,
521
+ ),
522
+ createImport(
523
+ dataContractImports,
524
+ "../generated/data-contracts",
525
+ useTypeOnlyImports,
526
+ ),
527
+ createImport(
528
+ helperValueImports,
529
+ options.runtimePackageName ?? "typedapi-client-helpers",
530
+ ),
531
+ createImport(
532
+ helperTypeImports,
533
+ options.runtimePackageName ?? "typedapi-client-helpers",
534
+ useTypeOnlyImports,
535
+ ),
373
536
  defaultHandlers
374
537
  ? `import { ${defaultHandlers.success} as typedApiDefaultSuccessHandler, ${defaultHandlers.error} as typedApiDefaultErrorHandler, ${defaultHandlers.errorMessage} as typedApiDefaultErrorMessage } from ${JSON.stringify(defaultHandlers.path)};`
375
538
  : "",
@@ -865,14 +865,15 @@ export function getOperationTypes(
865
865
  /(?:Api)?Pagination(?:Sort)?Response\b|Paged(?:Response|Result)?\b|Paginated(?:Response|Result)?\b/.test(
866
866
  responseType,
867
867
  );
868
- const isPurePaginatedQuery =
869
- paginated &&
868
+ const filterForm = Boolean(operationInfo.operation["x-typedapi-filter-form"]);
869
+ const isPureGeneratedQuery =
870
+ (paginated || filterForm) &&
870
871
  parametersByLocation.query.length > 0 &&
871
872
  !parametersByLocation.path.length &&
872
873
  !parametersByLocation.header.length &&
873
874
  !parametersByLocation.cookie.length &&
874
875
  !bodyType;
875
- const parameterTypeNames = isPurePaginatedQuery
876
+ const parameterTypeNames = isPureGeneratedQuery
876
877
  ? { query: parameterTypeName(operationTypeNameBase, "query") }
877
878
  : {};
878
879
 
@@ -883,7 +884,7 @@ export function getOperationTypes(
883
884
  methodName: operationInfo.methodName,
884
885
  methodNameSource: operationInfo.methodNameSource,
885
886
  paramsTypeName:
886
- hasNonBodyInput && !isPurePaginatedQuery
887
+ hasNonBodyInput && !isPureGeneratedQuery
887
888
  ? combinedParameterTypeName(operationTypeNameBase)
888
889
  : undefined,
889
890
  parameterTypeNames,
@@ -904,6 +905,7 @@ export function getOperationTypes(
904
905
  hasHeaderParams: parametersByLocation.header.length > 0,
905
906
  hasCookieParams: parametersByLocation.cookie.length > 0,
906
907
  paginationMetadata: operationInfo.operation["x-typedapi-pagination"],
908
+ filterFormMetadata: operationInfo.operation["x-typedapi-filter-form"],
907
909
  };
908
910
  }
909
911