typedapi-client-helpers 0.3.3 → 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 +47 -0
- package/README.md +376 -48
- 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/index.mjs +1 -1
- package/scripts/openapi-generator/method-template.mjs +220 -57
- package/scripts/openapi-generator/openapi-utils.mjs +412 -411
- package/scripts/openapi-generator/schema.mjs +476 -42
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
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
|
+
|
|
21
|
+
## [0.3.4] - 2026-07-22
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
* Added support for `x-typedapi-generic` metadata and reusable TypeScript generic declarations.
|
|
26
|
+
* Added exact generic substitutions for direct properties, array/collection items, dictionary values, and inherited generic wrappers.
|
|
27
|
+
* Added discriminator mapping support with literal discriminator properties and frontend union types.
|
|
28
|
+
* Added readable closed-generic reference handling such as `ApiPaginationResponseOfProjectModel` to `ApiPaginationResponse<ProjectModel>`.
|
|
29
|
+
* Added generator coverage for custom generic envelopes, pagination, nullability combinations, polymorphism, schema IDs, typed errors, and TypeScript compilation.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
* Requiredness and nullability now remain independent in generated property declarations.
|
|
34
|
+
* Polymorphic base schemas now generate a separate `Base` contract plus a union of concrete variants.
|
|
35
|
+
* Derived polymorphic schemas inherit from the base contract rather than from the union, preventing cyclic TypeScript types.
|
|
36
|
+
* Inline `allOf` object fragments are emitted directly in intersections instead of creating unnecessary numbered helper interfaces.
|
|
37
|
+
* Typed OpenAPI error responses are deduplicated into method-specific error unions.
|
|
38
|
+
* TypedApi contract version 2 is now supported.
|
|
39
|
+
|
|
40
|
+
### Fixed
|
|
41
|
+
|
|
42
|
+
* Fixed flattened inherited generic wrappers accidentally capturing the first concrete schema, such as generating `data: OrderModel[]` inside `ApiPaginationSortResponse<T>`.
|
|
43
|
+
* Fixed flattened generic inheritance being emitted as a duplicated standalone interface; annotated derived wrappers now preserve their reusable generic base intersection.
|
|
44
|
+
* Invalid generic metadata with unused type parameters now fails with a clear error instead of emitting a misleading generic declaration.
|
|
45
|
+
|
|
46
|
+
### Compatibility notes
|
|
47
|
+
|
|
48
|
+
* Pair this package with `TypedApi.Swagger` 0.3.1 or newer for all new features.
|
|
49
|
+
* Regenerate committed API files after upgrading.
|
|
50
|
+
* Generic reconstruction only occurs for schemas carrying `x-typedapi-generic`; ordinary closed generic schemas remain ordinary component types.
|
|
51
|
+
|
|
5
52
|
## [0.3.0] - 2026-07-10
|
|
6
53
|
|
|
7
54
|
### Added
|
package/README.md
CHANGED
|
@@ -2,25 +2,184 @@
|
|
|
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 highlights
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
+
```
|
|
95
|
+
|
|
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`.
|
|
97
|
+
|
|
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
|
|
119
|
+
|
|
120
|
+
With `[TypedApiGeneric]` on a backend wrapper, a closed schema such as `ApiEnvelope<ProjectModel>` becomes one reusable declaration:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
export interface ApiEnvelope<T> {
|
|
124
|
+
data: T;
|
|
125
|
+
relatedItems: T[];
|
|
126
|
+
itemsByKey: Record<string, T>;
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
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:
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
export type ApiPaginationSortResponse<T> = ApiPaginationResponse<T> & {
|
|
136
|
+
sortBy?: string | null;
|
|
137
|
+
sortDirection: SortDirection;
|
|
138
|
+
};
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Required and nullable properties
|
|
142
|
+
|
|
143
|
+
OpenAPI presence and nullability remain separate:
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
export interface NullabilityContract {
|
|
147
|
+
requiredText: string;
|
|
148
|
+
requiredNullableText: string | null;
|
|
149
|
+
optionalNullableText?: string | null;
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Discriminated unions
|
|
154
|
+
|
|
155
|
+
Serializer discriminator mappings produce narrowing unions without cyclic inheritance:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
export type NotificationModel =
|
|
159
|
+
| EmailNotificationModel
|
|
160
|
+
| SmsNotificationModel;
|
|
161
|
+
|
|
162
|
+
export type EmailNotificationModel = NotificationModelBase & {
|
|
163
|
+
emailAddress: string;
|
|
164
|
+
kind: "email";
|
|
165
|
+
};
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Typed errors
|
|
169
|
+
|
|
170
|
+
When the OpenAPI operation contains typed error response bodies, generated methods expose the union directly:
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
ApiResult<
|
|
174
|
+
ProjectModel,
|
|
175
|
+
HttpValidationProblemDetails | ProblemDetails
|
|
176
|
+
>
|
|
177
|
+
```
|
|
19
178
|
|
|
20
179
|
## Install
|
|
21
180
|
|
|
22
181
|
```bash
|
|
23
|
-
npm install typedapi-client-helpers@0.3.
|
|
182
|
+
npm install typedapi-client-helpers@0.3.5
|
|
24
183
|
```
|
|
25
184
|
|
|
26
185
|
The generator expects an OpenAPI 3.x document. Swagger 2.0 documents are rejected with a clear error.
|
|
@@ -65,14 +224,18 @@ src/api/
|
|
|
65
224
|
└── Products.api.ts
|
|
66
225
|
```
|
|
67
226
|
|
|
68
|
-
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:
|
|
69
230
|
|
|
70
231
|
```ts
|
|
71
232
|
import { getProducts } from "./api/methods/Product.api";
|
|
72
233
|
import type { ProductModel } from "./api/generated/data-contracts";
|
|
73
234
|
```
|
|
74
235
|
|
|
75
|
-
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.
|
|
76
239
|
|
|
77
240
|
## Frontend method names
|
|
78
241
|
|
|
@@ -87,10 +250,10 @@ Choose the source of generated function names with `typedApiMethodNameStyle`:
|
|
|
87
250
|
}
|
|
88
251
|
```
|
|
89
252
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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.
|
|
94
257
|
|
|
95
258
|
For an `OrderController` action named `GetOrderById`:
|
|
96
259
|
|
|
@@ -99,15 +262,18 @@ typedApiPrefixMethodNamesWithController: true -> orderGetOrderById(...)
|
|
|
99
262
|
typedApiPrefixMethodNamesWithController: false -> getOrderById(...)
|
|
100
263
|
```
|
|
101
264
|
|
|
102
|
-
Operation-specific types follow the selected name
|
|
265
|
+
Operation-specific types follow the selected name:
|
|
103
266
|
|
|
104
267
|
```text
|
|
105
268
|
true -> OrderGetOrderByIdParams
|
|
106
269
|
false -> GetOrderByIdParams
|
|
107
270
|
```
|
|
108
271
|
|
|
109
|
-
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`.
|
|
273
|
+
|
|
274
|
+
When the prefix is enabled, the controller name is read from `x-typedapi-operation.controllerName`, the first OpenAPI tag, or the route.
|
|
110
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"`.
|
|
111
277
|
|
|
112
278
|
## CI consistency check
|
|
113
279
|
|
|
@@ -120,15 +286,18 @@ This generates in memory and exits with an error when committed generated files
|
|
|
120
286
|
Other modes:
|
|
121
287
|
|
|
122
288
|
```bash
|
|
123
|
-
typedapi-generate --strict
|
|
124
|
-
typedapi-generate --offline
|
|
125
|
-
typedapi-generate --verbose
|
|
289
|
+
typedapi-generate --strict
|
|
290
|
+
typedapi-generate --offline
|
|
291
|
+
typedapi-generate --verbose
|
|
126
292
|
```
|
|
127
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.
|
|
128
297
|
|
|
129
298
|
## Generated property names
|
|
130
299
|
|
|
131
|
-
Generated interface and parameter members lowercase
|
|
300
|
+
Generated interface and parameter members lowercase only the first character:
|
|
132
301
|
|
|
133
302
|
```ts
|
|
134
303
|
Files -> files
|
|
@@ -137,15 +306,33 @@ ProductID -> productID
|
|
|
137
306
|
snake_case -> snake_case
|
|
138
307
|
```
|
|
139
308
|
|
|
140
|
-
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.
|
|
141
321
|
|
|
142
322
|
## Generated method shape
|
|
143
323
|
|
|
144
|
-
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:
|
|
145
327
|
|
|
146
328
|
```ts
|
|
147
329
|
await updateWarehouse(
|
|
148
|
-
{
|
|
330
|
+
{
|
|
331
|
+
id: requireId(
|
|
332
|
+
context.warehouseId,
|
|
333
|
+
"Warehouse ID",
|
|
334
|
+
),
|
|
335
|
+
},
|
|
149
336
|
warehouse,
|
|
150
337
|
);
|
|
151
338
|
```
|
|
@@ -156,23 +343,69 @@ The generated signature is:
|
|
|
156
343
|
export async function updateWarehouse(
|
|
157
344
|
pathParams: UpdateWarehouseParams,
|
|
158
345
|
data: WarehouseRequest,
|
|
159
|
-
options: ApiMethodOptions<
|
|
346
|
+
options: ApiMethodOptions<
|
|
347
|
+
WarehouseModel,
|
|
348
|
+
ApiHttpError,
|
|
349
|
+
RequestParams
|
|
350
|
+
> = {},
|
|
160
351
|
): Promise<ApiResult<WarehouseModel, ApiHttpError>>;
|
|
161
352
|
```
|
|
162
353
|
|
|
163
354
|
Path operations without a body use only the params object:
|
|
164
355
|
|
|
165
356
|
```ts
|
|
166
|
-
await deleteSupplier({
|
|
357
|
+
await deleteSupplier({
|
|
358
|
+
id: requireId(context.supplierId, "Supplier ID"),
|
|
359
|
+
});
|
|
167
360
|
```
|
|
168
361
|
|
|
169
362
|
Body-only operations accept their payload directly:
|
|
170
363
|
|
|
171
364
|
```ts
|
|
172
|
-
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
|
+
);
|
|
173
404
|
```
|
|
174
405
|
|
|
175
|
-
|
|
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.
|
|
176
409
|
|
|
177
410
|
Request options are always last:
|
|
178
411
|
|
|
@@ -181,50 +414,145 @@ await updateWarehouse(pathParams, data, {
|
|
|
181
414
|
params: {
|
|
182
415
|
signal: abortController.signal,
|
|
183
416
|
timeoutMs: 10_000,
|
|
184
|
-
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);
|
|
185
426
|
},
|
|
186
|
-
onSuccess: result => console.log(result.response),
|
|
187
|
-
onError: result => console.error(result.error),
|
|
188
427
|
});
|
|
189
428
|
```
|
|
190
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
|
+
|
|
191
473
|
## Typed errors
|
|
192
474
|
|
|
193
|
-
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`:
|
|
194
478
|
|
|
195
479
|
```ts
|
|
196
|
-
const result = await cancelOrder({
|
|
480
|
+
const result = await cancelOrder({
|
|
481
|
+
id,
|
|
482
|
+
reason,
|
|
483
|
+
});
|
|
197
484
|
|
|
198
485
|
if (!result.ok) {
|
|
199
486
|
if (result.error.kind === "http") {
|
|
200
|
-
console.error(
|
|
487
|
+
console.error(
|
|
488
|
+
result.error.status,
|
|
489
|
+
result.error.body,
|
|
490
|
+
);
|
|
201
491
|
} else {
|
|
202
|
-
// Structured TypedApi client error: network, aborted, or parse.
|
|
203
492
|
console.error(result.error);
|
|
204
493
|
}
|
|
205
494
|
}
|
|
206
495
|
```
|
|
207
496
|
|
|
208
|
-
|
|
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.
|
|
209
507
|
|
|
210
508
|
## Runtime configuration
|
|
211
509
|
|
|
212
510
|
```ts
|
|
213
|
-
import {
|
|
511
|
+
import {
|
|
512
|
+
configureApiClient,
|
|
513
|
+
setSecurityData,
|
|
514
|
+
} from "typedapi-client-helpers";
|
|
214
515
|
|
|
215
516
|
configureApiClient({
|
|
216
517
|
baseUrl: "https://api.example.com",
|
|
217
518
|
timeoutMs: 15_000,
|
|
218
|
-
securityWorker: token =>
|
|
219
|
-
|
|
220
|
-
|
|
519
|
+
securityWorker: token =>
|
|
520
|
+
token
|
|
521
|
+
? {
|
|
522
|
+
headers: {
|
|
523
|
+
Authorization: `Bearer ${token}`,
|
|
524
|
+
},
|
|
525
|
+
}
|
|
526
|
+
: undefined,
|
|
221
527
|
});
|
|
222
528
|
|
|
223
529
|
setSecurityData("access-token");
|
|
224
530
|
```
|
|
225
531
|
|
|
226
|
-
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`
|
|
227
544
|
|
|
228
545
|
## Backend pairing
|
|
229
546
|
|
|
230
|
-
Use `TypedApi.Swagger` 0.3 or newer on the backend
|
|
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":";;AAgBA,4CA2CC;AASD,gCAwBC;AAnFD;;;;;;GAMG;AACH,SAAgB,gBAAgB,CAC9B,OAAmC;IAEnC,OAAO,OAAO,CAAC,MAAM,CAAkB,CAAC,WAAW,EAAE,MAAM,EAAE,EAAE;QAC7D,MAAM,EAAE,UAAU,EAAE,aAAa,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,GACjE,MAAM,CAAC;QAET,IAAI,YAAY,CAAC,KAAK,CAAC,IAAI,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YAClD,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,IAAI,OAAO,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;YACpC,MAAM,eAAe,GAAG,KAAK;iBAC1B,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CACZ,WAAW,CAAC,IAAI,EAAE,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAC3D;iBACA,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;YAEnC,IAAI,eAAe,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC/B,WAAW,CAAC,UAAU,CAAC,GAAG,eAAuC,CAAC;YACpE,CAAC;YAED,OAAO,WAAW,CAAC;QACrB,CAAC;QAED,MAAM,QAAQ,GAAG,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;QAC5D,MAAM,cAAc,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAEnD,IAAI,cAAc,KAAK,IAAI,EAAE,CAAC;YAC5B,WAAW,CAAC,UAAU,CAAC,GAAG,cAAsC,CAAC;QACnE,CAAC;QAED,IAAI,aAAa,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC7C,MAAM,iBAAiB,GAAG,WAAW,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;YAEtD,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;gBAC/B,WAAW,CAAC,aAAa,CAAC;oBACxB,iBAAyC,CAAC;YAC9C,CAAC;QACH,CAAC;QAED,OAAO,WAAW,CAAC;IACrB,CAAC,EAAE,EAAE,CAAC,CAAC;AACT,CAAC;AAED;;;;;;GAMG;AACH,SAAgB,UAAU,CACxB,OAAmC,EACnC,IAAI,GAAG,CAAC,EACR,QAAQ,GAAG,GAAG,EACd,SAAkC,IAAI,EACtC,aAA6B;IAE7B,MAAM,KAAK,GAAG,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAEvC,KAAiC,CAAC,UAAU,GAAG,IAAI,CAAC;IAErD,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;QAChB,KAAiC,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACzD,CAAC;IAED,IAAI,MAAM,EAAE,CAAC;QACV,KAAiC,CAAC,MAAM,GAAG,MAAM,CAAC;IACrD,CAAC;IAED,IAAI,aAAa,KAAK,SAAS,IAAI,aAAa,KAAK,SAAS,EAAE,CAAC;QAC9D,KAAiC,CAAC,aAAa,GAAG,aAAa,CAAC;IACnE,CAAC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,YAAY,CAAC,KAAc;IAClC,OAAO,CACL,KAAK,KAAK,IAAI;QACd,KAAK,KAAK,SAAS;QACnB,KAAK,KAAK,EAAE;QACZ,KAAK,KAAK,MAAM;QAChB,KAAK,KAAK,WAAW,CACtB,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,KAAc;IACnC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,IAAI,KAAK,CAAC;AACzE,CAAC;AAED,SAAS,WAAW,CAAC,IAAY,EAAE,KAAc;IAC/C,IAAI,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC;QACxB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,IAAI,KAAK,YAAY,IAAI,EAAE,CAAC;gBAC1B,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;YACpE,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;YAClC,OAAO,MAAM,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC;QACxD,CAAC;QAED,KAAK,SAAS,CAAC;QACf,KAAK,gBAAgB,CAAC,CAAC,CAAC;YACtB,IAAI,OAAO,KAAK,KAAK,SAAS,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC;YACf,CAAC;YAED,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;gBAC9B,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;gBAE9C,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBACpD,OAAO,IAAI,CAAC;gBACd,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;oBACrD,OAAO,KAAK,CAAC;gBACf,CAAC;YACH,CAAC;YAED,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;QACxB,CAAC;QAED;YACE,OAAO,KAAK,CAAC;IACjB,CAAC;AACH,CAAC"}
|