@qualisero/openapi-endpoint 0.2.3
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 +62 -0
- package/bin/openapi-codegen.js +25 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +176 -0
- package/dist/index.d.ts +90 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/openapi-endpoint.d.ts +18 -0
- package/dist/openapi-endpoint.d.ts.map +1 -0
- package/dist/openapi-endpoint.js +39 -0
- package/dist/openapi-helpers.d.ts +10 -0
- package/dist/openapi-helpers.d.ts.map +1 -0
- package/dist/openapi-helpers.js +89 -0
- package/dist/openapi-mutation.d.ts +112 -0
- package/dist/openapi-mutation.d.ts.map +1 -0
- package/dist/openapi-mutation.js +135 -0
- package/dist/openapi-query.d.ts +209 -0
- package/dist/openapi-query.d.ts.map +1 -0
- package/dist/openapi-query.js +97 -0
- package/dist/openapi-utils.d.ts +10 -0
- package/dist/openapi-utils.d.ts.map +1 -0
- package/dist/openapi-utils.js +43 -0
- package/dist/types.d.ts +90 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +19 -0
- package/package.json +83 -0
package/README.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Vue OpenAPI Query
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/js/@qualisero%2Fopenapi-endpoint)
|
|
4
|
+
[](https://github.com/qualisero/openapi-endpoint/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/qualisero/openapi-endpoint)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
[](https://bundlephobia.com/package/@qualisero/openapi-endpoint)
|
|
8
|
+
|
|
9
|
+
Type-safe OpenAPI integration for Vue Query (TanStack Query).
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install @qualisero/openapi-endpoint
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Code Generation
|
|
18
|
+
|
|
19
|
+
This package includes a command-line tool to generate TypeScript types and operation definitions from your OpenAPI specification:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
# Generate from local file
|
|
23
|
+
npx @qualisero/openapi-endpoint ./api/openapi.json ./src/generated
|
|
24
|
+
|
|
25
|
+
# Generate from remote URL
|
|
26
|
+
npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/api
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
This will generate two files in your specified output directory:
|
|
30
|
+
|
|
31
|
+
- `openapi-types.ts` - TypeScript type definitions for your API
|
|
32
|
+
- `api-operations.ts` - Operation IDs and metadata for use with this library
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### 1. Initialize the package
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// api/init.ts
|
|
40
|
+
import { useOpenApi } from '@qualisero/openapi-endpoint'
|
|
41
|
+
import { QueryClient } from '@tanstack/vue-query'
|
|
42
|
+
import axios from 'axios'
|
|
43
|
+
|
|
44
|
+
// Import your generated OpenAPI types and operations
|
|
45
|
+
import type { operations } from './generated/openapi-types'
|
|
46
|
+
import { OperationId, OPERATION_INFO } from './generated/api-operations'
|
|
47
|
+
|
|
48
|
+
// Create axios instance
|
|
49
|
+
const axiosInstance = axios.create({
|
|
50
|
+
baseURL: 'https://api.example.com',
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
// Properly type the operations for the library
|
|
54
|
+
const operationInfoDict = OPERATION_INFO
|
|
55
|
+
type OperationsWithInfo = operations & typeof operationInfoDict
|
|
56
|
+
|
|
57
|
+
// Initialize the package
|
|
58
|
+
const api = useOpenApi({
|
|
59
|
+
operations: operationInfoDict as OperationsWithInfo,
|
|
60
|
+
axios: axiosInstance,
|
|
61
|
+
})
|
|
62
|
+
```
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { fileURLToPath } from 'url'
|
|
4
|
+
import { dirname, join } from 'path'
|
|
5
|
+
import { spawn } from 'child_process'
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
+
const __dirname = dirname(__filename)
|
|
9
|
+
|
|
10
|
+
// Execute the TypeScript CLI script
|
|
11
|
+
const scriptPath = join(__dirname, '..', 'dist', 'cli.js')
|
|
12
|
+
|
|
13
|
+
const child = spawn('node', [scriptPath, ...process.argv.slice(2)], {
|
|
14
|
+
stdio: 'inherit',
|
|
15
|
+
shell: false,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
child.on('exit', (code) => {
|
|
19
|
+
process.exit(code || 0)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
child.on('error', (error) => {
|
|
23
|
+
console.error('Error executing CLI:', error)
|
|
24
|
+
process.exit(1)
|
|
25
|
+
})
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { exec } from 'child_process';
|
|
4
|
+
import { promisify } from 'util';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
async function fetchOpenAPISpec(input) {
|
|
7
|
+
// Check if input is a URL
|
|
8
|
+
if (input.startsWith('http://') || input.startsWith('https://')) {
|
|
9
|
+
console.log(`📡 Fetching OpenAPI spec from URL: ${input}`);
|
|
10
|
+
// Use node's built-in fetch (available in Node 18+)
|
|
11
|
+
try {
|
|
12
|
+
const response = await fetch(input);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`HTTP error! status: ${response.status}`);
|
|
15
|
+
}
|
|
16
|
+
const content = await response.text();
|
|
17
|
+
return content;
|
|
18
|
+
}
|
|
19
|
+
catch (error) {
|
|
20
|
+
throw new Error(`Failed to fetch OpenAPI spec from URL: ${error}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
// Local file
|
|
25
|
+
console.log(`📂 Reading OpenAPI spec from file: ${input}`);
|
|
26
|
+
if (!fs.existsSync(input)) {
|
|
27
|
+
throw new Error(`File not found: ${input}`);
|
|
28
|
+
}
|
|
29
|
+
return fs.readFileSync(input, 'utf8');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function generateTypes(openapiContent, outputDir) {
|
|
33
|
+
console.log('🔨 Generating TypeScript types using openapi-typescript...');
|
|
34
|
+
// Write the OpenAPI spec to a temporary file
|
|
35
|
+
const tempSpecPath = path.join(outputDir, 'temp-openapi.json');
|
|
36
|
+
fs.writeFileSync(tempSpecPath, openapiContent);
|
|
37
|
+
try {
|
|
38
|
+
// Run openapi-typescript
|
|
39
|
+
const typesOutputPath = path.join(outputDir, 'openapi-types.ts');
|
|
40
|
+
const command = `npx openapi-typescript "${tempSpecPath}" --output "${typesOutputPath}"`;
|
|
41
|
+
await execAsync(command);
|
|
42
|
+
console.log(`✅ Generated types file: ${typesOutputPath}`);
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
// Clean up temp file
|
|
46
|
+
if (fs.existsSync(tempSpecPath)) {
|
|
47
|
+
fs.unlinkSync(tempSpecPath);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
function parseOperationsFromSpec(openapiContent) {
|
|
52
|
+
const openApiSpec = JSON.parse(openapiContent);
|
|
53
|
+
if (!openApiSpec.paths) {
|
|
54
|
+
throw new Error('Invalid OpenAPI spec: missing paths');
|
|
55
|
+
}
|
|
56
|
+
const operationIds = [];
|
|
57
|
+
const operationInfoMap = {};
|
|
58
|
+
// Iterate through all paths and methods to extract operationIds
|
|
59
|
+
Object.entries(openApiSpec.paths).forEach(([pathUrl, pathItem]) => {
|
|
60
|
+
Object.entries(pathItem).forEach(([method, operation]) => {
|
|
61
|
+
// Skip non-HTTP methods (like parameters)
|
|
62
|
+
const httpMethods = ['get', 'post', 'put', 'delete', 'patch', 'options', 'head', 'trace'];
|
|
63
|
+
if (!httpMethods.includes(method.toLowerCase())) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const op = operation;
|
|
67
|
+
if (op.operationId) {
|
|
68
|
+
operationIds.push(op.operationId);
|
|
69
|
+
operationInfoMap[op.operationId] = {
|
|
70
|
+
path: pathUrl,
|
|
71
|
+
method: method.toUpperCase(),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
// Sort operationIds for consistent output
|
|
77
|
+
operationIds.sort();
|
|
78
|
+
return { operationIds, operationInfoMap };
|
|
79
|
+
}
|
|
80
|
+
function generateApiOperationsContent(operationIds, operationInfoMap) {
|
|
81
|
+
// Generate enum-like object
|
|
82
|
+
const enumContent = operationIds.map((id) => ` ${id}: '${id}',`).join('\n');
|
|
83
|
+
// Generate dictionary
|
|
84
|
+
const dictionaryContent = operationIds
|
|
85
|
+
.map((id) => {
|
|
86
|
+
const info = operationInfoMap[id];
|
|
87
|
+
return ` ${id}: {\n path: '${info.path}',\n method: HttpMethod.${info.method},\n },`;
|
|
88
|
+
})
|
|
89
|
+
.join('\n');
|
|
90
|
+
return `// Auto-generated from OpenAPI specification
|
|
91
|
+
// Do not edit this file manually
|
|
92
|
+
|
|
93
|
+
export enum HttpMethod {
|
|
94
|
+
GET = 'GET',
|
|
95
|
+
POST = 'POST',
|
|
96
|
+
PUT = 'PUT',
|
|
97
|
+
PATCH = 'PATCH',
|
|
98
|
+
DELETE = 'DELETE',
|
|
99
|
+
HEAD = 'HEAD',
|
|
100
|
+
OPTIONS = 'OPTIONS',
|
|
101
|
+
TRACE = 'TRACE',
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export const OperationId = {
|
|
105
|
+
${enumContent}
|
|
106
|
+
} as const
|
|
107
|
+
|
|
108
|
+
export type OperationId = (typeof OperationId)[keyof typeof OperationId]
|
|
109
|
+
|
|
110
|
+
export const OPERATION_INFO = {
|
|
111
|
+
${dictionaryContent}
|
|
112
|
+
} as const
|
|
113
|
+
`;
|
|
114
|
+
}
|
|
115
|
+
async function generateApiOperations(openapiContent, outputDir) {
|
|
116
|
+
console.log('🔨 Generating api-operations.ts file...');
|
|
117
|
+
const { operationIds, operationInfoMap } = parseOperationsFromSpec(openapiContent);
|
|
118
|
+
// Generate TypeScript content
|
|
119
|
+
const tsContent = generateApiOperationsContent(operationIds, operationInfoMap);
|
|
120
|
+
// Write to output file
|
|
121
|
+
const outputPath = path.join(outputDir, 'api-operations.ts');
|
|
122
|
+
fs.writeFileSync(outputPath, tsContent);
|
|
123
|
+
console.log(`✅ Generated api-operations file: ${outputPath}`);
|
|
124
|
+
console.log(`📊 Found ${operationIds.length} operations`);
|
|
125
|
+
}
|
|
126
|
+
function printUsage() {
|
|
127
|
+
console.log(`
|
|
128
|
+
Usage: npx @qualisero/openapi-endpoint <openapi-input> <output-directory>
|
|
129
|
+
|
|
130
|
+
Arguments:
|
|
131
|
+
openapi-input Path to OpenAPI JSON file or URL to fetch it from
|
|
132
|
+
output-directory Directory where generated files will be saved
|
|
133
|
+
|
|
134
|
+
Examples:
|
|
135
|
+
npx @qualisero/openapi-endpoint ./api/openapi.json ./src/generated
|
|
136
|
+
npx @qualisero/openapi-endpoint https://api.example.com/openapi.json ./src/api
|
|
137
|
+
|
|
138
|
+
This command will generate:
|
|
139
|
+
- openapi-types.ts (TypeScript types from OpenAPI spec)
|
|
140
|
+
- api-operations.ts (Operation IDs and info for use with this library)
|
|
141
|
+
`);
|
|
142
|
+
}
|
|
143
|
+
async function main() {
|
|
144
|
+
const args = process.argv.slice(2);
|
|
145
|
+
if (args.length === 0 || args.includes('--help') || args.includes('-h')) {
|
|
146
|
+
printUsage();
|
|
147
|
+
process.exit(0);
|
|
148
|
+
}
|
|
149
|
+
if (args.length !== 2) {
|
|
150
|
+
console.error('❌ Error: Exactly 2 arguments are required');
|
|
151
|
+
printUsage();
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
const [openapiInput, outputDir] = args;
|
|
155
|
+
try {
|
|
156
|
+
// Ensure output directory exists
|
|
157
|
+
if (!fs.existsSync(outputDir)) {
|
|
158
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
159
|
+
console.log(`📁 Created output directory: ${outputDir}`);
|
|
160
|
+
}
|
|
161
|
+
// Fetch OpenAPI spec content
|
|
162
|
+
const openapiContent = await fetchOpenAPISpec(openapiInput);
|
|
163
|
+
// Generate both files
|
|
164
|
+
await Promise.all([generateTypes(openapiContent, outputDir), generateApiOperations(openapiContent, outputDir)]);
|
|
165
|
+
console.log('🎉 Code generation completed successfully!');
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
console.error('❌ Error:', error instanceof Error ? error.message : error);
|
|
169
|
+
process.exit(1);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
// Auto-execute main function
|
|
173
|
+
main().catch((error) => {
|
|
174
|
+
console.error('❌ Unexpected error:', error);
|
|
175
|
+
process.exit(1);
|
|
176
|
+
});
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { QueryClient } from '@tanstack/vue-query';
|
|
3
|
+
import { EndpointQueryReturn } from './openapi-query';
|
|
4
|
+
import { Operations, GetPathParameters, OpenApiConfig, QueryOptions, MutationOptions, IsQueryOperation } from './types';
|
|
5
|
+
export type { OperationInfo, QueryOptions } from './types';
|
|
6
|
+
export { type EndpointQueryReturn, useEndpointQuery } from './openapi-query';
|
|
7
|
+
export { type EndpointMutationReturn, useEndpointMutation } from './openapi-mutation';
|
|
8
|
+
export declare const queryClient: QueryClient;
|
|
9
|
+
export declare function useOpenApi<Ops extends Operations<Ops>>(config: OpenApiConfig<Ops>): {
|
|
10
|
+
useQuery: <Op extends keyof Ops>(operationId: IsQueryOperation<Ops, Op> extends true ? Op : never, pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | QueryOptions<Ops, Op>, optionsOrNull?: QueryOptions<Ops, Op>) => EndpointQueryReturn<Ops, Op>;
|
|
11
|
+
useMutation: <Op extends keyof Ops>(operationId: IsQueryOperation<Ops, Op> extends false ? Op : never, pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | MutationOptions<Ops, Op>, optionsOrNull?: MutationOptions<Ops, Op>) => {
|
|
12
|
+
data: import("vue").ComputedRef<import("./types").GetResponseData<Ops, Op> | undefined>;
|
|
13
|
+
isEnabled: import("vue").ComputedRef<boolean>;
|
|
14
|
+
extraPathParams: import("vue").Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
|
|
15
|
+
error: import("vue").Ref<null, null>;
|
|
16
|
+
isError: import("vue").Ref<false, false>;
|
|
17
|
+
isPending: import("vue").Ref<false, false>;
|
|
18
|
+
isSuccess: import("vue").Ref<false, false>;
|
|
19
|
+
status: import("vue").Ref<"idle", "idle">;
|
|
20
|
+
failureCount: import("vue").Ref<number, number>;
|
|
21
|
+
failureReason: import("vue").Ref<Error | null, Error | null>;
|
|
22
|
+
isPaused: import("vue").Ref<boolean, boolean>;
|
|
23
|
+
variables: import("vue").Ref<undefined, undefined>;
|
|
24
|
+
isIdle: import("vue").Ref<true, true>;
|
|
25
|
+
context: import("vue").Ref<unknown, unknown>;
|
|
26
|
+
submittedAt: import("vue").Ref<number, number>;
|
|
27
|
+
mutate: (variables: import("./types").MutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown> | undefined) => void;
|
|
28
|
+
mutateAsync: import("@tanstack/query-core").MutateFunction<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown>;
|
|
29
|
+
reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
|
|
30
|
+
} | {
|
|
31
|
+
data: import("vue").ComputedRef<import("./types").GetResponseData<Ops, Op> | undefined>;
|
|
32
|
+
isEnabled: import("vue").ComputedRef<boolean>;
|
|
33
|
+
extraPathParams: import("vue").Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
|
|
34
|
+
error: import("vue").Ref<null, null>;
|
|
35
|
+
isError: import("vue").Ref<false, false>;
|
|
36
|
+
isPending: import("vue").Ref<true, true>;
|
|
37
|
+
isSuccess: import("vue").Ref<false, false>;
|
|
38
|
+
status: import("vue").Ref<"pending", "pending">;
|
|
39
|
+
failureCount: import("vue").Ref<number, number>;
|
|
40
|
+
failureReason: import("vue").Ref<Error | null, Error | null>;
|
|
41
|
+
isPaused: import("vue").Ref<boolean, boolean>;
|
|
42
|
+
variables: import("vue").Ref<import("./types").MutationVars<Ops, Op>, import("./types").MutationVars<Ops, Op>>;
|
|
43
|
+
isIdle: import("vue").Ref<false, false>;
|
|
44
|
+
context: import("vue").Ref<unknown, unknown>;
|
|
45
|
+
submittedAt: import("vue").Ref<number, number>;
|
|
46
|
+
mutate: (variables: import("./types").MutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown> | undefined) => void;
|
|
47
|
+
mutateAsync: import("@tanstack/query-core").MutateFunction<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown>;
|
|
48
|
+
reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
|
|
49
|
+
} | {
|
|
50
|
+
data: import("vue").ComputedRef<import("./types").GetResponseData<Ops, Op> | undefined>;
|
|
51
|
+
isEnabled: import("vue").ComputedRef<boolean>;
|
|
52
|
+
extraPathParams: import("vue").Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
|
|
53
|
+
error: import("vue").Ref<Error, Error>;
|
|
54
|
+
isError: import("vue").Ref<true, true>;
|
|
55
|
+
isPending: import("vue").Ref<false, false>;
|
|
56
|
+
isSuccess: import("vue").Ref<false, false>;
|
|
57
|
+
status: import("vue").Ref<"error", "error">;
|
|
58
|
+
failureCount: import("vue").Ref<number, number>;
|
|
59
|
+
failureReason: import("vue").Ref<Error | null, Error | null>;
|
|
60
|
+
isPaused: import("vue").Ref<boolean, boolean>;
|
|
61
|
+
variables: import("vue").Ref<import("./types").MutationVars<Ops, Op>, import("./types").MutationVars<Ops, Op>>;
|
|
62
|
+
isIdle: import("vue").Ref<false, false>;
|
|
63
|
+
context: import("vue").Ref<unknown, unknown>;
|
|
64
|
+
submittedAt: import("vue").Ref<number, number>;
|
|
65
|
+
mutate: (variables: import("./types").MutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown> | undefined) => void;
|
|
66
|
+
mutateAsync: import("@tanstack/query-core").MutateFunction<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown>;
|
|
67
|
+
reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
|
|
68
|
+
} | {
|
|
69
|
+
data: import("vue").ComputedRef<import("./types").GetResponseData<Ops, Op> | undefined>;
|
|
70
|
+
isEnabled: import("vue").ComputedRef<boolean>;
|
|
71
|
+
extraPathParams: import("vue").Ref<GetPathParameters<Ops, Op>, GetPathParameters<Ops, Op>>;
|
|
72
|
+
error: import("vue").Ref<null, null>;
|
|
73
|
+
isError: import("vue").Ref<false, false>;
|
|
74
|
+
isPending: import("vue").Ref<false, false>;
|
|
75
|
+
isSuccess: import("vue").Ref<true, true>;
|
|
76
|
+
status: import("vue").Ref<"success", "success">;
|
|
77
|
+
failureCount: import("vue").Ref<number, number>;
|
|
78
|
+
failureReason: import("vue").Ref<Error | null, Error | null>;
|
|
79
|
+
isPaused: import("vue").Ref<boolean, boolean>;
|
|
80
|
+
variables: import("vue").Ref<import("./types").MutationVars<Ops, Op>, import("./types").MutationVars<Ops, Op>>;
|
|
81
|
+
isIdle: import("vue").Ref<false, false>;
|
|
82
|
+
context: import("vue").Ref<unknown, unknown>;
|
|
83
|
+
submittedAt: import("vue").Ref<number, number>;
|
|
84
|
+
mutate: (variables: import("./types").MutationVars<Ops, Op>, options?: import("@tanstack/query-core").MutateOptions<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown> | undefined) => void;
|
|
85
|
+
mutateAsync: import("@tanstack/query-core").MutateFunction<import("./types").GetResponseData<Ops, Op>, Error, import("./types").MutationVars<Ops, Op>, unknown>;
|
|
86
|
+
reset: import("@tanstack/query-core").MutationObserverResult<TData, TError, TVariables, TOnMutateResult>["reset"];
|
|
87
|
+
};
|
|
88
|
+
useEndpoint: <Op extends keyof Ops>(operationId: Op, pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | (IsQueryOperation<Ops, Op> extends true ? QueryOptions<Ops, Op> : MutationOptions<Ops, Op>), optionsOrNull?: IsQueryOperation<Ops, Op> extends true ? QueryOptions<Ops, Op> : MutationOptions<Ops, Op>) => EndpointQueryReturn<Ops, Op> | import("./openapi-mutation").EndpointMutationReturn<Ops, Op>;
|
|
89
|
+
};
|
|
90
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AAGjD,OAAO,EAAE,mBAAmB,EAAoB,MAAM,iBAAiB,CAAA;AAEvE,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,aAAa,EAAE,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAA;AAEvH,YAAY,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAC1D,OAAO,EAAE,KAAK,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAA;AAC5E,OAAO,EAAE,KAAK,sBAAsB,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAA;AAErF,eAAO,MAAM,WAAW,aAItB,CAAA;AAEF,wBAAgB,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC;eAE1D,EAAE,SAAS,MAAM,GAAG,eACzB,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,IAAI,GAAG,EAAE,GAAG,KAAK,wBAC1C,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,kBAC7F,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,KACpC,mBAAmB,CAAC,GAAG,EAAE,EAAE,CAAC;kBAMR,EAAE,SAAS,MAAM,GAAG,eAC5B,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,KAAK,GAAG,EAAE,GAAG,KAAK,wBAC3C,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,kBAChG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAOnB,EAAE,SAAS,MAAM,GAAG,eAC5B,EAAE,wBAEX,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAC/D,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,kBAC/E,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC;EAO9G"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { QueryClient } from '@tanstack/vue-query';
|
|
2
|
+
import { useEndpoint } from './openapi-endpoint';
|
|
3
|
+
import { useEndpointQuery } from './openapi-query';
|
|
4
|
+
import { useEndpointMutation } from './openapi-mutation';
|
|
5
|
+
import { getHelpers } from './openapi-helpers';
|
|
6
|
+
export { useEndpointQuery } from './openapi-query';
|
|
7
|
+
export { useEndpointMutation } from './openapi-mutation';
|
|
8
|
+
export const queryClient = new QueryClient({
|
|
9
|
+
defaultOptions: {
|
|
10
|
+
queries: { staleTime: 1000 * 60 * 5 },
|
|
11
|
+
},
|
|
12
|
+
});
|
|
13
|
+
export function useOpenApi(config) {
|
|
14
|
+
return {
|
|
15
|
+
useQuery: function (operationId, pathParamsOrOptions, optionsOrNull) {
|
|
16
|
+
const helpers = getHelpers(config);
|
|
17
|
+
return useEndpointQuery(operationId, helpers, pathParamsOrOptions, optionsOrNull);
|
|
18
|
+
},
|
|
19
|
+
useMutation: function (operationId, pathParamsOrOptions, optionsOrNull) {
|
|
20
|
+
const helpers = getHelpers(config);
|
|
21
|
+
return useEndpointMutation(operationId, helpers, pathParamsOrOptions, optionsOrNull);
|
|
22
|
+
},
|
|
23
|
+
useEndpoint: function (operationId, pathParamsOrOptions, optionsOrNull) {
|
|
24
|
+
const helpers = getHelpers(config);
|
|
25
|
+
return useEndpoint(operationId, helpers, pathParamsOrOptions, optionsOrNull);
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type MaybeRefOrGetter } from 'vue';
|
|
2
|
+
import { EndpointQueryReturn } from './openapi-query';
|
|
3
|
+
import { EndpointMutationReturn } from './openapi-mutation';
|
|
4
|
+
import type { GetPathParameters, QueryOptions, MutationOptions, Operations, IsQueryOperation } from './types';
|
|
5
|
+
import { getHelpers } from './openapi-helpers';
|
|
6
|
+
/**
|
|
7
|
+
* Composable for performing a strictly typed OpenAPI operation (query or mutation) using Vue Query.
|
|
8
|
+
* Automatically detects whether the operation is a query or mutation and delegates to the appropriate composable.
|
|
9
|
+
* Returns a reactive query or mutation object with strict typing and helpers.
|
|
10
|
+
*
|
|
11
|
+
* @template T OperationId type representing the OpenAPI operation.
|
|
12
|
+
* @param operationId The OpenAPI operation ID to execute.
|
|
13
|
+
* @param pathParamsOrOptions Optional path parameters for the endpoint, can be reactive.
|
|
14
|
+
* @param optionsOrNull Optional query or mutation options, including Vue Query options.
|
|
15
|
+
* @returns Query or mutation object with strict typing and helpers.
|
|
16
|
+
*/
|
|
17
|
+
export declare function useEndpoint<Ops extends Operations<Ops>, Op extends keyof Ops>(operationId: Op, helpers: ReturnType<typeof getHelpers<Ops, Op>>, pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | (IsQueryOperation<Ops, Op> extends true ? QueryOptions<Ops, Op> : MutationOptions<Ops, Op>), optionsOrNull?: IsQueryOperation<Ops, Op> extends true ? QueryOptions<Ops, Op> : MutationOptions<Ops, Op>): EndpointQueryReturn<Ops, Op> | EndpointMutationReturn<Ops, Op>;
|
|
18
|
+
//# sourceMappingURL=openapi-endpoint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-endpoint.d.ts","sourceRoot":"","sources":["../src/openapi-endpoint.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,KAAK,CAAA;AAC3C,OAAO,EAAE,mBAAmB,EAAoB,MAAM,iBAAiB,CAAA;AACvE,OAAO,EAAE,sBAAsB,EAAuB,MAAM,oBAAoB,CAAA;AAChF,OAAO,KAAK,EAEV,iBAAiB,EACjB,YAAY,EACZ,eAAe,EACf,UAAU,EACV,gBAAgB,EACjB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAA;AAmB9C;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,MAAM,GAAG,EAC3E,WAAW,EAAE,EAAE,EACf,OAAO,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAC/C,mBAAmB,CAAC,EAChB,gBAAgB,CAAC,iBAAiB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,GAC/D,CAAC,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,EAC/F,aAAa,GAAE,gBAAgB,CAAC,GAAG,EAAE,EAAE,CAAC,SAAS,IAAI,GAAG,YAAY,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,eAAe,CAAC,GAAG,EAAE,EAAE,CAAM,GAC5G,mBAAmB,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,sBAAsB,CAAC,GAAG,EAAE,EAAE,CAAC,CAahE"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useEndpointQuery } from './openapi-query';
|
|
2
|
+
import { useEndpointMutation } from './openapi-mutation';
|
|
3
|
+
// NOTE: rather than using conditional overloads, we are adjusting the signature in the calling code based on IsQueryOperation
|
|
4
|
+
// Conditional overload: if operation is a query, use QueryOptions and return QueryReturn
|
|
5
|
+
// export function useEndpoint<Ops extends Operations<Ops>, Op extends keyof Ops>(
|
|
6
|
+
// config: OpenApiConfig<Ops>,
|
|
7
|
+
// operationId: IsQueryOperation<Ops, Op> extends true ? Op : never,
|
|
8
|
+
// pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | QueryOptions<Ops, Op>,
|
|
9
|
+
// optionsOrNull?: QueryOptions<Ops, Op>
|
|
10
|
+
// ): EndpointQueryReturn<Ops, Op>
|
|
11
|
+
// // Conditional overload: if operation is a mutation, use MutationOptions and return MutationReturn
|
|
12
|
+
// export function useEndpoint<Ops extends Operations<Ops>, Op extends keyof Ops>(
|
|
13
|
+
// config: OpenApiConfig<Ops>,
|
|
14
|
+
// operationId: IsQueryOperation<Ops, Op> extends false ? Op : never,
|
|
15
|
+
// pathParamsOrOptions?: MaybeRefOrGetter<GetPathParameters<Ops, Op> | null | undefined> | MutationOptions<Ops, Op>,
|
|
16
|
+
// optionsOrNull?: MutationOptions<Ops, Op>
|
|
17
|
+
// ): EndpointMutationReturn<Ops, Op>
|
|
18
|
+
/**
|
|
19
|
+
* Composable for performing a strictly typed OpenAPI operation (query or mutation) using Vue Query.
|
|
20
|
+
* Automatically detects whether the operation is a query or mutation and delegates to the appropriate composable.
|
|
21
|
+
* Returns a reactive query or mutation object with strict typing and helpers.
|
|
22
|
+
*
|
|
23
|
+
* @template T OperationId type representing the OpenAPI operation.
|
|
24
|
+
* @param operationId The OpenAPI operation ID to execute.
|
|
25
|
+
* @param pathParamsOrOptions Optional path parameters for the endpoint, can be reactive.
|
|
26
|
+
* @param optionsOrNull Optional query or mutation options, including Vue Query options.
|
|
27
|
+
* @returns Query or mutation object with strict typing and helpers.
|
|
28
|
+
*/
|
|
29
|
+
export function useEndpoint(operationId, helpers, pathParamsOrOptions, optionsOrNull = {}) {
|
|
30
|
+
if (helpers.isMutationOperation(operationId)) {
|
|
31
|
+
return useEndpointMutation(operationId, helpers, pathParamsOrOptions, optionsOrNull);
|
|
32
|
+
}
|
|
33
|
+
else if (helpers.isQueryOperation(operationId)) {
|
|
34
|
+
return useEndpointQuery(operationId, helpers, pathParamsOrOptions, optionsOrNull);
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new Error(`Operation ${String(operationId)} is neither a query nor a mutation operation`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type OperationInfo, OpenApiConfig, Operations } from './types';
|
|
2
|
+
export declare function getHelpers<Ops extends Operations<Ops>, Op extends keyof Ops>(config: OpenApiConfig<Ops>): {
|
|
3
|
+
getOperationInfo: (operationId: Op) => OperationInfo;
|
|
4
|
+
getListOperationPath: (operationId: Op) => string | null;
|
|
5
|
+
getCrudListPathPrefix: (operationId: Op) => string | null;
|
|
6
|
+
isQueryOperation: (operationId: Op) => boolean;
|
|
7
|
+
isMutationOperation: (operationId: Op) => boolean;
|
|
8
|
+
axios: import("axios").AxiosInstance;
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=openapi-helpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"openapi-helpers.d.ts","sourceRoot":"","sources":["../src/openapi-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,KAAK,aAAa,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AAiBnF,wBAAgB,UAAU,CAAC,GAAG,SAAS,UAAU,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,MAAM,GAAG,EAAE,MAAM,EAAE,aAAa,CAAC,GAAG,CAAC;oCAE/D,EAAE,KAAG,aAAa;wCAMd,EAAE,KAAG,MAAM,GAAG,IAAI;yCAiCjB,EAAE,KAAG,MAAM,GAAG,IAAI;oCAgBvB,EAAE,KAAG,OAAO;uCAOT,EAAE,KAAG,OAAO;;EAcvD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { HttpMethod } from './types';
|
|
2
|
+
// helper returning the operationId prefix given an http method
|
|
3
|
+
function _getMethodPrefix(method) {
|
|
4
|
+
const methodPrefixes = {
|
|
5
|
+
[HttpMethod.GET]: 'get', // or 'list' depending on the operationId
|
|
6
|
+
[HttpMethod.POST]: 'create',
|
|
7
|
+
[HttpMethod.PUT]: 'update',
|
|
8
|
+
[HttpMethod.PATCH]: 'update',
|
|
9
|
+
[HttpMethod.DELETE]: 'delete',
|
|
10
|
+
[HttpMethod.HEAD]: null,
|
|
11
|
+
[HttpMethod.OPTIONS]: null,
|
|
12
|
+
[HttpMethod.TRACE]: null,
|
|
13
|
+
};
|
|
14
|
+
return methodPrefixes[method];
|
|
15
|
+
}
|
|
16
|
+
export function getHelpers(config) {
|
|
17
|
+
// Helper function to get operation info by ID
|
|
18
|
+
function getOperationInfo(operationId) {
|
|
19
|
+
return config.operations[operationId];
|
|
20
|
+
}
|
|
21
|
+
// Helper to return a url path for matching list endpoint (e.g. /items/123 -> /items/)
|
|
22
|
+
// Based on operationId prefix: createItem, updateItem -> listItems
|
|
23
|
+
function getListOperationPath(operationId) {
|
|
24
|
+
const opInfo = getOperationInfo(operationId);
|
|
25
|
+
const operationIdStr = operationId;
|
|
26
|
+
const operationPrefix = opInfo ? _getMethodPrefix(opInfo.method) : null;
|
|
27
|
+
// Make sure operationId matches `<operationPrefix><upercase resourceName>` pattern
|
|
28
|
+
if (!operationPrefix || !/^[A-Z]/.test(operationIdStr.charAt(operationPrefix.length)))
|
|
29
|
+
// If not, fallback to CRUD heuristic
|
|
30
|
+
return getCrudListPathPrefix(operationId);
|
|
31
|
+
const resourceName = operationIdStr.substring(operationPrefix.length);
|
|
32
|
+
const listOperationId = `list${resourceName}`;
|
|
33
|
+
let listOperationInfo = getOperationInfo(listOperationId);
|
|
34
|
+
if (!listOperationInfo) {
|
|
35
|
+
// Try pluralizing the resource name (simple heuristic)
|
|
36
|
+
let pluralResourceName = resourceName;
|
|
37
|
+
const addEsSuffixes = ['s', 'x', 'z', 'ch', 'sh', 'o'];
|
|
38
|
+
if (resourceName.endsWith('y')) {
|
|
39
|
+
pluralResourceName = resourceName.slice(0, -1) + 'ies';
|
|
40
|
+
}
|
|
41
|
+
else if (addEsSuffixes.some((suffix) => resourceName.endsWith(suffix))) {
|
|
42
|
+
pluralResourceName = resourceName + 'es';
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
pluralResourceName = resourceName + 's';
|
|
46
|
+
}
|
|
47
|
+
listOperationInfo = getOperationInfo(`list${pluralResourceName}`);
|
|
48
|
+
}
|
|
49
|
+
if (listOperationInfo && listOperationInfo.method === HttpMethod.GET) {
|
|
50
|
+
return listOperationInfo.path;
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
// Fallback to return a url path prefix matching list endpoint (e.g. /items/123 -> /items/)
|
|
55
|
+
// based on Assumes standard CRUD Restful patterns.
|
|
56
|
+
function getCrudListPathPrefix(operationId) {
|
|
57
|
+
const { path } = getOperationInfo(operationId);
|
|
58
|
+
// for PUT/PATCH/DELETE, strip last segment if it's a path parameter
|
|
59
|
+
const segments = path.split('/').filter((segment) => segment.length > 0);
|
|
60
|
+
if (segments.length >= 2 && /^{[^}]+}$/.test(segments.at(-1))) {
|
|
61
|
+
return '/' + segments.slice(0, -1).join('/') + '/';
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
// Helper function to get all operation IDs
|
|
66
|
+
// function getAllOperationIds(): OperationId<C>[] {
|
|
67
|
+
// return Object.values(OperationId)
|
|
68
|
+
// }
|
|
69
|
+
// Helper to check if an operation is a query method at runtime
|
|
70
|
+
function isQueryOperation(operationId) {
|
|
71
|
+
const { method } = getOperationInfo(operationId);
|
|
72
|
+
const queryMethods = [HttpMethod.GET, HttpMethod.HEAD, HttpMethod.OPTIONS];
|
|
73
|
+
return queryMethods.includes(method);
|
|
74
|
+
}
|
|
75
|
+
// Helper to check if an operation is a mutation method at runtime
|
|
76
|
+
function isMutationOperation(operationId) {
|
|
77
|
+
const { method } = getOperationInfo(operationId);
|
|
78
|
+
const mutationMethods = [HttpMethod.POST, HttpMethod.PUT, HttpMethod.PATCH, HttpMethod.DELETE];
|
|
79
|
+
return mutationMethods.includes(method);
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
getOperationInfo,
|
|
83
|
+
getListOperationPath,
|
|
84
|
+
getCrudListPathPrefix,
|
|
85
|
+
isQueryOperation,
|
|
86
|
+
isMutationOperation,
|
|
87
|
+
axios: config.axios,
|
|
88
|
+
};
|
|
89
|
+
}
|