Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add new client fetch #1353

Merged
merged 9 commits into from
Jun 6, 2024
Merged
2 changes: 1 addition & 1 deletion docs/src/pages/reference/configuration/output.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
29 changes: 29 additions & 0 deletions packages/fetch/README.md
Original file line number Diff line number Diff line change
@@ -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)

<p align="center">
<img src="./logo/orval-logo-horizontal.svg?raw=true" width="500" height="160" alt="orval - Restfull Client Generator" />
</p>
<h1 align="center">
Visit <a href="https://orval.dev" target="_blank">orval.dev</a> for docs, guides, API and beer!
</h1>

### 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)
18 changes: 18 additions & 0 deletions packages/fetch/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
144 changes: 144 additions & 0 deletions packages/fetch/src/index.ts
Original file line number Diff line number Diff line change
@@ -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})`
anymaniax marked this conversation as resolved.
Show resolved Hide resolved
: '';

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;
4 changes: 4 additions & 0 deletions packages/fetch/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "../../tsconfig.base.json",
"include": ["src/**/*.ts"]
}
1 change: 1 addition & 0 deletions packages/orval/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 2 additions & 0 deletions packages/orval/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -42,6 +43,7 @@ const getGeneratorClient = (
swr: swr()(),
zod: zod()(),
hono: hono()(),
fetch: fetchClient()(),
};

const generator = isFunction(outputClient)
Expand Down
75 changes: 75 additions & 0 deletions tests/configs/fetch.config.ts
Original file line number Diff line number Diff line change
@@ -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',
},
},
});
1 change: 1 addition & 0 deletions tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
9 changes: 9 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down