Skip to content

Commit 9d81269

Browse files
committed
feat: add tanstack presets for React and Vue, including configuration, parser, and integration tests; update CLI to support new presets
1 parent 94186cc commit 9d81269

20 files changed

Lines changed: 746 additions & 14 deletions

File tree

packages/core/src/cli/init.ts

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,15 @@ const logger = consola.withTag('genapi:init')
99

1010
const CONFIG = {
1111
presets: {
12-
axios: { deps: ['axios'], schema: false },
13-
fetch: { deps: [], schema: true },
14-
ky: { deps: ['ky'], schema: false },
15-
got: { deps: ['got'], schema: false },
16-
ofetch: { deps: ['ofetch'], schema: true },
17-
uni: { deps: ['@uni-helper/uni-network'], schema: false },
18-
} as Record<string, { deps: string[], schema: boolean }>,
12+
axios: { deps: ['axios'], schema: false, supportsJs: true },
13+
fetch: { deps: [], schema: true, supportsJs: true },
14+
ky: { deps: ['ky'], schema: false, supportsJs: true },
15+
got: { deps: ['got'], schema: false, supportsJs: true },
16+
ofetch: { deps: ['ofetch'], schema: true, supportsJs: true },
17+
reactQuery: { deps: ['@tanstack/react-query'], schema: false, supportsJs: false },
18+
uni: { deps: ['@uni-helper/uni-network'], schema: false, supportsJs: true },
19+
vueQuery: { deps: ['@tanstack/vue-query'], schema: false, supportsJs: false },
20+
} as Record<string, { deps: string[], schema: boolean, supportsJs?: boolean }>,
1921
}
2022

2123
async function mandate<T>(promise: Promise<any>): Promise<T> {
@@ -35,18 +37,19 @@ export async function initCommand() {
3537
options: Object.keys(CONFIG.presets).map(v => ({ value: v, label: v })),
3638
}))
3739

40+
const presetConfig = CONFIG.presets[preset]
3841
const mode = await mandate<('ts' | 'js' | 'schema')>(select({
3942
message: 'Select mode:',
4043
options: [
4144
{ value: 'ts', label: 'TS' },
42-
{ value: 'js', label: 'JS' },
43-
...(CONFIG.presets[preset].schema ? [{ value: 'schema', label: 'Schema' }] : []),
45+
...(presetConfig.supportsJs !== false ? [{ value: 'js', label: 'JS' }] : []),
46+
...(presetConfig.schema ? [{ value: 'schema', label: 'Schema' }] : []),
4447
],
4548
}))
4649

