@qualisero/openapi-endpoint 0.12.3 → 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(OperationId.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(OperationId.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,303 +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 { OperationId, openApiOperations, type OpenApiOperations } from './generated/api-operations'
56
-
57
- // Create axios instance
58
- const axiosInstance = axios.create({
59
- baseURL: 'https://api.example.com',
60
- })
61
-
62
- // Initialize the package with the auto-generated operations
63
- const api = useOpenApi<OpenApiOperations>({
64
- operations: openApiOperations,
65
- axios: axiosInstance,
66
- })
67
-
68
- // Export for use in other parts of your application
69
- export { api, OperationId }
74
+ const api = createApiClient(axiosInstance, queryClient?)
70
75
  ```
71
76
 
72
- ### 2. Use the API in your components
77
+ ### Queries (GET/HEAD/OPTIONS)
73
78
 
74
79
  ```typescript
75
- // In your Vue components
76
- import { api, OperationId } from './api/init'
77
-
78
- // Use queries for GET operations
79
- const { data: pets, isLoading } = api.useQuery(OperationId.listPets)
80
- const { data: pet } = api.useQuery(OperationId.getPet, { petId: '123' })
81
-
82
- // Use mutations for POST/PUT/PATCH/DELETE operations
83
- const createPetMutation = api.useMutation(OperationId.createPet)
84
-
85
- // Execute mutations
86
- await createPetMutation.mutateAsync({
87
- data: { name: 'Fluffy', species: 'cat' },
88
- })
89
- ```
90
-
91
- ## Advanced Usage
92
-
93
- ### Reactive Query Parameters
80
+ // No parameters
81
+ const { data, isLoading, error, refetch } = api.listPets.useQuery()
94
82
 
95
- The library supports type-safe, reactive query parameters that automatically trigger refetches when their values change:
83
+ // With path parameters
84
+ const { data } = api.getPet.useQuery({ petId: '123' })
96
85
 
97
- ```typescript
98
- import { ref, computed } from 'vue'
99
- import { api, OperationId } from './api/init'
100
-
101
- // Static query parameters
102
- const { data: pets } = api.useQuery(OperationId.listPets, {
103
- queryParams: { limit: 10 },
86
+ // With query parameters
87
+ const { data } = api.listPets.useQuery({
88
+ queryParams: { limit: 10, status: 'available' },
104
89
  })
105
- // Results in: GET /pets?limit=10
106
90
 
107
- // Reactive query parameters with computed
91
+ // Reactive parameters
108
92
  const limit = ref(10)
109
- const status = ref<'available' | 'pending' | 'sold'>('available')
110
-
111
- const petsQuery = api.useQuery(OperationId.listPets, {
112
- queryParams: computed(() => ({
113
- limit: limit.value,
114
- status: status.value,
115
- })),
93
+ const { data } = api.listPets.useQuery({
94
+ queryParams: computed(() => ({ limit: limit.value })),
116
95
  })
96
+ // Automatically refetches when limit.value changes
117
97
 
118
- // When limit or status changes, query automatically refetches
119
- limit.value = 20
120
- status.value = 'pending'
121
- // Query refetches with: GET /pets?limit=20&status=pending
122
-
123
- // Combine with path parameters
124
- const userPetsQuery = api.useQuery(
125
- OperationId.listUserPets,
126
- computed(() => ({ userId: userId.value })),
127
- {
128
- queryParams: computed(() => ({
129
- includeArchived: includeArchived.value,
130
- })),
131
- },
132
- )
133
- // Results in: GET /users/user-123/pets?includeArchived=false
134
- ```
135
-
136
- **Key Features:**
137
-
138
- - **Type-safe**: Query parameters are typed based on your OpenAPI specification
139
- - **Reactive**: Supports `ref`, `computed`, and function-based values
140
- - **Automatic refetch**: Changes to query params trigger automatic refetch via TanStack Query's key mechanism
141
- - **Backward compatible**: Works alongside existing `axiosOptions.params`
142
-
143
- ### Automatic Operation Type Detection with `api.useEndpoint`
144
-
145
- The `api.useEndpoint` method automatically detects whether an operation is a query (GET/HEAD/OPTIONS) or mutation (POST/PUT/PATCH/DELETE) based on the HTTP method defined in your OpenAPI specification:
146
-
147
- ```typescript
148
- import { ref, computed } from 'vue'
149
- import { api, OperationId } from './api/init'
150
-
151
- // Automatically becomes a query for GET operations
152
- const listEndpoint = api.useEndpoint(OperationId.listPets)
153
- // TypeScript knows this has query properties like .data, .isLoading, .refetch()
154
-
155
- // Automatically becomes a mutation for POST operations
156
- const createEndpoint = api.useEndpoint(OperationId.createPet)
157
- // TypeScript knows this has mutation properties like .mutate(), .mutateAsync()
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),
103
+ })
158
104
 
159
- // Use the endpoints according to their detected type
160
- const petData = listEndpoint.data // Query data
161
- await createEndpoint.mutateAsync({ data: { name: 'Fluffy' } }) // Mutation execution
105
+ // onLoad callback method
106
+ const query = api.getPet.useQuery({ petId: '123' })
107
+ query.onLoad((pet) => console.log('Pet:', pet.name))
162
108
  ```
163
109
 
164
- ### Automatic Cache Management and Refetching
165
-
166
- By default, mutations automatically:
167
-
168
- 1. Update cache for matching queries with returned data
169
- 2. Invalidate them to trigger a reload
170
- 3. Invalidate matching list endpoints
110
+ ### Mutations (POST/PUT/PATCH/DELETE)
171
111
 
172
112
  ```typescript
173
- // Default behavior: automatic cache management
174
- const createPet = api.useMutation(OperationId.createPet)
175
- // No additional configuration needed - cache management is automatic
176
-
177
- // Manual control over cache invalidation
178
- const updatePet = api.useMutation(OperationId.updatePet, {
179
- dontInvalidate: true, // Disable automatic invalidation
180
- dontUpdateCache: true, // Disable automatic cache updates
181
- invalidateOperations: [OperationId.listPets], // Manually specify operations to invalidate
182
- })
183
-
184
- // Refetch specific endpoints after mutation
185
- const petListQuery = api.useQuery(OperationId.listPets)
186
- const createPetWithRefetch = api.useMutation(OperationId.createPet, {
187
- 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),
188
130
  })
189
131
  ```
190
132
 
191
- ### File Upload Support with Multipart/Form-Data
133
+ ### Return Types
192
134
 
193
- 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:**
194
136
 
195
137
  ```typescript
196
- // Example file upload endpoint usage
197
- async function uploadPetPicture(petId: string, file: File) {
198
- const formData = new FormData()
199
- formData.append('file', file)
200
-
201
- const uploadMutation = api.useMutation(OperationId.uploadPetPic, { petId })
202
-
203
- return uploadMutation.mutateAsync({
204
- data: formData,
205
- axiosOptions: {
206
- headers: {
207
- 'Content-Type': 'multipart/form-data',
208
- },
209
- },
210
- })
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>
211
146
  }
147
+ ```
212
148
 
213
- // Alternative: using the object structure (if your API supports binary strings)
214
- async function uploadPetPictureAsString(petId: string, binaryData: string) {
215
- const uploadMutation = api.useMutation(OperationId.uploadPetPic, { petId })
216
-
217
- return uploadMutation.mutateAsync({
218
- data: {
219
- file: binaryData, // Binary data as string
220
- },
221
- })
222
- }
149
+ **Mutation Return:**
223
150
 
224
- // Complete example with error handling and cache invalidation
225
- async function handleFileUpload(event: Event, petId: string) {
226
- const files = (event.target as HTMLInputElement).files
227
- if (!files || files.length === 0) return
228
-
229
- const file = files[0]
230
- const formData = new FormData()
231
- formData.append('file', file)
232
-
233
- const uploadMutation = api.useMutation(
234
- OperationId.uploadPetPic,
235
- { petId },
236
- {
237
- invalidateOperations: [OperationId.getPet, OperationId.listPets],
238
- onSuccess: (data) => {
239
- console.log('Upload successful:', data)
240
- },
241
- onError: (error) => {
242
- console.error('Upload failed:', error)
243
- },
244
- },
245
- )
246
-
247
- try {
248
- await uploadMutation.mutateAsync({ data: formData })
249
- } catch (error) {
250
- console.error('Upload error:', error)
251
- }
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
252
159
  }
253
160
  ```
254
161
 
255
- ### Reactive Enabling/Disabling Based on Path Parameters
256
-
257
- One powerful feature is chaining queries where one query provides the parameters for another:
162
+ ### Type Helpers
258
163
 
259
164
  ```typescript
260
- import { ref, computed } from 'vue'
261
-
262
- // First query to get user information
263
- const userQuery = api.useQuery(OperationId.getUser, { userId: 123 })
264
-
265
- // Second query that depends on the first query's result
266
- const userPetsQuery = api.useQuery(
267
- OperationId.listUserPets,
268
- computed(() => ({
269
- userId: userQuery.data.value?.id, // Chain: use ID from first query
270
- })),
271
- )
272
-
273
- // Reactive parameter example
274
- const selectedPetId = ref<string | undefined>(undefined)
275
-
276
- // Query automatically enables/disables based on parameter availability
277
- const petQuery = api.useQuery(
278
- OperationId.getPet,
279
- computed(() => ({ petId: selectedPetId.value })),
280
- )
281
-
282
- // Query is automatically disabled when petId is null/undefined
283
- console.log(petQuery.isEnabled.value) // false when selectedPetId.value is null
284
-
285
- // Enable the query by setting the parameter
286
- selectedPetId.value = '123'
287
- console.log(petQuery.isEnabled.value) // true when selectedPetId.value is set
288
-
289
- // Complex conditional enabling
290
- const userId = ref<string>('user1')
291
- const shouldFetchPets = ref(true)
292
-
293
- const userPetsQuery = api.useQuery(
294
- OperationId.listUserPets,
295
- computed(() => ({ userId: userId.value })),
296
- {
297
- enabled: computed(
298
- () => shouldFetchPets.value, // Additional business logic
299
- ),
300
- },
301
- )
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' | ... }
302
180
  ```
303
181
 
304
- ### Reactive Query Parameters with Refs
182
+ ### Enums
183
+
184
+ The CLI generates type-safe enum constants:
305
185
 
306
186
  ```typescript
307
- import { ref } from 'vue'
187
+ import { PetStatus } from './generated/api-enums'
308
188
 
309
- // Use reactive query parameters
310
- const limit = ref(10)
311
- const petsQuery = api.useQuery(OperationId.listPets, {
312
- queryParams: { limit: limit.value },
189
+ // Use enum for intellisense and typo safety
190
+ const { data } = api.listPets.useQuery({
191
+ queryParams: { status: PetStatus.Available },
313
192
  })
314
193
 
315
- // When limit changes, the query automatically refetches
316
- limit.value = 20 // Query refetches with new parameter
317
- // 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
+ })
318
198
  ```
319
199
 
320
- ## API Documentation
321
-
322
- 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
323
201
 
324
- ## Contributing
202
+ For detailed guides, see [docs/manual/](docs/manual/):
325
203
 
326
- 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)
327
210
 
328
211
  ## License
329
212