@qualisero/openapi-endpoint 0.11.0 → 0.12.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 +1 -3
- package/dist/cli.js +152 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Vue OpenAPI Query
|
|
2
2
|
|
|
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
6
|
[](https://bundlephobia.com/package/@qualisero/openapi-endpoint)
|
|
@@ -139,8 +139,6 @@ const userPetsQuery = api.useQuery(
|
|
|
139
139
|
- **Automatic refetch**: Changes to query params trigger automatic refetch via TanStack Query's key mechanism
|
|
140
140
|
- **Backward compatible**: Works alongside existing `axiosOptions.params`
|
|
141
141
|
|
|
142
|
-
For detailed examples, see [Reactive Query Parameters Guide](./REACTIVE_QUERY_PARAMS.md).
|
|
143
|
-
|
|
144
142
|
### Automatic Operation Type Detection with `api.useEndpoint`
|
|
145
143
|
|
|
146
144
|
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:
|
package/dist/cli.js
CHANGED
|
@@ -53,6 +53,153 @@ async function generateTypes(openapiContent, outputDir) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Converts a snake_case string to PascalCase.
|
|
58
|
+
* Works with strings that have no underscores (just capitalizes them).
|
|
59
|
+
*
|
|
60
|
+
* @param str - The snake_case string (e.g., 'give_treats' or 'pets')
|
|
61
|
+
* @returns PascalCase string (e.g., 'GiveTreats' or 'Pets')
|
|
62
|
+
*/
|
|
63
|
+
function snakeToPascalCase(str) {
|
|
64
|
+
return str
|
|
65
|
+
.split('_')
|
|
66
|
+
.filter((part) => part.length > 0)
|
|
67
|
+
.map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase())
|
|
68
|
+
.join('');
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Generates an operationId based on the HTTP method and path.
|
|
72
|
+
* Uses heuristics to create meaningful operation names.
|
|
73
|
+
*
|
|
74
|
+
* @param pathUrl - The OpenAPI path (e.g., '/pets/{petId}')
|
|
75
|
+
* @param method - The HTTP method (e.g., 'get', 'post')
|
|
76
|
+
* @param prefixToStrip - Optional prefix to strip from path (e.g., '/api')
|
|
77
|
+
* @returns A generated operationId (e.g., 'getPet', 'listPets', 'createPet')
|
|
78
|
+
*/
|
|
79
|
+
function generateOperationId(pathUrl, method, prefixToStrip = '', existingIds) {
|
|
80
|
+
const methodLower = method.toLowerCase();
|
|
81
|
+
// Strip prefix if provided and path starts with it
|
|
82
|
+
let effectivePath = pathUrl;
|
|
83
|
+
if (prefixToStrip && pathUrl.startsWith(prefixToStrip)) {
|
|
84
|
+
effectivePath = pathUrl.substring(prefixToStrip.length);
|
|
85
|
+
}
|
|
86
|
+
// Remove leading/trailing slashes, replace slashes and periods with underscores
|
|
87
|
+
// Filter out path parameters (e.g., {petId})
|
|
88
|
+
// split by '/' or '.'
|
|
89
|
+
const pathSegments = effectivePath.split(/[/.]/);
|
|
90
|
+
const isParam = (segment) => segment.startsWith('{') && segment.endsWith('}');
|
|
91
|
+
const entityName = snakeToPascalCase(pathSegments
|
|
92
|
+
.filter((s) => !isParam(s))
|
|
93
|
+
.join('_')
|
|
94
|
+
.replace(/[^a-zA-Z0-9]/g, '_'));
|
|
95
|
+
// A collection is when there is a trailing slash
|
|
96
|
+
const isCollection = pathUrl.endsWith('/');
|
|
97
|
+
// Determine prefix based on method and whether it's a collection or single resource
|
|
98
|
+
let prefix = '';
|
|
99
|
+
switch (methodLower) {
|
|
100
|
+
case 'get':
|
|
101
|
+
// GET on file extension -> get, GET on collection -> list, GET on resource -> get
|
|
102
|
+
prefix = isCollection ? 'list' : 'get';
|
|
103
|
+
break;
|
|
104
|
+
case 'post':
|
|
105
|
+
prefix = isCollection ? 'create' : 'post';
|
|
106
|
+
break;
|
|
107
|
+
case 'put':
|
|
108
|
+
case 'patch':
|
|
109
|
+
prefix = 'update';
|
|
110
|
+
break;
|
|
111
|
+
case 'delete':
|
|
112
|
+
prefix = 'delete';
|
|
113
|
+
break;
|
|
114
|
+
case 'head':
|
|
115
|
+
prefix = 'head';
|
|
116
|
+
break;
|
|
117
|
+
case 'options':
|
|
118
|
+
prefix = 'options';
|
|
119
|
+
break;
|
|
120
|
+
case 'trace':
|
|
121
|
+
prefix = 'trace';
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
prefix = methodLower;
|
|
125
|
+
}
|
|
126
|
+
// Combine prefix and entity name
|
|
127
|
+
let generatedId = prefix + entityName;
|
|
128
|
+
// Handle collisions by appending path segments
|
|
129
|
+
if (existingIds.has(generatedId)) {
|
|
130
|
+
console.log(`⚠️ Collision detected: '${generatedId}' already used`);
|
|
131
|
+
// add parameters from the last to the first until not colliding
|
|
132
|
+
const params = pathSegments
|
|
133
|
+
.filter(isParam)
|
|
134
|
+
.map((s) => snakeToPascalCase(s.replace(/[{}]/g, '').replace(/[.:-]/g, '_')));
|
|
135
|
+
while (params.length > 0) {
|
|
136
|
+
generatedId += params.pop();
|
|
137
|
+
if (!existingIds.has(generatedId)) {
|
|
138
|
+
console.log(` ➜ Resolved collision with: '${generatedId}'`);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
if (existingIds.has(generatedId)) {
|
|
143
|
+
// If still colliding, append a counter
|
|
144
|
+
let counter = 2;
|
|
145
|
+
let uniqueId = `${generatedId}${counter}`;
|
|
146
|
+
while (existingIds.has(uniqueId)) {
|
|
147
|
+
counter++;
|
|
148
|
+
uniqueId = `${generatedId}${counter}`;
|
|
149
|
+
}
|
|
150
|
+
generatedId = uniqueId;
|
|
151
|
+
console.log(` ➜ Resolved collision with counter: '${generatedId}'`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return generatedId;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Adds operationId to operations that don't have one.
|
|
158
|
+
* Modifies the OpenAPI spec in place.
|
|
159
|
+
* Handles collisions by appending disambiguating segments.
|
|
160
|
+
*
|
|
161
|
+
* @param openApiSpec - The OpenAPI specification object
|
|
162
|
+
* @param prefixToStrip - Optional prefix to strip from paths (defaults to '/api')
|
|
163
|
+
*/
|
|
164
|
+
function addMissingOperationIds(openApiSpec, prefixToStrip = '/api') {
|
|
165
|
+
if (!openApiSpec.paths) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
// Log the prefix that will be stripped
|
|
169
|
+
if (prefixToStrip) {
|
|
170
|
+
console.log(`🔍 Path prefix '${prefixToStrip}' will be stripped from operation IDs`);
|
|
171
|
+
}
|
|
172
|
+
// Track used operationIds to detect collisions
|
|
173
|
+
const usedOperationIds = new Set();
|
|
174
|
+
// First pass: collect existing operationIds
|
|
175
|
+
Object.entries(openApiSpec.paths).forEach(([_, pathItem]) => {
|
|
176
|
+
Object.entries(pathItem).forEach(([method, op]) => {
|
|
177
|
+
const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'];
|
|
178
|
+
if (!httpMethods.includes(method.toLowerCase())) {
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (op.operationId) {
|
|
182
|
+
usedOperationIds.add(op.operationId);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
// Second pass: generate operationIds for missing ones
|
|
187
|
+
Object.entries(openApiSpec.paths).forEach(([pathUrl, pathItem]) => {
|
|
188
|
+
Object.entries(pathItem).forEach(([method, op]) => {
|
|
189
|
+
const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'];
|
|
190
|
+
if (!httpMethods.includes(method.toLowerCase())) {
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (!op.operationId) {
|
|
194
|
+
// Generate operationId with prefix stripped
|
|
195
|
+
let generatedId = generateOperationId(pathUrl, method, prefixToStrip, usedOperationIds);
|
|
196
|
+
op.operationId = generatedId;
|
|
197
|
+
usedOperationIds.add(generatedId);
|
|
198
|
+
console.log(`🏷️ Generated operationId '${generatedId}' for ${method.toUpperCase()} ${pathUrl}`);
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
}
|
|
56
203
|
function parseOperationsFromSpec(openapiContent) {
|
|
57
204
|
const openApiSpec = JSON.parse(openapiContent);
|
|
58
205
|
if (!openApiSpec.paths) {
|
|
@@ -174,7 +321,11 @@ async function main() {
|
|
|
174
321
|
console.log(`📁 Created output directory: ${outputDir}`);
|
|
175
322
|
}
|
|
176
323
|
// Fetch OpenAPI spec content
|
|
177
|
-
|
|
324
|
+
let openapiContent = await fetchOpenAPISpec(openapiInput);
|
|
325
|
+
// Parse spec and add missing operationIds
|
|
326
|
+
const openApiSpec = JSON.parse(openapiContent);
|
|
327
|
+
addMissingOperationIds(openApiSpec);
|
|
328
|
+
openapiContent = JSON.stringify(openApiSpec, null, 2);
|
|
178
329
|
// Generate both files
|
|
179
330
|
await Promise.all([generateTypes(openapiContent, outputDir), generateApiOperations(openapiContent, outputDir)]);
|
|
180
331
|
console.log('🎉 Code generation completed successfully!');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qualisero/openapi-endpoint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "https://github.com/qualisero/openapi-endpoint.git"
|
|
@@ -57,7 +57,8 @@
|
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@tanstack/vue-query": ">=5.90",
|
|
59
59
|
"axios": ">=1.12",
|
|
60
|
-
"vue": ">=3.3"
|
|
60
|
+
"vue": ">=3.3",
|
|
61
|
+
"openapi-typescript": "^7.9.1"
|
|
61
62
|
},
|
|
62
63
|
"devDependencies": {
|
|
63
64
|
"@eslint/js": "^9.37.0",
|
|
@@ -72,7 +73,6 @@
|
|
|
72
73
|
"gh-pages": "^6.3.0",
|
|
73
74
|
"globals": "^16.4.0",
|
|
74
75
|
"jsdom": "^27.0.0",
|
|
75
|
-
"openapi-typescript": "^7.9.1",
|
|
76
76
|
"prettier": "^3.6.2",
|
|
77
77
|
"typedoc": "^0.28.14",
|
|
78
78
|
"typescript": "^5.9.2",
|