diff --git a/docs/src/pages/reference/configuration/output.md b/docs/src/pages/reference/configuration/output.md
index d9ee72569..f6225672e 100644
--- a/docs/src/pages/reference/configuration/output.md
+++ b/docs/src/pages/reference/configuration/output.md
@@ -23,7 +23,7 @@ module.exports = {
Type: `String | Function`.
-Valid values: `angular`, `axios`, `axios-functions`, `react-query`, `svelte-query`, `vue-query`, `swr`, `zod`.
+Valid values: `angular`, `axios`, `axios-functions`, `react-query`, `svelte-query`, `vue-query`, `swr`, `zod`, `fetch`.
Default Value: `axios-functions`.
diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts
index 6d972694b..6c9072b72 100644
--- a/packages/core/src/types.ts
+++ b/packages/core/src/types.ts
@@ -212,6 +212,7 @@ export const OutputClient = {
SWR: 'swr',
ZOD: 'zod',
HONO: 'hono',
+ FETCH: 'fetch',
} as const;
export type OutputClient = (typeof OutputClient)[keyof typeof OutputClient];
diff --git a/packages/fetch/README.md b/packages/fetch/README.md
new file mode 100644
index 000000000..4364bb223
--- /dev/null
+++ b/packages/fetch/README.md
@@ -0,0 +1,29 @@
+[![npm version](https://badge.fury.io/js/orval.svg)](https://badge.fury.io/js/orval)
+[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
+[![tests](https://github.com/anymaniax/orval/actions/workflows/tests.yaml/badge.svg)](https://github.com/anymaniax/orval/actions/workflows/tests.yaml)
+
+
+
+
+
+ Visit orval.dev for docs, guides, API and beer!
+
+
+### Code Generation
+
+`orval` is able to generate client with appropriate type-signatures (TypeScript) from any valid OpenAPI v3 or Swagger v2 specification, either in `yaml` or `json` formats.
+
+`Generate`, `valid`, `cache` and `mock` in your React, Vue, Svelte and Angular applications all with your OpenAPI specification.
+
+### Samples
+
+You can find below some samples
+
+- [react app](https://github.com/anymaniax/orval/tree/master/samples/react-app)
+- [react query](https://github.com/anymaniax/orval/tree/master/samples/react-query)
+- [svelte query](https://github.com/anymaniax/orval/tree/master/samples/svelte-query)
+- [vue query](https://github.com/anymaniax/orval/tree/master/samples/vue-query)
+- [react app with swr](https://github.com/anymaniax/orval/tree/master/samples/react-app-with-swr)
+- [nx fastify react](https://github.com/anymaniax/orval/tree/master/samples/nx-fastify-react)
+- [angular app](https://github.com/anymaniax/orval/tree/master/samples/angular-app)
+- [hono](https://github.com/anymaniax/orval/tree/master/samples/hono)
diff --git a/packages/fetch/package.json b/packages/fetch/package.json
new file mode 100644
index 000000000..247fbc0d9
--- /dev/null
+++ b/packages/fetch/package.json
@@ -0,0 +1,18 @@
+{
+ "name": "@orval/fetch",
+ "version": "6.29.1",
+ "license": "MIT",
+ "main": "./dist/index.js",
+ "types": "./dist/index.d.ts",
+ "files": [
+ "dist"
+ ],
+ "scripts": {
+ "build": "tsup ./src/index.ts --target node12 --clean --sourcemap --dts",
+ "dev": "tsup ./src/index.ts --target node12 --clean --sourcemap --watch src",
+ "lint": "eslint src/**/*.ts"
+ },
+ "dependencies": {
+ "@orval/core": "6.29.1"
+ }
+}
diff --git a/packages/fetch/src/index.ts b/packages/fetch/src/index.ts
new file mode 100644
index 000000000..e8aefdb04
--- /dev/null
+++ b/packages/fetch/src/index.ts
@@ -0,0 +1,144 @@
+import {
+ camel,
+ ClientBuilder,
+ ClientDependenciesBuilder,
+ ClientGeneratorsBuilder,
+ generateFormDataAndUrlEncodedFunction,
+ generateVerbImports,
+ GeneratorDependency,
+ GeneratorOptions,
+ GeneratorVerbOptions,
+ GetterPropType,
+ stringify,
+ toObjectString,
+ generateBodyOptions,
+ isObject,
+} from '@orval/core';
+
+const generateRequestFunction = (
+ {
+ queryParams,
+ operationName,
+ response,
+ body,
+ props,
+ verb,
+ formData,
+ formUrlEncoded,
+ override,
+ }: GeneratorVerbOptions,
+ { route }: GeneratorOptions,
+) => {
+ const isRequestOptions = override?.requestOptions !== false;
+ const isFormData = override?.formData !== false;
+ const isFormUrlEncoded = override?.formUrlEncoded !== false;
+
+ const getUrlFnName = camel(`get-${operationName}-url`);
+ const getUrlFnProps = toObjectString(
+ props.filter(
+ (prop) =>
+ prop.type === GetterPropType.PARAM ||
+ prop.type === GetterPropType.NAMED_PATH_PARAMS ||
+ prop.type === GetterPropType.QUERY_PARAM,
+ ),
+ 'implementation',
+ );
+ const getUrlFnImplementation = `export const ${getUrlFnName} = (${getUrlFnProps}) => {
+${
+ queryParams
+ ? `
+ const normalizedParams = new URLSearchParams();
+
+ Object.entries(params || {}).forEach(([key, value]) => {
+ if (value === null) {
+ normalizedParams.append(key, 'null');
+ } else if (value !== undefined) {
+ normalizedParams.append(key, value.toString());
+ }
+ });`
+ : ''
+}
+
+ return \`${route}${queryParams ? '?${normalizedParams.toString()}' : ''}\`
+}\n`;
+ const getUrlFnProperties = props
+ .filter(
+ (prop) =>
+ prop.type === GetterPropType.PARAM ||
+ prop.type === GetterPropType.QUERY_PARAM ||
+ prop.type === GetterPropType.NAMED_PATH_PARAMS,
+ )
+ .map((param) => {
+ if (param.type === GetterPropType.NAMED_PATH_PARAMS) {
+ return param.destructured;
+ } else {
+ return param.name;
+ }
+ })
+ .join(',');
+
+ const args = `${toObjectString(props, 'implementation')} ${isRequestOptions ? `options?: RequestInit` : ''}`;
+ const retrunType = `Promise<${response.definition.success || 'unknown'}>`;
+
+ const globalFetchOptions = isObject(override?.requestOptions)
+ ? `${stringify(override?.requestOptions)?.slice(1, -1)?.trim()}`
+ : '';
+ const fetchMethodOption = `method: '${verb.toUpperCase()}'`;
+
+ const requestBodyParams = generateBodyOptions(
+ body,
+ isFormData,
+ isFormUrlEncoded,
+ );
+ const fetchBodyOption = requestBodyParams
+ ? `body: JSON.stringify(${requestBodyParams})`
+ : '';
+
+ const fetchResponseImplementation = `const res = await fetch(
+ ${getUrlFnName}(${getUrlFnProperties}),
+ {${globalFetchOptions ? '\n' : ''} ${globalFetchOptions}
+ ${isRequestOptions ? '...options,' : ''}
+ ${fetchMethodOption}${fetchBodyOption ? ',' : ''}
+ ${fetchBodyOption}
+ }
+ )
+
+ return res.json()
+`;
+
+ const bodyForm = generateFormDataAndUrlEncodedFunction({
+ formData,
+ formUrlEncoded,
+ body,
+ isFormData,
+ isFormUrlEncoded,
+ });
+
+ const fetchImplementationBody =
+ `${bodyForm ? ` ${bodyForm}\n` : ''}` + ` ${fetchResponseImplementation}`;
+ const fetchImplementation = `export const ${operationName} = async (${args}): ${retrunType} => {\n${fetchImplementationBody}}`;
+
+ const implementation =
+ `${getUrlFnImplementation}\n` + `${fetchImplementation}\n`;
+
+ return implementation;
+};
+
+export const generateClient: ClientBuilder = (verbOptions, options) => {
+ const imports = generateVerbImports(verbOptions);
+ const functionImplementation = generateRequestFunction(verbOptions, options);
+
+ return {
+ implementation: `${functionImplementation}\n`,
+ imports,
+ };
+};
+
+const fetchClientBuilder: ClientGeneratorsBuilder = {
+ client: generateClient,
+ dependencies: () => [],
+};
+
+export const builder = () => () => fetchClientBuilder;
+
+export default builder;
diff --git a/packages/fetch/tsconfig.json b/packages/fetch/tsconfig.json
new file mode 100644
index 000000000..9e25e6ece
--- /dev/null
+++ b/packages/fetch/tsconfig.json
@@ -0,0 +1,4 @@
+{
+ "extends": "../../tsconfig.base.json",
+ "include": ["src/**/*.ts"]
+}
diff --git a/packages/orval/package.json b/packages/orval/package.json
index 13896429a..1cb0bb26e 100644
--- a/packages/orval/package.json
+++ b/packages/orval/package.json
@@ -58,6 +58,7 @@
"@orval/angular": "6.29.1",
"@orval/axios": "6.29.1",
"@orval/core": "6.29.1",
+ "@orval/fetch": "6.29.1",
"@orval/hono": "6.29.1",
"@orval/mock": "6.29.1",
"@orval/query": "6.29.1",
diff --git a/packages/orval/src/client.ts b/packages/orval/src/client.ts
index 0c8ac506d..ce5ee7e1c 100644
--- a/packages/orval/src/client.ts
+++ b/packages/orval/src/client.ts
@@ -25,6 +25,7 @@ import query from '@orval/query';
import swr from '@orval/swr';
import zod from '@orval/zod';
import hono from '@orval/hono';
+import fetchClient from '@orval/fetch';
const DEFAULT_CLIENT = OutputClient.AXIOS;
@@ -42,6 +43,7 @@ const getGeneratorClient = (
swr: swr()(),
zod: zod()(),
hono: hono()(),
+ fetch: fetchClient()(),
};
const generator = isFunction(outputClient)
diff --git a/tests/configs/fetch.config.ts b/tests/configs/fetch.config.ts
new file mode 100644
index 000000000..cd18f75e0
--- /dev/null
+++ b/tests/configs/fetch.config.ts
@@ -0,0 +1,75 @@
+import { defineConfig } from 'orval';
+
+export default defineConfig({
+ petstore: {
+ output: {
+ target: '../generated/fetch/petstore/endpoints.ts',
+ schemas: '../generated/fetch/petstore/model',
+ mock: true,
+ client: 'axios',
+ },
+ input: {
+ target: '../specifications/petstore.yaml',
+ },
+ },
+ multiArguments: {
+ output: {
+ target: '../generated/fetch/multi-arguments/endpoints.ts',
+ schemas: '../generated/fetch/multi-arguments/model',
+ mock: true,
+ client: 'fetch',
+ },
+ input: {
+ target: '../specifications/petstore.yaml',
+ },
+ },
+ petstoreTagsSplit: {
+ output: {
+ target: '../generated/fetch/petstore-tags-split/endpoints.ts',
+ schemas: '../generated/fetch/petstore-tags-split/model',
+ mock: true,
+ mode: 'tags-split',
+ client: 'fetch',
+ },
+ input: {
+ target: '../specifications/petstore.yaml',
+ },
+ },
+ petstoreSplit: {
+ output: {
+ target: '../generated/fetch/split/endpoints.ts',
+ schemas: '../generated/fetch/split/model',
+ mock: true,
+ mode: 'split',
+ client: 'fetch',
+ },
+ input: {
+ target: '../specifications/petstore.yaml',
+ },
+ },
+ petstoreTags: {
+ output: {
+ target: '../generated/fetch/tags/endpoints.ts',
+ schemas: '../generated/fetch/tags/model',
+ mock: true,
+ mode: 'tags',
+ client: 'fetch',
+ },
+ input: {
+ target: '../specifications/petstore.yaml',
+ },
+ },
+ namedParameters: {
+ output: {
+ target: '../generated/fetch/named-parameters/endpoints.ts',
+ schemas: '../generated/fetch/named-parameters/model',
+ client: 'fetch',
+ override: {
+ useNamedParameters: true,
+ },
+ },
+ input: {
+ target: '../specifications/petstore.yaml',
+ },
+ },
+});
diff --git a/tests/package.json b/tests/package.json
index 6b7eca718..07d0b1cd4 100644
--- a/tests/package.json
+++ b/tests/package.json
@@ -17,6 +17,7 @@
"generate:multi-file": "yarn orval --config ./configs/multi-file.config.ts",
"generate:zod": "yarn orval --config ./configs/zod.config.ts",
"generate:mock": "yarn orval --config ./configs/mock.config.ts",
+ "generate:fetch": "yarn orval --config ./configs/fetch.config.ts",
"build": "tsc"
},
"author": "Victor Bury",
diff --git a/yarn.lock b/yarn.lock
index b09cc296c..8b22e9e5c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -909,6 +909,14 @@ __metadata:
languageName: unknown
linkType: soft
+"@orval/fetch@npm:6.29.1, @orval/fetch@workspace:packages/fetch":
+ version: 0.0.0-use.local
+ resolution: "@orval/fetch@workspace:packages/fetch"
+ dependencies:
+ "@orval/core": "npm:6.29.1"
+ languageName: unknown
+ linkType: soft
+
"@orval/hono@npm:6.29.1, @orval/hono@workspace:packages/hono":
version: 0.0.0-use.local
resolution: "@orval/hono@workspace:packages/hono"
@@ -7136,6 +7144,7 @@ __metadata:
"@orval/angular": "npm:6.29.1"
"@orval/axios": "npm:6.29.1"
"@orval/core": "npm:6.29.1"
+ "@orval/fetch": "npm:6.29.1"
"@orval/hono": "npm:6.29.1"
"@orval/mock": "npm:6.29.1"
"@orval/query": "npm:6.29.1"