typedapi-client-helpers 0.1.4 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +597 -88
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,36 +1,33 @@
1
1
  # typedapi-client-helpers
2
2
 
3
- Reusable TypeScript helpers for generated typed OpenAPI clients.
4
-
5
- ## What this package includes
6
-
7
- * `typedapi-generate` CLI command
8
- * OpenAPI TypeScript client generation
9
- * Typed API wrapper methods
10
- * Typed query and pagination helpers
11
- * `ApiResult<T>` response wrapper
12
- * Toast helpers using `react-toastify`
13
- * `TypedApiToastProvider`
14
- * `buildQuery` helper for filters, paging, and sorting
15
- * `toFormData` helper for file uploads
16
- * Unauthorized handling for `401` responses
17
- * Sort direction conversion helpers
3
+ Generate a typed TypeScript API client from an OpenAPI/Swagger document and use reusable helpers for API responses, filtering, sorting, pagination, callbacks, and multipart form data.
18
4
 
19
- ## Installation
5
+ The generated client uses the native `fetch` API and is created inside your own project, so the generated models and methods remain available for inspection and customization.
20
6
 
21
- ```bash
22
- npm install typedapi-client-helpers
23
- ```
7
+ ## Features
8
+
9
+ * Generates TypeScript models and API methods from OpenAPI
10
+ * Uses the native `fetch` API
11
+ * Generates methods grouped by OpenAPI tags
12
+ * Creates typed wrapper methods around generated requests
13
+ * Returns a consistent `ApiResult<T>`
14
+ * Supports success and error callbacks
15
+ * Supports custom request parameters and headers
16
+ * Supports query parameters, pagination, filtering, and sorting
17
+ * Converts upload payloads to `FormData`
18
+ * Supports local Swagger files and remote Swagger URLs
19
+ * Supports type-only imports
20
+ * Works with TypeScript frontend projects such as React, Angular, and Vue
24
21
 
25
- This package expects React to already be installed:
22
+ ## Installation
26
23
 
27
24
  ```bash
28
- npm install react react-dom react-toastify
25
+ npm install typedapi-client-helpers
29
26
  ```
30
27
 
31
- ## Generate API files
28
+ ## Add the generator command
32
29
 
33
- Add a script to your project:
30
+ Add the following script to your project's `package.json`:
34
31
 
35
32
  ```json
36
33
  {
@@ -40,33 +37,41 @@ Add a script to your project:
40
37
  }
41
38
  ```
42
39
 
43
- Run:
40
+ You can then generate the API client with:
44
41
 
45
42
  ```bash
46
43
  npm run generate:api
47
44
  ```
48
45
 
49
- By default, the generator reads Swagger from:
46
+ ## Configuration
50
47
 
51
- ```text
52
- https://localhost:7000/swagger/v1/swagger.json
53
- ```
48
+ The generator can be configured through the `config` property in your project's `package.json`.
54
49
 
55
- and writes generated files to:
50
+ ### Generate from a Swagger URL
56
51
 
57
- ```text
58
- src/api
52
+ ```json
53
+ {
54
+ "scripts": {
55
+ "generate:api": "typedapi-generate"
56
+ },
57
+ "config": {
58
+ "swaggerUrl": "https://localhost:7000/swagger/v1/swagger.json",
59
+ "apiOutput": "src/api",
60
+ "typedApiUseTypeOnlyImports": true,
61
+ "typedApiUseFilterFormValues": false
62
+ }
63
+ }
59
64
  ```
60
65
 
61
- ## Configuration
62
-
63
- Configure the generator in your `package.json`:
66
+ ### Generate from a local Swagger file
64
67
 
65
68
  ```json