4750
const isTS = mode !== 'js'
4851
const fileName = `genapi.config.${isTS ? 'ts' : 'js'}`
49-
const presetValue = `${preset}.${mode}`
52+
const presetValue = presetConfig.supportsJs === false ? preset : `${preset}.${mode}`
5053
const content = isTS
5154
? `import { defineConfig } from '@genapi/core'
5255
import { ${preset} } from '@genapi/presets'

packages/presets/package.json

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
"./swag-ky-js": "./src/ky/js/index.ts",
2828
"./swag-ky-ts": "./src/ky/ts/index.ts",
2929
"./swag-ofetch-js": "./src/ofetch/js/index.ts",
30-
"./swag-ofetch-ts": "./src/ofetch/ts/index.ts"
30+
"./swag-ofetch-ts": "./src/ofetch/ts/index.ts",
31+
"./react-query": "./src/react-query/index.ts",
32+
"./vue-query": "./src/vue-query/index.ts"
3133
},
3234
"main": "./dist/index.mjs",
3335
"module": "./dist/index.mjs",
@@ -86,6 +88,14 @@
8688
"./swag-ofetch-ts": {
8789
"types": "./dist/ofetch/ts/index.d.mts",
8890
"default": "./dist/ofetch/ts/index.mjs"
91+
},
92+
"./reactQuery": {
93+
"types": "./dist/react-query/index.d.mts",
94+
"default": "./dist/react-query/index.mjs"
95+
},
96+
"./vueQuery": {
97+
"types": "./dist/vue-query/index.d.mts",
98+
"default": "./dist/vue-query/index.mjs"
8999
}
90100
}
91101
},

packages/presets/src/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as fetch from './fetch'
33
import * as got from './got'
44
import * as ky from './ky'
55
import * as ofetch from './ofetch'
6+
import * as tanstackQuery from './tanstack'
67
import * as uni from './uni'
78

89
export {
@@ -11,11 +12,12 @@ export {
1112
got,
1213
ky,
1314
ofetch,
15+
tanstackQuery,
1416
uni,
1517
}
1618

1719
/**
18-
* Preset pipelines by HTTP client: axios, fetch, ky, got, ofetch, uni.
20+
* Preset pipelines by HTTP client: axios, fetch, ky, got, ofetch, react-query, vue-query, uni.
1921
* Each key has `.ts` and `.js` (e.g. `presets.axios.ts`, `presets.axios.js`).
2022
*/
2123
export default {
@@ -24,5 +26,6 @@ export default {
2426
got,
2527
ky,
2628
ofetch,
29+
tanstackQuery,
2730
uni,
2831
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { default as react } from './react'
2+
export { default as vue } from './vue'
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ApiPipeline } from '@genapi/shared'
2+
import { config as _config } from '@genapi/pipeline'
3+
4+
export function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
5+
userConfig.meta = userConfig.meta || {}
6+
userConfig.meta.import = userConfig.meta.import || {}
7+
8+
const configRead = _config(userConfig)
9+
10+
configRead.graphs.imports.push({
11+
names: ['useQuery', 'useMutation'],
12+
value: '@tanstack/react-query',
13+
})
14+
15+
return configRead
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { ApiPipeline } from '@genapi/shared'
2+
import pipeline, { compiler, dest, generate, original } from '@genapi/pipeline'
3+
4+
import { config } from './config'
5+
import { parser } from './parser'
6+
7+
function openapiPipeline(userConfig: ApiPipeline.Config) {
8+
const process = pipeline(
9+
userConfig => config(userConfig),
10+
configRead => original(configRead),
11+
configRead => parser(configRead),
12+
configRead => compiler(configRead),
13+
configRead => generate(configRead),
14+
configRead => dest(configRead),
15+
)
16+
return process(userConfig)
17+
}
18+
export { compiler, config, dest, generate, original, parser }
19+
20+
openapiPipeline.config = config
21+
openapiPipeline.original = original
22+
openapiPipeline.parser = parser
23+
openapiPipeline.compiler = compiler
24+
openapiPipeline.generate = generate
25+
openapiPipeline.dest = dest
26+
export default openapiPipeline
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import {
2+
createParser,
3+
parseMethodMetadata,
4+
parseMethodParameters,
5+
transformBodyStringify,
6+
transformFetchBody,
7+
transformHeaderOptions,
8+
transformParameters,
9+
transformQueryParams,
10+
transformUrlSyntax,
11+
} from '@genapi/parser'
12+
13+
function hookName(fetcherName: string) {
14+
return `use${fetcherName.charAt(0).toUpperCase()}${fetcherName.slice(1)}`
15+
}
16+
17+
export const parser = createParser((config, { configRead, functions, interfaces }) => {
18+
const { parameters, interfaces: attachInters, options } = parseMethodParameters(config)
19+
let { name, description, url, responseType, body } = parseMethodMetadata(config)
20+
21+
interfaces.push(...attachInters)
22+
const fetcherParams = [...parameters]
23+
fetcherParams.push({
24+
name: 'config',
25+
type: 'RequestInit',
26+
required: false,
27+
})
28+
29+
if (config.method.toLowerCase() !== 'get')
30+
options.unshift(['method', `"${config.method}"`])
31+
32+
transformHeaderOptions('body', { options, parameters })
33+
34+
options.push(['...', 'config'])
35+
36+
const { spaceResponseType } = transformParameters(parameters, {
37+
syntax: 'typescript',
38+
configRead,
39+
description,
40+
interfaces,
41+
responseType,
42+
})
43+
44+
transformBodyStringify('body', { options, parameters })
45+
url = transformQueryParams('query', { body, options, url })
46+
url = transformUrlSyntax(url, { baseURL: configRead.config.meta?.baseURL })
47+
const fetchBody = transformFetchBody(url, options, spaceResponseType)
48+
49+
// 1. Fetcher function (same as fetch preset)
50+
functions.push({
51+
export: true,
52+
async: true,
53+
name,
54+
description,
55+
parameters: fetcherParams,
56+
body: [
57+
...body,
58+
...fetchBody,
59+
],
60+
})
61+
62+
// 2. Query/Mutation hook
63+
const isRead = ['get', 'head'].includes(config.method.toLowerCase())
64+
const hook = hookName(name)
65+
const paramNames = fetcherParams.map(p => p.name).join(', ')
66+
67+
if (isRead) {
68+
const queryKeyItems = `'${name}', ${paramNames}`
69+
functions.push({
70+
export: true,
71+
name: hook,
72+
description: description ? [...(Array.isArray(description) ? description : [description]), `@wraps ${name}`] : [`@wraps ${name}`],
73+
parameters: fetcherParams,
74+
body: [
75+
`return useQuery({ queryKey: [${queryKeyItems}], queryFn: () => ${name}(${paramNames}) })`,
76+
],
77+
})
78+
}
79+
else {
80+
functions.push({
81+
export: true,
82+
name: hook,
83+
description: description ? [...(Array.isArray(description) ? description : [description]), `@wraps ${name}`] : [`@wraps ${name}`],
84+
parameters: [],
85+
body: [
86+
`return useMutation({ mutationFn: ${name} })`,
87+
],
88+
})
89+
}
90+
})
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import type { ApiPipeline } from '@genapi/shared'
2+
import { config as _config } from '@genapi/pipeline'
3+
4+
export function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
5+
userConfig.meta = userConfig.meta || {}
6+
userConfig.meta.import = userConfig.meta.import || {}
7+
8+
const configRead = _config(userConfig)
9+
10+
configRead.graphs.imports.push({
11+
names: ['useQuery', 'useMutation'],
12+
value: '@tanstack/vue-query',
13+
})
14+
15+
return configRead
16+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import type { ApiPipeline } from '@genapi/shared'
2+
import pipeline, { compiler, dest, generate, original } from '@genapi/pipeline'
3+
4+
import { config } from './config'
5+
import { parser } from './parser'
6+
7+
function openapiPipeline(userConfig: ApiPipeline.Config) {
8+
const process = pipeline(
9+
userConfig => config(userConfig),
10+
configRead => original(configRead),
11+
configRead => parser(configRead),
12+
configRead => compiler(configRead),
13+
configRead => generate(configRead),
14+
configRead => dest(configRead),
15+
)
16+
return process(userConfig)
17+
}
18+
export { compiler, config, dest, generate, original, parser }
19+
20+
openapiPipeline.config = config
21+
openapiPipeline.original = original
22+
openapiPipeline.parser = parser
23+
openapiPipeline.compiler = compiler
24+
openapiPipeline.generate = generate
25+
openapiPipeline.dest = dest
26+
export default openapiPipeline
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import {
2+
createParser,
3+
parseMethodMetadata,
4+
parseMethodParameters,
5+
transformBodyStringify,
6+
transformFetchBody,
7+
transformHeaderOptions,
8+
transformParameters,
9+
transformQueryParams,
10+
transformUrlSyntax,
11+
} from '@genapi/parser'
12+
13+
function hookName(fetcherName: string) {
14+
return `use${fetcherName.charAt(0).toUpperCase()}${fetcherName.slice(1)}`
15+
}
16+
17+
export const parser = createParser((config, { configRead, functions, interfaces }) => {
18+
const { parameters, interfaces: attachInters, options } = parseMethodParameters(config)
19+
let { name, description, url, responseType, body } = parseMethodMetadata(config)
20+
21+
interfaces.push(...attachInters)
22+
const fetcherParams = [...parameters]
23+
fetcherParams.push({
24+
name: 'config',
25+
type: 'RequestInit',
26+
required: false,
27+
})
28+
29+
if (config.method.toLowerCase() !== 'get')
30+
options.unshift(['method', `"${config.method}"`])
31+
32+
transformHeaderOptions('body', { options, parameters })
33+
34+
options.push(['...', 'config'])
35+
36+
const { spaceResponseType } = transformParameters(parameters, {
37+
syntax: 'typescript',
38+
configRead,
39+
description,
40+
interfaces,
41+
responseType,
42+
})
43+
44+
transformBodyStringify('body', { options, parameters })
45+
url = transformQueryParams('query', { body, options, url })
46+
url = transformUrlSyntax(url, { baseURL: configRead.config.meta?.baseURL })
47+
const fetchBody = transformFetchBody(url, options, spaceResponseType)
48+
49+
functions.push({
50+
export: true,
51+
async: true,
52+
name,
53+
description,
54+
parameters: fetcherParams,
55+
body: [
56+
...body,
57+
...fetchBody,
58+
],
59+
})
60+
61+
const isRead = ['get', 'head'].includes(config.method.toLowerCase())
62+
const hook = hookName(name)
63+
const paramNames = fetcherParams.map(p => p.name).join(', ')
64+
65+
if (isRead) {
66+
const queryKeyItems = `'${name}', ${paramNames}`
67+
functions.push({
68+
export: true,
69+
name: hook,
70+
description: description ? [...(Array.isArray(description) ? description : [description]), `@wraps ${name}`] : [`@wraps ${name}`],
71+
parameters: fetcherParams,
72+
body: [
73+
`return useQuery({ queryKey: [${queryKeyItems}], queryFn: () => ${name}(${paramNames}) })`,
74+
],
75+
})
76+
}
77+
else {
78+
functions.push({
79+
export: true,
80+
name: hook,
81+
description: description ? [...(Array.isArray(description) ? description : [description]), `@wraps ${name}`] : [`@wraps ${name}`],
82+
parameters: [],
83+
body: [
84+
`return useMutation({ mutationFn: ${name} })`,
85+
],
86+
})
87+
}
88+
})

0 commit comments

Comments
 (0)