Skip to content

Commit 0e63ce2

Browse files
committed
feat: nitro support
1 parent 3a8f612 commit 0e63ce2

File tree

4 files changed

+178
-71
lines changed

4 files changed

+178
-71
lines changed

src/config.ts

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,12 @@ export type AutoDiscoverConfig = {
1414
openApiFileName?: string;
1515
};
1616

17-
type SupportTarget = 'nitro' | 'nuxt';
18-
1917
// Module options TypeScript interface definition
2018
export type ModuleOptions = GlobalOrSpecificOptions & {
21-
/**
22-
* Enable/disable the auto import of clients
23-
* @default true */
24-
autoImport?: boolean;
19+
/** Configure the generation of clients.
20+
* @default { nuxt: true, nitro: true}
21+
*/
22+
clients?: ClientsConfig;
2523
} & (
2624
| {
2725
/**
@@ -73,10 +71,17 @@ export type ModuleOptions = GlobalOrSpecificOptions & {
7371
}
7472
);
7573

76-
export type GlobalOrSpecificOptions = {
77-
/** @default ['nitro', 'nuxt'] */
78-
for?: SupportTarget | SupportTarget[];
74+
type ClientConfig = {
75+
/** @default true */
76+
autoImport?: boolean;
77+
};
78+
79+
type ClientsConfig = {
80+
nitro?: false | ClientConfig;
81+
nuxt?: false | ClientConfig;
82+
};
7983

84+
export type GlobalOrSpecificOptions = {
8085
/**
8186
* The [openapi-ts config]{@link https://openapi-ts.dev/cli#flags} to pass to the generator
8287
* @default { generatePathParams: true, pathParamsAsTypes: false, alphabetize: true, } */
@@ -94,10 +99,12 @@ export type ApiConfig<RequireOpenApiObject extends boolean = false> =
9499
GlobalOrSpecificOptions & {
95100
/** The base url for the api client to use. */
96101
baseUrl: string;
97-
/**
98-
* undefined = use global option
99-
* @default undefined */
100-
autoImport?: boolean;
102+
103+
/** Configure the generation of clients.
104+
* undefined = use module config
105+
* @default undefined
106+
*/
107+
clients?: ClientsConfig;
101108
} & (true extends RequireOpenApiObject
102109
? {
103110
/** The explicitly provided openapi document to use.
@@ -117,14 +124,13 @@ export const defaultConfig = {
117124
dirname: 'openapi',
118125
openApiFileName: 'openapi.{json,yaml}',
119126
},
127+
clients: { nitro: { autoImport: true }, nuxt: { autoImport: true } },
120128
apis: {},
121129
openApiTsConfig: {
122130
generatePathParams: true,
123131
pathParamsAsTypes: false,
124132
alphabetize: true,
125133
},
126-
autoImport: true,
127-
for: ['nitro', 'nuxt'],
128134
} as const satisfies ModuleOptions;
129135

130136
export const applyConfig = (config: ModuleOptions) => {

src/generate.ts

Lines changed: 85 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,10 @@ import openapiTS, {
1010
import {
1111
addImports,
1212
addServerImports,
13-
addServerTemplate,
1413
addTemplate,
1514
createResolver,
16-
resolvePath,
1715
} from '@nuxt/kit';
18-
import { pascalCase } from 'es-toolkit';
19-
import { ensureArray } from './runtime/fetchUtils';
16+
import { pascalCase, toMerged } from 'es-toolkit';
2017

2118
type GenerateArgs = {
2219
moduleConfig: ResolvedConfig;
@@ -39,7 +36,7 @@ export const generate = async ({ moduleConfig, nuxt }: GenerateArgs) => {
3936
for (const [collectionName, apiConfig] of apis) {
4037
const openApiTsFilePath = `${moduleFolderName}/${collectionName}/${openApiTsFileName}.ts`;
4138

42-
addTemplate({
39+
const { dst: openAPITSTypesDST } = addTemplate({
4340
filename: openApiTsFilePath,
4441
getContents: async () => {
4542
const openApiTs = await getOpenApiTs({
@@ -62,9 +59,12 @@ export const generate = async ({ moduleConfig, nuxt }: GenerateArgs) => {
6259
const useClientName = `use${pascalCasedName}Fetch`;
6360
const useLazyClientName = `useLazy${pascalCasedName}Fetch`;
6461

65-
const target = ensureArray(apiConfig.for ?? moduleConfig.for);
62+
const clientConfig = resolveClientConfig(
63+
moduleConfig.clients,
64+
apiConfig.clients,
65+
);
6666

67-
if (target.includes('nuxt')) {
67+
if (clientConfig.nuxt !== false) {
6868
const nuxtClientPath = `${moduleFolderName}/${collectionName}/index.ts`;
6969
const { dst } = addTemplate({
7070
filename: nuxtClientPath,
@@ -124,7 +124,7 @@ export const ${useLazyClientName}: UseLazyFetch<${pathsTypeName}> = (path, opts?
124124

125125
addedNuxtFiles.add(dst);
126126

127-
if (apiConfig.autoImport ?? moduleConfig.autoImport)
127+
if (clientConfig.nuxt.autoImport)
128128
addImports([
129129
{
130130
name: pathsTypeName,
@@ -146,50 +146,55 @@ export const ${useLazyClientName}: UseLazyFetch<${pathsTypeName}> = (path, opts?
146146
]);
147147
}
148148

149-
if (target.includes('nitro')) {
150-
const nitroClientPath = `${moduleFolderName}/${collectionName}/nitro.ts`;
151-
const { filename } = addTemplate({
152-
filename: nitroClientPath,
153-
getContents: () =>
154-
`import type { paths as ${pathsTypeName} } from './${openApiTsFileName}';
155-
import type { Fetch, SimplifiedFetchOptions } from '${resolver.resolve('./runtime/fetchTypes')}';
156-
import { handleFetchPathParams } from '${resolver.resolve('./runtime/handlePathParams')}'
149+
if (clientConfig.nitro !== false) {
150+
const nitroClientPath = `${moduleFolderName}/${collectionName}/nitro`;
151+
152+
addNitroTsFile(nuxt, openAPITSTypesDST, false);
153+
154+
const { dst } = addTemplate({
155+
filename: `${nitroClientPath}.ts`,
156+
getContents:
157+
() => `import type { paths as ${pathsTypeName} } from './${openApiTsFileName}'
158+
import { handleFetchPathParams } from '${resolver.resolve('./runtime/server')}'
159+
import type { NitroFetch, SimplifiedNitroFetchOptions } from '${resolver.resolve('./runtime/server')}'
157160
158161
export type { paths as ${pathsTypeName}, components as ${componentsTypeName} } from './${openApiTsFileName}'
159162
160-
${tsIgnoreError}
161-
export const ${clientName}: Fetch<${pathsTypeName}> = (path, opts?) => {
162-
const options = (opts ?? {}) as SimplifiedFetchOptions
163+
${tsIgnoreError}
164+
export const ${clientName}: NitroFetch<${pathsTypeName}> = (path, opts) => {
165+
const options = (opts ?? {}) as SimplifiedNitroFetchOptions
163166
options.baseURL ??= "${apiConfig.baseUrl}"
164167
165-
let finalPath = path as string
168+
let finalPath = path as string;
166169
if (options.pathParams) {
167170
finalPath = handleFetchPathParams(path, options.pathParams)
168171
}
169172
170173
const { pathParams, ...rest } = options;
171174
172-
${tsIgnoreError}
175+
${tsIgnoreError}
173176
return $fetch(finalPath, rest)
174177
};`,
178+
write: true,
175179
});
176180

177-
const fullPath = path.join(nuxt.options.buildDir, filename);
181+
addNitroTsFile(nuxt, dst, true);
178182

179-
addedNitroFiles.add(fullPath);
183+
addedNitroFiles.add(dst);
180184

181-
if (apiConfig.autoImport ?? moduleConfig.autoImport)
185+
if (clientConfig.nitro.autoImport) {
182186
addServerImports([
183187
{
184188
name: pathsTypeName,
185-
from: fullPath,
189+
from: dst,
186190
type: true,
187191
},
188192
{
189193
name: clientName,
190-
from: fullPath,
194+
from: dst,
191195
},
192196
]);
197+
}
193198
}
194199
}
195200

@@ -216,6 +221,28 @@ export const ${clientName}: Fetch<${pathsTypeName}> = (path, opts?) => {
216221
});
217222

218223
if (addedNitroFiles.size > 0) {
224+
const { dst } = addTemplate({
225+
filename: `${moduleFolderName}/nitro.ts`,
226+
getContents: () => {
227+
const result = addedNitroFiles
228+
.values()
229+
.map((x) => {
230+
// get rid of '.ts' extension
231+
const exportFrom = path.join(path.dirname(x), path.parse(x).name);
232+
return `export * from "${resolver.resolve(exportFrom)}";`;
233+
})
234+
.toArray();
235+
236+
result.unshift(
237+
`export type * from "${resolver.resolve('./runtime/fetchTypes')}";\nexport * from "${resolver.resolve('./runtime/fetchUtils')}"`,
238+
);
239+
240+
return result.join('\n');
241+
},
242+
write: true,
243+
});
244+
245+
addNitroTsFile(nuxt, dst, true);
219246
}
220247
};
221248

@@ -245,20 +272,23 @@ const getOpenApiTs = async ({
245272
}
246273

247274
const openApiTsConfig = apiConfig.openApiTsConfig
248-
? { ...apiConfig.openApiTsConfig, ...staticOpenApiTsConfig }
275+
? {
276+
...toMerged(moduleConfig.openApiTsConfig, apiConfig.openApiTsConfig),
277+
...staticOpenApiTsConfig,
278+
}
249279
: { ...moduleConfig.openApiTsConfig, ...staticOpenApiTsConfig };
250280

251281
if (apiConfig.openApi) {
252282
return await openapiTS(apiConfig.openApi, openApiTsConfig);
253283
}
254284

255-
const optionsFilePath = discoverOpenApiObjectFilePath({
285+
const openAPIFilePath = discoverOpenApiObjectFilePath({
256286
moduleConfig,
257287
nuxt,
258288
collectionName,
259289
});
260290

261-
return await openapiTS(optionsFilePath, openApiTsConfig);
291+
return await openapiTS(new URL(openAPIFilePath), openApiTsConfig);
262292
};
263293

264294
type DiscoverOpenApiObjectFilePathArgs = {
@@ -298,3 +328,29 @@ const discoverOpenApiObjectFilePath = ({
298328
`no openapi file found for "${collectionName}". Used file paths: ${JSON.stringify(triedPaths)}`,
299329
);
300330
};
331+
332+
const addNitroTsFile = (
333+
nuxt: Nuxt,
334+
file: string,
335+
nuxtIgnore: boolean = false,
336+
) => {
337+
nuxt.options.nitro.typescript ??= {};
338+
nuxt.options.nitro.typescript.tsConfig ??= {};
339+
nuxt.options.nitro.typescript.tsConfig.include ??= [];
340+
nuxt.options.nitro.typescript.tsConfig.include.push(file);
341+
342+
if (nuxtIgnore) {
343+
nuxt.options.typescript.tsConfig.exclude ??= [];
344+
nuxt.options.typescript.tsConfig.exclude.push(file);
345+
}
346+
};
347+
348+
const resolveClientConfig = (
349+
moduleConfig: ResolvedConfig['clients'],
350+
apiConfig: ApiConfig['clients'],
351+
) => {
352+
if (apiConfig) {
353+
return toMerged(moduleConfig, apiConfig);
354+
}
355+
return moduleConfig;
356+
};

src/runtime/fetchTypes.ts

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,11 @@ import type {
77
DefaultAsyncDataErrorValue,
88
DefaultAsyncDataValue,
99
} from 'nuxt/app/defaults';
10-
import type { NitroFetchRequest, AvailableRouterMethod } from 'nitropack/types';
10+
import type {
11+
NitroFetchRequest,
12+
AvailableRouterMethod,
13+
NitroFetchOptions,
14+
} from 'nitropack/types';
1115
import type {
1216
PickFrom,
1317
KeysOf,
@@ -32,7 +36,12 @@ export type UntypedFetchOptions = OmitStrict<
3236
UntypedOptionsToReplaceWithTypedOptions
3337
>;
3438

35-
/** @see similair to {@link https://github.com/nuxt/nuxt/blob/d0a61fc69061690ffda41d9dfe321e400da9da80/packages/nuxt/src/app/composables/fetch.ts#L29} */
39+
export type UntypedNitroFetchOptions = OmitStrict<
40+
NitroFetchOptions<NitroFetchRequest>,
41+
UntypedOptionsToReplaceWithTypedOptions
42+
>;
43+
44+
/** @see similair to {@link https://github.com/nuxt/nuxt/blob/d0a61fc69061690ffda41d9dfe321e400da9da80/packages/nuxt/src/app/composables/fetch.ts#L29} */
3645
export type ComputedUntypedFetchOptions = ComputedOptions<UntypedFetchOptions>;
3746

3847
// useFetch
@@ -152,10 +161,6 @@ export type SimplifiedFetchOptions = FetchOptions & {
152161
pathParams?: Record<string, string | number>;
153162
};
154163

155-
export type SimplifiedUseFetchOptions = UseFetchOptions<void> & {
156-
pathParams?: MaybeRef<ComputedOptions<Record<string, string | number>>>;
157-
};
158-
159164
export type Fetch<Paths extends Record<string, any>> = <
160165
Path extends keyof Paths,
161166
PathInfo extends Paths[Path],
@@ -201,27 +206,58 @@ export type Fetch<Paths extends Record<string, any>> = <
201206
]
202207
) => Promise<Response>;
203208

204-
// useFetch;
205-
// type UseFetch = <
206-
// ResT = void,
207-
// ErrorT = FetchError,
208-
// ReqT extends NitroFetchRequest = NitroFetchRequest,
209-
// Method extends AvailableRouterMethod<ReqT> = ResT extends void
210-
// ? 'get' extends AvailableRouterMethod<ReqT>
211-
// ? 'get'
212-
// : AvailableRouterMethod<ReqT>
213-
// : AvailableRouterMethod<ReqT>,
214-
// _ResT = ResT extends void ? FetchResult<ReqT, Method> : ResT,
215-
// DataT = _ResT,
216-
// PickKeys extends KeysOf<DataT> = KeysOf<DataT>,
217-
// DefaultT = DefaultAsyncDataValue,
218-
// >(
219-
// request: Ref<ReqT> | ReqT | (() => ReqT),
220-
// opts?: UseFetchOptions<_ResT, DataT, PickKeys, DefaultT, ReqT, Method>,
221-
// ) => AsyncData<
222-
// PickFrom<DataT, PickKeys> | DefaultT,
223-
// ErrorT | DefaultAsyncDataErrorValue
224-
// >;
209+
export type SimplifiedNitroFetchOptions =
210+
NitroFetchOptions<NitroFetchRequest> & {
211+
pathParams?: Record<string, string | number>;
212+
};
213+
214+
export type NitroFetch<Paths extends Record<string, any>> = <
215+
Path extends string & keyof Paths,
216+
PathInfo extends Paths[Path],
217+
// credit to nuxt-open-fetch for the complex method generics.
218+
MethodOptions extends GetSupportedHttpMethods<PathInfo>,
219+
MethodLiteral extends MethodOptions | Uppercase<MethodOptions>,
220+
Method extends Lowercase<MethodLiteral> extends MethodOptions
221+
? Lowercase<MethodLiteral>
222+
: MethodOptions,
223+
// use get when method is not specified
224+
ResolvedMethod extends 'get' extends Method ? 'get' : Method,
225+
Operation extends PathInfo[ResolvedMethod],
226+
Body extends GetBody<Operation>,
227+
PathParams extends GetPathParams<Operation>,
228+
Query extends GetQueryParams<Operation>,
229+
Headers extends GetHeaders<Operation>,
230+
Response extends GetReponses<Operation>,
231+
>(
232+
path: Path, // see: https://stackoverflow.com/a/78720068/11463241
233+
...opts: HasRequiredProperties<
234+
Headers &
235+
Query &
236+
PathParams &
237+
Body &
238+
GetMethodProp<MethodOptions, MethodLiteral>
239+
> extends true
240+
? [
241+
config: UntypedNitroFetchOptions &
242+
GetMethodProp<MethodOptions, MethodLiteral> &
243+
Body &
244+
PathParams &
245+
Query &
246+
Headers,
247+
]
248+
: [
249+
config?: UntypedNitroFetchOptions &
250+
GetMethodProp<MethodOptions, MethodLiteral> &
251+
Body &
252+
PathParams &
253+
Query &
254+
Headers,
255+
]
256+
) => Promise<Response>;
257+
258+
export type SimplifiedUseFetchOptions = UseFetchOptions<void> & {
259+
pathParams?: MaybeRef<ComputedOptions<Record<string, string | number>>>;
260+
};
225261

226262
export type UseFetch<
227263
Paths extends Record<string, any>,

0 commit comments

Comments
 (0)