typedapi-client-helpers 0.3.4 → 0.3.5

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 CHANGED
@@ -2,6 +2,22 @@
2
2
 
3
3
  All notable changes to `typedapi-client-helpers` are documented in this file.
4
4
 
5
+ ## [0.3.5] - 2026-07-24
6
+
7
+ ### Added
8
+
9
+ * Added support for `x-typedapi-filter-form` on non-paginated query endpoints.
10
+ * Added `buildFilterQuery<TQuery>()` for converting `FilterFormValues` without adding pagination or sorting.
11
+ * Generated filter-form methods now group normal query values and typed filter-form values inside a single `filterForm` argument.
12
+ * The `filterForm.query` values are merged with the output of `buildFilterQuery(filterForm.filters)` before sending the request.
13
+ * Optional-query endpoints default the complete `filterForm` argument to an empty object.
14
+
15
+ ### Compatibility notes
16
+
17
+ * Pair this package with `TypedApi.Swagger` 0.3.2 or newer to use `[TypedApiFilterForm]`.
18
+ * Existing paginated method signatures and `buildQuery()` behaviour are unchanged.
19
+ * Regenerate committed API files because filter-form endpoint signatures have changed.
20
+
5
21
  ## [0.3.4] - 2026-07-22
6
22
 
7
23
  ### Added
package/README.md CHANGED
@@ -2,17 +2,120 @@
2
2
 
3
3
  A small Fetch runtime and OpenAPI 3 TypeScript generator designed to work with `TypedApi.Swagger` on ASP.NET Core.
4
4
 
5
- ## Version 0.3.4 highlights
5
+ ## Version 0.3.5 highlights
6
+
7
+ * Supports `[TypedApiFilterForm]` endpoints through the `x-typedapi-filter-form` OpenAPI extension.
8
+ * Generates grouped filter-form arguments containing normal query values and `FilterFormValues`.
9
+ * Provides `buildFilterQuery<TQuery>()` for converting filters without adding pagination or sorting.
10
+ * Reconstructs opted-in closed .NET generic schemas as reusable TypeScript generics.
11
+ * Supports exact generic bindings for direct properties, arrays, collections, and dictionary values.
12
+ * Preserves required-but-nullable properties as required `T | null` instead of optional values.
13
+ * Generates non-cyclic discriminated unions with literal discriminator fields.
14
+ * Consumes readable generic schema IDs such as `ApiPaginationResponseOfProjectModel`.
15
+ * Generates method-specific unions from typed `ProblemDetails` error responses.
16
+ * Validates TypedApi OpenAPI contract version 2.
17
+
18
+ ## Filter-form endpoints
19
+
20
+ `TypedApi.Swagger` can mark a non-paginated query endpoint with `[TypedApiFilterForm]`.
21
+
22
+ The attribute can be placed on the query parameter:
23
+
24
+ ```csharp
25
+ [HttpGet("map-items")]
26
+ public async Task<ActionResult<IReadOnlyList<MapItemResponse>>> GetMapItems(
27
+ [FromQuery, TypedApiFilterForm] ChargePointMapFilter filter,
28
+ CancellationToken token)
29
+ {
30
+ return Ok(await service.GetMapItemsAsync(filter, token));
31
+ }
32
+ ```
33
+
34
+ It can also be placed on the endpoint:
35
+
36
+ ```csharp
37
+ [TypedApiFilterForm]
38
+ [HttpGet("map-items")]
39
+ public async Task<ActionResult<IReadOnlyList<MapItemResponse>>> GetMapItems(
40
+ [FromQuery] ChargePointMapFilter filter,
41
+ CancellationToken token)
42
+ {
43
+ return Ok(await service.GetMapItemsAsync(filter, token));
44
+ }
45
+ ```
46
+
47
+ Placing the attribute on the query parameter is preferred because it explicitly identifies which parameter represents the filter form.
48
+
49
+ The generated frontend method groups normal query values and dynamic filters inside one `filterForm` argument:
50
+
51
+ ```ts
52
+ export async function chargePointGetMapItems(
53
+ filterForm: {
54
+ query: ChargePointGetMapItemsQueryParams;
55
+ filters?: FilterFormValues<ChargePointGetMapItemsQueryParams>[];
56
+ },
57
+ options: ApiMethodOptions<
58
+ MapItemResponse[],
59
+ HttpValidationProblemDetails | ProblemDetails,
60
+ RequestParams
61
+ > = {},
62
+ ): Promise<
63
+ ApiResult<
64
+ MapItemResponse[],
65
+ HttpValidationProblemDetails | ProblemDetails
66
+ >
67
+ >;
68
+ ```
69
+
70
+ Call the generated endpoint with fixed query values and optional filters:
71
+
72
+ ```ts
73
+ await chargePointGetMapItems({
74
+ query: {
75
+ west,
76
+ south,
77
+ east,
78
+ north,
79
+ zoom,
80
+ },
81
+ filters: activeFilters,
82
+ });
83
+ ```
84
+
85
+ The generated implementation merges both sources before sending the request:
86
+
87
+ ```ts
88
+ const builtQuery = {
89
+ ...(filterForm.query ?? {}),
90
+ ...buildFilterQuery<ChargePointGetMapItemsQueryParams>(
91
+ filterForm.filters ?? [],
92
+ ),
93
+ };
94
+ ```
6
95
 
