@qualisero/openapi-endpoint 0.13.2 → 0.14.0

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/README.md CHANGED
@@ -3,22 +3,39 @@
3
3
  [![npm version](https://badge.fury.io/js/@qualisero%2Fopenapi-endpoint.svg?refresh=1763799820)](https://badge.fury.io/js/@qualisero%2Fopenapi-endpoint)
4
4
  [![CI](https://github.com/qualisero/openapi-endpoint/workflows/CI/badge.svg)](https://github.com/qualisero/openapi-endpoint/actions/workflows/ci.yml)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
- [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@qualisero/openapi-endpoint)](https://bundlephobia.com/package/@qualisero/openapi-endpoint)
6
+ [![npm bundle size](https://img.shields.io/bundlephobia/minzip/@qualisero%2Fopenapi-endpoint)](https://bundlephobia.com/package/@qualisero%2Fopenapi-endpoint)
7
7
  [![Documentation](https://img.shields.io/badge/docs-online-brightgreen.svg)](https://qualisero.github.io/openapi-endpoint/)
8
8
 
9
- Turns your `openapi.json` into typesafe API composables using Vue Query (TanStack Query): guaranteeing that your frontend and backend share the same contract.
9
+ Type-safe API composables for Vue using TanStack Query. Generate fully-typed API clients from your OpenAPI specification.
10
10
 
11
- ## Overview
12
-
13
- Let's you get TanStack Vue Query composables that enforce consistency (name of endpoints, typing) with your API's `openapi.json` file:
11
+ ## Quick Start
14
12
 
15
13
  ```typescript
16
- const { data, isLoading } = api.useQuery(QueryOperationId.getPet, { petId: '123' })
14
+ // 1. Generate types from your OpenAPI spec
15
+ npx @qualisero/openapi-endpoint ./api/openapi.json ./src/generated
16
+
17
+ // 2. Initialize the API client
18
+ import { createApiClient } from '@qualisero/openapi-endpoint'
19
+ import axios from 'axios'
20
+
21
+ const api = createApiClient(axios.create({ baseURL: 'https://api.example.com' }))
22
+
23
+ // 3. Use in your Vue components
24
+ const { data: pets, isLoading } = api.listPets.useQuery()
25
+ const { data: pet } = api.getPet.useQuery({ petId: '123' })
17
26
 
18
- const createPetMutation = api.useMutation(MutationOperationId.createPet)
19
- createPetMutation.mutate({ data: { name: 'Fluffy', species: 'cat' } })
27
+ const createPet = api.createPet.useMutation()
28
+ await createPet.mutateAsync({ data: { name: 'Fluffy', species: 'cat' } })
20
29
  ```
21
30
 
31
+ ## Features
32
+
33
+ - **Fully typed** - Operations, parameters, and responses type-checked against your OpenAPI spec
34
+ - **Reactive parameters** - Query params automatically refetch when values change
35
+ - **Automatic cache management** - Mutations invalidate and update related queries
36
+ - **Vue 3 + TanStack Query** - Built on proven reactive patterns
37
+ - **File uploads** - Support for multipart/form-data endpoints
38
+
22
39
  ## Installation
23
40
 
24
41
  ```bash
@@ -27,287 +44,169 @@ npm install @qualisero/openapi-endpoint
27
44
 
28
45
  ## Code Generation
29
46
 
30
- This package includes a command-line tool to generate TypeScript types and operation definitions from your OpenAPI specification:
31
-
32
47
  ```bash
33
- # Generate from local file
48
+ # From local file
34
49
  npx @qualisero/openapi-endpoint ./api/openapi.json ./src/generated
35
50
 
36
- # Generate from remote URL
37
- npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/api
51
+ # From remote URL
52
+ npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/generated
38
53
  ```
39
54
 
40
- This will generate two files in your specified output directory:
55
+ Generated files:
41
56
 
42
- - `openapi-types.ts` - TypeScript type definitions for your API
43
- - `api-operations.ts` - Operation definitions combining metadata and types
57
+ | File | Description |
58
+ | ------------------- | -------------------------------------------- |
59
+ | `api-client.ts` | `createApiClient` factory (main entry point) |
60
+ | `api-operations.ts` | Operations map and type helpers |
61
+ | `api-types.ts` | Type namespace for response/request types |
62
+ | `api-enums.ts` | Schema enums |
63
+ | `api-schemas.ts` | Schema type aliases |
64
+ | `openapi-types.ts` | Raw OpenAPI types |
44
65
 
45
- ## Usage
66
+ ## API Reference
46
67
 
47
- ### 1. Initialize the package
68
+ ### Initialization
48
69
 
49
70
  ```typescript
50
- // api/init.ts
51
- import { useOpenApi } from '@qualisero/openapi-endpoint'
71
+ import { createApiClient } from '@qualisero/openapi-endpoint'
52
72
  import axios from 'axios'
53
73
 
54
- // Import your auto-generated operations (includes both metadata and types)
55
- import {
56
- QueryOperationId,
57
- MutationOperationId,
58
- openApiOperations,
59
- type OpenApiOperations,
60
- } from './generated/api-operations'
61
-
62
- // Create axios instance
63
- const axiosInstance = axios.create({
64
- baseURL: 'https://api.example.com',
65
- })
66
-
67
- // Initialize the package with the auto-generated operations
68
- const api = useOpenApi<OpenApiOperations>({
69
- operations: openApiOperations,
70
- axios: axiosInstance,
71
- })
72
-
73
- // Export for use in other parts of your application
74
- export { api, QueryOperationId, MutationOperationId, OperationId }
74
+ const api = createApiClient(axiosInstance, queryClient?)
75
75
  ```
76
76
 
77
- ### 2. Use the API in your components
77
+ ### Queries (GET/HEAD/OPTIONS)
78
78
 
79
79
  ```typescript
80
- // In your Vue components
81
- import { api, QueryOperationId, MutationOperationId } from './api/init'
82
-
83
- // Use queries for GET operations
84
- const { data: pets, isLoading } = api.useQuery(QueryOperationId.listPets)
85
- const { data: pet } = api.useQuery(QueryOperationId.getPet, { petId: '123' })
80
+ // No parameters
81
+ const { data, isLoading, error, refetch } = api.listPets.useQuery()
86
82
 
87
- // Use mutations for POST/PUT/PATCH/DELETE operations
88
- const createPetMutation = api.useMutation(MutationOperationId.createPet)
83
+ // With path parameters
84
+ const { data } = api.getPet.useQuery({ petId: '123' })
89
85
 
90
- // Execute mutations
91
- await createPetMutation.mutateAsync({
92
- data: { name: 'Fluffy', species: 'cat' },
86
+ // With query parameters
87
+ const { data } = api.listPets.useQuery({
88
+ queryParams: { limit: 10, status: 'available' },
93
89
  })
94
- ```
95
-
96
- ## Advanced Usage
97
-
98
- ### Reactive Query Parameters
99
-
100
- The library supports type-safe, reactive query parameters that automatically trigger refetches when their values change:
101
-
102
- ```typescript
103
- import { ref, computed } from 'vue'
104
- import { api, QueryOperationId } from './api/init'
105
90
 
106
- // Static query parameters
107
- const { data: pets } = api.useQuery(QueryOperationId.listPets, {
108
- queryParams: { limit: 10 },
109
- })
110
- // Results in: GET /pets?limit=10
111
-
112
- // Reactive query parameters with computed
91
+ // Reactive parameters
113
92
  const limit = ref(10)
114
- const status = ref<'available' | 'pending' | 'sold'>('available')
93
+ const { data } = api.listPets.useQuery({
94
+ queryParams: computed(() => ({ limit: limit.value })),
95
+ })
96
+ // Automatically refetches when limit.value changes
115
97
 
116
- const petsQuery = api.useQuery(QueryOperationId.listPets, {
117
- queryParams: computed(() => ({
118
- limit: limit.value,
119
- status: status.value,
120
- })),
98
+ // With options
99
+ const { data, onLoad } = api.listPets.useQuery({
100
+ enabled: computed(() => isLoggedIn.value),
101
+ staleTime: 5000,
102
+ onLoad: (data) => console.log('Loaded:', data),
121
103
  })
122
104
 
123
- // When limit or status changes, query automatically refetches
124
- limit.value = 20
125
- status.value = 'pending'
126
- // Query refetches with: GET /pets?limit=20&status=pending
127
-
128
- // Combine with path parameters
129
- const userPetsQuery = api.useQuery(
130
- QueryOperationId.listUserPets,
131
- computed(() => ({ userId: userId.value })),
132
- {
133
- queryParams: computed(() => ({
134
- includeArchived: includeArchived.value,
135
- })),
136
- },
137
- )
138
- // Results in: GET /users/user-123/pets?includeArchived=false
105
+ // onLoad callback method
106
+ const query = api.getPet.useQuery({ petId: '123' })
107
+ query.onLoad((pet) => console.log('Pet:', pet.name))
139
108
  ```
140
109
 
141
- **Key Features:**
142
-
143
- - **Type-safe**: Query parameters are typed based on your OpenAPI specification
144
- - **Reactive**: Supports `ref`, `computed`, and function-based values
145
- - **Automatic refetch**: Changes to query params trigger automatic refetch via TanStack Query's key mechanism
146
- - **Backward compatible**: Works alongside existing `axiosOptions.params`
147
-
148
- ### Automatic Cache Management and Refetching
149
-
150
- By default, mutations automatically:
151
-
152
- 1. Update cache for matching queries with returned data
153
- 2. Invalidate them to trigger a reload
154
- 3. Invalidate matching list endpoints
110
+ ### Mutations (POST/PUT/PATCH/DELETE)
155
111
 
156
112
  ```typescript
157
- // Default behavior: automatic cache management
158
- const createPet = api.useMutation(MutationOperationId.createPet)
159
- // No additional configuration needed - cache management is automatic
160
-
161
- // Manual control over cache invalidation
162
- const updatePet = api.useMutation(MutationOperationId.updatePet, {
163
- dontInvalidate: true, // Disable automatic invalidation
164
- dontUpdateCache: true, // Disable automatic cache updates
165
- invalidateOperations: [QueryOperationId.listPets], // Manually specify operations to invalidate
166
- })
167
-
168
- // Refetch specific endpoints after mutation
169
- const petListQuery = api.useQuery(QueryOperationId.listPets)
170
- const createPetWithRefetch = api.useMutation(MutationOperationId.createPet, {
171
- refetchEndpoints: [petListQuery], // Manually refetch these endpoints
113
+ // Simple mutation
114
+ const createPet = api.createPet.useMutation()
115
+ await createPet.mutateAsync({ data: { name: 'Fluffy' } })
116
+
117
+ // With path parameters
118
+ const updatePet = api.updatePet.useMutation({ petId: '123' })
119
+ await updatePet.mutateAsync({ data: { name: 'Updated' } })
120
+
121
+ // Override path params at call time
122
+ const deletePet = api.deletePet.useMutation()
123
+ await deletePet.mutateAsync({ pathParams: { petId: '123' } })
124
+
125
+ // With options
126
+ const mutation = api.createPet.useMutation({
127
+ dontInvalidate: true,
128
+ invalidateOperations: ['listPets'],
129
+ onSuccess: (response) => console.log('Created:', response.data),
172
130
  })
173
131
  ```
174
132
 
175
- ### File Upload Support with Multipart/Form-Data
133
+ ### Return Types
176
134
 
177
- The library supports file uploads through endpoints that accept `multipart/form-data` content type. For these endpoints, you can pass either a `FormData` object or the schema-defined object structure:
135
+ **Query Return:**
178
136
 
179
137
  ```typescript
180
- // Example file upload endpoint usage
181
- async function uploadPetPicture(petId: string, file: File) {
182
- const formData = new FormData()
183
- formData.append('file', file)
184
-
185
- const uploadMutation = api.useMutation(MutationOperationId.uploadPetPic, { petId })
186
-
187
- return uploadMutation.mutateAsync({
188
- data: formData,
189
- axiosOptions: {
190
- headers: {
191
- 'Content-Type': 'multipart/form-data',
192
- },
193
- },
194
- })
138
+ {
139
+ data: ComputedRef<T | undefined>
140
+ isLoading: ComputedRef<boolean>
141
+ error: ComputedRef<Error | null>
142
+ isEnabled: ComputedRef<boolean>
143
+ queryKey: ComputedRef<unknown[]>
144
+ onLoad: (cb: (data: T) => void) => void
145
+ refetch: () => Promise<void>
195
146
  }
147
+ ```
196
148
 
197
- // Alternative: using the object structure (if your API supports binary strings)
198
- async function uploadPetPictureAsString(petId: string, binaryData: string) {
199
- const uploadMutation = api.useMutation(MutationOperationId.uploadPetPic, { petId })
149
+ **Mutation Return:**
200
150
 
201
- return uploadMutation.mutateAsync({
202
- data: {
203
- file: binaryData, // Binary data as string
204
- },
205
- })
206
- }
207
-
208
- // Complete example with error handling and cache invalidation
209
- async function handleFileUpload(event: Event, petId: string) {
210
- const files = (event.target as HTMLInputElement).files
211
- if (!files || files.length === 0) return
212
-
213
- const file = files[0]
214
- const formData = new FormData()
215
- formData.append('file', file)
216
-
217
- const uploadMutation = api.useMutation(
218
- MutationOperationId.uploadPetPic,
219
- { petId },
220
- {
221
- invalidateOperations: [QueryOperationId.getPet, QueryOperationId.listPets],
222
- onSuccess: (data) => {
223
- console.log('Upload successful:', data)
224
- },
225
- onError: (error) => {
226
- console.error('Upload failed:', error)
227
- },
228
- },
229
- )
230
-
231
- try {
232
- await uploadMutation.mutateAsync({ data: formData })
233
- } catch (error) {
234
- console.error('Upload error:', error)
235
- }
151
+ ```typescript
152
+ {
153
+ data: ComputedRef<AxiosResponse<T> | undefined>
154
+ isPending: ComputedRef<boolean>
155
+ error: ComputedRef<Error | null>
156
+ mutate: (vars) => void
157
+ mutateAsync: (vars) => Promise<AxiosResponse>
158
+ extraPathParams: Ref<PathParams> // for dynamic path params
236
159
  }
237
160
  ```
238
161
 
239
- ### Reactive Enabling/Disabling Based on Path Parameters
240
-
241
- You can chain queries where one query provides the parameters for another:
162
+ ### Type Helpers
242
163
 
243
164
  ```typescript
244
- import { ref, computed } from 'vue'
245
-
246
- // First query to get user information
247
- const userQuery = api.useQuery(QueryOperationId.getUser, { userId: 123 })
248
-
249
- // Second query that depends on the first query's result
250
- const userPetsQuery = api.useQuery(
251
- QueryOperationId.listUserPets,
252
- computed(() => ({
253
- userId: userQuery.data.value?.id, // Chain: use ID from first query
254
- })),
255
- )
256
-
257
- // Reactive parameter example
258
- const selectedPetId = ref<string | undefined>(undefined)
259
-
260
- // Query automatically enables/disables based on parameter availability
261
- const petQuery = api.useQuery(
262
- QueryOperationId.getPet,
263
- computed(() => ({ petId: selectedPetId.value })),
264
- )
265
-
266
- // Query is automatically disabled when petId is null/undefined
267
- console.log(petQuery.isEnabled.value) // false when selectedPetId.value is null
268
-
269
- // Enable the query by setting the parameter
270
- selectedPetId.value = '123'
271
- console.log(petQuery.isEnabled.value) // true when selectedPetId.value is set
272
-
273
- // Complex conditional enabling
274
- const userId = ref<string>('user1')
275
- const shouldFetchPets = ref(true)
276
-
277
- const userPetsQuery = api.useQuery(
278
- QueryOperationId.listUserPets,
279
- computed(() => ({ userId: userId.value })),
280
- {
281
- enabled: computed(
282
- () => shouldFetchPets.value, // Additional business logic
283
- ),
284
- },
285
- )
165
+ import type {
166
+ ApiResponse, // Response type (all fields required)
167
+ ApiResponseSafe, // Response with optional fields
168
+ ApiRequest, // Request body type
169
+ ApiPathParams, // Path parameters type
170
+ ApiQueryParams, // Query parameters type
171
+ } from './generated/api-operations'
172
+
173
+ // ApiResponse - ALL fields required
174
+ type PetResponse = ApiResponse<OpType.getPet>
175
+ // { readonly id: string, name: string, tag: string, status: 'available' | ... }
176
+
177
+ // ApiResponseSafe - only readonly required, others optional
178
+ type PetResponseSafe = ApiResponseSafe<OpType.getPet>
179
+ // { readonly id: string, name: string, tag?: string, status?: 'available' | ... }
286
180
  ```
287
181
 
288
- ### Reactive Query Parameters with Refs
182
+ ### Enums
183
+
184
+ The CLI generates type-safe enum constants:
289
185
 
290
186
  ```typescript
291
- import { ref } from 'vue'
187
+ import { PetStatus } from './generated/api-enums'
292
188
 
293
- // Use reactive query parameters
294
- const limit = ref(10)
295
- const petsQuery = api.useQuery(QueryOperationId.listPets, {
296
- queryParams: { limit: limit.value },
189
+ // Use enum for intellisense and typo safety
190
+ const { data } = api.listPets.useQuery({
191
+ queryParams: { status: PetStatus.Available },
297
192
  })
298
193
 
299
- // When limit changes, the query automatically refetches
300
- limit.value = 20 // Query refetches with new parameter
301
- // Results in: GET /pets?limit=20
194
+ // Still works with string literals
195
+ const { data } = api.listPets.useQuery({
196
+ queryParams: { status: 'available' }, // also valid
197
+ })
302
198
  ```
303
199
 
304
- ## API Documentation
305
-
306
- Full API documentation is available at [https://qualisero.github.io/openapi-endpoint/](https://qualisero.github.io/openapi-endpoint/). The documentation includes detailed information about all methods, types, and configuration options.
200
+ ## Documentation
307
201
 
308
- ## Contributing
202
+ For detailed guides, see [docs/manual/](docs/manual/):
309
203
 
310
- Contributions are welcome! Please read our contributing guidelines and ensure all tests pass.
204
+ - [Getting Started](docs/manual/01-getting-started.md)
205
+ - [Queries](docs/manual/02-queries.md)
206
+ - [Mutations](docs/manual/03-mutations.md)
207
+ - [Reactive Parameters](docs/manual/04-reactive-parameters.md)
208
+ - [File Uploads](docs/manual/05-file-uploads.md)
209
+ - [Cache Management](docs/manual/06-cache-management.md)
311
210
 
312
211
  ## License
313
212