66
69
  {
70
+ "scripts": {
71
+ "generate:api": "typedapi-generate"
72
+ },
67
73
  "config": {
68
- "swaggerUrl": "https://localhost:7000/swagger/v1/swagger.json",
69
- "swaggerFile": "./swagger.json",
74
+ "swaggerFile": "swagger/openapi.json",
70
75
  "apiOutput": "src/api",
71
76
  "typedApiUseTypeOnlyImports": true,
72
77
  "typedApiUseFilterFormValues": false
@@ -74,94 +79,598 @@ Configure the generator in your `package.json`:
74
79
  }
75
80
  ```
76
81
 
77
- ### Available Settings
82
+ When `swaggerFile` is configured, it takes precedence over `swaggerUrl`.
83
+
84
+ ## Configuration options
85
+
86
+ | Option | Default | Description |
87
+ | ----------------------------- | ------------------------------------------------ | -------------------------------------------------------- |
88
+ | `swaggerUrl` | `https://localhost:7000/swagger/v1/swagger.json` | URL of the OpenAPI document |
89
+ | `swaggerFile` | — | Path to a local OpenAPI document |
90
+ | `apiOutput` | `src/api` | Directory where the generated API is written |
91
+ | `typedApiUseTypeOnlyImports` | `false` | Generates `import type` statements for type-only imports |
92
+ | `typedApiUseFilterFormValues` | `false` | Generates filter-form arguments for paginated methods |
93
+
94
+ ## Environment variables
78
95
 
79
- | Setting | Description | Default |
80
- | ----------------------------- | ---------------------------------------------------------------- | ------------------------------------------------ |
81
- | `swaggerUrl` | URL to the Swagger/OpenAPI document | `https://localhost:7000/swagger/v1/swagger.json` |
82
- | `swaggerFile` | Local Swagger/OpenAPI file. Overrides `swaggerUrl` when provided | Not set |
83
- | `apiOutput` | Output directory for generated files | `src/api` |
84
- | `typedApiUseTypeOnlyImports` | Uses `import type` statements when possible | `false` |
85
- | `typedApiUseFilterFormValues` | Generates pagination methods using `FilterFormValues[]` | `false` |
96
+ Configuration can also be supplied through environment variables.
86
97
 
87
- ### Environment Variables
98
+ | Environment variable | Description |
99
+ | ---------------------------------- | -------------------------------------------------------- |
100
+ | `SWAGGER_URL` | URL of the OpenAPI document |
101
+ | `SWAGGER_FILE` | Path to a local OpenAPI document |
102
+ | `API_OUTPUT` | Generated API output directory |
103
+ | `TYPED_API_USE_TYPE_ONLY_IMPORTS` | Enables type-only imports when set to `true` |
104
+ | `TYPED_API_USE_FILTER_FORM_VALUES` | Enables filter-form method generation when set to `true` |
105
+
106
+ Example:
88
107
 
89
108
  ```bash
90
109
  SWAGGER_URL=https://localhost:7000/swagger/v1/swagger.json npm run generate:api
91
- SWAGGER_FILE=./swagger.json npm run generate:api
92
- API_OUTPUT=src/api npm run generate:api
93
- TYPED_API_USE_TYPE_ONLY_IMPORTS=true npm run generate:api
94
- TYPED_API_USE_FILTER_FORM_VALUES=true npm run generate:api
95
110
  ```
96
111
 
97
- ## Usage
112
+ Command-line npm configuration is also supported:
113
+
114
+ ```bash
115
+ npm run generate:api --swagger-url=https://localhost:7000/swagger/v1/swagger.json
116
+ ```
117
+
118
+ ```bash
119
+ npm run generate:api --swagger-file=swagger/openapi.json
120
+ ```
121
+
122
+ ```bash
123
+ npm run generate:api --api-output=src/api
124
+ ```
125
+
126
+ ## Generated structure
127
+
128
+ Running the generator creates an API directory similar to:
129
+
130
+ ```text
131
+ src/
132
+ └── api/
133
+ ├── generated/
134
+ │ ├── data-contracts.ts
135
+ │ ├── http-client.ts
136
+ │ ├── Product.ts
137
+ │ ├── ProductRoute.ts
138
+ │ ├── Supplier.ts
139
+ │ └── SupplierRoute.ts
140
+ ├── methods/
141
+ │ ├── Product.api.ts
142
+ │ └── Supplier.api.ts
143
+ └── index.ts
144
+ ```
145
+
146
+ The `generated` directory contains the client produced from the OpenAPI document.
147
+
148
+ The `methods` directory contains typed wrapper functions that:
98
149
 
99
- Import generated API functions:
150
+ * Handle successful and failed responses
151
+ * Return `ApiResult<T>`
152
+ * Support success and error callbacks
153
+ * Accept custom request parameters
154
+ * Convert multipart request objects to `FormData`
155
+
156
+ ## Export the generated API
157
+
158
+ Create or update `src/api/index.ts` so consumers can import the generated types and methods from one location:
159
+
160
+ ```ts
161
+ export * from "./generated/data-contracts";
162
+ export * from "./generated/http-client";
163
+
164
+ export * from "./methods/Product.api";
165
+ export * from "./methods/Supplier.api";
166
+ ```
167
+
168
+ The exact method files depend on the tags in your OpenAPI document.
169
+
170
+ ## Calling generated methods
171
+
172
+ Generated wrapper methods return a typed `ApiResult<T>`.
100
173
 
101
174
  ```ts
102
- import { getProducts } from "./api";
175
+ import { getSuppliers } from "./api";
103
176
 
104
- const result = await getProducts();
177
+ const result = await getSuppliers({
178
+ pageNumber: 1,
179
+ pageSize: 25,
180
+ });
105
181
 
106
182
  if (result.ok) {
107
- console.log(result.response);
183
+ console.log(result.response);
184
+ } else {
185
+ console.error(result.status, result.error);
108
186
  }
109
187
  ```
110
188
 
111
- ### Toast Notifications
189
+ `ApiResult<T>` is a discriminated union:
112
190
 
113
191
  ```ts
114
- await createProduct(product, {
115
- toastSuccess: {
116
- title: "Saved",
117
- message: "Product created successfully."
118
- },
119
- toastError: {
120
- title: "Error",
121
- message: "Unable to create product."
192
+ type ApiResult<T> =
193
+ | {
194
+ ok: true;
195
+ status: number;
196
+ response: T;
122
197
  }
198
+ | {
199
+ ok: false;
200
+ status: number;
201
+ response?: T;
202
+ error: unknown;
203
+ };
204
+ ```
205
+
206
+ Checking `result.ok` automatically narrows the result to either the successful or failed response type.
207
+
208
+ ## Success and error callbacks
209
+
210
+ Generated methods support optional success and error callbacks.
211
+
212
+ ```ts
213
+ import { getSuppliers } from "./api";
214
+
215
+ const result = await getSuppliers(
216
+ {
217
+ pageNumber: 1,
218
+ pageSize: 25,
219
+ },
220
+ async (response) => {
221
+ console.log("Suppliers loaded:", response);
222
+ },
223
+ async (error) => {
224
+ console.error("Unable to load suppliers:", error);
225
+ },
226
+ );
227
+ ```
228
+
229
+ The method still returns an `ApiResult<T>`, even when callbacks are provided.
230
+
231
+ ## Create and update requests
232
+
233
+ Body-based API methods keep the argument types generated from the OpenAPI document.
234
+
235
+ ```ts
236
+ import { createSupplier } from "./api";
237
+
238
+ const result = await createSupplier({
239
+ id: "supplier-1",
240
+ name: "Example Supplier",
241
+ active: true,
242
+ countryCode: "NL",
243
+ email: "supplier@example.com",
244
+ rating: 4.5,
123
245
  });
246
+
247
+ if (!result.ok) {
248
+ console.error("Supplier creation failed:", result.error);
249
+ }
250
+ ```
251
+
252
+ Callbacks can be added after the generated API arguments:
253
+
254
+ ```ts
255
+ await createSupplier(
256
+ {
257
+ id: "supplier-1",
258
+ name: "Example Supplier",
259
+ active: true,
260
+ countryCode: "NL",
261
+ email: "supplier@example.com",
262
+ rating: 4.5,
263
+ },
264
+ (supplier) => {
265
+ console.log("Created supplier:", supplier);
266
+ },
267
+ (error) => {
268
+ console.error("Creation failed:", error);
269
+ },
270
+ );
271
+ ```
272
+
273
+ ## Custom request parameters
274
+
275
+ The final argument of a generated wrapper method can contain request configuration supported by the generated HTTP client.
276
+
277
+ For example, custom headers can be supplied as follows:
278
+
279
+ ```ts
280
+ import { getSuppliers } from "./api";
281
+
282
+ const result = await getSuppliers(
283
+ {
284
+ pageNumber: 1,
285
+ pageSize: 25,
286
+ },
287
+ undefined,
288
+ undefined,
289
+ {
290
+ headers: {
291
+ Authorization: `Bearer ${token}`,
292
+ },
293
+ },
294
+ );
124
295
  ```
125
296
 
126
- Add the provider once in your application:
297
+ ## Filter-form generation
127
298
 
128
- ```tsx
129
- import { TypedApiToastProvider } from "typedapi-client-helpers";
299
+ Set `typedApiUseFilterFormValues` to `true` to generate paginated methods that accept filter-form values rather than a query object.
130
300
 
131
- function App() {
132
- return (
133
- <>
134
- <TypedApiToastProvider />
135
- {/* application */}
136
- </>
137
- );
301
+ ```json
302
+ {
303
+ "config": {
304
+ "swaggerUrl": "https://localhost:7000/swagger/v1/swagger.json",
305
+ "apiOutput": "src/api",
306
+ "typedApiUseFilterFormValues": true
307
+ }
138
308
  }
139
309
  ```
140
310
 
141
- ### File Uploads
311
+ A generated paginated method can then be called with filters, pagination, and sorting:
312
+
313
+ ```ts
314
+ import type { FilterFormValues } from "typedapi-client-helpers";
315
+ import {
316
+ getSuppliers,
317
+ type GetSuppliersQuery,
318
+ } from "./api";
319
+
320
+ const filters: FilterFormValues<GetSuppliersQuery>[] = [
321
+ {
322
+ name: "Supplier name",
323
+ filterName: "name",
324
+ type: "string",
325
+ value: "Example",
326
+ isAList: false,
327
+ },
328
+ {
329
+ name: "Active",
330
+ filterName: "active",
331
+ type: "boolean",
332
+ value: true,
333
+ isAList: false,
334
+ },
335
+ ];
336
+
337
+ const result = await getSuppliers(
338
+ filters,
339
+ 1,
340
+ 25,
341
+ "name",
342
+ "Ascending",
343
+ );
344
+ ```
345
+
346
+ Empty filter values are ignored automatically.
347
+
348
+ ## Supported filter types
349
+
350
+ ```ts
351
+ type FilterType =
352
+ | "number"
353
+ | "date"
354
+ | "string"
355
+ | "timespan"
356
+ | "boolean"
357
+ | "boolean-button"
358
+ | "OptionValue";
359
+ ```
360
+
361
+ A filter configuration uses the following structure:
362
+
363
+ ```ts
364
+ interface FilterFormValues<TQuery> {
365
+ name: string;
366
+ filterName: keyof TQuery;
367
+ filterNameMax?: keyof TQuery;
368
+ type: FilterType;
369
+ value:
370
+ | number
371
+ | string
372
+ | Date
373
+ | boolean
374
+ | null
375
+ | OptionValue[]
376
+ | string[];
377
+ maxValue?: number | string | Date | null;
378
+ isAList: boolean;
379
+ }
380
+ ```
381
+
382
+ ## Range filters
383
+
384
+ Use `filterNameMax` and `maxValue` to create range filters:
385
+
386
+ ```ts
387
+ const filters: FilterFormValues<GetSuppliersQuery>[] = [
388
+ {
389
+ name: "Minimum rating",
390
+ filterName: "minimumRating",
391
+ filterNameMax: "maximumRating",
392
+ type: "number",
393
+ value: 3,
394
+ maxValue: 5,
395
+ isAList: false,
396
+ },
397
+ ];
398
+ ```
399
+
400
+ ## Option filters
401
+
402
+ Option-based filters display a name while sending a separate value to the API.
403
+
404
+ ```ts
405
+ import type {
406
+ FilterFormValues,
407
+ OptionValue,
408
+ } from "typedapi-client-helpers";
409
+
410
+ const countries: OptionValue[] = [
411
+ {
412
+ name: "Netherlands",
413
+ value: "NL",
414
+ },
415
+ {
416
+ name: "Belgium",
417
+ value: "BE",
418
+ },
419
+ ];
420
+
421
+ const filters: FilterFormValues<GetSuppliersQuery>[] = [
422
+ {
423
+ name: "Countries",
424
+ filterName: "countryCodes",
425
+ type: "OptionValue",
426
+ value: countries,
427
+ isAList: true,
428
+ },
429
+ ];
430
+ ```
431
+
432
+ The generator sends the values `"NL"` and `"BE"` rather than the display names.
433
+
434
+ ## Using `buildQuery` directly
435
+
436
+ The `buildQuery` helper can also be used without generated filter-form methods.
437
+
438
+ ```ts
439
+ import {
440
+ buildQuery,
441
+ type FilterFormValues,
442
+ } from "typedapi-client-helpers";
443
+
444
+ interface ProductQuery {
445
+ name?: string;
446
+ minimumPrice?: number;
447
+ maximumPrice?: number;
448
+ pageNumber?: number;
449
+ pageSize?: number;
450
+ sortBy?: string;
451
+ sortDirection?: string;
452
+ }
453
+
454
+ const filters: FilterFormValues<ProductQuery>[] = [
455
+ {
456
+ name: "Name",
457
+ filterName: "name",
458
+ type: "string",
459
+ value: "Laptop",
460
+ isAList: false,
461
+ },
462
+ {
463
+ name: "Price",
464
+ filterName: "minimumPrice",
465
+ filterNameMax: "maximumPrice",
466
+ type: "number",
467
+ value: 500,
468
+ maxValue: 1500,
469
+ isAList: false,
470
+ },
471
+ ];
472
+
473
+ const query = buildQuery<ProductQuery>(
474
+ filters,
475
+ 1,
476
+ 25,
477
+ "name",
478
+ "Ascending",
479
+ );
480
+ ```
481
+
482
+ The result is equivalent to:
483
+
484
+ ```ts
485
+ {
486
+ name: "Laptop",
487
+ minimumPrice: 500,
488
+ maximumPrice: 1500,
489
+ pageNumber: 1,
490
+ pageSize: 25,
491
+ sortBy: "name",
492
+ sortDirection: "Ascending"
493
+ }
494
+ ```
495
+
496
+ ## Sorting helpers
497
+
498
+ The package includes helpers for converting between UI sort states and API sort directions.
499
+
500
+ ```ts
501
+ import {
502
+ getSortDirectionFromSortType,
503
+ getSortTypeFromSortDirection,
504
+ } from "typedapi-client-helpers";
505
+
506
+ const apiDirection =
507
+ getSortDirectionFromSortType("Neutral");
508
+
509
+ // "Default"
510
+
511
+ const uiSortType =
512
+ getSortTypeFromSortDirection("Descending");
513
+
514
+ // "Descending"
515
+ ```
516
+
517
+ Supported UI sort states are:
518
+
519
+ ```ts
520
+ type SortType =
521
+ | "Default"
522
+ | "Neutral"
523
+ | "Ascending"
524
+ | "Descending";
525
+ ```
526
+
527
+ `Neutral` is a UI-only state and is converted to `Default` before it is sent to the API.
528
+
529
+ ## File uploads
530
+
531
+ Multipart methods are detected during generation. Their payloads are converted to `FormData` automatically.
532
+
533
+ ```ts
534
+ import { uploadDocument } from "./api";
535
+
536
+ const result = await uploadDocument({
537
+ file: selectedFile,
538
+ description: "Supplier contract",
539
+ });
540
+ ```
541
+
542
+ The `toFormData` helper supports:
543
+
544
+ * `File`
545
+ * `Blob`
546
+ * Arrays of files or values
547
+ * Strings
548
+ * Numbers
549
+ * Booleans
550
+ * Nested objects
551
+
552
+ Nested objects are serialized as JSON strings.
553
+
554
+ You can also use the helper directly:
142
555
 
143
556
  ```ts
144
557
  import { toFormData } from "typedapi-client-helpers";
145
558
 
146
559
  const formData = toFormData({
147
- file,
148
- description: "Import file"
560
+ file: selectedFile,
561
+ tags: ["contract", "supplier"],
562
+ metadata: {
563
+ department: "Purchasing",
564
+ },
149
565
  });
150
566
  ```
151
567
 
152
- ## Recommended Project Setup
568
+ ## Handling HTTP responses directly
153
569
 
154
- ```text
155
- src/
156
- ├── api/
157
- │ ├── clients/
158
- │ ├── models/
159
- │ └── index.ts
160
- ├── pages/
161
- ├── components/
162
- └── services/
570
+ The `handleApiResponse` helper can wrap any compatible generated HTTP request:
571
+
572
+ ```ts
573
+ import {
574
+ handleApiResponse,
575
+ type HttpResponse,
576
+ } from "typedapi-client-helpers";
577
+
578
+ interface Product {
579
+ id: string;
580
+ name: string;
581
+ }
582
+
583
+ async function requestProduct(): Promise<
584
+ HttpResponse<Product>
585
+ > {
586
+ // Return a compatible generated HTTP response.
587
+ throw new Error("Implement request");
588
+ }
589
+
590
+ const result = await handleApiResponse(
591
+ () => requestProduct(),
592
+ {
593
+ onSuccess: (product) => {
594
+ console.log(product);
595
+ },
596
+ onError: (error) => {
597
+ console.error(error);
598
+ },
599
+ },
600
+ );
163
601
  ```
164
602
 
165
- ## Notes
603
+ The response body is parsed as JSON when the response content type contains `application/json`. Empty responses, including HTTP `204` responses, return `undefined`.
604
+
605
+ ## Regenerating the client
166
606
 
167
- Generated files should not be manually modified. Regenerate them whenever the backend OpenAPI specification changes.
607
+ Run the generator whenever the OpenAPI document changes:
608
+
609
+ ```bash
610
+ npm run generate:api
611
+ ```
612
+
613
+ The existing generated and method directories inside the configured API output are replaced during generation. Keep custom application code outside those generated directories.
614
+
615
+ ## Recommended workflow
616
+
617
+ 1. Start the backend API.
618
+ 2. Confirm that its OpenAPI document is available.
619
+ 3. Run the API generator.
620
+ 4. Build or start the frontend application.
621
+
622
+ Example:
623
+
624
+ ```bash
625
+ npm run generate:api
626
+ npm run dev
627
+ ```
628
+
629
+ ## Package exports
630
+
631
+ The package exports the following reusable types and helpers:
632
+
633
+ ```ts
634
+ import {
635
+ buildQuery,
636
+ getSortDirectionFromSortType,
637
+ getSortTypeFromSortDirection,
638
+ handleApiResponse,
639
+ sortTypes,
640
+ toFormData,
641
+ } from "typedapi-client-helpers";
642
+
643
+ import type {
644
+ ApiErrorHandler,
645
+ ApiMethodCallbacks,
646
+ ApiResult,
647
+ ApiSuccessHandler,
648
+ FilterFormValues,
649
+ FilterType,
650
+ HttpResponse,
651
+ OptionValue,
652
+ SortType,
653
+ } from "typedapi-client-helpers";
654
+ ```
655
+
656
+ Generated API models and methods are written into your project and should be imported from your configured API output directory:
657
+
658
+ ```ts
659
+ import {
660
+ createSupplier,
661
+ getSuppliers,
662
+ type SupplierModel,
663
+ } from "./api";
664
+ ```
665
+
666
+ ## TypeScript
667
+
668
+ The package includes generated declaration files and can be used directly in TypeScript projects.
669
+
670
+ ```ts
671
+ import type {
672
+ ApiResult,
673
+ FilterFormValues,
674
+ SortType,
675
+ } from "typedapi-client-helpers";
676
+ ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "typedapi-client-helpers",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Reusable typed OpenAPI client helpers.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",