@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 +135 -252
- package/dist/cli.js +1318 -58
- package/dist/index.d.ts +9 -213
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -143
- package/dist/openapi-helpers.d.ts +4 -17
- package/dist/openapi-helpers.d.ts.map +1 -1
- package/dist/openapi-helpers.js +3 -93
- package/dist/openapi-mutation.d.ts +48 -111
- package/dist/openapi-mutation.d.ts.map +1 -1
- package/dist/openapi-mutation.js +75 -104
- package/dist/openapi-query.d.ts +46 -209
- package/dist/openapi-query.d.ts.map +1 -1
- package/dist/openapi-query.js +50 -88
- package/dist/openapi-utils.d.ts +31 -4
- package/dist/openapi-utils.d.ts.map +1 -1
- package/dist/openapi-utils.js +45 -56
- package/dist/types.d.ts +250 -280
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +11 -0
- package/package.json +3 -2
- package/dist/openapi-endpoint.d.ts +0 -18
- package/dist/openapi-endpoint.d.ts.map +0 -1
- package/dist/openapi-endpoint.js +0 -24
- package/dist/types-documentation.d.ts +0 -158
- package/dist/types-documentation.d.ts.map +0 -1
- package/dist/types-documentation.js +0 -9
package/README.md
CHANGED
|
@@ -3,22 +3,39 @@
|
|
|
3
3
|
[](https://badge.fury.io/js/@qualisero%2Fopenapi-endpoint)
|
|
4
4
|
[](https://github.com/qualisero/openapi-endpoint/actions/workflows/ci.yml)
|
|
5
5
|
[](https://opensource.org/licenses/MIT)
|
|
6
|
-
[](https://bundlephobia.com/package/@qualisero%2Fopenapi-endpoint)
|
|
7
7
|
[](https://qualisero.github.io/openapi-endpoint/)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
Type-safe API composables for Vue using TanStack Query. Generate fully-typed API clients from your OpenAPI specification.
|
|
10
10
|
|
|
11
|
-
##
|
|
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
|
-
|
|
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
|
|
19
|
-
|
|
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
|
-
#
|
|
48
|
+
# From local file
|
|
34
49
|
npx @qualisero/openapi-endpoint ./api/openapi.json ./src/generated
|
|
35
50
|
|
|
36
|
-
#
|
|
37
|
-
npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/
|
|
51
|
+
# From remote URL
|
|
52
|
+
npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/generated
|
|
38
53
|
```
|
|
39
54
|
|
|
40
|
-
|
|
55
|
+
Generated files:
|
|
41
56
|
|
|
42
|
-
|
|
43
|
-
|
|
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
|
-
##
|
|
66
|
+
## API Reference
|
|
46
67
|
|
|
47
|
-
###
|
|
68
|
+
### Initialization
|
|
48
69
|
|
|
49
70
|
```typescript
|
|
50
|
-
|
|
51
|
-
import { useOpenApi } from '@qualisero/openapi-endpoint'
|
|
71
|
+
import { createApiClient } from '@qualisero/openapi-endpoint'
|
|
52
72
|
import axios from 'axios'
|
|
53
73
|
|
|
54
|
-
|
|
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
|
-
###
|
|
77
|
+
### Queries (GET/HEAD/OPTIONS)
|
|
73
78
|
|
|
74
79
|
```typescript
|
|
75
|
-
//
|
|
76
|
-
|
|
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
|
-
|
|
83
|
+
// With path parameters
|
|
84
|
+
const { data } = api.getPet.useQuery({ petId: '123' })
|
|
96
85
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
91
|
+
// Reactive parameters
|
|
108
92
|
const limit = ref(10)
|
|
109
|
-
const
|
|
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
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
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
|
-
//
|
|
160
|
-
const
|
|
161
|
-
|
|
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
|
-
###
|
|
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
|
-
//
|
|
174
|
-
const createPet = api.useMutation(
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
//
|
|
178
|
-
const updatePet = api.useMutation(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const
|
|
187
|
-
|
|
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
|
-
###
|
|
133
|
+
### Return Types
|
|
192
134
|
|
|
193
|
-
|
|
135
|
+
**Query Return:**
|
|
194
136
|
|
|
195
137
|
```typescript
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
###
|
|
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 {
|
|
261
|
-
|
|
262
|
-
//
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
//
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
-
###
|
|
182
|
+
### Enums
|
|
183
|
+
|
|
184
|
+
The CLI generates type-safe enum constants:
|
|
305
185
|
|
|
306
186
|
```typescript
|
|
307
|
-
import {
|
|
187
|
+
import { PetStatus } from './generated/api-enums'
|
|
308
188
|
|
|
309
|
-
// Use
|
|
310
|
-
const
|
|
311
|
-
|
|
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
|
-
//
|
|
316
|
-
|
|
317
|
-
|
|
194
|
+
// Still works with string literals
|
|
195
|
+
const { data } = api.listPets.useQuery({
|
|
196
|
+
queryParams: { status: 'available' }, // also valid
|
|
197
|
+
})
|
|
318
198
|
```
|
|
319
199
|
|
|
320
|
-
##
|
|
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
|
-
|
|
202
|
+
For detailed guides, see [docs/manual/](docs/manual/):
|
|
325
203
|
|
|
326
|
-
|
|
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
|
|