Skip to content

Commit

Permalink
feat: add new client fetch (#1353)
Browse files Browse the repository at this point in the history
* feat: add `@orval/fetch` package

* chore: add `fetch` tests

* docs: add `fetch` in valid values of `client` option on guide

* chore: update `fetch` package to `6.29.1`

* fix: query params join fetch url

* fix: considering the case of intentional null specification

* fix: removed `qs` import to reduce dependent libraries

* fix: remove unnecessary object creation processes

* chore: update `yarn.lock` and deps
  • Loading branch information
soartec-lab authored Jun 6, 2024
1 parent ce95b68 commit d1cf87f
Show file tree
Hide file tree
Showing 11 changed files with 285 additions and 1 deletion.
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})`
: '';

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

0 comments on commit d1cf87f

Please sign in to comment.