7
- - Reconstructs opted-in closed .NET generic schemas as reusable TypeScript generics.
8
- - Supports exact generic bindings for direct properties, arrays/collections, and dictionary values.
9
- - Preserves required-but-nullable properties as required `T | null` instead of optional values.
10
- - Generates non-cyclic discriminated unions with literal discriminator fields.
11
- - Consumes readable generic schema IDs such as `ApiPaginationResponseOfProjectModel`.
12
- - Generates method-specific unions from typed `ProblemDetails` error responses.
13
- - Validates TypedApi OpenAPI contract version 2.
96
+ Values produced by `buildFilterQuery()` are applied after normal query values. This means an active filter with the same property name overrides the value from `filterForm.query`.
14
97
 
15
- ### Generic contracts
98
+ Filter-form endpoints do not automatically add pagination or sorting. Paginated endpoints continue to use `buildQuery()`.
99
+
100
+ For an endpoint where every query property is optional, the generated argument defaults to an empty object:
101
+
102
+ ```ts
103
+ export async function searchItems(
104
+ filterForm: {
105
+ query?: SearchItemsQueryParams;
106
+ filters?: FilterFormValues<SearchItemsQueryParams>[];
107
+ } = {},
108
+ options: ApiMethodOptions<
109
+ SearchResult[],
110
+ ProblemDetails,
111
+ RequestParams
112
+ > = {},
113
+ );
114
+ ```
115
+
116
+ For endpoints containing required query properties, `filterForm` and `filterForm.query` remain required.
117
+
118
+ ## Generic contracts
16
119
 
17
120
  With `[TypedApiGeneric]` on a backend wrapper, a closed schema such as `ApiEnvelope<ProjectModel>` becomes one reusable declaration:
18
121
 
@@ -24,7 +127,9 @@ export interface ApiEnvelope<T> {
24
127
  }
25
128
  ```
26
129
 
27
- Endpoints then use `ApiEnvelope<ProjectModel>` rather than a repeated `ApiEnvelopeOfProjectModel` interface. Inherited generic wrappers are reconstructed whether Swagger uses `allOf` or emits a flattened schema:
130
+ Endpoints then use `ApiEnvelope<ProjectModel>` rather than a repeated `ApiEnvelopeOfProjectModel` interface.
131
+
132
+ Inherited generic wrappers are reconstructed whether Swagger uses `allOf` or emits a flattened schema:
28
133
 
29
134
  ```ts
