typedapi-client-helpers 0.3.4 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/generate-api-minimal.mjs +50 -9
- package/scripts/openapi-generator/index.mjs +101 -15
- 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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"}
|