Skip to content

Commit 28629ad

Browse files
committed
feat: implement shared query configuration and parser functions for TanStack, enhancing modularity and reusability across presets
1 parent c0c657c commit 28629ad

File tree

9 files changed

+164
-420
lines changed

9 files changed

+164
-420
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import type { ApiPipeline } from '@genapi/shared'
2+
import path from 'node:path'
3+
import { config as _config } from '@genapi/pipeline'
4+
import { inject } from '@genapi/shared'
5+
6+
/** Query preset Default Output: main(hooks)、api(apis)、type(types);Query preset only effective output.main / output.api / output.type;type is false, not default api, keep single file main */
7+
export function ensureQueryOutput(userConfig: ApiPipeline.Config) {
8+
userConfig.output = userConfig.output && typeof userConfig.output === 'object' ? userConfig.output : {}
9+
const out = userConfig.output as Record<string, string | false | undefined>
10+
out.main = out.main ?? 'src/api/index.ts'
11+
out.type = out.type !== false ? (out.type ?? (typeof out.main === 'string' ? out.main.replace(/\.(ts|js)$/, '.type.ts') : 'src/api/index.type.ts')) : false
12+
if (out.type !== false)
13+
out.api = out.api ?? (typeof out.main === 'string' ? out.main.replace(/index\.(ts|js)$/, 'index.api.$1') : 'src/api/index.api.ts')
14+
}
15+
16+
/** main import api file (api.xxx reference); api import type file (Types reference) */
17+
export function addApiImportToMain(configRead: ApiPipeline.ConfigRead) {
18+
const mainOut = configRead.outputs.find(o => o.type === 'main')
19+
const apiOut = configRead.outputs.find(o => o.type === 'api')
20+
const typeOut = configRead.outputs.find(o => o.type === 'type')
21+
const { imports } = inject()
22+
if (mainOut && apiOut) {
23+
const rel = path.relative(path.dirname(mainOut.path), apiOut.path).replace(/\.(ts|js)$/, '')
24+
const value = (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
25+
imports.add('main', { value, namespace: true, name: 'api' })
26+
}
27+
if (apiOut && typeOut) {
28+
const rel = path.relative(path.dirname(apiOut.path), typeOut.path).replace(/\.(ts|js)$/, '')
29+
const value = (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
30+
imports.add('api', { value, namespace: true, name: 'Types', type: true })
31+
}
32+
}
33+
34+
export interface QueryConfigOptions {
35+
/** Query library name (e.g., @pinia/colada, @tanstack/react-query, @tanstack/vue-query) */
36+
queryLibrary: string
37+
}
38+
39+
export function createQueryConfig(options: QueryConfigOptions | string) {
40+
const queryLibrary = typeof options === 'string' ? options : options.queryLibrary
41+
return function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
42+
userConfig.meta = userConfig.meta || {}
43+
userConfig.meta.import = userConfig.meta.import || {}
44+
45+
ensureQueryOutput(userConfig)
46+
const configRead = _config(userConfig)
47+
const { imports } = inject()
48+
49+
addApiImportToMain(configRead)
50+
imports.add('main', {
51+
names: ['useQuery', 'useMutation'],
52+
value: queryLibrary,
53+
})
54+
55+
return configRead
56+
}
57+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export { addApiImportToMain, createQueryConfig, ensureQueryOutput, type QueryConfigOptions } from './config'
2+
export { createQueryParser } from './parser'
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
/** Query preset 统一 parser:固定三文件,useQuery(queryKey, queryFn)、useMutation(mutationFn),description 统一 @wraps */
18+
export function createQueryParser() {
19+
return createParser((config, { configRead, functions, interfaces }) => {
20+
const { parameters, interfaces: attachInters, options: optList } = parseMethodParameters(config)
21+
let { name, description, url, responseType, body } = parseMethodMetadata(config)
22+
23+
attachInters.forEach(i => interfaces.add('type', i))
24+
25+
const fetcherParams = [...parameters]
26+
fetcherParams.push({
27+
name: 'config',
28+
type: 'RequestInit',
29+
required: false,
30+
})
31+
32+
if (config.method.toLowerCase() !== 'get')
33+
optList.unshift(['method', `"${config.method}"`])
34+
35+
transformHeaderOptions('body', { options: optList, parameters })
36+
37+
optList.push(['...', 'config'])
38+
39+
const { spaceResponseType } = transformParameters(parameters, {
40+
syntax: 'typescript',
41+
interfaces: interfaces.all(),
42+
configRead,
43+
description,
44+
responseType,
45+
})
46+
47+
transformBodyStringify('body', { options: optList, parameters })
48+
url = transformQueryParams('query', { body, options: optList, url })
49+
url = transformUrlSyntax(url, { baseURL: configRead.config.meta?.baseURL })
50+
const fetchBody = transformFetchBody(url, optList, spaceResponseType)
51+
52+
const fetcherRef = `api.${name}`
53+
functions.add('api', {
54+
export: true,
55+
async: true,
56+
name,
57+
description,
58+
parameters: fetcherParams,
59+
body: [
60+
...body,
61+
...fetchBody,
62+
],
63+
})
64+
65+
const isRead = ['get', 'head'].includes(config.method.toLowerCase())
66+
const hook = hookName(name)
67+
const paramNames = fetcherParams.map(p => p.name).join(', ')
68+
69+
if (isRead) {
70+
const keyItems = `'${name}', ${paramNames}`
71+
functions.add('main', {
72+
export: true,
73+
name: hook,
74+
description: [`@wraps ${name}`],
75+
parameters: fetcherParams,
76+
body: [
77+
`return useQuery({ queryKey: [${keyItems}], queryFn: () => ${fetcherRef}(${paramNames}) })`,
78+
],
79+
})
80+
}
81+
else {
82+
functions.add('main', {
83+
export: true,
84+
name: hook,
85+
description: [`@wraps ${name}`],
86+
parameters: [],
87+
body: [
88+
`return useMutation({ mutationFn: ${fetcherRef} })`,
89+
],
90+
})
91+
}
92+
})
93+
}
Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,3 @@
1-
import type { ApiPipeline } from '@genapi/shared'
2-
import path from 'node:path'
3-
import { config as _config } from '@genapi/pipeline'
4-
import { inject } from '@genapi/shared'
1+
import { createQueryConfig } from '../../../_shared/query'
52

6-
/** Query preset 默认生成三文件:main(hooks)、api(apis)、type(types);仅 query 生效 output.main / output.api / output.type;type 为 false 时不默认 api,保持单文件 main */
7-
function ensureQueryOutput(userConfig: ApiPipeline.Config) {
8-
userConfig.output = userConfig.output && typeof userConfig.output === 'object' ? userConfig.output : {}
9-
const out = userConfig.output as Record<string, string | false | undefined>
10-
out.main = out.main ?? 'src/api/index.ts'
11-
out.type = out.type !== false ? (out.type ?? (typeof out.main === 'string' ? out.main.replace(/\.(ts|js)$/, '.type.ts') : 'src/api/index.type.ts')) : false
12-
if (out.type !== false)
13-
out.api = out.api ?? (typeof out.main === 'string' ? out.main.replace(/index\.(ts|js)$/, 'index.api.$1') : 'src/api/index.api.ts')
14-
}
15-
16-
function addApiImportToMain(configRead: ApiPipeline.ConfigRead) {
17-
const mainOut = configRead.outputs.find(o => o.type === 'main')
18-
const apiOut = configRead.outputs.find(o => o.type === 'api')
19-
const typeOut = configRead.outputs.find(o => o.type === 'type')
20-
const { imports } = inject()
21-
if (mainOut && apiOut) {
22-
const rel = path.relative(path.dirname(mainOut.path), apiOut.path).replace(/\.(ts|js)$/, '')
23-
const value = (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
24-
imports.add('main', { value, namespace: true, name: 'Api' })
25-
}
26-
if (apiOut && typeOut) {
27-
const rel = path.relative(path.dirname(apiOut.path), typeOut.path).replace(/\.(ts|js)$/, '')
28-
const value = (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
29-
imports.add('api', { value, namespace: true, name: 'Types', type: true })
30-
}
31-
}
32-
33-
export function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
34-
userConfig.meta = userConfig.meta || {}
35-
userConfig.meta.import = userConfig.meta.import || {}
36-
37-
ensureQueryOutput(userConfig)
38-
const configRead = _config(userConfig)
39-
const { imports } = inject()
40-
41-
addApiImportToMain(configRead)
42-
imports.add('main', {
43-
names: ['useQuery', 'useMutation'],
44-
value: '@pinia/colada',
45-
})
46-
47-
return configRead
48-
}
3+
export const config = createQueryConfig('@pinia/colada')
Lines changed: 2 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,3 @@
1-
import {
2-
createParser,
3-
parseMethodMetadata,
4-
parseMethodParameters,
5-
transformBodyStringify,
6-
transformFetchBody,
7-
transformHeaderOptions,
8-
transformParameters,
9-
transformQueryParams,
10-
transformUrlSyntax,
11-
} from '@genapi/parser'
1+
import { createQueryParser } from '../../../_shared/query'
122

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-
attachInters.forEach(i => interfaces.add('type', i))
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: interfaces.all(),
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-
const hasApiOutput = configRead.outputs.some(o => o.type === 'api')
50-
const fetcherScope = hasApiOutput ? 'api' : 'main'
51-
const fetcherRef = hasApiOutput ? `Api.${name}` : name
52-
53-
functions.add(fetcherScope, {
54-
export: true,
55-
async: true,
56-
name,
57-
description,
58-
parameters: fetcherParams,
59-
body: [
60-
...body,
61-
...fetchBody,
62-
],
63-
})
64-
65-
const isRead = ['get', 'head'].includes(config.method.toLowerCase())
66-
const hook = hookName(name)
67-
const paramNames = fetcherParams.map(p => p.name).join(', ')
68-
69-
if (isRead) {
70-
const keyItems = `'${name}', ${paramNames}`
71-
functions.add('main', {
72-
export: true,
73-
name: hook,
74-
description: [`@wraps ${name}`],
75-
parameters: fetcherParams,
76-
body: [
77-
`return useQuery({ key: [${keyItems}], query: () => ${fetcherRef}(${paramNames}) })`,
78-
],
79-
})
80-
}
81-
else {
82-
functions.add('main', {
83-
export: true,
84-
name: hook,
85-
description: description ? [...(Array.isArray(description) ? description : [description]), `@wraps ${name}`] : [`@wraps ${name}`],
86-
parameters: [],
87-
body: [
88-
`return useMutation({ mutation: ${fetcherRef} })`,
89-
],
90-
})
91-
}
92-
})
3+
export const parser = createQueryParser()
Lines changed: 2 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,3 @@
1-
import type { ApiPipeline } from '@genapi/shared'
2-
import path from 'node:path'
3-
import { config as _config } from '@genapi/pipeline'
4-
import { inject } from '@genapi/shared'
1+
import { createQueryConfig } from '../../../_shared/query'
52

6-
/** Query preset 默认生成三文件:main(hooks)、api(apis)、type(types);仅 query 生效 output.main / output.api / output.type;type 为 false 时不默认 api,保持单文件 main */
7-
function ensureQueryOutput(userConfig: ApiPipeline.Config) {
8-
userConfig.output = userConfig.output && typeof userConfig.output === 'object' ? userConfig.output : {}
9-
const out = userConfig.output as Record<string, string | false | undefined>
10-
out.main = out.main ?? 'src/api/index.ts'
11-
out.type = out.type !== false ? (out.type ?? (typeof out.main === 'string' ? out.main.replace(/\.(ts|js)$/, '.type.ts') : 'src/api/index.type.ts')) : false
12-
if (out.type !== false)
13-
out.api = out.api ?? (typeof out.main === 'string' ? out.main.replace(/index\.(ts|js)$/, 'index.api.$1') : 'src/api/index.api.ts')
14-
}
15-
16-
function addApiImportToMain(configRead: ApiPipeline.ConfigRead) {
17-
const mainOut = configRead.outputs.find(o => o.type === 'main')
18-
const apiOut = configRead.outputs.find(o => o.type === 'api')
19-
const typeOut = configRead.outputs.find(o => o.type === 'type')
20-
const { imports } = inject()
21-
if (mainOut && apiOut) {
22-
const rel = path.relative(path.dirname(mainOut.path), apiOut.path).replace(/\.(ts|js)$/, '')
23-
const value = (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
24-
imports.add('main', { value, namespace: true, name: 'Api' })
25-
}
26-
if (apiOut && typeOut) {
27-
const rel = path.relative(path.dirname(apiOut.path), typeOut.path).replace(/\.(ts|js)$/, '')
28-
const value = (rel.startsWith('.') ? rel : `./${rel}`).replace(/\\/g, '/')
29-
imports.add('api', { value, namespace: true, name: 'Types', type: true })
30-
}
31-
}
32-
33-
export function config(userConfig: ApiPipeline.Config): ApiPipeline.ConfigRead {
34-
userConfig.meta = userConfig.meta || {}
35-
userConfig.meta.import = userConfig.meta.import || {}
36-
37-
ensureQueryOutput(userConfig)
38-
const configRead = _config(userConfig)
39-
const { imports } = inject()
40-
41-
addApiImportToMain(configRead)
42-
imports.add('main', {
43-
names: ['useQuery', 'useMutation'],
44-
value: '@tanstack/react-query',
45-
})
46-
47-
return configRead
48-
}
3+
export const config = createQueryConfig('@tanstack/react-query')

0 commit comments

Comments
 (0)