30
135
  export type ApiPaginationSortResponse<T> = ApiPaginationResponse<T> & {
@@ -33,7 +138,7 @@ export type ApiPaginationSortResponse<T> = ApiPaginationResponse<T> & {
33
138
  };
34
139
  ```
35
140
 
36
- ### Required and nullable properties
141
+ ## Required and nullable properties
37
142
 
38
143
  OpenAPI presence and nullability remain separate:
39
144
 
@@ -45,12 +150,14 @@ export interface NullabilityContract {
45
150
  }
46
151
  ```
47
152
 
48
- ### Discriminated unions
153
+ ## Discriminated unions
49
154
 
50
155
  Serializer discriminator mappings produce narrowing unions without cyclic inheritance:
51
156
 
52
157
  ```ts
53
- export type NotificationModel = EmailNotificationModel | SmsNotificationModel;
158
+ export type NotificationModel =
159
+ | EmailNotificationModel
160
+ | SmsNotificationModel;
54
161
 
55
162
  export type EmailNotificationModel = NotificationModelBase & {
56
163
  emailAddress: string;
@@ -58,18 +165,21 @@ export type EmailNotificationModel = NotificationModelBase & {
58
165
  };
59
166
  ```
60
167
 
61
- ### Typed errors
168
+ ## Typed errors
62
169
 
63
170
  When the OpenAPI operation contains typed error response bodies, generated methods expose the union directly:
64
171
 
65
172
  ```ts
66
- ApiResult<ProjectModel, HttpValidationProblemDetails | ProblemDetails>
173
+ ApiResult<
174
+ ProjectModel,
175
+ HttpValidationProblemDetails | ProblemDetails
176
+ >
67
177
  ```
68
178
 
69
179
  ## Install
70
180
 
71
181
  ```bash
72
- npm install typedapi-client-helpers@0.3.4
182
+ npm install typedapi-client-helpers@0.3.5
73
183
  ```
74
184
 
75
185
  The generator expects an OpenAPI 3.x document. Swagger 2.0 documents are rejected with a clear error.
@@ -114,14 +224,18 @@ src/api/
114
224
  └── Products.api.ts
115
225
  ```
116
226
 
117
- The generator intentionally does not create `src/api/index.ts`. Import generated methods and contracts directly from their files:
227
+ The generator intentionally does not create `src/api/index.ts`.
228
+
229
+ Import generated methods and contracts directly from their files:
118
230
 
119
231
  ```ts
120
232
  import { getProducts } from "./api/methods/Product.api";
121
233
  import type { ProductModel } from "./api/generated/data-contracts";
122
234
  ```
123
235
 
124
- When upgrading from an earlier 0.3.0 build, the next generation removes the old generated `src/api/index.ts`. The Swagger backup belongs to the consuming project, not this npm package.
236
+ When upgrading from an earlier 0.3.0 build, the next generation removes the old generated `src/api/index.ts`.
237
+
238
+ The Swagger backup belongs to the consuming project, not this npm package.
125
239
 
126
240
  ## Frontend method names
127
241
 
@@ -136,10 +250,10 @@ Choose the source of generated function names with `typedApiMethodNameStyle`:
136
250
  }
137
251
  ```
138
252
 
139
- - `"operationId"` uses the unique OpenAPI operation ID. The controller-prefix setting does not affect this mode.
140
- - `"action"` uses the original ASP.NET controller action name.
141
- - `typedApiPrefixMethodNamesWithController: true` is the default in action mode and prefixes the normalized controller name.
142
- - `typedApiPrefixMethodNamesWithController: false` removes the controller prefix.
253
+ * `"operationId"` uses the unique OpenAPI operation ID. The controller-prefix setting does not affect this mode.
254
+ * `"action"` uses the original ASP.NET controller action name.
255
+ * `typedApiPrefixMethodNamesWithController: true` is the default in action mode and prefixes the normalized controller name.
256
+ * `typedApiPrefixMethodNamesWithController: false` removes the controller prefix.
143
257
 
144
258
  For an `OrderController` action named `GetOrderById`:
145
259
 
@@ -148,15 +262,18 @@ typedApiPrefixMethodNamesWithController: true -> orderGetOrderById(...)
148
262
  typedApiPrefixMethodNamesWithController: false -> getOrderById(...)
149
263
  ```
150
264
 
151
- Operation-specific types follow the selected name too:
265
+ Operation-specific types follow the selected name:
152
266
 
153
267
  ```text
154
268
  true -> OrderGetOrderByIdParams
155
269
  false -> GetOrderByIdParams
156
270
  ```
157
271
 
158
- Action-name mode requires the matching `TypedApi.Swagger` package, which emits `x-typedapi-operation.actionName`. When the prefix is enabled, the controller name is read from `x-typedapi-operation.controllerName`, the first OpenAPI tag, or the route. If disabling the prefix creates duplicate names across controllers, generation stops with a clear collision error; enable the prefix, rename an action, or use `"operationId"`.
272
+ Action-name mode requires the matching `TypedApi.Swagger` package, which emits `x-typedapi-operation.actionName`.
159
273
 
274
+ When the prefix is enabled, the controller name is read from `x-typedapi-operation.controllerName`, the first OpenAPI tag, or the route.
275
+
276
+ If disabling the prefix creates duplicate names across controllers, generation stops with a clear collision error. Enable the prefix, rename an action, or use `"operationId"`.
160
277
 
161
278
  ## CI consistency check
162
279
 
@@ -169,15 +286,18 @@ This generates in memory and exits with an error when committed generated files
169
286
  Other modes:
170
287
 
171
288
  ```bash
172
- typedapi-generate --strict # fail instead of using a backup after a download error
173
- typedapi-generate --offline # intentionally generate from the backup
174
- typedapi-generate --verbose # print additional diagnostics
289
+ typedapi-generate --strict
290
+ typedapi-generate --offline
291
+ typedapi-generate --verbose
175
292
  ```
176
293
 
294
+ * `--strict` fails instead of using a backup after a Swagger download error.
295
+ * `--offline` intentionally generates from the configured Swagger backup.
296
+ * `--verbose` prints additional generator diagnostics.
177
297
 
178
298
  ## Generated property names
179
299
 
180
- Generated interface and parameter members lowercase **only the first character**:
300
+ Generated interface and parameter members lowercase only the first character:
181
301
 
182
302
  ```ts
183
303
  Files -> files
@@ -186,15 +306,33 @@ ProductID -> productID
186
306
  snake_case -> snake_case
187
307
  ```
188
308
 
189
- The rest of each name is left unchanged. Generated wire metadata converts these local names back to the exact OpenAPI names for JSON, multipart forms, query parameters, headers, cookies, and path parameters. Responses and documented error bodies are converted in the opposite direction.
309
+ The remainder of each property name is preserved.
310
+
311
+ Generated wire metadata converts local property names back to the exact OpenAPI names for:
312
+
313
+ * JSON bodies
314
+ * Multipart forms
315
+ * Query parameters
316
+ * Header parameters
317
+ * Cookie parameters
318
+ * Path parameters
319
+
320
+ Responses and documented error bodies are converted in the opposite direction.
190
321
 
191
322
  ## Generated method shape
192
323
 
193
- Generated methods keep parameters and request bodies as separate positional groups. Non-body parameters come first, the body comes second, and request options remain last:
324
+ Generated methods keep parameters and request bodies in separate positional groups.
325
+
326
+ Non-body parameters come first, the body comes second, and request options remain last:
194
327
 
195
328
  ```ts
196
329
  await updateWarehouse(
197
- { id: requireId(context.warehouseId, "Warehouse ID") },
330
+ {
331
+ id: requireId(
332
+ context.warehouseId,
333
+ "Warehouse ID",
334
+ ),
335
+ },
198
336
  warehouse,
199
337
  );
200
338
  ```
@@ -205,23 +343,69 @@ The generated signature is:
205
343
  export async function updateWarehouse(
206
344
  pathParams: UpdateWarehouseParams,
207
345
  data: WarehouseRequest,
208
- options: ApiMethodOptions<WarehouseModel, ApiHttpError, RequestParams> = {},
346
+ options: ApiMethodOptions<
347
+ WarehouseModel,
348
+ ApiHttpError,
349
+ RequestParams
350
+ > = {},
209
351
  ): Promise<ApiResult<WarehouseModel, ApiHttpError>>;
210
352
  ```
211
353
 
212
354
  Path operations without a body use only the params object:
213
355
 
214
356
  ```ts
215
- await deleteSupplier({ id: requireId(context.supplierId, "Supplier ID") });
357
+ await deleteSupplier({
358
+ id: requireId(context.supplierId, "Supplier ID"),
359
+ });
216
360
  ```
217
361
 
218
362
  Body-only operations accept their payload directly:
219
363
 
220
364
  ```ts
221
- await uploadProductFiles({ files: selectedFiles });
365
+ await uploadProductFiles({
366
+ files: selectedFiles,
367
+ });
368
+ ```
369
+
370
+ Regular query-only operations use a `query` argument:
371
+
372
+ ```ts
373
+ await searchProducts({
374
+ search: "charger",
375
+ limit: 25,
376
+ });
377
+ ```
378
+
379
+ Endpoints marked with `x-typedapi-filter-form` use a grouped `filterForm` argument containing `query` and optional `filters` properties:
380
+
381
+ ```ts
382
+ await searchMapItems({
383
+ query: {
384
+ west,
385
+ south,
386
+ east,
387
+ north,
388
+ zoom,
389
+ },
390
+ filters: activeFilters,
391
+ });
392
+ ```
393
+
394
+ Paginated endpoints use their existing filter, page, page-size, and sorting arguments:
395
+
396
+ ```ts
397
+ await getProducts(
398
+ activeFilters,
399
+ 1,
400
+ 100,
401
+ "name",
402
+ SortDirection.Ascending,
403
+ );
222
404
  ```
223
405
 
224
- Query-only operations use a `query` argument. Operations involving headers or cookies without path parameters use `requestParams`. All generated parameter interfaces are named `OperationNameParams`; nested `path`, `query`, `headers`, `cookies`, or `body` wrappers are not generated.
406
+ Operations involving headers or cookies without path parameters use `requestParams`.
407
+
408
+ All generated parameter interfaces are named `OperationNameParams`. Nested `path`, `query`, `headers`, `cookies`, or `body` wrappers are not generated for regular endpoint methods.
225
409
 
226
410
  Request options are always last:
227
411
 
@@ -230,50 +414,145 @@ await updateWarehouse(pathParams, data, {
230
414
  params: {
231
415
  signal: abortController.signal,
232
416
  timeoutMs: 10_000,
233
- headers: new Headers({ Authorization: "Bearer ..." }),
417
+ headers: new Headers({
418
+ Authorization: "Bearer ...",
419
+ }),
420
+ },
421
+ onSuccess: result => {
422
+ console.log(result.response);
423
+ },
424
+ onError: result => {
425
+ console.error(result.error);
234
426
  },
235
- onSuccess: result => console.log(result.response),
236
- onError: result => console.error(result.error),
237
427
  });
238
428
  ```
239
429
 
430
+ ## Filter conversion
431
+
432
+ Use `buildFilterQuery()` when filters must be converted without adding pagination or sorting:
433
+
434
+ ```ts
435
+ const query = buildFilterQuery<ProductQueryParams>(
436
+ activeFilters,
437
+ );
438
+ ```
439
+
440
+ Use `buildQuery()` for paginated endpoints:
441
+
442
+ ```ts
443
+ const query = buildQuery<
444
+ ProductQueryParams,
445
+ ProductTableRow
446
+ >(
447
+ activeFilters,
448
+ page,
449
+ pageSize,
450
+ sortBy,
451
+ sortDirection,
452
+ );
453
+ ```
454
+
455
+ `buildFilterQuery()` supports the same `FilterFormValues` conversion behavior as `buildQuery()`, including:
456
+
457
+ * Primitive values
458
+ * Minimum and maximum values
459
+ * Lists
460
+ * `OptionValue` values
461
+ * Booleans
462
+ * Numbers
463
+ * Dates
464
+ * Strings
465
+
466
+ It does not add:
467
+
468
+ * `pageNumber`
469
+ * `pageSize`
470
+ * `sortBy`
471
+ * `sortDirection`
472
+
240
473
  ## Typed errors
241
474
 
242
- When the OpenAPI document describes error response bodies, generated methods expose their generated type or union. When no error schema is documented, the method uses `ApiHttpError` instead of `unknown`:
475
+ When the OpenAPI document describes error response bodies, generated methods expose their generated type or union.
476
+
477
+ When no error schema is documented, the method uses `ApiHttpError` instead of `unknown`:
243
478
 
244
479
  ```ts
245
- const result = await cancelOrder({ id, reason });
480
+ const result = await cancelOrder({
481
+ id,
482
+ reason,
483
+ });
246
484
 
247
485
  if (!result.ok) {
248
486
  if (result.error.kind === "http") {
249
- console.error(result.error.status, result.error.body);
487
+ console.error(
488
+ result.error.status,
489
+ result.error.body,
490
+ );
250
491
  } else {
251
- // Structured TypedApi client error: network, aborted, or parse.
252
492
  console.error(result.error);
253
493
  }
254
494
  }
255
495
  ```
256
496
 
257
- Malformed JSON is reported as a structured parse error instead of being cast to the expected response type. Consumer callback exceptions are allowed to propagate and are not disguised as API failures.
497
+ Structured client errors include:
498
+
499
+ * Network errors
500
+ * Aborted requests
501
+ * Timeout errors
502
+ * Malformed response errors
503
+
504
+ Malformed JSON is reported as a structured parse error instead of being cast to the expected response type.
505
+
506
+ Exceptions thrown by consumer `onSuccess` and `onError` callbacks propagate normally.
258
507
 
259
508
  ## Runtime configuration
260
509
 
261
510
  ```ts
262
- import { configureApiClient, setSecurityData } from "typedapi-client-helpers";
511
+ import {
512
+ configureApiClient,
513
+ setSecurityData,
514
+ } from "typedapi-client-helpers";
263
515
 
264
516
  configureApiClient({
265
517
  baseUrl: "https://api.example.com",
266
518
  timeoutMs: 15_000,
267
- securityWorker: token => token
268
- ? { headers: { Authorization: `Bearer ${token}` } }
269
- : undefined
519
+ securityWorker: token =>
520
+ token
521
+ ? {
522
+ headers: {
523
+ Authorization: `Bearer ${token}`,
524
+ },
525
+ }
526
+ : undefined,
270
527
  });
271
528
 
272
529
  setSecurityData("access-token");
273
530
  ```
274
531
 
275
- The runtime also exports `createApiClient`, `request`, `abortRequest`, `mergeHeaders`, `toRequestHeaders`, `toCookieHeader`, `handleApiResponse`, `buildQuery`, `toFormData`.
532
+ The runtime also exports:
533
+
534
+ * `createApiClient`
535
+ * `request`
536
+ * `abortRequest`
537
+ * `mergeHeaders`
538
+ * `toRequestHeaders`
539
+ * `toCookieHeader`
540
+ * `handleApiResponse`
541
+ * `buildFilterQuery`
542
+ * `buildQuery`
543
+ * `toFormData`
276
544
 
277
545
  ## Backend pairing
278
546
 
279
- Use `TypedApi.Swagger` 0.3.1 or newer on the backend for generic reconstruction, corrected nullability, discriminators, readable schema IDs, and typed default errors. It emits `x-typedapi.contractVersion = 2`. The generator validates the contract version before replacing generated files.
547
+ Use `TypedApi.Swagger` 0.3.2 or newer on the backend for:
548
+
549
+ * `[TypedApiFilterForm]`
550
+ * Generic reconstruction
551
+ * Corrected nullability
552
+ * Discriminators
553
+ * Readable schema IDs
554
+ * Typed default errors
555
+
556
+ The backend package emits `x-typedapi-filter-form` for opted-in filter endpoints.
557
+
558
+ It also emits `x-typedapi.contractVersion = 2` for contract compatibility. The generator validates the contract version before replacing generated files.
@@ -1,15 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildFilterQuery = buildFilterQuery;
3
4
  exports.buildQuery = buildQuery;
4
5
  /**
5
- * Builds a typed query object from filter form values, pagination, and sorting.
6
+ * Builds a typed query object from filter form values without adding pagination or sorting.
6
7
  *
7
8
  * Empty filter values are ignored.
8
9
  * List filters are converted item by item.
9
10
  * `OptionValue` lists send their `value` fields instead of the display names.
10
11
  */
11
- function buildQuery(filters, page = 1, pageSize = 100, sortBy = null, sortDirection) {
12
- const query = filters.reduce((queryResult, filter) => {
12
+ function buildFilterQuery(filters) {
13
+ return filters.reduce((queryResult, filter) => {
13
14
  const { filterName, filterNameMax, type, value, maxValue, isAList } = filter;
14
15
  if (isEmptyValue(value) && isEmptyValue(maxValue)) {
15
16
  return queryResult;
@@ -31,11 +32,22 @@ function buildQuery(filters, page = 1, pageSize = 100, sortBy = null, sortDirect
31
32
  if (filterNameMax && !isEmptyValue(maxValue)) {
32
33
  const convertedMaxValue = convertType(type, maxValue);
33
34
  if (convertedMaxValue !== null) {
34
- queryResult[filterNameMax] = convertedMaxValue;
35
+ queryResult[filterNameMax] =
36
+ convertedMaxValue;
35
37
  }
36
38
  }
37
39
  return queryResult;
38
40
  }, {});
41
+ }
42
+ /**
43
+ * Builds a typed query object from filter form values, pagination, and sorting.
44
+ *
45
+ * Empty filter values are ignored.
46
+ * List filters are converted item by item.
47
+ * `OptionValue` lists send their `value` fields instead of the display names.
48
+ */
49
+ function buildQuery(filters, page = 1, pageSize = 100, sortBy = null, sortDirection) {
50
+ const query = buildFilterQuery(filters);
39
51
  query.pageNumber = page;
40
52
  if (pageSize > 0) {
41
53
  query.pageSize = pageSize;
@@ -1 +1 @@
1
- {"version":3,"file":"buildQuery.js","sourceRoot":"","sources":["../../../src/utils/buildQuery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"buildQuery.js","sourceRoot":"","sources":["../../../src/utils/buildQuery.ts"],"names":[],"mappings":"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"}
@@ -1,12 +1,12 @@
1
1
  /**
2
- * Builds a typed query object from filter form values, pagination, and sorting.
2
+ * Builds a typed query object from filter form values without adding pagination or sorting.
3
3
  *
4
4
  * Empty filter values are ignored.
5
5
  * List filters are converted item by item.
6
6
  * `OptionValue` lists send their `value` fields instead of the display names.
7
7
  */
8
- export function buildQuery(filters, page = 1, pageSize = 100, sortBy = null, sortDirection) {
9
- const query = filters.reduce((queryResult, filter) => {
8
+ export function buildFilterQuery(filters) {
9
+ return filters.reduce((queryResult, filter) => {
10
10
  const { filterName, filterNameMax, type, value, maxValue, isAList } = filter;
11
11
  if (isEmptyValue(value) && isEmptyValue(maxValue)) {
12
12
  return queryResult;
@@ -28,11 +28,22 @@ export function buildQuery(filters, page = 1, pageSize = 100, sortBy = null, sor
28
28
  if (filterNameMax && !isEmptyValue(maxValue)) {
29
29
  const convertedMaxValue = convertType(type, maxValue);
30
30
  if (convertedMaxValue !== null) {
31
- queryResult[filterNameMax] = convertedMaxValue;
31
+ queryResult[filterNameMax] =
32
+ convertedMaxValue;
32
33
  }
33
34
  }
34
35
  return queryResult;
35
36
  }, {});
37
+ }
38
+ /**
39
+ * Builds a typed query object from filter form values, pagination, and sorting.
40
+ *
41
+ * Empty filter values are ignored.
42
+ * List filters are converted item by item.
43
+ * `OptionValue` lists send their `value` fields instead of the display names.
44
+ */
45
+ export function buildQuery(filters, page = 1, pageSize = 100, sortBy = null, sortDirection) {
46
+ const query = buildFilterQuery(filters);
36
47
  query.pageNumber = page;
37
48
  if (pageSize > 0) {
38
49
  query.pageSize = pageSize;
@@ -1 +1 @@
1
- {"version":3,"file":"buildQuery.js","sourceRoot":"","sources":["../../../src/utils/buildQuery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"buildQuery.js","sourceRoot":"","sources":["../../../src/utils/buildQuery.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,14 @@ import { FilterFormValues } from "../interfaces/Filter";
5
5
  * APIs often expose sort direction as either a string enum or numeric enum, so both are supported.
6
6
  */
7
7
  export type SortDirection = number | string;
8
+ /**
9
+ * Builds a typed query object from filter form values without adding pagination or sorting.
10
+ *
11
+ * Empty filter values are ignored.
12
+ * List filters are converted item by item.
13
+ * `OptionValue` lists send their `value` fields instead of the display names.
14
+ */
15
+ export declare function buildFilterQuery<TQuery>(filters: FilterFormValues<TQuery>[]): Partial<TQuery>;
8
16
  /**
9
17
  * Builds a typed query object from filter form values, pagination, and sorting.
10
18
  *
@@ -1 +1 @@
1
- {"version":3,"file":"buildQuery.d.ts","sourceRoot":"","sources":["../../../src/utils/buildQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAe,MAAM,sBAAsB,CAAC;AAErE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EACpD,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,EACnC,IAAI,SAAI,EACR,QAAQ,SAAM,EACd,MAAM,GAAE,MAAM,UAAU,GAAG,IAAW,EACtC,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,MAAM,CAAC,CAwDjB"}
1
+ {"version":3,"file":"buildQuery.d.ts","sourceRoot":"","sources":["../../../src/utils/buildQuery.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAe,MAAM,sBAAsB,CAAC;AAErE;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5C;;;;;;GAMG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EACrC,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,GAClC,OAAO,CAAC,MAAM,CAAC,CAyCjB;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,MAAM,EACpD,OAAO,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,EACnC,IAAI,SAAI,EACR,QAAQ,SAAM,EACd,MAAM,GAAE,MAAM,UAAU,GAAG,IAAW,EACtC,aAAa,CAAC,EAAE,aAAa,GAC5B,OAAO,CAAC,MAAM,CAAC,CAkBjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typedapi-client-helpers",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "description": "Reusable typed OpenAPI client helpers.",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -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