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 +16 -0
- package/README.md +328 -49
- package/dist/cjs/utils/buildQuery.js +16 -4
- package/dist/cjs/utils/buildQuery.js.map +1 -1
- package/dist/esm/utils/buildQuery.js +15 -4
- package/dist/esm/utils/buildQuery.js.map +1 -1
- package/dist/types/utils/buildQuery.d.ts +8 -0
- package/dist/types/utils/buildQuery.d.ts.map +1 -1
- package/package.json +1 -1
- package/scripts/openapi-generator/method-template.mjs +220 -57
- package/scripts/openapi-generator/schema.mjs +6 -4
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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<
|
|
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.
|
|
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`.
|
|
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`.
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
|
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`.
|
|
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
|
|
173
|
-
typedapi-generate --offline
|
|
174
|
-
typedapi-generate --verbose
|
|
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
|
|
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
|
|
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
|
|
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
|
-
{
|
|
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<
|
|
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({
|
|
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({
|
|
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
|
-
|
|
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({
|
|
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.
|
|
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({
|
|
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(
|
|
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
|
-
|
|
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 {
|
|
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 =>
|
|
268
|
-
|
|
269
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
12
|
-
|
|
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] =
|
|
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":";;AAgBA,
|
|
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
|
|
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
|
|
9
|
-
|
|
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] =
|
|
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":"AASA;;;;;;GAMG;AACH,MAAM,UAAU,
|
|
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,
|
|
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
|
@@ -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)
|
|
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(
|
|
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(
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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) =>
|
|
42
|
-
|
|
43
|
-
|
|
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) =>
|
|
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 (
|
|
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":
|
|
93
|
-
|
|
94
|
-
case "
|
|
95
|
-
|
|
96
|
-
case "
|
|
97
|
-
|
|
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 (
|
|
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 =
|
|
149
|
-
.
|
|
150
|
-
|
|
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(
|
|
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
|
|
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 =
|
|
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, {
|
|
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
|
-
)
|
|
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 =
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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))
|
|
327
|
-
|
|
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(
|
|
444
|
+
dataContractImports.push(
|
|
445
|
+
...extractTopLevelTypeNames(operationTypes.bodyType),
|
|
446
|
+
);
|
|
330
447
|
}
|
|
331
448
|
if (!isPrimitiveType(operationTypes.responseType)) {
|
|
332
|
-
dataContractImports.push(
|
|
449
|
+
dataContractImports.push(
|
|
450
|
+
...extractTopLevelTypeNames(operationTypes.responseType),
|
|
451
|
+
);
|
|
333
452
|
}
|
|
334
453
|
if (!isPrimitiveType(operationTypes.errorType)) {
|
|
335
|
-
dataContractImports.push(
|
|
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
|
|
464
|
+
if (operationTypes.hasHeaderParams) needsHeaderParams = true;
|
|
465
|
+
if (operationTypes.hasCookieParams) needsCookieParams = true;
|
|
344
466
|
if (
|
|
345
|
-
operationTypes
|
|
346
|
-
|
|
347
|
-
|
|
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(
|
|
480
|
+
methods.push(
|
|
481
|
+
createMethod(
|
|
482
|
+
operationInfo,
|
|
483
|
+
operationTypes,
|
|
484
|
+
options.defaultHandlers,
|
|
485
|
+
options,
|
|
486
|
+
),
|
|
487
|
+
);
|
|
354
488
|
}
|
|
355
489
|
|
|
356
|
-
if (
|
|
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",
|
|
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(
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
createImport(
|
|
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
|
|
869
|
-
|
|
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 =
|
|
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 && !
|
|
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
|
|